您需要先安装一个扩展,例如 篡改猴、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 7.2
- // @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 = true;
- /* 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("");
- /* Create ID anchor on heading */
- headings[i].id = "h" + level + "-" + headingText.replace(/\s/g, "");
- /* 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].insertAdjacentHTML("beforeend", "<a class='to-top' href='#toc' title='Back to table of contents'> ^ toc</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();
- }
- });
- }