Remove Portal Powered By Ghost bug?

Is it possible to remove the Portal powered by Ghost bug?

<style>
.gh-portal-powered a {
    display: none;
}
</style>

Doesn’t seem todo it.

Hey ryanau,
yeah the “powered by ghost” advertisement on the portal overlay is inside an iFrame and injected there after dom is loaded, so main site CSS isn’t going to reach it.
(Actually the react app that injects it adds multiple iFrames).

That said,
I was able to reach it by 1) first getting the iFrame for the popup trigger icon and adding a click event listener, and 2) adding a click event listener at window level to grab any template link clicks with a context action of openPopup. Both of these let us know when that iframe is injected into the DOM

This seems to have solved it for me. I am more than happy to take any edits for efficiency, etc.

I’ll walk through it, but the full code without comments is at the end.

Make sure page is loaded:

window.onload = () => {

The below is adding a click event listener to main window. Looks at event.view.$r.context.action and calls the clk() method (documented below) to hide advert if click event appears to be opening the modal.

window.addEventListener('click', function(event) {
    if (event.view.$r.context.action.startsWith('openPopup')) {
        clk(event)
    }
}, false)

This is grabbing the iframe that contains the portal’s floating trigger button and adding a click event listener to it. That event then calls clk() method (documented below)

function trggrClk() {
    const trggr = document.querySelector("#ghost-membersjs-root > iframe").contentDocument
    trggr.addEventListener('click', function(event) {clk(event);}, false)
}

This is the clk() method. It’s where we apply display:none on the advert:

function clk(data)  {
    const memDivFrame = document.querySelector("#ghost-membersjs-root > div > iframe")

    memDivFrame.addEventListener('load', (event) => {
        const doc = memDivFrame.contentDocument
        const toRemove = doc.querySelector("body > div.gh-portal-popup-wrapper > div.gh-portal-powered")
        if (toRemove) {
            toRemove.setAttribute("style", "display:none;")
        }
    })
}

Finally, had to wrap the onload call to trggrClk() in setTimeout, otherwise certain lazyload pages seemed to fire through before iFrames existed. Someone more versed in this may be able to get the onload/wait etc more efficient than just a 500 delay.

setTimeout(function(){
    trggrClk()
}, 500)

FULL CODE
I added this javascript in its own file and have template pulling into header.hbs
but it should work in the injection section just the same. Here it is, hope it helps:

window.onload = () => {
  function clk(data)  {
    const memDivFrame = document.querySelector("#ghost-membersjs-root > div > iframe")

    memDivFrame.addEventListener('load', (event) => {
        const doc = memDivFrame.contentDocument
        const toRemove = doc.querySelector("body > div.gh-portal-popup-wrapper > div.gh-portal-powered")
        if (toRemove) {
            toRemove.setAttribute("style", "display:none;")
        }
    })
  }
  function trggrClk() {
    const trggr = document.querySelector("#ghost-membersjs-root > iframe").contentDocument
    trggr.addEventListener('click', function(event) {clk(event);}, false)
  }

  setTimeout(function(){
    trggrClk()
  }, 500)

  window.addEventListener('click', function(event) {
    if (event.view.$r.context.action.startsWith('openPopup')) {
        clk(event)
    }
  }, false)
}
2 Likes

Thanks, hwl but I couldn’t get your code to work in the code injection. Would you please be more specific about how to go about it? Thank you.

hey @chrisNYC, yeah a lot of changes surrounding the portal and login happened after this. (this was for version 3.40.2, and some important updates were made overall after that. I think current is 3.41.8).

The real fix of course would need to come from ghost’s side providing an option to show/hide the advertisement.

That said, I’ll try and take a look at the latest and see if it can still be grabbed with this or a similar method.

(could be a div id name change, could be an implementation that blocks the above now. dunno yet.)

cheers

Thank you, @hwl - much appreciated.

Hey @chrisNYC, are you using the portal button (the floating icon and/or text in the bottom right) to trigger the modal?

I’m still able to get the above to work with the portal button. However, for templates using the data-portal custom attribute on links, I added another conditional.

Both of these are working for me locally, so if they don’t work for you may need a little info like browser, ghost version, template, etc., to try and see what i’m missing. Again, on its best day this is a cobbled hack around. Ghost providing the option to turn the advertisement off would be much more stable.

Below is a copy/paste friendly version for header injection.


// to add in header, makes sure you're enclosing it in script tags
<script type="text/javascript">
window.onload = () => {
    function clk(data)  {
        const memDivFrame = document.querySelector("#ghost-membersjs-root > div > iframe")
        memDivFrame.addEventListener('load', (event) => {
            const doc = memDivFrame.contentDocument
            const toRemove = doc.querySelector("body > div.gh-portal-popup-wrapper > div.gh-portal-powered")
            if (toRemove) {
                toRemove.setAttribute("style", "display:none;")
            }
        })
    }
    function trggrClk() {
        const trggr = document.querySelector("#ghost-membersjs-root > iframe").contentDocument
        trggr.addEventListener('click', function(event) {clk(event);}, false)
    }

    setTimeout(function(){
        trggrClk()
    }, 500)

    window.addEventListener('click', function(event) {
        //console.log(event)
        if (event.view.$r) {
            if (event.view.$r.context.action.startsWith('openPopup')) {
                clk(event)
            }
        }
        // added the below to grab any link clicks with the data-portal attribute
        if (event.srcElement.attributes.getNamedItem('data-portal')) {
            clk(event)
        }
 

    }, false)
}
</script>

EDIT:
I went ahead and made a gist for this to keep up with any edits.
https://gist.github.com/hwlmatt/8d712607fdbdeeee9b4f0640824b2b00

Just an update with a different approach to the same end. Using mutation observer instead of trying to listen to click events.
same as above, to include in header injection wrap all this in <script> </script> tags.

https://gist.github.com/hwlmatt/6b45e2d0d149d65e2f4f452a466060e4

window.onload = () => {

  function cb (mutations, observer) {
    mutations.forEach((mutation) => {
      if (mutation.type === 'childList'
        && mutation.target.id === 'ghost-membersjs-root') {

          var iframe = document.querySelector("#ghost-membersjs-root > div > iframe");
          if (!iframe) {
            return
          }
          iframe.addEventListener('load', (event) => {
            var d = iframe.contentDocument || iframe.contentWindow.document;
            if (d.readyState === 'complete') {
              var r = d.querySelector("body > div.gh-portal-popup-wrapper > div.gh-portal-powered");
              if (r) {
                r.setAttribute("style", "display:none;");
              }
            }
          })
      }
    })
  }
  var obsvr = new MutationObserver(cb)
  obsvr.observe(document.body, {
    childList: true,
    subtree: true
  });
}

It’s not a bug, and there won’t be a “fix”. If you don’t want to use Portal the way we’ve built it into the free open source software that you enjoy at zero cost, you’re very welcome to build your own script / UI, or build a members UI directly into your theme. There are plenty of docs on how to do it:

The suggestions in this thread are not good ones, and the amount of work that has gone into this is more effort than it would have been just writing your own UI.