简书最好用的地方在简洁,最难用的也是简洁,竟然不给目录。。。
对于想写长文,或者连载的用户简直要命。
设置左侧展示目录
通过 chrome 浏览器 可以实现查看内容时,设置左侧展示目录
安装 Tampermonkey 油猴
新建脚本:
以下三个脚本均来自网络,风格略有差异。
其实代码都差不多,但是...懒人怎么会去改代码呢。
脚本一:
截屏.png
// ==UserScript==
// @name 自动生成简书文章目录
// @namespace create-catalog
// @version 1.3
// @author 2024
// @description 给简书增加目录功能(通过识别h1-h6标题,自动生成目录放于页面的左侧)
// @match http://www.jianshu.com/p/*
// @match https://www.jianshu.com/p/*
// @grant none
// @require https://cdn.bootcss.com/jquery/3.4.1/jquery.js
// @require https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js
// ==/UserScript==
var version = 1;
// 获取简书阅读模式
function getReadMode(){
return $.cookie('read_mode') || 'day'
}
// 获取样式风格
function getStyle(){
if(getReadMode() === 'day'){
return {
background: 'white',
color: 'black'
}
} else {
return {
background:'#3d3d3d',
color:'#b3b3b3'
}
}
}
//去除字符串所有空格
function trim (str, is_global) {
var result;
result = str.replace(/(^\s+)|(\s+$)/g, "");
if (is_global&&is_global.toLowerCase() == "g") {
result = result.replace(/\s/g, "");
}
return result;
}
//转义尖括号
function toTxt(str) {
var RexStr = /\<|\>/g
str = str.replace(RexStr, function(MatchStr) {
switch (MatchStr) {
case "<":
return "<";
break;
case ">":
return ">";
break;
default:
break;
}
})
return str;
}
var menuIndex = 0; //初始化标题索引
// 在侧边栏中添加目录项
function appendMenuItem(tagName,id,content) {
let paddingLeft = tagName.substring(1) * 10; //添加标题缩进
let style = getStyle();
$('#menu_nav_ol').append(`<li class="${id}" style="padding-left: ${paddingLeft}px;line-height:40px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;max-width: 200px"><a href=javascript:; title="${toTxt(trim(content,"g"))}" style="color:${style.color}">${content}</a></li>`);
}
(function() {
// 获取标题元素
let titles = $('body article').find('h1,h2,h3,h4,h5,h6');
let style = getStyle();
if(titles.length === 0) {
return;
}
// 将文章内容右移
// 在 body 标签内部添加 aside 侧边栏,用于显示文档目录
let asideContent = `<aside id="sideMenu" style="font-size:15px;color:${style.color};position: fixed;padding: 20px 15px 20px 0;top: 66px;left: 10px;margin-bottom:20px;background-color: ${style.background};z-index: 810;overflow-y: auto;height:calc(100% - 124px);border-radius:4px"></aside>`;
$('body').prepend(asideContent);
$('#sideMenu').append('<ol id="menu_nav_ol" style="list-style:none;margin:0px;padding:0 0 0 10px">');// 不显示 li 项前面默认的点标志, 也不使用默认缩进
$('div[role="main"]').css('paddingLeft','160px');
// 遍历文章中的所有标题行, 按需添加id值, 并增加记录到目录列表中
titles.each(function() {
let tagName = $(this)[0].tagName.toLocaleLowerCase();
let content = $(this).text();
// 若标题的id不存在,则使用新id
let newTagId =$(this).attr('id');
if(content){
if(!$(this).attr('id')) {
newTagId = 'id_'+menuIndex;
$(this).attr('id',newTagId);
$(this).addClass('main-title')
menuIndex++;
}
appendMenuItem(tagName,newTagId,content);
}
});
$('#sideMenu').append('</ol>');
if(!$('#menu_nav_ol li').length){
$('#sideMenu').remove()
}
// 绑定目录li点击事件,点击时跳转到对应的位置
$('#menu_nav_ol li').on('click',function() {
let targetId = $(this).attr('class');
var _top=$("#"+targetId).offset().top-75
$('html,body').animate({
scrollTop:_top
}, 300);
});
//滚动页面增加左侧菜单高亮
var active=function(){
var scrollTop=$(window).scrollTop();
$('.main-title').each(function(i){
if(i<$('.main-title').length-1){
if(scrollTop+76>=$(this).offset().top&&scrollTop+76<$('.main-title').eq(i+1).offset().top){
$('#sideMenu li a').css({color: style.color});
$('#sideMenu li').eq(i).find('a').css({color:'#61aeee'});
$('#sideMenu li').css({borderLeft:'0'})
$('#sideMenu li').eq(i).css({borderLeft:'5px solid #61aeee'});
}
}else{
if(scrollTop+76>=$(this).offset().top){
$('#sideMenu li a').css({color: style.color});
$('#sideMenu li').eq(i).find('a').css({color:'#61aeee'});
$('#sideMenu li').css({borderLeft:'0'})
$('#sideMenu li').eq(i).css({borderLeft:'5px solid #61aeee'});
}
}
})
}
active()
var timer=null;
$(window).scroll(function(){
clearTimeout(timer)
timer=setTimeout(function(){
active()
},10)
})
})();
脚本二:
截屏.png
// ==UserScript==
// @name 自动生成简书文章目录
// @namespace create-catalog
// @version 1.3
// @author 2024
// @description 给简书增加目录功能(通过识别h1-h6标题,自动生成目录放于页面的左侧)
// @match http://www.jianshu.com/p/*
// @match https://www.jianshu.com/p/*
// @grant none
// @require https://cdn.bootcss.com/jquery/3.4.1/jquery.js
// @require https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js
// ==/UserScript==
document.getElementsByClassName('_2OwGUo')[0].style.display='none';
document.getElementsByClassName('_3Pnjry')[0].style.display='none';
document.getElementsByClassName('_1jKNin')[0].style.display='none';
var version = 1;
// 获取简书阅读模式
function getReadMode(){
return $.cookie('read_mode') || 'day'
}
// 获取样式风格
function getStyle(){
if(getReadMode() === 'day'){
return {
background: 'white',
color: 'black'
}
} else {
return {
background:'#3d3d3d',
color:'#b3b3b3'
}
}
}
//去除字符串所有空格
function trim (str, is_global) {
var result;
result = str.replace(/(^\s+)|(\s+$)/g, "");
if (is_global&&is_global.toLowerCase() == "g") {
result = result.replace(/\s/g, "");
}
return result;
}
//转义尖括号
function toTxt(str) {
var RexStr = /\<|\>/g
str = str.replace(RexStr, function(MatchStr) {
switch (MatchStr) {
case "<":
return "<";
break;
case ">":
return ">";
break;
default:
break;
}
})
return str;
}
var menuIndex = 0; //初始化标题索引
// 在侧边栏中添加目录项
function appendMenuItem(tagName,id,content) {
let paddingLeft = tagName.substring(1) * 10; //添加标题缩进
let style = getStyle();
$('#menu_nav_ol').append(`<li class="${id}" style="padding-left: ${paddingLeft}px;line-height:40px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;max-width: 200px"><a href=javascript:; title="${toTxt(trim(content,"g"))}" style="color:${style.color}">${content}</a></li>`);
}
(function() {
// 获取标题元素
let titles = $('body article').find('h1,h2,h3,h4,h5,h6');
let style = getStyle();
if(titles.length === 0) {
return;
}
// 将文章内容右移
// 在 body 标签内部添加 aside 侧边栏,用于显示文档目录
let asideContent = `<aside id="sideMenu" style="font-size:15px;color:${style.color};position: fixed;padding: 20px 15px 20px 0;top: 66px;left: 10px;margin-bottom:20px;background-color: ${style.background};z-index: 810;overflow-y: auto;height:calc(100% - 124px);border-radius:4px"></aside>`;
$('body').prepend(asideContent);
$('#sideMenu').append('<ol id="menu_nav_ol" style="list-style:none;margin:0px;padding:0 0 0 10px">');// 不显示 li 项前面默认的点标志, 也不使用默认缩进
$('div[role="main"]').css('paddingLeft','160px');
// 遍历文章中的所有标题行, 按需添加id值, 并增加记录到目录列表中
titles.each(function() {
let tagName = $(this)[0].tagName.toLocaleLowerCase();
let content = $(this).text();
// 若标题的id不存在,则使用新id
let newTagId =$(this).attr('id');
if(content){
if(!$(this).attr('id')) {
newTagId = 'id_'+menuIndex;
$(this).attr('id',newTagId);
$(this).addClass('main-title')
menuIndex++;
}
appendMenuItem(tagName,newTagId,content);
}
});
$('#sideMenu').append('</ol>');
if(!$('#menu_nav_ol li').length){
$('#sideMenu').remove()
}
// 绑定目录li点击事件,点击时跳转到对应的位置
$('#menu_nav_ol li').on('click',function() {
let targetId = $(this).attr('class');
var _top=$("#"+targetId).offset().top-75
$('html,body').animate({
scrollTop:_top
}, 300);
});
//滚动页面增加左侧菜单高亮
var active=function(){
var scrollTop=$(window).scrollTop();
$('.main-title').each(function(i){
if(i<$('.main-title').length-1){
if(scrollTop+76>=$(this).offset().top&&scrollTop+76<$('.main-title').eq(i+1).offset().top){
$('#sideMenu li a').css({color: style.color});
$('#sideMenu li').eq(i).find('a').css({color:'#61aeee'});
$('#sideMenu li').css({borderLeft:'0'})
$('#sideMenu li').eq(i).css({borderLeft:'5px solid #61aeee'});
}
}else{
if(scrollTop+76>=$(this).offset().top){
$('#sideMenu li a').css({color: style.color});
$('#sideMenu li').eq(i).find('a').css({color:'#61aeee'});
$('#sideMenu li').css({borderLeft:'0'})
$('#sideMenu li').eq(i).css({borderLeft:'5px solid #61aeee'});
}
}
})
}
active()
var timer=null;
$(window).scroll(function(){
clearTimeout(timer)
timer=setTimeout(function(){
active()
},10)
})
})();
脚本三:
截屏.png
// ==UserScript==
// @name 11简书网站左侧目录生成
// @namespace http://tampermonkey.net/
// @version 1.1.1
// @description 简书网站左侧目录生成,支持非h1标题,支持滚动
// @author https://github.com/lxx2013
// @match http://www.jianshu.com/p/*
// @match https://www.jianshu.com/p/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
initSidebar('.sidebar', 'article');
})();
/**
* 简书网站左侧目录生成插件
* 代码参考了 https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js
* @param {string} sidebarQuery - 目录 Element 的 query 字符串
* @param {string} contentQuery - 正文 Element 的 query 字符串
*/
function initSidebar(sidebarQuery, contentQuery) {
addAllStyle()
var body = document.body
var sidebar = document.querySelector(sidebarQuery)
// 在 body 标签内部添加 div.sidebar 侧边栏,用于显示文档目录
if (!sidebar) {
sidebar = document.createElement('div')
body.insertBefore(sidebar, body.firstChild)
}
sidebar.classList.add('sidebar')
var content = document.querySelector(contentQuery)
if (!content) {
throw ('Error: content not find!')
return
}
content.classList.add('content-with-sidebar');
var ul = document.createElement('ul')
ul.classList.add('menu-root')
sidebar.appendChild(ul)
var allHeaders = []
// 遍历文章中的所有 h1或 h2(取决于最大的 h 是多大) , 编辑为li.h3插入 ul
//因为标题一定是 h1 所以优先处理,然后再看文章正文部分是以 h1作为一级标题还是 h2或 h3作为一级标题
//采用的方法是优先遍历正文, 然后再插入标题这个h1
var i = 1
var headers = [].slice.call(content.querySelectorAll('h' + i++), 1)
while (!headers.length && i <= 6) {
headers = Array.from(content.querySelectorAll('h' + i++))
}
if( content.querySelector('h1') ) {
[].unshift.call(headers, content.querySelector('h1'))
}
if (headers.length) {
[].forEach.call(headers, function (h) {
var h1 = makeLink(h, 'a', 'h1-link')
ul.appendChild(h1)
allHeaders.push(h)
//寻找h1的子标题
var h2s = collectHs(h)
if (h2s.length) {
[].forEach.call(h2s, function (h2) {
allHeaders.push(h2)
var h3s = collectHs(h2)
h2 = makeLink(h2, 'a', 'h2-link')
ul.appendChild(h2)
//再寻找 h2 的子标题 h3
if (h3s.length) {
var subUl = document.createElement('ul')
subUl.classList.add('menu-sub')
h2.appendChild(subUl)
;[].forEach.call(h3s, function (h3) {
allHeaders.push(h3)
h3 = makeLink(h3, 'a', 'h3-link')
subUl.appendChild(h3)
})
}
})
}
})
}
//增加 click 点击处理,使用 scrollIntoView,增加控制滚动的 flag
var scrollFlag = 0
var scrollFlagTimer
sidebar.addEventListener('click', function (e) {
e.preventDefault()
if (e.target.href) {
scrollFlag = 1
clearTimeout(scrollFlagTimer)
scrollFlagTimer = setTimeout(() => scrollFlag = 0, 1500)
setActive(e.target, sidebar)
var target = document.getElementById(e.target.getAttribute('href').slice(1))
target.scrollIntoView({ behavior: 'smooth', block: "center" })
}
})
//监听窗口的滚动和缩放事件
window.addEventListener('scroll', updateSidebar)
window.addEventListener('resize', throttle(updateSidebar))
function updateSidebar() {
if (scrollFlag)
return
var doc = document.documentElement
var top = doc && doc.scrollTop || document.body.scrollTop
if (!allHeaders.length) return
var last
for (var i = 0; i < allHeaders.length; i++) {
var link = allHeaders[i]
if (link.offsetTop > (top + document.body.clientHeight / 2 - 73)) {
if (!last) { last = link }
break
} else {
last = link
}
}
if (last) {
setActive(last.id, sidebar)
}
}
}
/**
>为正文的标题创建一个对应的锚,返回的节点格式为`<li><tag class="className"> some text </tag><li>`
@param {HTMLElement} h - 需要在目录中为其创建链接的一个标题,它的`NodeType`可能为`H1 | H2 | H3`
@param {string} tag - 返回的 li 中的节点类型, 默认为 a
@param {string} className - 返回的 tag 的 class ,默认为空
@returns {HTMLElement} 返回的节点格式为`<li><a> some text </a><li>`
*/
function makeLink(h, tag, className) {
tag = tag || 'a'
className = className || ''
var link = document.createElement('li')
var text = [].slice.call(h.childNodes).map(function (node) {
if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue
} else if (['CODE', 'SPAN', 'A'].indexOf(node.tagName) !== -1) {
return node.textContent
} else {
return ''
}
}).join('').replace(/\(.*\)$/, '')
if (!h.id) h.id = IdEscape(text)
link.innerHTML =
`<${tag} class="${className}" href="#${h.id}">${htmlEscape(text)}</${tag}>`
return link
}
/**
*对 id 进行格式化.把空白字符和引号转义为下划线
*>注意:id值使用字符时,除了 ASCII字母和数字、“—”、“-"、"."之外,可能会引起兼容性问题,因为在HTML4中是不允许包含这些字符的,这个限制在HTML5中更加严格,为了兼容性id值必须由字母开头,同时不允许其中有空格。参考https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/id
*>但是本程序中使用了 document.getElementById 的要求稍放宽了一些,"#3.1_createComponent"这样的 id能成功执行
@param {string} text - HTML特殊字符
@returns {string} 转义后的字符串,例如`# 1'2"3标题`被转义为`#_1_2_3标题`
*/
function IdEscape(text) {
return text.replace(/[\s"']/g, '_') //注意这里不加 g 的话就会只匹配第一个匹配,所以会出错
}
/**
>HTML 特殊字符[ &, ", ', <, > ]转义
@param {string} text - HTML特殊字符
@returns {string} 转义后的字符,例如`<`被转义为`<`
*/
function htmlEscape(text) {
return text
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>')
}
/**
*为一个 `h(x)`标题节点收集跟在它屁股后面的 `h(x+1)`标题节点,
>若屁股后面没有`h(x+1)`节点,则收集`h(x+2)`节点甚至`h(x+3)`,毕竟不知道文章作者喜欢用哪种大小做标题
>收集过程中若遇到 `h(x)或h(x-1)`节点的话要立即返回
@param {HTMLElement} h - HTML 标题节点 `H1~H6`
@returns {HTMLElement[]} 一个由 h(x+1)或 h(x+2)等后代目录节点组成的数组
*/
function collectHs(h) {
var childIndexes = []
var thisTag = h.tagName
var count = 1
do {
var childTag = h.tagName[0] + (parseInt(h.tagName[1]) + count++)
var next = h.nextElementSibling
while (next) {
if (next.tagName[0] == 'H' && next.tagName[1] <= thisTag[1]) {
break
}
else if (next.tagName === childTag) {
childIndexes.push(next)
}
next = next.nextElementSibling
}
} while (childTag < 'H6' && childIndexes.length == 0)
return childIndexes
}
/**
*设置目录的激活状态,按既定规则添加 active 和 current 类
*>无论对h2还是 h3进行操作,首先都要移除所有的 active 和 current 类, 然后对 h2添加 active 和 current, 或对 h3添加 active 对其父目录添加 current
@param {String|HTMLElement} id - HTML标题节点或 querySelector 字符串
@param {HTMLElement} sidebar - 边栏的 HTML 节点
*/
function setActive(id, sidebar) {
//1.无论对h2还是 h3进行操作,首先都要移除所有的 active 和 current 类,
var previousActives = sidebar.querySelectorAll(`.active`)
;[].forEach.call(previousActives, function (h) {
h.classList.remove('active')
})
previousActives = sidebar.querySelectorAll(`.current`)
;[].forEach.call(previousActives, function (h) {
h.classList.remove('current')
})
//获取要操作的目录节点
var currentActive = typeof id === 'string'
? sidebar.querySelector('a[href="#' + id + '"]')
: id
if (currentActive.classList.contains('h2-link') != -1) {
//2. 若为 h2,则添加 active 和 current
currentActive.classList.add('active', 'current')
}
if ([].indexOf.call(currentActive.classList, 'h3-link') != -1) {
//3. 若为 h3,则添加 active 且对其父目录添加 current
currentActive.classList.add('active')
var parent = currentActive
while (parent && parent.tagName != 'UL') {
parent = parent.parentNode
}
parent.parentNode.querySelector('.h2-link').classList.add('current', 'active')
}
//左侧目录太长时的效果
currentActive.scrollIntoView({ behavior: 'smooth' })
}
/**
>增加 sidebar 需要的全部样式
@param {string} highlightColor - 高亮颜色, 默认为'#c7254e'
*/
function addAllStyle(highlightColor) {
highlightColor = highlightColor || "#c7254e"
var sheet = newStyleSheet()
/**
>创建一个新的`<style></style>`标签插入`<head>`中
@return {Object} style.sheet,`它具有方法insertRule`
*/
function newStyleSheet() {
var style = document.createElement("style");
// 对WebKit hack :(
style.appendChild(document.createTextNode(""));
// 将 <style> 元素加到页面中
document.head.appendChild(style);
return style.sheet;
}
var position = 0
/**
>添加一条 css 规则
@param {string} str - css样式,也可以是@media
*/
function addStyle(str) {
sheet.insertRule(str,position++);
}
addStyle(`.sidebar{position:fixed; z-index: 10;
top: 61px;
left: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 40px 20px 60px 30px;
max-width: 310px;
}`)
addStyle(`.menu-root { list-style:none; text-align:left }`)
addStyle(`.menu-root .h1-link{
display:inline-block;
color:rgb(44, 62, 80);
font-family:"source sans pro", "helvetica neue", Arial, sans-serif;
font-size:17.55px;
font-weight:600;
height:22px;
line-height:22.5px;
list-style-type:none;
margin-block-end:11px;
margin-block-start:11px;
}`)
addStyle(`.menu-root .h2-link:hover {
border-bottom: 2px solid ${highlightColor};
}`)
addStyle(`.menu-root .h2-link.current+.menu-sub{
display:block;
}`)
addStyle(`.menu-root .h2-link{
color:rgb(127,140,141);
cursor:pointer;
font-family:"source sans pro", "helvetica neue", Arial, sans-serif;
font-size:15px;
height:auto;
line-height:22.5px;
list-style-type:none;
text-align:left;
text-decoration-color:rgb(127, 140, 141);
text-decoration-line:none;
text-decoration-style:solid;
margin-left:12.5px;
}`)
addStyle(`.menu-sub {
padding-left:25px;
list-style:none;
display:none;
}`)
addStyle(`.menu-sub .h3-link{
color:#333333;
cursor:pointer;
display:inline;
font-family:"source sans pro", "helvetica neue", Arial, sans-serif;
font-size:12.75px;
height:auto;
line-height:19.125px;
list-style-type:none;
text-align:left;
text-decoration-color:rgb(52, 73, 94);
text-decoration-line:none;
text-decoration-style:solid;
}`)
addStyle(`@media only screen and (max-width : 1300px){
.content-with-sidebar {
margin-left:310px !important;
}
}`)
addStyle(`.sidebar .active{
color:${highlightColor};
font-weight:700;
}`)
}
/**
>函数节流
>参考https://juejin.im/entry/58c0379e44d9040068dc952f
@param {Fuction} fn - 要执行的函数
*/
function throttle(fn, interval = 300) {
let canRun = true;
return function () {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true;
}, interval);
};
}












网友评论