Greasy Fork is available in English.

TreeBar

目录树导航 - 显示文章目录大纲导航

Verze ze dne 30. 10. 2017. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         TreeBar
// @name:zh-CN   目录树导航
// @namespace    https://github.com/zhilidali/TreeBar/
// @version      0.1.3
// @description  目录树导航 - 显示文章目录大纲导航
// @description:zh-cn    目录树导航 - 显示文章目录大纲导航
// @author       zhilidali
// @mail         zhilidali@qq.com
// @license      MIT Licensed
// @match        http*://www.jianshu.com/p/*
// @match        http*://juejin.im/post/*
// @match        http*://sspai.com/*
// @match        http*://zhuanlan.zhihu.com/p/*
// @match        http*://mp.weixin.qq.com/s?*
// @match        http*://cnodejs.org/topic/*
// @match        http*://div.io/topic/*
// @match        http*://www.zcfy.cc/article/*
// @grant        none
// ==/UserScript==

(function() {
	'use strict';

	var map = {
		jianshu: {
			tagName: '.show-content',
			sync: true,
			style: {
				top: '55px',
				color: '#ea6f5a',
			}
		},
		zhihu: {
			tagName: '.PostIndex-content',
			sync: true,
		},
		sspai: {
			tagName: '#article-content',
			sync: true,
		},
		juejin: {
			tagName: '.post-content-container',
		},
		div: {
			tagName: '.topic-firstfloor-detail'
		},
		zcfy: {
			tagName: '.markdown-html'
		},
		default: {
			tagName: 'body'
		}
	};
	var treeBar = window.treeBar = {
		site: {
			name: '',
			tagName: '',
		},
		className: `treeBar`,
		style: `
				.treeBar {
					z-index: 99999;
					position: fixed;
					top: 10%;
					right: 0;
					bottom: 10px;
					max-width: 300px;
					max-height: 90%;
					overflow: visible;
					border: 1px solid #ddd;
					background-color: rgba(255, 255, 255, .9);
				}
				.treeBar-resize {
					position: absolute;
					// cursor: col-resize;
					width: 5px;
					left: -2px;
					top: 0;
					bottom: 0;
				}
				.treeBar-btn {
					position: absolute;
					top: -1px;
					left: -1px;
					width: 72px;
					height: 28px;
					padding: 0;
					box-sizing: border-box;
					border: 1px solid #ddd;
					border-radius: 3px;
					box-shadow: 0 1px 1px 1px #ddd;
					font-size: 14px;
					background-color: #fff;
					vertical-align: middle;
					text-align: center;
					outline: none;
					cursor: pointer;
					color: #333;
				}
				.treeBar ul {
					padding-left: 1.1em;
					margin: 0;
				}
				.treeBar > ul {
				    box-sizing: border-box;
					height: 100%;
					margin-left: 10px;
					overflow-y: auto;
					padding-top: 30px;
				}
				.treeBar > ul > li {
				    list-style-type: disc;
				}
				.treeBar > ul > li > ul > li {
				    list-style-type: circle;
				}
				.treeBar > ul > li > ul > li > ul > li {
				    list-style-type: square;
				}
				.treeBar > ul > li a {
					line-height: 30px;
					/*overflow: hidden;
					white-space: nowrap;
					text-overflow: ellipsis;*/
					text-decoration: none;
					font-size: 14px;
					cursor: pointer;
					color: #0371e9;
				}
				.treeBar > ul > li a:hover {
					text-decoration: underline;
				}`,
		innerDom: `<div><button class="treeBar-btn">TreeBar</button><div class="treeBar-resize"></div></div>`,
		matchSite: function() {/* 匹配站点 */
			var domain = location.href.match(/([\d\w]+)\.(com|cn|net|org|im|io|cc)/i);
			this.site.name = (domain && domain[1]);
			var siteInfo = map[this.site.name] || map.default;
			this.site.tagName = siteInfo.tagName;
			this.site.sync = siteInfo.sync || false;
			if (siteInfo.style) {
				this.style += `
					.treeBar {
						top: ${siteInfo.style.top};
						border-color: ${siteInfo.style.color};
					}
					.treeBar-btn {
						border-color: ${siteInfo.style.color};
						background-color: ${siteInfo.style.color};
						color: #fff;
					}
					.treeBar > ul > li a {
						color: ${siteInfo.style.color};
					}
				`;
			}
		},
		createDom: function() {/* 创建DOM */
			var style = document.createElement('style'),
				dom = document.createElement('div');
			style.innerHTML = this.style;
			document.head.appendChild(style);
			dom.className = this.className;
			dom.innerHTML = this.innerDom + this.ul;
			document.body.appendChild(dom);
		},
		onEvent: function() {
			document.querySelector('.treeBar-btn').onclick = function () {
				var ul = document.querySelector('.treeBar > ul');
				ul.style.display = ul.style.display === 'none' ? 'block' : 'none';
				var position = {
					none: '-71px',
					block: '-1px'
				};
				this.style.left = position[ul.style.display];
			};
			/*var resize = {};
			document.body.onmouseup = function() {
				console.log('up');
				resize.flag = false;
			};
			document.querySelector('.treeBar-resize').onmousedown = function() {
				resize.flag = true;
			};
			document.body.onmousemove = function() {
				if (!resize.flag) return;
				var tree = document.querySelector('.treeBar');
				if (resize.current === undefined) {
					resize.current = event.x;
					return;
				}
				var x = event.x - resize.current;
				if(Math.abs(x) >= 10) {
					tree.style.width = tree.offsetWidth - x + 'px';
					console.log(x);
					resize.current = event.x;
				}
			};*/
		},
		init: function() {
			console.time('TreeBar');

			// 1. 获取DOM
			var hList = document.querySelector(treeBar.site.tagName)
						.querySelectorAll('h1, h2, h3, h4, h5, h6');
			// 2. 构建数据
			var tree = transformTree(Array.from(hList));
			// 3. 构建DOM
			treeBar.ul = compileList(tree);
			treeBar.createDom();
			// 4. 注册事件
			treeBar.onEvent();

			console.timeEnd('TreeBar');
		}
	};

	// 0. 匹配站点
	treeBar.matchSite();
	if (treeBar.site.sync) {
		treeBar.init();
	} else {
		document.onreadystatechange = function () {
			document.readyState === "complete" && treeBar.init();
		};
	}


	/* 解析DOM,构建树形数据 */
	function transformTree(list) {
		var result = [];
		list.reduce(function (res, cur, index, arr) {
			var prev = res[res.length - 1];
			if (compare(prev, cur)) {
				if (!prev.sub) prev.sub = [];
				prev.sub.push(cur);
				if (index === arr.length - 1) prev.sub = transformTree(prev.sub);
			} else {
				construct(res, cur);
				if (prev && prev.sub) prev.sub = transformTree(prev.sub);
			}
			return res;
		}, result);

		// 转为树形结构的条件依据
		function compare(prev, cur) {
			return prev && cur.tagName.replace(/h/i, '') > prev.tagName.replace(/h/i, '');
		}

		// 转为树形结构后的数据改造
		function construct(arr, obj) {
			arr.push({
				name: obj.innerText,
				id: obj.id = obj.innerText,
				tagName: obj.tagName
			});
		}

		return result;
	}

	/* 根据数据构建目录 */
	function compileList(tree) {
		var list = '';
		tree.forEach(function(item) {
			var ul = item.sub ? compileList(item.sub) : '';
			list +=
				`<li>
					<a href="#${item.id}" title="${item.name}">${item.name}</a>${ul}
				</li>`;
		});
		return `<ul>${list}</ul>`;
	}

})();