// ==UserScript==
// @name 简书目录树
// @name:zh 简书目录树
// @name:en jianshu-dir-tree
// @namespace https://github.com/WumaCoder/mini-tools.git
// @homepageURL https://github.com/WumaCoder/mini-tools.git
// @version 1.0.4
// @description 简书目录的完美解决方案
// @description:en JIANSHU directory
// @author WumaCoder
// @match *://www.jianshu.com/p/*
// @require https://cdnjs.cloudflare.com/ajax/libs/animejs/3.1.0/anime.min.js
// @grant none
// @license GPL-3.0-only
// ==/UserScript==
(function () {
/**
* 请求拦截器
* @param {Function} callback({status,responseURL}) 回调函数
*/
function responseHander (callback) {
const statusText = {
'Continue': 100,
'Switching Protocols': 101,
'OK': 200,
'Created': 201,
'Accepted': 202,
'Non-Authoritative Information': 203,
'No Content': 204,
'Reset Content': 205,
'Partial Content': 206,
'Multiple Choices': 300,
'Moved Permanently': 301,
'Found': 302,
'See Other': 303,
'Not Modified ': 304,
'Use Proxy': 305,
'Temporary Redirect': 307,
'Bad Request': 400,
'Unauthorized': 401,
'Payment Required': 402,
'Forbidden': 403,
'Not Found': 404,
'Method Not Allowed': 405,
'Not Acceptable ': 406,
'Proxy Authentication Required ': 407,
'Request Timeout': 408,
'Conflict': 409,
'Gone': 410,
'Length Required': 411,
'Precondition Failed': 412,
'Request Entity Too Large': 413,
'Request-URI Too Long': 414,
'Unsupported Media Type': 415,
'Requested Range Not Suitable': 416,
'Expectation Failed': 417,
'Internal Server Error': 500,
'Not Implemented': 501,
'Bad Gateway': 502,
'Service Unavailable': 503,
'Gateway Timeout': 504,
'HTTP Version Not Supported': 505,
'Other': 12029,
}
Object.defineProperty(XMLHttpRequest.prototype, "status", {
get: function () {
const status = statusText[this.statusText] || statusText['Other'];
callback({ status, responseURL: this.responseURL });
return status;
},
})
}
/**
* 提取主要信息
* @param {Element} el 页面元素
*/
function parserTag (el) {
return {
hx: el.tagName.substr(1) * 1,
name: el.innerText,
top: el.offsetTop - 80
}
}
/**
* 获取简书文章中的标题
*/
function getHxTags () {
const hxTags = [];
const tags = document.querySelector("section:nth-child(1) > article").children;
for (let i = 0; i < tags.length; i++) {
const item = tags[i];
if (['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(item.tagName)) {
hxTags.push(item);
}
}
return hxTags;
}
/**
* 初始化自定义样式
*/
function initStyle () {
const style = document.createElement("style");
style.innerText = `
.jianshu-a{
display: block;
font-size: 14px;
line-height: 22px;
color: #2d2d2d;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
}
.jianshu-active{
color:#ec7259;
}
.jianshu-a:hover{
color:inherit;
text-decoration: underline;
}
.jianshu-scroll{
overflow-y: auto;
height: 85vh;
}
.jianshu-scroll::-webkit-scrollbar,html::-webkit-scrollbar{
background:#fff;
width:0px;
transintion:all 0.36s;
}
html::-webkit-scrollbar{
width:6px
}
.jianshu-scroll::-webkit-scrollbar-thumb,html::-webkit-scrollbar-thumb{
background:#ec7259;
}
#jianshu-dir:hover > .jianshu-scroll::-webkit-scrollbar{
width:6px;
}
`;
document.body.appendChild(style);
}
/**
* 初始化事件
*/
function initEvent () {
window.scrollTop = 0;
const activeCall = throttle(active, 100);
window.addEventListener('scroll', (e) => {
window.scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
activeCall();
});
}
/**
* 节流函数
* @param {Function} func 回调
* @param {Number} wait 时间
*/
function throttle (func, wait) {
let previous = 0;
return function () {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
// 防抖
function debounce (fn, wait) {
let timeout = null;
return function () {
if (timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
/**
* 激活当前标题
*/
function active () {
const hxTags = window.hxTags;
const scrollTop = window.scrollTop;
const hxElements = document.getElementsByClassName('jianshu-a');
const jianshuDir = document.getElementsByClassName('jianshu-scroll')[0];
let j = 0;
for (let i = 0; i < hxTags.length; i++) {
const el = hxTags[i];
if (el.top > scrollTop) {
hxElements[i].classList.add('jianshu-active');
j = i + 1;
break;
} else {
hxElements[i].classList.remove('jianshu-active');
}
}
if (j - 1 < 10)
toScroll((x, y) => jianshuDir.scrollTop = y, jianshuDir.scrollTop, 0);
else
toScroll((x, y) => jianshuDir.scrollTop = y, jianshuDir.scrollTop, hxElements[j - 10 % hxTags.length].offsetTop);
for (; j < hxTags.length; j++) {
const el = hxElements[j];
el.classList.remove('jianshu-active');
}
}
/**
* 设置滚动条位置
* @param {number} top 滚动条跳转位置
*/
function toScroll (scrollFun, start, end) {
let scroll = {
charged: '0%',
cycles: start
}
anime({
targets: scroll,
charged: '100%',
cycles: end,
round: 1,
easing: 'easeInOutQuint',
update: function () {
scrollFun(0, scroll.cycles);
}
});
}
/**
* 创建目录面板
* @param {Array} hxTags 标题标签
*/
function createDirView (hxTags) {
const listElement = document.querySelector("[role=main] > aside > div > div > section")
const titleElement = listElement.firstChild.cloneNode();
const itemElement = listElement.lastChild.cloneNode();
let minHx = 10;
listElement.innerHTML = "";
listElement.parentNode.id = "jianshu-dir";
listElement.classList.add('jianshu-scroll');
titleElement.innerHTML = "目录";
listElement.appendChild(titleElement);
hxTags.forEach(item => {
if (minHx > item.hx) {
minHx = item.hx;
}
});
hxTags.forEach(item => {
const listItem = itemElement.cloneNode();
listItem.addEventListener('click', () => toScroll(window.scrollTo, window.scrollTop, item.top));
listItem.style.marginLeft = `${(item.hx - minHx) * 10}px`;
const tag = `<a class="jianshu-a">${item.name}</a>`;
listItem.innerHTML = tag;
listElement.appendChild(listItem);
})
if (hxTags.length === 0) {
const listItem = itemElement.cloneNode();
listItem.id = "listItem";
listItem.innerHTML = `<span style="font-size: 12px;color: #969696;">本篇文章暂无目录</span>`;
listElement.appendChild(listItem);
}
console.log("============================================");
console.log("jianshu-dir-tree load ok");
console.log("github: https://github.com/WumaCoder/mini-tools 获取更多脚本");
console.log("jianshu-dir-tree version 1.0.2");
console.log("============================================");
}
/**
* 入口
*/
function main () {
const fun = debounce(() => {
const hxTags = getHxTags().map(item => parserTag(item));
window.hxTags = hxTags;
createDirView(hxTags);
}, 100);
responseHander(({ responseURL }) => {
if (responseURL.indexOf('recommendations') === -1) {
return;
}
fun();
});
}
initEvent();
initStyle();
main();
// Your code here...
})();