Code Monkey home page Code Monkey logo

Comments (6)

jsbrain avatar jsbrain commented on May 22, 2024 2

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.

ZionDials avatar ZionDials commented on May 22, 2024 1

@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.
Code Injection Ghost CMS Header If you visit my website you can see how it is interpolated.
ZionDials Website Code

from gatsby-starter-ghost.

ScreamZ avatar ScreamZ commented on May 22, 2024

Any news on this ?

from gatsby-starter-ghost.

aileen avatar aileen commented on May 22, 2024

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.

aileen avatar aileen commented on May 22, 2024

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.

Nevensky avatar Nevensky commented on May 22, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.