Difficulties in correctly adding external JavaScript

TL;DR Summary

  • I could be wrong, but it seems like the order of <script> tags in the </body> theme customization section is not respected.
  • I can’t figure out how to get https://faithlife.com/products/reftagger to execute when I put it in the </body> theme customization section.
  • Once I resolve the above, I’m not exactly sure how I’d go about wrapping it in a call to api.onPageChange() to make it work when more posts are loaded.

Background

I am attempting to make use of a JavaScript library that adds tooltips to Bible verses upon hover. I haven’t really looked at the code too closely, but I think it scans text in the DOM for things that match its regular expressions, then adds links with a CSS class that enables the tooltip hover behavior. Here’s a simple HTML page as an example:

<html>
	<head>
		<title>Reftagger test</title>
	</head>

	<body>
		<p>A sentence without a link that should get picked up.</p>
		<p>A link that should get picked up: Romans 8:28.</p>
		
		<script>
		var refTagger = {
			settings: {
				bibleVersion: "NKJV",
				convertHyperlinks: true,		
				roundCorners: true,
				socialSharing: [],
				tagChapters: true
			}
		};
		</script>
		
		<script src="https://api.reftagger.com/v2/RefTagger.js" type="text/javascript"></script>

	</body>

</html>

I’ve been trying to add this functionality to my Discourse install for a few hours now, and I can’t figure out exactly what I need to do.

My first attempt

I whitelisted the API I wish to use in the Content Security Policy customization section (as described in this prior forum post), like so:

Then I added the JavaScript to the </body> section of theme customization:

After doing these things, it does not appear to work, even on the first page. For one thing, the order of the <script> tags does not seem to be respected, as the settings are placed after the external call, opposite the order in the customization section (at least according to Chrome dev tools):

image

I might be able to fix this by putting the settings in the </head> customization section, but it seems a bit hackish to me. Is the order of <script> tags in the customization sections not being respected, or am I doing something wrong?

Regardless, I don’t think this is the underlying reason why the JavaScript isn’t working, but I don’t know what else is interfering.

Additional issues

From what I have read, I would need to wrap the execution of the verse-tagging code in an api.onPageChange() call for it to work as more posts get loaded. Something like the following:

<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() => {
        // call verse tagging functionality
    });
</script>

I haven’t gotten here yet (as I wanted to figure out how to get it to work initially before worrying about this), but I figured I’d mention that this would probably be necessary. I’m a bit rusty on my JavaScript, so I’m not entirely sure how I would go about converting the external call via <script> tag into something callable from inside a another script tag. That is, how to put

<script src="https://api.reftagger.com/v2/RefTagger.js" type="text/javascript"></script>

Into the form

<script>
    // execute contents of https://api.reftagger.com/v2/RefTagger.js
</script>

The JavaScript I’m calling is fairly minified, which makes it hard to follow.

Closing observations

I’d appreciate any feedback/pointers. I am attempting to not just copy-paste stuff until something works, because I’d like to understand more or less what’s going on. I’m definitely not above using code that works and then tracing backwards though. :slightly_smiling_face:

Might any of this be made easier by editing template files directly on the hosting server?

1 Like

Hopefully this isn’t too much of a necrobump.

I have the script working on initial page load.

Customization code

<script>
	var refTagger = {
		settings: {
			bibleVersion: "NKJV",
			convertHyperlinks: true,		
			roundCorners: true,
			socialSharing: [],
			tagChapters: true
		}
	};
</script>

<script>
    function downloadJSAtOnload() {
        var element = document.createElement("script");
        element.src = "https://api.reftagger.com/v2/RefTagger.js";
        document.body.appendChild(element);
    }
    
    window.addEventListener("load", downloadJSAtOnload, false);
</script>

Working screenshot

Problems

The problem with this is that it only works when visiting a URL directly. Visiting a page from another page on the Discourse site or having more posts load from scrolling down are both situations in which the verse-tagging script does not work.

I’ve done some more reading (A tour of how the Widget (Virtual DOM) code in Discourse works), and am pretty sure that these problems are related to the tagging script not having access to memory objects in the virtual DOM:

This will work if you want something done when topic pages are opened:

const TopicRoute = require("discourse/routes/topic").default;
TopicRoute.reopen({
	activate: function() {
		this._super();
		Em.run.next(function() {
			// do stuff here when a topic page is opend
		});
	}
});

And you can do stuff HOWEVER, you wont be able to modify the contents of the topic because they are in virtual dom and that’s why jQuery was not able to modify the topic contents while it was able to do stuff like console.log and modify parts of the pre-existing actual dom…

My understanding is that the script I wish to use goes through the DOM while regex matching on Bible verses, creates links to a specific site for things it matches, and adds a particular class to these links. The reason why the script is not working on page change or when more posts are loaded is because the changes in content are not propagating to the actual HTML in the DOM, but are in memory in the virtual DOM.

This explains why I have had no luck with api.onPageChange() – calling the script again when the content changed would work fine if the new content actually showed up in the DOM. Since it does not, it makes sense that this call doesn’t make the script work on with content introduced with a page change or when more posts are loaded, since such content is in the virtual DOM

Moving forward

I feel like I understand the problem more now, but I think I’m now batting above my level. It seems like what I have to do would be to translate the JavaScript code that adds links of a specific class to Bible verses into something using the plugin API that operates on widgets, as described in Developer’s guide to Discourse Themes - decorateCooked().

The problem is, the JavaScript for the library is minified and obfuscated. For example, the first major function in the script, after being beautified, looks like

        function s(e, t) {
            var o, n, r, s, i, a, l, c, d, h, u, f = t && t.split("/"),
                p = T.map,
                g = p && p["*"] || {};
            if (e && "." === e.charAt(0))
                if (t) {
                    for (f = f.slice(0, f.length - 1), e = e.split("/"), i = e.length - 1, T.nodeIdCompat && x.test(e[i]) && (e[i] = e[i].replace(x, "")), e = f.concat(e), d = 0; d < e.length; d += 1)
                        if (u = e[d], "." === u) e.splice(d, 1), d -= 1;
                        else if (".." === u) {
                        if (1 === d && (".." === e[2] || ".." === e[0])) break;
                        d > 0 && (e.splice(d - 1, 2), d -= 2)
                    }
                    e = e.join("/")
                } else 0 === e.indexOf("./") && (e = e.substring(2));
            if ((f || g) && p) {
                for (o = e.split("/"), d = o.length; d > 0; d -= 1) {
                    if (n = o.slice(0, d).join("/"), f)
                        for (h = f.length; h > 0; h -= 1)
                            if (r = p[f.slice(0, h).join("/")], r && (r = r[n])) {
                                s = r, a = d;
                                break
                            } if (s) break;
                    !l && g && g[n] && (l = g[n], c = d)
                }!s && l && (s = l, a = c), s && (o.splice(0, a, s), e = o.join("/"))
            }
            return e
        }

If I could invoke the JavaScript on individual elements (like a <p> tag within a post) that might work too – but that’s not how the script is set up initially. (It targets the whole document).


Hopefully I’ve explained myself clearly. @Johani – your posts keep coming up in my search. Does what I’m writing seem to make sense? Suggestions?

3 Likes