Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@mariolopjr
Last active December 30, 2023 23:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mariolopjr/926875d4e7b6453378d2be5091cdfdbc to your computer and use it in GitHub Desktop.
Save mariolopjr/926875d4e7b6453378d2be5091cdfdbc to your computer and use it in GitHub Desktop.
Prism Script for Ghost
// License: MIT
Prism.plugins.autoloader.languages_path = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/';
Prism.plugins.autoloader.ignored_language = 'none'; // Update to allow arrays...
Prism.plugins.NormalizeWhitespace.setDefaults({
'remove-trailing': true,
'remove-indent': true,
'left-trim': true,
'right-trim': true,
});
var codes = $('code');
codes.addClass( 'line-numbers' );
$(document).ready(() => {
codes.each( ( i, el ) => {
var code = $( el );
var nonum = code.hasClass( 'language-nonum' );
const regex = /PS[a-zA-z]:\\/g;
if ( nonum ) {
code.children( 'span.line-numbers-rows' ).remove();
code.removeClass( 'language-nonum' );
// Construct classes
var codeClass = code.attr( 'class' );
var s = codeClass.substring(1, codeClass.length).split('-');
codeClass = 'language-' + s[0];
code.attr( 'class' , codeClass );
// Sets command line user if exists (enabled command line for code block)
var host = s.length >= 2 ? s[1] === 'l' ? 'localhost' : s[1] : 'none';
if ( host !== 'none' ) {
if ( host.match(regex) !== null ) {
code.parent().attr( { 'data-prompt': 'PS ' + host.slice(2) } );
} else {
code.parent().attr( { 'data-host': host } );
}
codeClass += ' command-line'
}
// Sets command line user (enabled command line for code block)
var user = s.length >= 3 ? s[2] === 'ps' ? 'none' : s[2] : 'none';
if ( user !== 'none' ) {
code.parent().attr( { 'data-user': user } );
}
// Set command line output lines if exists
var lines = '';
if ( s.length > 4 ) {
lines = s[3];
for (var i of s.slice(4)) {
lines += '-' + i;
}
}
else if ( s.length === 4 ) {
lines = s[3];
}
if ( s.length >= 4 ) {
code.parent().attr( { 'data-output': lines } );
}
code.parent().attr( 'class' , codeClass );
Prism.highlightElement(code[0]);
}
});
});
Prism.plugins.autoloader.languages_path='https://cdnjs.cloudflare.com/ajax/libs/prism/1.9.0/components/',Prism.plugins.autoloader.ignored_language='none',Prism.plugins.NormalizeWhitespace.setDefaults({'remove-trailing':!0,'remove-indent':!0,'left-trim':!0,'right-trim':!0});var codes=$('code');codes.addClass('line-numbers'),$(document).ready(()=>{codes.each((a,b)=>{var c=$(b),d=c.hasClass('language-nonum');const e=/PS[a-zA-z]:\\/g;if(d){c.children('span.line-numbers-rows').remove(),c.removeClass('language-nonum');var f=c.attr('class'),g=f.substring(1,f.length).split('-');f='language-'+g[0],c.attr('class',f);var h=2<=g.length?'l'===g[1]?'localhost':g[1]:'none';'none'!==h&&(null===h.match(e)?c.parent().attr({'data-host':h}):c.parent().attr({'data-prompt':'PS '+h.slice(2)}),f+=' command-line');var j=3<=g.length?'ps'===g[2]?'none':g[2]:'none';'none'!==j&&c.parent().attr({'data-user':j});var k='';if(4<g.length){k=g[3];for(var a of g.slice(4))k+='-'+a}else 4===g.length&&(k=g[3]);4<=g.length&&c.parent().attr({'data-output':k}),c.parent().attr('class',f),Prism.highlightElement(c[0])}})});
@kirvedx
Copy link

kirvedx commented Dec 19, 2023

Here's a vanilla implementation that works for current Ghost (5.74.5) + PrismJS (12/19/2023):

I hope you'll accept the MIT license, and it would actually be great if you could license your gist as MIT so that mine would be valid whilst giving credit to you. I wholly rewrote it but took alot of inspiration from your script. The Apache-2.0 WITH LLVM-exception license is compatible as well

prism-custom__vanilla--modified.js

/**
 * SPDX-PackageName: Custom Prism Helper
 * SPDX-PackageVersion: 0.0.1
 * SPDX-FileCopyrightText: © 2023 Mario Lopez <@mariolopjr (GitHub)>
 *                         © 2023 Richard Winters <kirvedx@gmail.com> and contributors
 * SPDX-License-Identifier: (Apache-2.0 WITH LLVM-exception OR MIT)
 */

// Make an array of all <code> elements using [modern] vanilla javascript
var codes = [ ...document.getElementsByTagName( 'code' ) ];

// When the document is ready
document.addEventListener("DOMContentLoaded", () => {
    console.log( 'Custom Prism Helper Triggered' );

    // Set the regex for checking for a powershell prompt
    const regex = /PS[a-zA-z]:\\/g;

    // For each <code> element found, check for shorthand
    codes.forEach( ( code ) => {
        // Check if the element's first class includes a substring flag
        let nonum = code?.classList[0]?.includes( 'language-nonum' );

        // If the flag wasn't found, ensure line-numbers are enabled
        if( !nonum ) {
            // Add the `line-numbers` spec to <code> and its parent <pre>
            code.classList.add( 'line-numbers' );
            code.parentElement.classList.add( 'line-numbers' );
        }
        else {
            // Otherwise, remove line numbers and determine if we are to
            // create command-line output - leveraging provided markdown shorthand
            let providedClass = code.classList[0];
            let codeClass = "language-";

            // Normalize the <code> and the outer <pre>
            // by removing the added line-numbers
            let targetChildren = [ ...code.getElementsByClassName( 'line-numbers-rows' ) ];
            if( targetChildren.length )
                targetChildren.forEach( ( child ) => code.removeChild( child ) );

            // and the injected shorthand (which gets duplicated to the pre)
            code.classList.remove( providedClass );
            code.classList.remove( 'line-numbers' );
            code.parentElement.classList.remove( providedClass );
            code.parentElement.classList.remove( 'line-numbers' );

            // Here we split the compiled class name by its hyphens, to interpret the
            // shorthand
            let s/*egments*/ = providedClass.split( '-' );

            /**
             * For the example language-nonum-bash-devrik-rik-2-3
             *
             * language is s[0], and is the language prefix added by markdown
             * nonum is s[1], and is the first bit of the string we'd have provided in the codeblock (i.e.```nonum-)
             * bash is s[2], and is the language spec
             * devrik is s[3], and is the positional argument reserved for command-line -host- name.
             * rik is s[4], and is the positional argument reserved for command-line -user- name
             * everything else is the specification of the output range if present.
             */
            const clCount = s.length,
                  clLang = s[2],
                  clHost = ( clCount >= 4 ) ? s[3] === 'l' ? 'localhost' : s[3] : 'none',
                  clUser = ( clCount >= 5 ) ? s[4] === 'ps' ? 'none' : s[4] : 'none';

            // Create the true language class
            codeClass += clLang;

            // And distribute the true language class to the <code> and outer <pre>
            code.classList.add( codeClass );
            code.parentElement.classList.add( codeClass );

            // Sets command line user if exists (and enables command line output for code block)
            if( clHost !== 'none' ) {
                // Check for powershell spec
                if( clHost.match( regex ) !== null ) {
                    // i.e. language-nonum-powershell-PSC:\Users\Chris>-ps-2-19
                    // Here we set from 'C:\Users\Chris>` as prompt attribute
                    let promptAttribute = document.createAttribute( "data-prompt" );
                    promptAttribute.value = 'PS ' + clHost.slice( 2 );
                    code.parentElement.attributes.setNamedItem( promptAttribute );
                }
                else {
                    // Otherwise, prepare a terminal output style
                    let hostAttribute = document.createAttribute( "data-host" );
                    hostAttribute.value = clHost;
                    code.parentElement.attributes.setNamedItem( hostAttribute );
                }

                // Our rule of thumb is that a host must be set to enable command-line
                // output - so we'll add the 'command-line' class to the <pre>, per
                // the plug-in's instructions
                code.parentElement.classList.add( 'command-line' );
            }

            // Sets command line user (when command line output is flagged)
            if( clUser !== 'none' ) {
                let userAttribute = document.createAttribute( "data-user" );
                userAttribute.value = clUser;
                code.parentElement.attributes.setNamedItem( userAttribute );
            }

            // Set command line output lines if exists (limited to single line or range at this time)
            let lines = '';

            // If there are more than enough indices to specify at least a single output line
            if( clCount >= 6 ) {
                // Indicate the first output line
                lines += s[5];

                // If there are more than a single output line specified
                if( clCount > 6 ) {
                    // Then capture the end of the range
                    for( let i of s.slice(6)) {
                        lines += '-' + i;
                    }
                }

                // Since we're already acting on known circumstances, specify the output lines
                let outputAttribute = document.createAttribute( "data-output" );
                outputAttribute.value = lines;
                code.parentElement.attributes.setNamedItem( outputAttribute );
            }

            // Finally, run highlight on the resulting <code>
            Prism.highlightElement( code );

            // Changes to the <code>'s parent <pre> were for styling only, do not
            // highlight the parent, or the <code> will be removed.
        }
    });
});

@kirvedx
Copy link

kirvedx commented Dec 19, 2023

For my use-case, I only wanted to be able to dictate nonum (or not), and/or specify command-line or powershell output using a markdown code-block, rather than having to use raw HTML. So the implementation I provide does only that - responding to nonum and additionally creating command-line or powershell output if also specified (requires nonum).

My implementation is based on self-hosting the components, so I downloaded the components to /var/www/<ghost-app>/versions/<ver>/themes/assets/components, and I symlinked components directly to assets/built/components. I drop this file, along with my basic prism.js into assets/js/lib and run npm run zip.

I did have to do an add_header directive (Content-Security-Policy spec) in my nginx configuration:

add_header Content-Security-Policy "script-src * 'self' 'unsafe-eval' 'unsafe-inline' blob: data:; script-src-elem * 'self' 'unsafe-eval' 'unsafe-inline' blob: data:" always;

Place it just inside of the location / {} directive. I tried other variations, but everything started working properly once I did this. I'd recommend getting specific and doing a granting list which properly specifies all sources, rather than blindly accepting every source like I did; It's only provided this way for insight.

The steps will be similar and can be adjusted for whatever theme you're using - in my case I'm using a custom source theme I've dubbed authoritative.

Notice (those of you borrowing) the use of rest spread, and null safety; Keep this in mind with regards to compatibility!

Thanks for the idea, the inspiration, and the effort - and more importantly for sharing it!

Top - Raw HTML, Bottom - Helper
Top (Raw HTML) Bottom (Helper)

Implementation for above
Implementation for Helper variant above

Log file, tail'd in command-line
Log file, tail'd in command-line

Implementation for tail'd log
Implementation for tail'd log

Helper doesn't break anything
Helper doesn't break anything

Clean console
Clean console

Content Security Policy
Content Security Policy

@mariolopjr
Copy link
Author

@kirvedx wow this is very detailed! Yes, feel free to use, modify, etc! I haven't used Ghost for years so this was a blast from the past!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment