Dan Dan - 5 months ago 10
Node.js Question

How to implement a tabbed codeblock tag for Hexo

I am trying to create tabbed code-blocks (as a tag plugin) in hexo but I cannot figure out where to put my js function. I thought I could load the function using the js helper but I don't know where to include the helper. I tried, and failed, to add it into the tag plugin. This is the tag plugin code (saved as testtag.js):

hexo.extend.tag.register('testtag', function(args, content){
var className = args.join(' ');

var result = '';
result += "<\%- js('\\themes\\bootstrap-blog\\scripts\\tab.js') \%>"
result += '<div class="tabs">';
result += '<ul>';
result += '<li class="li_tab1" onclick="tab(&apos;tab1&apos;)"><a>Tab 1</a></li>';
result += '<li class="li_tab2" onclick="tab(&apos;tab2&apos;)"><a>Tab 2</a></li>';
result += '</ul>';
result += '<div class="contentarea">';
result += '<div id="tab1">';
result += '<p>' + content + '</p>';
result += '</div>';
result += '<div id="tab2" style="display: none;">'
result += '<p>This is the text for tab 2.</p>'
result += '</div>'
result += '</div>'
result += '</div>'

return result;

}, {ends: true});


which does work. However, the onclick event of the tags just raises the error that it can't find the
tab
function. Note that the first line of
result
above was my failed attempt to use the helper.

This is my
tab
function, tab.js:

function tab(tab) {
document.getElementById('tab1').style.display = 'none';
document.getElementById('tab2').style.display = 'none';
document.getElementById('li_tab1').setAttribute("class", "");
document.getElementById('li_tab2').setAttribute("class", "");
document.getElementById(tab).style.display = 'block';
document.getElementById('li_'+tab).setAttribute("class", "active");
}


Both tab.js and testtag.js as saved in the *\themes\bootstrap-blog\scripts* folder.

I saw this answer which I though might help but I can't figure out what a view is. I couldn't find anything about views in the Hexo docs.

Answer

There are too many mistake in your code so I prefer to give you a full example with explanations.

Here is what we need :

  1. A custom tag to build the HTML structure of this multiple codeblock
  2. A CSS file to stylize the code block and code coloration
  3. A JS script to animate the codeblock (tabs)

Custom tag : m_codeblock (JS server side)

We need to allow user to define :

  • a name or a link
  • multiple codeblock in this tab, so we defined a tag to separate and list each tab

Here is the syntax :

{% m_codeblock [name] [link] %}
    <!-- tab [lang] -->
        source_code
    <!-- endtab -->
{% endm_codeblock %}

and an example :

{% m_codeblock stack overflow https://example.fr %}
    <!-- tab html -->
        <html>
            <body>
                <h1>Hey dan</h1>
            </body>
        </html>
    <!-- endtab -->
    <!-- tab css -->
        h1 {
            color:red;
        }
    <!-- endtab -->
{% endm_codeblock %}        

Install these dependencies in your blog folder (not theme folder):

  • run npm install jsdom --save
  • run npm install jquery --save

and here is the source code of this custom tag put in themes/theme_name/scripts/m_codeblock.js:

'use strict';

var util = require('hexo-util');
var highlight = util.highlight;
var stripIndent = require('strip-indent');
var rCaptionUrl = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)/i;
var rCaption = /(\S[\S\s]*)/;
var rTab = /<!--\s*tab (\w*)\s*-->\n([\w\W\s\S]*?)<!--\s*endtab\s*-->/g;

// create a window with a document to use jQuery library
require("jsdom").env("", function(err, window) {
    if (err) {
        console.error(err);
        return;
    }

    var $ = require("jquery")(window);

    /**
     * Multi code block
     * @param args
     * @param content
     * @returns {string}
     */
    function multiCodeBlock(args, content) {
        var arg = args.join(' ');
        // get blog config
        var config = hexo.config.highlight || {};

        if (!config.enable) {
            return '<pre><code>' + content + '</code></pre>';
        }

        var html;
        var matches = [];
        var match;
        var caption = '';
        var codes = '';

        // extract languages and source codes
        while (match = rTab.exec(content)) {
            matches.push(match[1]);
            matches.push(match[2]);
        }
        // create tabs and tabs content
        for (var i = 0; i < matches.length; i += 2) {
            var lang = matches[i];
            var code = matches[i + 1];
            var $code;
            // trim code
            code = stripIndent(code).trim();
            // add tab
            // active the first tab
            if (i == 0) {
                caption += '<li class="tab active">' + lang + '</li>';
            }
            else {
                caption += '<li class="tab">' + lang + '</li>';
            }
            // highlight code
            code = highlight(code, {
                lang: lang,
                gutter: config.line_number,
                tab: config.tab_replace,
                autoDetect: config.auto_detect
            });
            // used to parse HTML code and ease DOM manipulation
            // display the first code block
            $code = $('<div>').append(code).find('>:first-child');
            if (i == 0) {
                $code.css('display', 'block');
            }
            else {
                $code.css('display', 'none');
            }

            codes += $code.prop('outerHTML');
        }
        // build caption
        caption = '<ul class="tabs">' + caption + '</ul>';
        // add caption title
        if (rCaptionUrl.test(arg)) {
            match = arg.match(rCaptionUrl);
            caption = '<a href="' + match[2] + match[3] + '">' + match[1] + '</a>' + caption;
        }
        else if (rCaption.test(arg)) {
            match = arg.match(rCaption);
            caption = '<span>' + match[1] + '</span>' + caption;
        }
        codes = '<div class="tabs-content">' + codes + '</div>';
        // wrap caption
        caption = '<figcaption>' + caption + '</figcaption>';
        html = '<figure class="highlight multi">' + caption + codes + '</figure>';
        return html;
    }

    /**
     * Multi code block tag
     *
     * Syntax:
     *   {% m_codeblock %}
     *   <!-- tab [lang] -->
     *       content
     *   <!-- endtab -->
     *   {% endm_codeblock %}
     * E.g:
     *   {% m_codeblock %}
     *   <!-- tab js -->
     *       var test = 'test';
     *   <!-- endtab -->
     *   <!-- tab css -->
     *       .btn {
     *           color: red;
     *       }
     *   <!-- endtab -->
     *   {% endm_codeblock %}
     */
    hexo.extend.tag.register('m_codeblock', multiCodeBlock, {ends: true});
});

Read the comment to understand the code.

All you need to do is put your JavaScript files in the scripts folder and Hexo will load them during initialization.

Stylize the code block

By default, only the first tab is displayed and others hidden and we did that in the custom tag source code here :

$code = $('<div>').append(code).find('>:first-child');
if (i == 0) {
  $code.css('display', 'block');
}
else {
  $code.css('display', 'none');
}

So you just need more css to improve user interface and code coloration. Put this file in theme/theme_name/assets/css/style.css and link it to a layout.

Animate the code block (JS client side)

We need some javascript to animate the tab. When we click on a tab, all tab contents must be hidden and only the right tab displayed. Put this script in theme/theme_name/assets/js/script.js and link it to a layout.

$(document).ready(function() {
  $('.highlight.multi').find('.tab').click(function() {
    var $codeblock = $(this).parent().parent().parent();
    var $tab = $(this);
    // remove `active` css class on all tabs
    $tab.siblings().removeClass('active');
    // add `active` css class on the clicked tab
    $tab.addClass('active');
    // hide all tab contents
    $codeblock.find('.highlight').hide();
    // show only the right one
    $codeblock.find('.highlight.' + $tab.text()).show();
  });  
});

Your issue was the opportunity to build this custom tag and I'm gonna integrate it in the next release of a hexo theme (Tranquilpeak) that I developed.

Here is the result : result

Check it live on JSFiddle

Comments