mindoc.js

GitHub: https://bitfragment.github.io/mindoc

Begin module.

var mindoc = (function() {

Handling Pandoc output

Return a capitalized string

    function capitalize(str) {
        str = str.toLowerCase();
        return str.charAt(0).toUpperCase() + str.substr(1);
    }

Dehyphenate a string of hyphenated words

Replace hyphens with spaces.

    function deHyphenateWords(str) {
        var re = new RegExp(/^\b[a-z]\S+\b-\b\S+\b/);
        if (re.test(str)) str = str.replace(/-+/g, ' ');
        return str;
    }

Resolve Pandoc section ‘id’ attributes

Replace hyphens with spaces and capitalize the first (or only) word.

    function resolvePandocId(id) {
        id = deHyphenateWords(id);
        return capitalize(id);
    }

DOM manipulation

Test, add, and remove DOM element class attributes

Adapted from http://jaketrent.com/post/addremove-classes-raw-javascript/

Pandoc interprets $ in a template as the start of a template variable, so we have to avoid the regexp metacharacter $ here. Instead, we match space or nothing: (\s|)

    function hasClass(element, cls) {
        return !!element.className.match(new RegExp('(\\s|^)' + cls + '(\\s|)'));
    }

    function addClass(element, cls) {
        if (!hasClass(element, cls)) element.className += ' ' + cls;
    }

    function removeClass(element, cls) {
        if (hasClass(element, cls)) {
            var re = new RegExp('(\\s|^)' + cls + '(\\s|)');
            element.className = element.className.replace(re, ' ');
        }
    }

Add Pure.css classes

    function addPureClasses() {

        var elts, eltslen,
            pureClasses = {
                'table': 'pure-table pure-table-bordered'
            };

        Object.keys(pureClasses).forEach(function(key) {
            elts = document.getElementsByTagName(key);
            eltslen = elts.length;
            if (eltslen > 1) {
                for (var i = 0; i < eltslen; i++) {
                    addClass(elts[i], pureClasses[key]);
                }
            }
        });

    }
    function addRefLinkEventListeners(citeElts, refsElt) {
        for (var i = 0, l = citeElts.length; i < l; i++) {
            var citeLinks = citeElts[i].getElementsByTagName('a');
            for (var j = 0, ll = citeLinks.length; j < ll; j++) {
                citeLinks[j].addEventListener('click', function() {
                    removeClass(refsElt, 'hidden');
                });
            }
        }
    }
    function addFnLinkEventListeners(fnElts, notesElt) {
        for (var i = 0, l = fnElts.length; i < l; i++) {
            fnElts[i].addEventListener('click', function() {
                removeClass(notesElt, 'hidden');
            });
        }
    }

Create navigation

When Pandoc is invoked with its --section-divs option, HTML output includes the class ‘level’ + n (where n is an integer) added to <div> elements enclosing document sections, plus ‘id’ attributes generated from header text:

<div id="foo" class="section level2">
<h2>Foo</h2>
...
</div>

If html5 is specified as Pandoc’s output format, Pandoc creates <section> elements instead:

<section id="foo" class="level2">
<h2>Foo</h2>
...
</div>

Create a menu list item element

    function createMenuListItemElt() {
        var elt = document.createElement('li');
        addClass(elt, 'pure-menu-item');
        return elt;
    }

Create a menu list item anchor element

    function createMenuAElt(sectionId) {
        var elt = document.createElement('a');
        elt.id = 'menu-' + sectionId;
        elt.href = '#';
        elt.innerHTML = resolvePandocId(sectionId);
        addClass(elt, 'pure-menu-link');
        return elt;
    }

Create and add nav menu to the page

    function createNavigation(sectionElts) {
        var docFrag = document.createDocumentFragment(),
            navNav  = document.createElement('nav'),
            navDiv  = document.createElement('div'),
            navList = document.createElement('ul');

        docFrag.appendChild(navNav);
        navNav.appendChild(navDiv);
        navDiv.appendChild(navList);
        addClass(navDiv, 'pure-menu');
        addClass(navList,  'pure-menu-list');

Add ‘All sections’ as first menu item.

        var allSectionsId = 'All sections',
            allSections = createMenuListItemElt();

        addClass(allSections, 'pure-menu-selected')
        navList.appendChild(allSections);
        allSections.appendChild(createMenuAElt(allSectionsId));

Add menu list items and links, using ‘id’ attributes of section elements.

        var sectionId, menuListElement;
        for (var i = 0, l = sectionElts.length; i < l; i++) {
            sectionId = sectionElts[i].getAttribute('id');
            menuListElement = createMenuListItemElt();
            navList.appendChild(menuListElement);
            menuListElement.appendChild(createMenuAElt(sectionId));
        }

        var pageContent = document.getElementById('page-content');
        document.querySelector('body').insertBefore(docFrag, pageContent);
    }

Toggle selected menu element

Toggle by removing a class from the currently selected element and adding it to the element passed to this function.

    function toggleSelectedMenuElt(elt) {
        var selectedElement;
        if (!elt.hasAttribute('pure-menu-selected')) {
            selectedElement = document.querySelector('.pure-menu-selected');
            removeClass(selectedElement, 'pure-menu-selected');
            addClass(elt, 'pure-menu-selected');
        }
    }

Event listener to show and hide document sections

Toggle the selected menu item, then hide the document section currently shown and show the section with the relevant id attribute.

    function showHideSectionElts(sectionElts, link) {
        var elt,
            clickedId = link.getAttribute('id'),
            resolvedClickedId = clickedId.replace(/menu-/, ''),
            clickedParent = document.getElementById(clickedId).parentNode;

        toggleSelectedMenuElt(clickedParent);

        for (var section in sectionElts) {
            elt = sectionElts[section];
            if (!hasClass(elt, 'hidden')) addClass(elt, 'hidden');
            if (elt.getAttribute('id') === resolvedClickedId &&
                hasClass(elt, 'hidden')) removeClass(elt, 'hidden');
        }
    }

Event listener to show all document sections

    function showAllSectionElts(sectionElts) {
        var elt;
        for (var section in sectionElts) {
            elt = sectionElts[section];
            if (hasClass(elt, 'hidden')) removeClass(elt, 'hidden');
        }
    }
    function addMenuEventListeners(sectionElts) {
        var menuLinks = document.querySelectorAll('.pure-menu-link');
        for (var i = 0, l = menuLinks.length; i < l; i++) {
            if (i === 0) { // 'All sections' link
                menuLinks[i].addEventListener('click', function() {
                    showAllSectionElts(sectionElts);
                });
            } else { // Other section links
                menuLinks[i].addEventListener('click', function() {
                    showHideSectionElts(sectionElts, this);
                });
            }
        }
    }

Main function

    return {

        main: function() {

Add selected Pure.css classes

            addPureClasses();

Collect document section elements

Test to see if Pandoc was invoked with --section-divs.

            if (document.getElementsByClassName('level2').length > 0) {
                var elts, sectionElts = [];

                ['abstract', 'level2', 'footnotes'].forEach(function(cls) {
                    elts = document.getElementsByClassName(cls);
                    for (var i = 0, l = elts.length; i < l; i++) {
                        sectionElts.push(elts[i]);
                    }
                });
            } else {

If Pandoc wasn’t invoked with --section-divs, we’re done here.

                return;
            }

Create navigation menu

            var elt;

Pandoc 1.17.1 gives all sections the class ‘level2’ except for the footnotes section. We want the footnotes section to appear in the navigation menu, so we add it here. Pandoc 1.17.1 does not give the footnotes section an ‘id’ attribute, so we add that here too.

            for (var section in sectionElts) {
                elt = sectionElts[section];

Add class ‘level2’ to any element that does not have it, so that it appears in the navigation menu.

                if (!hasClass(elt, 'level2')) addClass(elt, 'level2');

Add an ‘id’ attribute to the footnotes section.

                if (hasClass(elt, 'footnotes')) elt.setAttribute('id', 'footnotes');
            }

Now create the navigation menu.

            createNavigation(sectionElts);

Add click handlers to menu items.

            addMenuEventListeners(sectionElts);

Add event listeners to other elements

Add click handlers to reference links.

            var citeElts = document.getElementsByClassName('citation'),
                refsElt  = document.getElementById('references');

            addRefLinkEventListeners(citeElts, refsElt);

Add click handlers to footnote links.

            var fnElts = document.getElementsByClassName('footnoteRef'),
                notesElt = document.getElementById('footnotes');

            addFnLinkEventListeners(fnElts, notesElt);

        }
    }

End module.

}());

Load event listener

window.addEventListener('load', function() {
    mindoc.main();
});
h