自动抽取文章的标题|一键生成文章目录

自动抽取文章的标题, 生成目录树, 可跳转, 可拖拽

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         自动抽取文章的标题|一键生成文章目录
// @name:en      Auto Extract Article Title|Generate article table of contents with one click
// @namespace    http://life666.top/
// @version      0.2
// @description  自动抽取文章的标题, 生成目录树, 可跳转, 可拖拽
// @description:en  Automatically extract the title of the article, generate table of content, jump, drag and drop
// @author       Nisus Liu
// @license      MIT
// @match        *://cuiqingcai.com/*
// @match        *://juejin.cn/*
// @match        *://*.zhihu.com/*
// @match        *://*.cnblogs.com/*
// @match        *://*.notion.so/*
// @match        *://segmentfault.com/*
// @match        *://*.csdn.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
// @grant        none
// @require      https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/limonte-sweetalert2/11.4.4/sweetalert2.all.min.js
// @require      https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js
// ==/UserScript==


(function () {
  'use strict';
  // Your code here...
  // console.log('[Auto Extract Article Title] script starting ...')
  // Swal.fire('傻子都会计算机')
  console.log(`[Auto Extract Article Title] ${document.domain}`)

  let $tmTocW = $("<div class='TM-AEAT-toc-w'></div>");
  let $tocTtn;
  let hasExtracted = false;
  $tocTtn = $('<button class="TM-AEAT-toc-btn">抽取标题</button>');
  $tocTtn.appendTo('body');
  $tocTtn.on('click', function () {
    if (!hasExtracted) {
      extractToc();
    } else {
      removeToc();
    }
  })

  const idGenerater = (() => {
    let id = 0;
    return function () {
      return ++id;
    }
  })()

  /**
   * 抽取标题, 生成 TOC
   */
  function extractToc() {
    // h1~h6
    let $headers = $(":header");
    // console.log($headers)
    let hs = []
    $headers.each((ix, ele) => hs.push(ele))
    // console.log(hs)
    let $tmToc = $("<div id='TM-AEAT-toc' class='TM-AEAT-toc'></div>");
    $tmTocW.appendTo("body");
    $tmTocW.append($tmToc);
    let tmpLvls = [0, 0, 0, 0, 0, 0]; // 支持6级标题
    hs.forEach(h => {
      // <p class="h2 TM-AEAT-toc-item"><span class="TM-AEAT-toc-item-seq">序号</span> 标题内容 </p>
      let hTitle = h.innerText;
      // 这里需要克隆节点, 否则就是'移动'的效果了
      // let tocH  = h.cloneNode(true);
      let tocHName = h.tagName.toLowerCase();
      let lvl = parseInt(tocHName.substring(1));
      let seq = '';
      if (lvl >= 1 && lvl <= 6) {
        tmpLvls[lvl - 1] += 1; // 当前级别的标题序号增 1
        // 当前层级及之前的序号拼接起来就是需要的序号
        seq = tmpLvls.slice(0, lvl).join('.');
      }

      let hId = h.getAttribute('id');
      if (hId == null) {
        hId = 'TM-AEAT-' + idGenerater();
        h.setAttribute('id', hId);
      }

      let $tocItem = $(`<p class="${tocHName} TM-AEAT-toc-item"><span class="TM-AEAT-toc-item-seq">${seq}</span><a href="#${hId}">${hTitle}</a></p>`);
      // $(tocH).addClass(`TM-AEAT-toc-header-${lvl}`);
      if (lvl > 1) {
        $tocItem.css("text-indent", `${lvl - 1}em`);
      }
      $tmToc.append($tocItem);
    })

    hasExtracted = true;
    $tocTtn.text('取消抽取');
    $tocTtn.addClass('extracted');

    listenDrag();
  }

  /**
   * 溢出 TOC
   */
  function removeToc() {
    $tmTocW.empty();
    $tmTocW.remove();
    $tocTtn.text('抽取标题');
    $tocTtn.removeClass('extracted');
    hasExtracted = false;
  }

  setCustomStyle();

  function listenDrag() {
    // 拖拽能力
    // 鼠标的当前位置
    let x = 0;
    let y = 0;

    // 找到要拖拽的元素
    const ele = document.getElementById('TM-AEAT-toc');

    // 处理鼠标按下事件
    // 用户拖拽时触发
    const mouseDownHandler = function (e) {
      // 获取到鼠标位置
      x = e.clientX;
      y = e.clientY;

      // 对document增加监听鼠标移动事件和鼠标松开事件
      document.addEventListener('mousemove', mouseMoveHandler);
      document.addEventListener('mouseup', mouseUpHandler);
    };

    const mouseMoveHandler = function (e) {
      // 鼠标移动的距离
      const dx = e.clientX - x;
      const dy = e.clientY - y;

      // 设置元素的位置
      ele.style.top = `${ele.offsetTop + dy}px`;
      ele.style.left = `${ele.offsetLeft + dx}px`;

      // 重新分配鼠标的坐标
      x = e.clientX;
      y = e.clientY;
    };

    const mouseUpHandler = function () {
      // 取消对document对象的事件监听
      document.removeEventListener('mousemove', mouseMoveHandler);
      document.removeEventListener('mouseup', mouseUpHandler);
    };

    ele.addEventListener('mousedown', mouseDownHandler);
  }

  function setCustomStyle() {
    //获取 style 节点
    let domStyle = document.createElement('style');
    domStyle.type = 'text/css';
    domStyle.rel = 'stylesheet';
    //追加文本节点, 文本节点里内容就是 css 样式长字符串
    domStyle.appendChild(document.createTextNode(`
.TM-AEAT-toc-w {
  position:relative;
}
.TM-AEAT-toc {
  background: #e8f3ffcc;
  position: fixed;
  top: 40px;
  z-index: 10000;
  font-size: 16px;
  width: 20vw;
  border-radius: 0.3em;
  box-shadow: 0 0 10px #a2aab4cc;
}
.TM-AEAT-toc-btn {
  position: fixed;
  bottom: 40px;
  left: 20px;
  z-index: 20000;
  border: none;
  outline: none;
  background-color: red;
  color: white;
  cursor: pointer;
  padding: 15px;
  border-radius: 50%;
  opacity:0.5;
}
.TM-AEAT-toc-item-seq {
  color: #ff7f7f;
  margin-right: .5em;
}
    `));
    let domHead = document.getElementsByTagName('head')[0];
    domHead.appendChild(domStyle);
  }


})();