您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically adds a table of contents to Kanka entity pages under the Pins sidebar.
当前为
// ==UserScript== // @name Kanka Automatic Table of Contents // @namespace http://tampermonkey.net/ // @version 8 // @description Automatically adds a table of contents to Kanka entity pages under the Pins sidebar. // @author Salvatos // @match https://kanka.io/* // @exclude */html-export // @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: 30px; /* For consistency with other boxes */ } #tableofcontents, #tableofcontents ul { list-style: none; padding: 0; text-indent: -5px; } #tableofcontents { padding: 0 10px; margin: 0; overflow: hidden; word-wrap: anywhere; } #tableofcontents ul { padding-left: 10px; } #tableofcontents a { font-size: 14px; } #tableofcontents li.toc-level-0 a { font-weight: bold; } #tableofcontents .text-muted { display: none; } .to-top { vertical-align: super; font-variant: all-petite-caps; font-size: 10px; } `); /* Set arrays and prefs */ var headings = []; var tag_names = { h1:1, h2:1, h3:1, h4:1, h5:1, h6:1 }; const addTopLink = false; /* Pre-cleaning: remove stray line breaks left by Summernote at the end of headings so our TOC link doesn't get pushed to a new line */ $('h1 br:last-child, h2 br:last-child, h3 br:last-child, h4 br:last-child, h5 br:last-child, h6 br:last-child').remove(); /* 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 a check 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] ); } } } } // Find and walk through the main content block, based on entity type if (document.getElementById('app').parentNode.classList.contains("kanka-entity-calendar")) { walk( document.getElementsByClassName('entity-main-block')[0] ); } else { walk( document.getElementsByClassName('entity-story-block')[0] ); } /* Start main list */ var level = 0; var past_level = 0; var hList = ` <div id='toc' class='sidebar-section-box'> <div class="sidebar-section-title cursor" data-toggle="collapse" data-target="#sidebar-toc-list"> <i class="fa fa-chevron-right" style="display: none" aria-hidden="true"></i> <i class="fa fa-chevron-down" aria-hidden="true"></i> Table of contents </div> <div class="sidebar-elements collapse in" id="sidebar-toc-list"> <ul id='tableofcontents'> `; /* Create sublists to reflect heading level */ for( var i = 0; i < headings.length; i++ ) { // "Entry", Mentions and post 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>"; } } /* Handle heading text (it gets complicated with Timeline elements and inline tags) */ var headingText = headings[i], child = headingText.firstChild, texts = []; // Iterate through heading nodes while (child) { // Not a tag (text node) if (!child.tagName) { texts.push(child.data); } // Identify and manage HTML tags else { // Text-muted tag, i.e. a Timeline date if ($(child).hasClass("text-muted")) { texts.push('<span class="text-muted">' + child.innerText + '</span>'); } // Screenreader prompt else if ($(child).hasClass("sr-only")) { // exclude } else { texts.push(child.innerText); } } child = child.nextSibling; } headingText = texts.join(""); /* Check if heading already has an ID, else create one */ if (headings[i].id.length < 1) { headings[i].id = i + "-" + headingText.replace(/\s/g, ""); // We indicate a unique ID to acccount for duplicate titles } /* Create link in TOC */ hList += "<li class='toc-level-" + level + "'><a href='#" + headings[i].id + "' parent-post='" + $(headings[i]).parent().parent().attr('id') + "'>" + headingText + "</a></li>"; /* Add "toc" link to non-box headings */ if (addTopLink && level > 0 && $(headings[i]).parent('a.entity-mention').length == 0) { // That last condition is to omit Extraordinary Tooltips headings[i].insertAdjacentHTML("beforeend", "<a class='to-top' href='#toc' title='Back to table of contents'> ^ " + addTopLink + "</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></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-submenu')[0].insertAdjacentHTML("beforeend", hList); } /* Everything else */ else { document.getElementsByClassName('entity-sidebar')[0].insertAdjacentHTML("beforeend", hList); } /* Listener: If the target heading is in a collapsed post, expand it first */ $("#tableofcontents :not(.toc-level-0) a").click(function() { var targetPost = $(this).attr('parent-post'); if (!$("#" + targetPost)[0].classList.contains("in")) { $("h3[data-target='#" + targetPost + "']").click(); } }); $("#tableofcontents .toc-level-0 a").click(function() { var targetPost = $(this).attr('parent-post'); if (targetPost != "undefined" && !$("#" + targetPost + " > .box-header > h3")[0].classList.contains("in")) { $("#" + targetPost + " > .box-header > h3").click(); } }); }