Kanka Automatic Table of Contents

Automatically adds a table of contents to Kanka entity pages under the Pins sidebar.

目前为 2021-07-16 提交的版本。查看 最新版本

// ==UserScript==
// @name         Kanka Automatic Table of Contents
// @namespace    http://tampermonkey.net/
// @version      2
// @description  Automatically adds a table of contents to Kanka entity pages under the Pins sidebar.
// @author       Salvatos
// @match        https://kanka.io/*
// @icon         https://www.google.com/s2/favicons?domain=kanka.io
// @grant        GM_addStyle
// ==/UserScript==

// Run only on entity Story pages
if (document.getElementById('app').parentNode.classList.contains("entity-story")) {
    GM_addStyle(`
	.entity-links {
		margin-bottom: 20px;
	}
	div#toc h3 {
	    text-align: center;
	    margin: 0 0 10px;
	    font-family: Source Sans Pro,Helvetica Neue,Helvetica,Arial,sans-serif;
	    font-weight: bold;
	    font-size: 17px;
	    color: #444;
	}
	#tableofcontent, #tableofcontent ul {
	    list-style: none;
	    padding: 0;
	    text-indent: -5px;
	}
	#tableofcontent {
	    padding: 0 10px;
	    overflow: hidden;
	    word-wrap: anywhere;
	}
	#tableofcontent ul {
	    padding-left: 10px;
	}
	#tableofcontent a {
	    font-size: 14px;
	    color: #3e456c;
	}
	#tableofcontent li.toc-level-0 a {
	    font-weight: bold;
	}
	.to-top {
	    vertical-align: super;
	    font-variant: all-petite-caps;
	    font-size: 10px;
	}
	`);

	/* Set arrays */
	var headings = [];
	var tag_names = { h1:1, h2:1, h3:1, h4:1, h5:1, h6:1 };

	/* Walks through DOM looking for selected elements */
	function walk( root ) {
	    if( root.nodeType === 1 && root.nodeName !== 'script' && !root.classList.contains("calendar") && !root.classList.contains("modal") ) { // Added checks to exclude modals and calendar dates
	        if( tag_names.hasOwnProperty(root.nodeName.toLowerCase()) ) {
	            headings.push( root );
	        } else {
	            for( var i = 0; i < root.childNodes.length; i++ ) {
	                walk( root.childNodes[i] );
	            }
	        }
	    }
	}
	walk( document.getElementsByClassName('entity-story-block')[0] );

	/* Start main list */
	var level =0;
	var past_level = 0;
	var hList= `
		<div id='toc' class='box box-solid' style='padding:10px;'>
		<h3>Table of contents</h3>
		<ul id='tableofcontent'>
	`;

	/* Create sublists to reflect heading level */
	for( var i = 0; i < headings.length; i++ ) {
	    // "Entry" and entity note titles act as level-0 headers
	    level = (headings[i].classList.contains("box-title")) ? 0 : headings[i].nodeName.substr(1);

	    if (level > past_level) { // Go down a level
	        for(var j = 0; j < level - past_level; j++) {
	            hList += "<li><ul>";
	        }
	    }
	    else if (level < past_level) { // Go up a level
	        for(var j = 0; j < past_level - level; j++) {
	            hList += "</ul></li>";
	        }
	    }

	    /* Create ID anchor on header */
	    headings[i].id = "h" + level + "-" + headings[i].innerText.replace(/\s/g, "");
        /* Create link in TOC */
	    hList += "<li class='toc-level-" + level + "'><a href='#" + headings[i].id + "'>" + headings[i].innerText + "</a></li>";
        /* Add "top" link to header */
	    headings[i].insertAdjacentHTML("beforeend", "<a class='to-top' href='#toc'> ^ top</a>");

        /* Update past_level */
	    past_level = level;
	}

	/* Close sublists per current level */
	for(var k = 0; k < past_level; k++) {
	    hList += "</li></ul>";
	}
	/* Close TOC */
	hList += "</div>";

	/* Insert element after Pins (and entity links) */
	/* Calendars use only one sidebar */
	if (document.getElementById('app').parentNode.classList.contains("kanka-entity-calendar")) {
	    document.getElementsByClassName('entity-sidebar-submenu')[0].insertAdjacentHTML("beforeend", hList);
	}
	/* Everything else */
	else {
	    document.getElementsByClassName('entity-sidebar-pins')[0].insertAdjacentHTML("beforeend", hList);
	}
}