Steemit Hits & Tips - Syntax Highlighting with TamperMonkey

in #steemit7 years ago (edited)

This post is part of the Steemit Hints & Tips Serie!

Let me know in the comments if you have have nice ideas to share!

hr

As we described before, Syntax Highlighting is not supported by Steemit. This feature, for obscure reasons, has been missing for years...

Here is a (honestly, poor...) workaround! We say it's poor because it will only allow you to add Highlighting support for yourself. It will not change the way other people see your posts.

So, to make Steemit a better place for your followers, please, recommend our solution for Syntax Highlighting with TamperMonkey!

Tampermonkey is the most popular userscript manager, with over 10 million users.

First, I'll let you install the TamperMonkey Chrome Extension.

TamperMonkey is also available for Firefox, Safari, Microsoft Edge, Opera, Dolphin Browser & UC Browser. Check TamperMonkey Website for details.

UserScript Description

Our script will integrate nicely in Steemit Toolbar:

By default, we configured our UserScript to highlight source code with the GitHub Styles. By clicking on the Syntax Highlighter icon you may either disable the syntax highlighting or choose another theme.

Note that when you change the theme for highlighting, TamperMonkey will remember this and reuse the same theme throughout Steemit. It will remember it even if you close and reopen your browser!

So, how do you get the UserScript to work on your browser?

Easy Solution

Just install our UserScript, we published it as a GitHub Gist.

Once you installed TamperMonkey extension to your browser, just visit our Gist page and click on the raw button:

And then, install:

If you check in TamperMonkey extension menu, you see that Syntax Highlighting for Steemit is now installed. From the menu, you can enable / disable the script and check for updates:

TamperMonkey is as safe as you allow it to be!!!

You should never blindly install a UserScript. Some scripts could steal your passwords, credit card numbers, etc. Be very careful and inspect the source code. Or, even better...

Write the script yourself!

We will guide you through the implementation of this TamperMonkey UserScript.

For the Syntax Highlighting, we chose Highlight.js. It supports 176 languages and 79 styles and also automatic language detection.

Automatic language detection is mandatory for our script because of the way Steemit renders code fragments. It simply drops the specified language.

The following Markdown code fragment:

```css
html {
    background-color: #a8a8a8;
}
` ``

is rendered as:

<pre>
    <code>
        html {
            background-color: #a8a8a8;
        }
    </code>
</pre>

While it should use <pre><code class="css">...</code></pre>.

UserScript definition

Every TamperMonkey UserScript starts with some metadata describing the script. Ours will look like this:

// ==UserScript==
// 🅰
// @name         Syntax Highlighting for Steemit 
// @version      1.1
// @description  Adding source code syntax highlighting to Steemit!
// @author       Grooviz
// @license      GPLv3 - http://www.gnu.org/licenses/gpl-3.0.txt
// 🅱
// @match        http*://*.steemit.com/*
// 🅲
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_setValue
// @grant        GM_getValue
// 🅳
// @require      https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js
// 🅴
// @resource     atom-one-dark https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-dark.min.css
// @resource     atom-one-light https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css
// @resource     dark https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/dark.min.css
// @resource     github https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css
// @resource     solarized-dark https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-dark.min.css
// @resource     solarized-light https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-light.min.css
// ==/UserScript==

🅰 We start with the UserScript name, version, description, author and license. The version is used to identify updates and should be incremented every time you commit changes.
🅱 We continue with the target url. Here, we will match all HTTP and HTTPS url on steemit.com.
🅲 We define which special TamperMonkey function will be used by our script. (note: GM stands for GreaseMonkey, a previous script manager for Firefox)
🅳 We require HighlightJS script
🅴 We define one resource for each theme we want to support. You may add as many or as few themes as you wish.

Then, our script will be encapsulated in a

(function() {
    'use strict';

    // Here goes your script!

})();

Our script will consist of:

(function() {
    'use strict';

    var timer; // 🅰
    var currentTheme; // 🅱

    function modifyDOM() { } // 🅲

    function toggle() { } // 🅳

    function switchTheme(theme) { } // 🅴

    switchTheme(GM_getValue('theme') || 'github'); // 🅵
    timer = setInterval(modifyDOM, 1000); // 🅶
    hljs.initHighlightingOnLoad(); // 🅷

})();

🅰 timer will be used to wait for the Toolbar DOM Element to be available.
🅱 currentTheme keeps track of the current theme's <style></style> DOM Element to easily replace it when switching themes.
🅲 The function modifyDOM() will add our Syntax Highlighting icon, switcher and related CSS Styles.
🅳 The function toggle() will be triggered when the user clicks on the icon to show / hide the dropdown selection of Highlight.js themes.
🅴 The function switchTheme(theme) will replace the current theme by a new one and remember it in TamperMonkey storage.
🅵 We get the last used theme from TamperMonkey variable storage, or we default to github theme.
🅶 We modify the DOM, using a Timer to wait for the Toolbar if needed.
🅷 Finally, we initialize the HighlightJS script.

Let's implement the modification of the DOM:

function modifyDOM() {
    GM_addStyle(` 
        /* 🅰 */
        .hidden { display: none; }
        #hljs-switcher > span {
            display: flex;
            flex-direction: row;
        }
        #hljs-switcher img {
            margin: 0 10px;
        }
        /* 🅱 */
        pre > code {
            overflow: auto !important;
            margin-bottom: 2em;
            padding: 1em !important;
        }
    `);

    // 🅲
    var toolbar = document.getElementsByClassName("Header__buttons")[0];
    if (toolbar) {

        clearTimeout(timer);

        // 🅳
        var switcher = document.createElement("div");
        switcher.setAttribute("id", "hljs-switcher");
        switcher.innerHTML = `
            <span class="Header__search--desktop">
                &lt;img alt="highligh" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAgCAIAAACKIl8oAAABwElEQVRIx71WPUvDUBQ99yZpFVEH6eCgWwUFwS39BSKICCIK/oAuFnTzNzjpKF11Udz8ExV3EcWpigiiaGlr2+Q9h4THaxpbkybeIVxu3jvvvPPO+yDbtpFOmAAqlUriuIVCgZFamCq7upz4Y5/VjTfmzMBmcVi7biMa67XNr7QECcTWcbByvgeiCNARBHFlQqzPSuiiSDA5IeiM9ifUPN7aTF+XVeXFLg7rkGFZ67G89ijcb71iGKMAXjuNm6UdH4g4DnQ2m1O5aDfuSrnQZu8AgIVyPQgtpZRSEhFpFlNFAN6XrZE4ghCREIKImDmAqxKA8of3rtNhZiklJJjJmpoFUG3VhOtWW7WZ7HgXtNfTMAxVcRzHsixfByH8pNN8OJhTbbzpK5/oJunnEH0Ybx4AyMwk4BAppZ77irORP3pmAkAAnPoHET0tbgMQwpVOW41t9sIJITy+ROQh9tky3tr4owqhnzIcMIPC7SUeGgFHhQvSu4xK3z7nrY6r2g/Q+rY4FqjMn3wSm6kcqkjqUFX79T9uGRWnu8hayQoS8br5lfXFfprvkJX1qmVNxobmvu+NZjKsE3+HpHg3UnqP4B/ZuaLIOF8kiwAAAABJRU5ErkJggg==">
                <select id="hljs-themes" class="hidden">
                    <option value="DISABLED">DISABLED</option>
                </select>
            </span>
        `;
        toolbar.appendChild(switcher);

        // 🅴
        switcher.getElementsByTagName("img")[0].addEventListener("click", toggle);

        // 🅵
        var themes = ["agate", "androidstudio", "arduino-light", "arta", "ascetic", "atelier-cave-dark", "atelier-cave-light", "atelier-dune-dark", "atelier-dune-light", "atelier-estuary-dark", "atelier-estuary-light", "atelier-forest-dark", "atelier-forest-light", "atelier-heath-dark", "atelier-heath-light", "atelier-lakeside-dark", "atelier-lakeside-light", "atelier-plateau-dark", "atelier-plateau-light", "atelier-savanna-dark", "atelier-savanna-light", "atelier-seaside-dark", "atelier-seaside-light", "atelier-sulphurpool-dark", "atelier-sulphurpool-light", "atom-one-dark", "atom-one-light", "brown-paper", "codepen-embed", "color-brewer", "darcula", "dark", "darkula", "default", "docco", "dracula", "far", "foundation", "github-gist", "github", "googlecode", "grayscale", "gruvbox-dark", "gruvbox-light", "hopscotch", "hybrid", "idea", "ir-black", "kimbie.dark", "kimbie.light", "magula", "mono-blue", "monokai-sublime", "monokai", "obsidian", "ocean", "paraiso-dark", "paraiso-light", "pojoaque", "purebasic", "qtcreator_dark", "qtcreator_light", "railscasts", "rainbow", "routeros", "school-book", "solarized-dark", "solarized-light", "sunburst", "tomorrow-night-blue", "tomorrow-night-bright", "tomorrow-night-eighties", "tomorrow-night", "tomorrow", "vs", "vs2015", "xcode", "xt256", "zenburn"];
        var themeSelect = switcher.getElementsByTagName('select')[0];
        var currentThemeName = GM_getValue('theme');
        themes.forEach(function(theme) {
            var opt = document.createElement("option");
            opt.value= theme;
            opt.innerHTML = theme;
            opt.selected = (theme == currentThemeName);
            themeSelect.appendChild(opt);
        });
        // 🅶
        themeSelect.addEventListener("change", function() {
            switchTheme(themeSelect.options[themeSelect.selectedIndex].value);
        });

    }
}

OK, lot's of things to cover here!

Let's go step by step...

🅰 We add some basic styling for the switcher inside the Toolbar:

  • We use a .hidden class to show / hide the dropdown selection of themes.
  • The icon and dropdown will be organized as a horizontal Flex Box.
  • We add some margins left and right of the icon.

🅱 We fix some padding and margin on the code fragments. We also hide the scrollbars when it's not needed.

🅲 We try to get a reference to the Toolbar. Once we get the Toolbar, we clear the timer. Otherwise, we return and wait for the next interval call to modifyDOM

🅳 We create our switcher as a div containing the icon and the dropdown with just a DISABLED option. Note that we embedded the icon as a data string. We generated this data string using the Image to data-URI Converter from websemantics.uk. Just drag and drop your icon and copy the resulting img code.

🅴 We register the toggle() function for the click event on the icon.

🅵 We populate the dropdown with all the themes, making sure to select the current one. Make sure that all these themes have been defined in the script metadata.

🅶 We register the function switchTheme(theme) to the changeevent on the dropdown.

Now, we just need to implement two small functions and our script will be ready!

toggle() is really basic:

function toggle() {
    document.getElementById("hljs-themes").classList.toggle("hidden");
}

switchTheme(theme) is a tid bit more complex:

function switchTheme(theme) {
    if (currentTheme) { currentTheme.remove(); } // 🅰
    if (theme == "DISABLED") { return; } // 🅱

    // 🅲
    var cssStr = GM_getResourceText(theme);
    const regex = /(background:#.{6})/g;
    const subst = `$1 !important`;
    cssStr = cssStr.replace(regex, subst);

    currentTheme = GM_addStyle(cssStr); // 🅳
    GM_setValue('theme', theme); // 🅴
}

🅰 Remove the current theme if defined

🅱 If the user is disabling highlighting, we're done!

🅲 ⚠ The background defined by Highlight.js is overwritten by the Markdown styles, making all the dark temes useless. Before we add the theme's styles, we will switch on the !important flag on the background property.

🅳 We add the new theme's CSS

🅴 We remember the new theme in TamperMonkey store

We're done!

Remember, the script is available as a GitHub Gist.

Once you implement (or install) this UserScript, refresh this page and see how much more readable the post becomes!

hr

Have a look at our Series:

Stay tuned! We're just getting started!