Comments (6)
I'm wondering how this issue became stale as it seems to be a major feature and therefore I expected this to be needed by many ... but anyhow, I just came to be in need of this too so I implemented it in a way that I think could serve as a possible solution, even while it's still a little bit hacky some might say but actually works like a charm.
First, we build ourselves some kind of function that can extract our wanted script tags from the data delivered by gatsby-source-ghost
. We do this by utilizing a little "hack" that helps us to convert the plain HTML string available under codeinjection_{head|foot} to "real" HTML elements so we can make use of the native HTML functions, queries, etc. Why? Because it is easy, reliable, and dependency-free. Also, it allows us to manipulate the elements in any way we want, for example adding special ids, attributes, classes, and so on:
const extractCodeInjectionElements = (
type: "script" | "style",
scriptString: string
) => {
let scriptElements = []
if (documentGlobal) {
// We create a script wrapper to render the scriptString to real HTML
const scriptWrapper = documentGlobal.createElement("div")
scriptWrapper.innerHTML = scriptString
// Now we can use native HTML queries to get our script|style elements (in this case a
// collection)
const scriptsCollection = scriptWrapper.getElementsByTagName(type)
// Lets map the collection to a "normal" array of script elements
for (let i = 0; i < scriptsCollection.length; i++) {
scriptElements.push(scriptsCollection.item(i))
}
}
// And return them ...
return scriptElements
}
Note that I created that function to work with 2 types of tags, script
and style
. This is because somehow for me, the codeinjection_styles
property is just always undefined, no matter how many style tags I put into the head or foot section of the code injection in my ghost instance. Don't know why this is, maybe @aileen can enlighten me? Anyhow instead of trying to figure out why it doesn't work for me I just wrote the function to work for both:
// Transform the codeinjection string to an array of "real" HTML script elements
const headScriptElements = extractCodeInjectionElements(
"script",
data.ghostPage?.codeinjection_head || ""
)
const footScriptElements = extractCodeInjectionElements(
"script",
data.ghostPage?.codeinjection_foot || ""
)
// As `codeinjection_styles` prop is always undefined, we just extract the style elements
// from head and foot on our own.
const styleElements = extractCodeInjectionElements(
"style",
"".concat(
data.ghostPage?.codeinjection_head || "",
data.ghostPage?.codeinjection_foot || ""
)
)
So that's basically it. Now we just map over the returned elements in our JSX, create the respecting script or style tags and "fill" them with the raw code by accessing the element.innerText
property. This method works both in development, production, and with or without Helmet. The full implementation looks like this:
import { graphql } from "gatsby"
import React from "react"
import { Helmet } from "react-helmet"
import Layout from "../components/Layout"
import { documentGlobal } from "../utils/helpers"
const Page: React.FC<{ data: GatsbyTypes.GhostPageQuery }> = ({ data }) => {
const extractCodeInjectionElements = (
type: "script" | "style",
scriptString: string
) => {
let scriptElements = []
if (documentGlobal) {
// We create a script wrapper to render the scriptString to real HTML
const scriptWrapper = documentGlobal.createElement("div")
scriptWrapper.innerHTML = scriptString
// Now we can use native HTML queries to get our script elements (in this case a
// collection)
const scriptsCollection = scriptWrapper.getElementsByTagName(type)
// Lets map the collection to a "normal" array of script elements
for (let i = 0; i < scriptsCollection.length; i++) {
scriptElements.push(scriptsCollection.item(i))
}
}
// And return them ...
return scriptElements
}
// Transform the codeinjection string to an array of "real" HTML script elements
const headScriptElements = extractCodeInjectionElements(
"script",
data.ghostPage?.codeinjection_head || ""
)
const footScriptElements = extractCodeInjectionElements(
"script",
data.ghostPage?.codeinjection_foot || ""
)
// Same for the style elements
const styleElements = extractCodeInjectionElements(
"style",
"".concat(
data.ghostPage?.codeinjection_head || "",
data.ghostPage?.codeinjection_foot || ""
)
)
return (
<Layout>
<Helmet>
{/* Now we just iterate over the elements and create a script tag for each
of them inside Helmet to exactly replicate our script tags from Ghost
(ghost_head in this case). */}
{headScriptElements.map((script, index) => (
<script
key={`ci_h_${index}`}
type="text/javascript"
src={script?.getAttribute("src") || undefined}
async={script?.hasAttribute("async")}
>{`${script?.innerText}`}</script>
))}
{/* Or, if we want it clean, we just join multiple elements into one. */}
<style type="text/css">{`${styleElements
.map((e) => e?.innerText)
.join("")}`}</style>
</Helmet>
{/* Our normal html from Ghost. */}
<div className="tw-px-6 sm:tw-px-8 lg:tw-px-10 tw-py-8 tw-content-page">
<div
dangerouslySetInnerHTML={{ __html: data.ghostPage?.html || "" }}
></div>
</div>
{/* Finally, add the scripts from the ghost_foot section */}
{footScriptElements.map((script, index) => (
<script
key={`ci_f_${index}`}
type="text/javascript"
src={script?.getAttribute("src") || undefined}
async={script?.hasAttribute("async")}
>{`${script?.innerText}`}</script>
))}
</Layout>
)
}
export default Page
// This page query loads all posts sorted descending by published date
// The `limit` and `skip` values are used for pagination
export const pageQuery = graphql`
query GhostPage($slug: String!) {
ghostPage(slug: { eq: $slug }) {
...GhostPageFields
}
}
`
So there you go, that's my implementation. If you guys like it you could basically implement an improved version of the extractCodeInjectionElements
, maybe inside the @ghost/helpers package or so to provide a unified version. Let me know what you think! 😃
EDIT: forgot to add the src and async attributes to the final head
and foot
script elements
from gatsby-starter-ghost.
@AileenCGN Personally, I have implemented this in my own Fork of this repository. What I have found, as a workaround, is to place the functions themselves inside of the Ghost CMS without <script>
tags. Here is how I have it implemented in the Layout Component.
If you visit my website you can see how it is interpolated.
from gatsby-starter-ghost.
Any news on this ?
from gatsby-starter-ghost.
Code injection is now working for <style>
tags (both for site and post code injection). I tried to do the same for <script>
, but the html parser was having issues with the content and cut it off. I tested this with a normal Google analytics scripts, which is the most common use case for site code injection.
The code for that parses the content is here.
If anyone has an idea, how to solve this, please let me know, or - even better - submit a PR 😊
from gatsby-starter-ghost.
Thank you @ZionDials for this! This might be a good solution for some users already. The problem is, that we have to cover the case that both, <script>
and <style>
tags are used in codeinjection and your solution would not work in this case. But it would work for users who know exactly that they'll only use scripts in their codeinjection, so thank you very much for sharing this 🤗
from gatsby-starter-ghost.
Any updates on this @aileen, is there a particular reason why @jsbrain solution wasn't implemented in Ghost?
from gatsby-starter-ghost.
Related Issues (20)
- Can't get the "Deploy to netlify" button to work HOT 2
- Favicon and title error HOT 1
- Unable to enabe Google Analytics HOT 2
- How to add tag conditions in gatsby ghost blog? HOT 1
- Action Required: Fix Renovate Configuration
- Ghost localhost image URLs pushed to live site via Gatsby. HOT 1
- When we will get an update version of Ghost 4? HOT 1
- Error: cheerio.load() expects a string HOT 1
- Broken install HOT 7
- Dependency Dashboard
- ValidationError: Invalid options object. Ignore Plugin has been initialized using an options object that does not match the API schema. HOT 2
- Deploy to netlify does not work HOT 2
- gatsby develop build fails with Invalid options object - Fresh Installation HOT 10
- Add Strapi/other CMS support
- Lint script fails due to parsing errors
- Unhandled Runtime Error: _gatsbyScript.PartytownContext is undefined HOT 6
- Can I run this at folder level? HOT 1
- Error in function PartytownProvider HOT 2
- 0.001 s 0/4 0% Building static HTML for pages
- X
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from gatsby-starter-ghost.