您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
可以为github、简书、cnblog等网站生成TOC
- // ==UserScript==
- // @name bc-SmartTOC
- // @name:zh-CN bc-SmartTOC
- // @icon data:image/png;base64,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
- // @namespace BC
- // @version 0.1
- // @description TOC can be generated for github、jianshu.com、cnblogs.com and other sites
- // @description:zh-cn 可以为github、简书、cnblog等网站生成TOC
- // @collaborator ChandlerVer5
- // @match http*://coursehunters.online/t/*
- // @match *://docs.spring.io/*
- // @match *flaviocopes.com/*
- // @match http*://*/book/*
- // @match http*://www.freecodecamp.org/news/*
- // @match http*://www.cnblogs.com/*/p/*
- // @match http*://*/doc*/*
- // @match http*://blog.csdn.net/*
- // @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.runoob.com/*/*
- // @match http*://www.code4app.com/article/*
- // @match http*://www.zcfy.cc/article/*
- // @match http*://github.com/*
- // @match http*://gist.github.com/*
- // @grant none
- // ==/UserScript==
- (function () {
- 'use strict';
- function getRootWindow() {
- let w = window
- while (w !== w.parent) {
- w = w.parent
- }
- return w
- }
- function getMaster(root) {
- const iframes = [].slice.apply(root.document.getElementsByTagName('iframe'))
- if (iframes.length === 0) {
- return root
- } else {
- const largestChild = iframes
- .map(f => ({
- elem: f,
- area: f.offsetWidth * f.offsetHeight
- }))
- .sort((a, b) => b.area - a.area)[0]
- const html = root.document.documentElement
- return largestChild.area / (html.offsetWidth * html.offsetHeight) > 0.5
- ? largestChild.elem.contentWindow
- : root
- }
- }
- function isMasterFrame(w) {
- const root = getRootWindow()
- const master = getMaster(root)
- return w === master
- }
- var toastCSS = "#smarttoc-toast {\n all: initial;\n}\n\n#smarttoc-toast * {\n all: unset;\n}\n\n#smarttoc-toast {\n display: none;\n position: fixed;\n right: 0;\n top: 0;\n margin: 1em 2em;\n padding: 1em;\n z-index: 10000;\n box-sizing: border-box;\n background-color: #fff;\n border: 1px solid rgba(158, 158, 158, 0.22);\n color: gray;\n font-size: calc(12px + 0.15vw);\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-weight: normal;\n -webkit-font-smoothing: subpixel-antialiased;\n font-smoothing: subpixel-antialiased;\n transition: opacity 200ms ease-out, transform 200ms ease-out;\n}\n\n#smarttoc-toast.enter {\n display: block;\n opacity: 0.01;\n transform: translate3d(0, -2em, 0);\n}\n\n#smarttoc-toast.enter.enter-active {\n display: block;\n opacity: 1;\n transform: translate3d(0, 0, 0);\n}\n\n#smarttoc-toast.leave {\n display: block;\n opacity: 1;\n transform: translate3d(0, 0, 0);\n}\n\n#smarttoc-toast.leave.leave-active {\n display: block;\n opacity: 0.01;\n transform: translate3d(0, -2em, 0);\n}\n";
- function log() {
- if (false) {}
- }
- function draw(elem, color = 'red') {
- if (false && elem) {}
- }
- function assert(condition, error) {
- if (!condition) {
- throw new Error(error)
- }
- }
- // '12px' => 12
- const num = (size = '0') =>
- typeof size === 'number' ? size : +size.replace(/px/, '')
- // '12px' <= 12
- const px = (size = 0) => num(size) + 'px'
- function throttle(fn, delay) {
- if (delay) {
- let timer
- return function timerThrottled(...args) {
- clearTimeout(timer)
- timer = setTimeout(function() {
- fn(...args)
- }, delay)
- }
- } else {
- let request
- return function rafThrottled(...args) {
- cancelAnimationFrame(request)
- request = requestAnimationFrame(function() {
- fn(...args)
- })
- }
- }
- }
- const safe = str => str.replace(/\s+/g, '-')
- const unique = (function uniqueGenerator() {
- let set = new Set()
- return function unique(str) {
- let id = 1
- while (set.has(str)) {
- str = str.replace(/(\$\d+)?$/, '') + '$' + id
- id++
- }
- set.add(str)
- return str
- }
- })()
- const getScroll = (elem, direction = 'top') => {
- if (elem === document.body) {
- return direction === 'top'
- ? document.documentElement.scrollTop || document.body.scrollTop
- : document.documentElement.scrollLeft || document.body.scrollLeft
- } else {
- return direction === 'top' ? elem.scrollTop : elem.scrollLeft
- }
- }
- const setScroll = (elem, val, direction = 'top') => {
- if (elem === document.body) {
- if (direction === 'top') {
- document.documentElement.scrollTop = val
- window.scrollTo(window.scrollX, val)
- } else {
- document.documentElement.scrollLeft = val
- window.scrollTo(val, window.scrollY)
- }
- } else {
- if (direction === 'top') {
- elem.scrollTop = val
- } else {
- elem.scrollLeft = val
- }
- }
- }
- const scrollTo = (function scrollToFactory() {
- let request
- const easeOutQuad = function(t, b, c, d) {
- t /= d
- return -c * t * (t - 2) + b
- }
- return function scrollTo({
- targetElem,
- scrollElem,
- topMargin = 0,
- maxDuration = 300,
- easeFn,
- callback
- }) {
- cancelAnimationFrame(request)
- let rect = targetElem.getBoundingClientRect()
- let endScrollTop =
- rect.top -
- (scrollElem === document.body
- ? 0
- : scrollElem.getBoundingClientRect().top) +
- getScroll(scrollElem) -
- topMargin
- let startScrollTop = getScroll(scrollElem)
- let distance = endScrollTop - startScrollTop
- let startTime
- let ease = easeFn || easeOutQuad
- let distanceRatio = Math.min(Math.abs(distance) / 10000, 1)
- let duration = Math.max(
- maxDuration * distanceRatio * (2 - distanceRatio),
- 10
- )
- if (!maxDuration) {
- setScroll(scrollElem, endScrollTop)
- if (callback) {
- callback()
- }
- } else {
- requestAnimationFrame(update)
- }
- function update(timestamp) {
- if (!startTime) {
- startTime = timestamp
- }
- let progress = (timestamp - startTime) / duration
- if (progress < 1) {
- setScroll(
- scrollElem,
- ease(timestamp - startTime, startScrollTop, distance, duration)
- )
- requestAnimationFrame(update)
- } else {
- setScroll(scrollElem, endScrollTop)
- if (callback) {
- callback()
- }
- }
- }
- }
- })()
- function toDash(str) {
- return str.replace(/([A-Z])/g, (match, p1) => '-' + p1.toLowerCase())
- }
- function applyStyle(elem, style = {}, reset = false) {
- if (reset) {
- elem.style = ''
- }
- if (typeof style === 'string') {
- elem.style = style
- } else {
- for (let prop in style) {
- if (typeof style[prop] === 'number') {
- elem.style.setProperty(toDash(prop), px(style[prop]), 'important')
- } else {
- elem.style.setProperty(toDash(prop), style[prop], 'important')
- }
- }
- }
- }
- function translate3d(x = 0, y = 0, z = 0) {
- return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, ${Math.round(
- z
- )}px)` // 0.5px => blurred text
- }
- function setClass(elem, names, delay) {
- if (delay === undefined) {
- elem.classList = names
- } else {
- return setTimeout(() => {
- elem.classList = names
- }, delay)
- }
- }
- const toast = (function toastFactory() {
- let timers = []
- return function toast(msg) {
- let toast
- insertCSS(toastCSS, 'smarttoc-toast__css')
- if (document.getElementById('smarttoc-toast')) {
- toast = document.getElementById('smarttoc-toast')
- } else {
- toast = document.createElement('DIV')
- toast.id = 'smarttoc-toast'
- document.body.appendChild(toast)
- }
- toast.textContent = msg
- timers.forEach(clearTimeout)
- toast.classList = ''
- const set = setClass.bind(null, toast)
- toast.classList = 'enter'
- timers = [
- set('enter enter-active', 0),
- set('leave', 3000),
- set('leave leave-active', 3000),
- set('', 3000 + 200)
- ]
- }
- })()
- const insertCSS = function(css, id) {
- if (!document.getElementById(id)) {
- let style = document.createElement('STYLE')
- style.type = 'text/css'
- style.id = id
- style.textContent = css
- document.head.appendChild(style)
- return
- }
- }
- var tocCSS = "/* EVERYTHING HERE WILL BE '!IMPORTANT' */\n\n/* reset */\n\n#smarttoc {\n all: initial;\n}\n\n#smarttoc * {\n all: unset;\n}\n\n/* container */\n\n#smarttoc {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n position: fixed;\n max-width: 22em;\n min-width: 14em;\n max-height: calc(100vh - 100px);\n z-index: 10000;\n box-sizing: border-box;\n background-color: #fff;\n color: gray;\n font-size: calc(12px + 0.1vw);\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n line-height: 1.5;\n font-weight: normal;\n border: 1px solid rgba(158, 158, 158, 0.22);\n -webkit-font-smoothing: subpixel-antialiased;\n font-smoothing: subpixel-antialiased;\n overflow: hidden;\n will-change: transform, max-width;\n transition: max-width 0.3s;\n contain: content;\n}\n\n#smarttoc:hover {\n max-width: 33vw;\n}\n\n#smarttoc.hidden {\n display: none;\n}\n\n#smarttoc .handle {\n -webkit-user-select: none;\n user-select: none;\n\n border-bottom: 1px solid rgba(158, 158, 158, 0.22);\n padding: 0.1em 0.7em;\n font-variant-caps: inherit;\n font-variant: small-caps;\n font-size: 0.9em;\n color: #bbb;\n cursor: pointer;\n text-align: center;\n opacity: 0;\n will-change: opacity;\n transition: opacity 0.3s;\n}\n\n#smarttoc:hover .handle {\n max-width: 33vw;\n opacity: 1;\n}\n\n#smarttoc .handle:hover,\n#smarttoc .handle:active {\n cursor: move;\n}\n\n#smarttoc .handle:active {\n background: #f9f9f9;\n}\n\n#smarttoc > ul {\n flex-grow: 1;\n padding: 0 1.3em 1.3em 1em;\n overflow-y: auto;\n}\n\n/* all headings */\n\n#smarttoc ul,\n#smarttoc li {\n list-style: none;\n display: block;\n}\n\n#smarttoc a {\n text-decoration: none;\n color: gray;\n display: block;\n line-height: 1.3;\n padding-top: 0.2em;\n padding-bottom: 0.2em;\n text-overflow: ellipsis;\n overflow-x: hidden;\n white-space: nowrap;\n}\n\n#smarttoc a:hover,\n#smarttoc a:active {\n border-left-color: rgba(86, 61, 124, 0.5);\n color: #563d7c;\n}\n\n#smarttoc li.active > a {\n border-left-color: #563d7c;\n color: #563d7c;\n}\n\n/* heading level: 1 */\n\n#smarttoc ul {\n line-height: 2;\n}\n\n#smarttoc ul a {\n font-size: 1em;\n padding-left: 1.3em;\n cursor:pointer \n}\n\n#smarttoc ul a:hover,\n#smarttoc ul a:active,\n#smarttoc ul li.active > a {\n border-left-width: 3px;\n border-left-style: solid;\n padding-left: calc(1.3em - 3px);\n}\n\n#smarttoc ul li.active > a {\n font-weight: 700;\n}\n\n/* heading level: 2 (hidden only when there are too many headings) */\n\n#smarttoc ul ul {\n line-height: 1.8;\n}\n\n#smarttoc.lengthy ul ul {\n display: none;\n}\n\n#smarttoc.lengthy ul li.active > ul {\n display: block;\n}\n\n#smarttoc ul ul a {\n font-size: 1em;\n padding-left: 2.7em;\n}\n\n#smarttoc ul ul a:hover,\n#smarttoc ul ul a:active,\n#smarttoc ul ul li.active > a {\n border-left-width: 2px;\n border-left-style: solid;\n padding-left: calc(2.7em - 2px);\n font-weight: normal;\n}\n\n/* heading level: 3 (hidden unless parent is active) */\n\n#smarttoc ul ul ul {\n line-height: 1.7;\n display: none;\n}\n\n#smarttoc ul ul li.active > ul {\n display: block;\n}\n\n#smarttoc ul ul ul a {\n font-size: 1em;\n padding-left: 4em;\n}\n\n#smarttoc ul ul ul a:hover,\n#smarttoc ul ul ul a:active,\n#smarttoc ul ul ul li.active > a {\n border-left-width: 1px;\n border-left-style: solid;\n padding-left: calc(4em - 1px);\n font-weight: normal;\n}\n";
- const proto = {
- subscribe(cb, emitOnSubscribe = true) {
- if (emitOnSubscribe && this.value !== undefined) {
- cb(this.value)
- }
- this.listeners.push(cb)
- },
- addDependent(dependent) {
- this.dependents.push(dependent)
- },
- update(val) {
- this.value = val
- this.changed = true
- this.dependents.forEach(dep => dep.update(val))
- },
- flush() {
- if (this.changed) {
- this.changed = false
- this.listeners.forEach(l => l(this.value))
- this.dependents.forEach(dep => dep.flush())
- }
- },
- unique() {
- let lastValue = this.value
- let $unique = Stream(lastValue)
- this.subscribe(val => {
- if (val !== lastValue) {
- $unique(val)
- lastValue = val
- }
- })
- return $unique
- },
- map(f) {
- return Stream.combine(this, f)
- },
- filter(f) {
- return this.map(output => (f(output) ? output : undefined))
- },
- throttle(delay) {
- let $throttled = Stream(this.value)
- const emit = throttle($throttled, delay)
- this.subscribe(emit)
- return $throttled
- },
- log(name) {
- this.subscribe(e => console.log(`[${name}]: `, e))
- return this
- }
- }
- function Stream(init) {
- let s = function(val) {
- if (val === undefined) return s.value
- s.update(val)
- s.flush(val)
- }
- s.value = init
- s.changed = false
- s.listeners = []
- s.dependents = []
- return Object.assign(s, proto)
- }
- Stream.combine = function(...streams) {
- const combiner = streams.pop()
- let cached = streams.map(s => s())
- const combined = Stream(combiner(...cached))
- streams.forEach((s, i) => {
- const dependent = {
- update(val) {
- cached[i] = val
- combined.update(combiner(...cached))
- },
- flush() {
- combined.flush()
- }
- }
- s.addDependent(dependent)
- })
- return combined
- }
- Stream.interval = function(int) {
- let $interval = Stream()
- setInterval(() => $interval(null), int)
- return $interval
- }
- Stream.fromEvent = function(elem, type) {
- let $event = Stream()
- elem.addEventListener(type, $event)
- return $event
- }
- var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
- function createCommonjsModule(fn, module) {
- return module = { exports: {} }, fn(module, module.exports), module.exports;
- }
- var mithril = createCommonjsModule(function (module) {
- ;(function() {
- "use strict"
- function Vnode(tag, key, attrs0, children, text, dom) {
- return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false}
- }
- Vnode.normalize = function(node) {
- if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
- if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined)
- return node
- }
- Vnode.normalizeChildren = function normalizeChildren(children) {
- for (var i = 0; i < children.length; i++) {
- children[i] = Vnode.normalize(children[i])
- }
- return children
- }
- var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
- var selectorCache = {}
- var hasOwn = {}.hasOwnProperty
- function compileSelector(selector) {
- var match, tag = "div", classes = [], attrs = {}
- while (match = selectorParser.exec(selector)) {
- var type = match[1], value = match[2]
- if (type === "" && value !== "") tag = value
- else if (type === "#") attrs.id = value
- else if (type === ".") classes.push(value)
- else if (match[3][0] === "[") {
- var attrValue = match[6]
- if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
- if (match[4] === "class") classes.push(attrValue)
- else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true
- }
- }
- if (classes.length > 0) attrs.className = classes.join(" ")
- return selectorCache[selector] = {tag: tag, attrs: attrs}
- }
- function execSelector(state, attrs, children) {
- var hasAttrs = false, childList, text
- var className = attrs.className || attrs.class
- for (var key in state.attrs) {
- if (hasOwn.call(state.attrs, key)) {
- attrs[key] = state.attrs[key]
- }
- }
- if (className !== undefined) {
- if (attrs.class !== undefined) {
- attrs.class = undefined
- attrs.className = className
- }
- if (state.attrs.className != null) {
- attrs.className = state.attrs.className + " " + className
- }
- }
- for (let key in attrs) {
- if (hasOwn.call(attrs, key) && key !== "key") {
- hasAttrs = true
- break
- }
- }
- if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") {
- text = children[0].children
- } else {
- childList = children
- }
- return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text)
- }
- function hyperscript(selector) {
- // Because sloppy mode sucks
- var attrs = arguments[1], start = 2, children
- if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
- throw Error("The selector must be either a string or a component.");
- }
- if (typeof selector === "string") {
- var cached = selectorCache[selector] || compileSelector(selector)
- }
- if (attrs == null) {
- attrs = {}
- } else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
- attrs = {}
- start = 1
- }
- if (arguments.length === start + 1) {
- children = arguments[start]
- if (!Array.isArray(children)) children = [children]
- } else {
- children = []
- while (start < arguments.length) children.push(arguments[start++])
- }
- var normalized = Vnode.normalizeChildren(children)
- if (typeof selector === "string") {
- return execSelector(cached, attrs, normalized)
- } else {
- return Vnode(selector, attrs.key, attrs, normalized)
- }
- }
- hyperscript.trust = function(html) {
- if (html == null) html = ""
- return Vnode("<", undefined, undefined, html, undefined, undefined)
- }
- hyperscript.fragment = function(attrs1, children) {
- return Vnode("[", attrs1.key, attrs1, Vnode.normalizeChildren(children), undefined, undefined)
- }
- var m = hyperscript
- /** @constructor */
- var PromisePolyfill = function(executor) {
- if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`")
- if (typeof executor !== "function") throw new TypeError("executor must be a function")
- var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
- var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
- var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
- function handler(list, shouldAbsorb) {
- return function execute(value) {
- var then
- try {
- if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
- if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
- executeOnce(then.bind(value))
- }
- else {
- callAsync(function() {
- if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
- for (var i = 0; i < list.length; i++) list[i](value)
- resolvers.length = 0, rejectors.length = 0
- instance.state = shouldAbsorb
- instance.retry = function() {execute(value)}
- })
- }
- }
- catch (e) {
- rejectCurrent(e)
- }
- }
- }
- function executeOnce(then) {
- var runs = 0
- function run(fn) {
- return function(value) {
- if (runs++ > 0) return
- fn(value)
- }
- }
- var onerror = run(rejectCurrent)
- try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
- }
- executeOnce(executor)
- }
- PromisePolyfill.prototype.then = function(onFulfilled, onRejection) {
- var self = this, instance = self._instance
- function handle(callback, list, next, state) {
- list.push(function(value) {
- if (typeof callback !== "function") next(value)
- else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
- })
- if (typeof instance.retry === "function" && state === instance.state) instance.retry()
- }
- var resolveNext, rejectNext
- var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
- handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
- return promise
- }
- PromisePolyfill.prototype.catch = function(onRejection) {
- return this.then(null, onRejection)
- }
- PromisePolyfill.resolve = function(value) {
- if (value instanceof PromisePolyfill) return value
- return new PromisePolyfill(function(resolve) {resolve(value)})
- }
- PromisePolyfill.reject = function(value) {
- return new PromisePolyfill(function(resolve, reject) {reject(value)})
- }
- PromisePolyfill.all = function(list) {
- return new PromisePolyfill(function(resolve, reject) {
- var total = list.length, count = 0, values = []
- if (list.length === 0) resolve([])
- else for (var i = 0; i < list.length; i++) {
- (function(i) {
- function consume(value) {
- count++
- values[i] = value
- if (count === total) resolve(values)
- }
- if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
- list[i].then(consume, reject)
- }
- else consume(list[i])
- })(i)
- }
- })
- }
- PromisePolyfill.race = function(list) {
- return new PromisePolyfill(function(resolve, reject) {
- for (var i = 0; i < list.length; i++) {
- list[i].then(resolve, reject)
- }
- })
- }
- if (typeof window !== "undefined") {
- if (typeof window.Promise === "undefined") window.Promise = PromisePolyfill
- let PromisePolyfill = window.Promise
- } else if (typeof commonjsGlobal !== "undefined") {
- if (typeof commonjsGlobal.Promise === "undefined") commonjsGlobal.Promise = PromisePolyfill
- let PromisePolyfill = commonjsGlobal.Promise
- } else {
- }
- var buildQueryString = function(object) {
- if (Object.prototype.toString.call(object) !== "[object Object]") return ""
- var args = []
- for (var key0 in object) {
- destructure(key0, object[key0])
- }
- return args.join("&")
- function destructure(key0, value) {
- if (Array.isArray(value)) {
- for (var i = 0; i < value.length; i++) {
- destructure(key0 + "[" + i + "]", value[i])
- }
- }
- else if (Object.prototype.toString.call(value) === "[object Object]") {
- for (let i in value) {
- destructure(key0 + "[" + i + "]", value[i])
- }
- }
- else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
- }
- }
- var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i")
- var _8 = function($window, Promise) {
- var callbackCount = 0
- var oncompletion
- function setCompletionCallback(callback) {oncompletion = callback}
- function finalizer() {
- var count = 0
- function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
- return function finalize(promise0) {
- var then0 = promise0.then
- promise0.then = function() {
- count++
- var next = then0.apply(promise0, arguments)
- next.then(complete, function(e) {
- complete()
- if (count === 0) throw e
- })
- return finalize(next)
- }
- return promise0
- }
- }
- function normalize(args, extra) {
- if (typeof args === "string") {
- var url = args
- args = extra || {}
- if (args.url == null) args.url = url
- }
- return args
- }
- function request(args, extra) {
- var finalize = finalizer()
- args = normalize(args, extra)
- var promise0 = new Promise(function(resolve, reject) {
- if (args.method == null) args.method = "GET"
- args.method = args.method.toUpperCase()
- var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true)
- if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify
- if (typeof args.deserialize !== "function") args.deserialize = deserialize
- if (typeof args.extract !== "function") args.extract = extract
- args.url = interpolate(args.url, args.data)
- if (useBody) args.data = args.serialize(args.data)
- else args.url = assemble(args.url, args.data)
- var xhr = new $window.XMLHttpRequest(),
- aborted = false,
- _abort = xhr.abort
- xhr.abort = function abort() {
- aborted = true
- _abort.call(xhr)
- }
- xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
- if (args.serialize === JSON.stringify && useBody) {
- xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
- }
- if (args.deserialize === deserialize) {
- xhr.setRequestHeader("Accept", "application/json, text/*")
- }
- if (args.withCredentials) xhr.withCredentials = args.withCredentials
- for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) {
- xhr.setRequestHeader(key, args.headers[key])
- }
- if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
- xhr.onreadystatechange = function() {
- // Don't throw errors on xhr.abort().
- if(aborted) return
- if (xhr.readyState === 4) {
- try {
- var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
- resolve(cast(args.type, response))
- }
- else {
- var error = new Error(xhr.responseText)
- for (var key in response) error[key] = response[key]
- reject(error)
- }
- }
- catch (e) {
- reject(e)
- }
- }
- }
- if (useBody && (args.data != null)) xhr.send(args.data)
- else xhr.send()
- })
- return args.background === true ? promise0 : finalize(promise0)
- }
- function jsonp(args, extra) {
- var finalize = finalizer()
- args = normalize(args, extra)
- var promise0 = new Promise(function(resolve, reject) {
- var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
- var script = $window.document.createElement("script")
- $window[callbackName] = function(data) {
- script.parentNode.removeChild(script)
- resolve(cast(args.type, data))
- delete $window[callbackName]
- }
- script.onerror = function() {
- script.parentNode.removeChild(script)
- reject(new Error("JSONP request failed"))
- delete $window[callbackName]
- }
- if (args.data == null) args.data = {}
- args.url = interpolate(args.url, args.data)
- args.data[args.callbackKey || "callback"] = callbackName
- script.src = assemble(args.url, args.data)
- $window.document.documentElement.appendChild(script)
- })
- return args.background === true? promise0 : finalize(promise0)
- }
- function interpolate(url, data) {
- if (data == null) return url
- var tokens = url.match(/:[^\/]+/gi) || []
- for (var i = 0; i < tokens.length; i++) {
- var key = tokens[i].slice(1)
- if (data[key] != null) {
- url = url.replace(tokens[i], data[key])
- }
- }
- return url
- }
- function assemble(url, data) {
- var querystring = buildQueryString(data)
- if (querystring !== "") {
- var prefix = url.indexOf("?") < 0 ? "?" : "&"
- url += prefix + querystring
- }
- return url
- }
- function deserialize(data) {
- try {return data !== "" ? JSON.parse(data) : null}
- catch (e) {throw new Error(data)}
- }
- function extract(xhr) {return xhr.responseText}
- function cast(type0, data) {
- if (typeof type0 === "function") {
- if (Array.isArray(data)) {
- for (var i = 0; i < data.length; i++) {
- data[i] = new type0(data[i])
- }
- }
- else return new type0(data)
- }
- return data
- }
- return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
- }
- var requestService = _8(window, PromisePolyfill)
- var coreRenderer = function($window) {
- var $doc = $window.document
- var $emptyFragment = $doc.createDocumentFragment()
- var nameSpace = {
- svg: "http://www.w3.org/2000/svg",
- math: "http://www.w3.org/1998/Math/MathML"
- }
- var onevent
- function setEventCallback(callback) {return onevent = callback}
- function getNameSpace(vnode) {
- return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]
- }
- //create
- function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
- for (var i = start; i < end; i++) {
- var vnode = vnodes[i]
- if (vnode != null) {
- createNode(parent, vnode, hooks, ns, nextSibling)
- }
- }
- }
- function createNode(parent, vnode, hooks, ns, nextSibling) {
- var tag = vnode.tag
- if (typeof tag === "string") {
- vnode.state = {}
- if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
- switch (tag) {
- case "#": return createText(parent, vnode, nextSibling)
- case "<": return createHTML(parent, vnode, nextSibling)
- case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
- default: return createElement(parent, vnode, hooks, ns, nextSibling)
- }
- }
- else return createComponent(parent, vnode, hooks, ns, nextSibling)
- }
- function createText(parent, vnode, nextSibling) {
- vnode.dom = $doc.createTextNode(vnode.children)
- insertNode(parent, vnode.dom, nextSibling)
- return vnode.dom
- }
- function createHTML(parent, vnode, nextSibling) {
- var match1 = vnode.children.match(/^\s*?<(\w+)/im) || []
- var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div"
- var temp = $doc.createElement(parent1)
- temp.innerHTML = vnode.children
- vnode.dom = temp.firstChild
- vnode.domSize = temp.childNodes.length
- var fragment = $doc.createDocumentFragment()
- var child
- while (child = temp.firstChild) {
- fragment.appendChild(child)
- }
- insertNode(parent, fragment, nextSibling)
- return fragment
- }
- function createFragment(parent, vnode, hooks, ns, nextSibling) {
- var fragment = $doc.createDocumentFragment()
- if (vnode.children != null) {
- var children = vnode.children
- createNodes(fragment, children, 0, children.length, hooks, null, ns)
- }
- vnode.dom = fragment.firstChild
- vnode.domSize = fragment.childNodes.length
- insertNode(parent, fragment, nextSibling)
- return fragment
- }
- function createElement(parent, vnode, hooks, ns, nextSibling) {
- var tag = vnode.tag
- var attrs2 = vnode.attrs
- var is = attrs2 && attrs2.is
- ns = getNameSpace(vnode) || ns
- var element = ns ?
- is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) :
- is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag)
- vnode.dom = element
- if (attrs2 != null) {
- setAttrs(vnode, attrs2, ns)
- }
- insertNode(parent, element, nextSibling)
- if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
- setContentEditable(vnode)
- }
- else {
- if (vnode.text != null) {
- if (vnode.text !== "") element.textContent = vnode.text
- else vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
- }
- if (vnode.children != null) {
- var children = vnode.children
- createNodes(element, children, 0, children.length, hooks, null, ns)
- setLateAttrs(vnode)
- }
- }
- return element
- }
- function initComponent(vnode, hooks) {
- var sentinel
- if (typeof vnode.tag.view === "function") {
- vnode.state = Object.create(vnode.tag)
- sentinel = vnode.state.view
- if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
- sentinel.$$reentrantLock$$ = true
- } else {
- vnode.state = void 0
- sentinel = vnode.tag
- if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
- sentinel.$$reentrantLock$$ = true
- vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
- }
- vnode._state = vnode.state
- if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
- initLifecycle(vnode._state, vnode, hooks)
- vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
- if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
- sentinel.$$reentrantLock$$ = null
- }
- function createComponent(parent, vnode, hooks, ns, nextSibling) {
- initComponent(vnode, hooks)
- if (vnode.instance != null) {
- var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
- vnode.dom = vnode.instance.dom
- vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
- insertNode(parent, element, nextSibling)
- return element
- }
- else {
- vnode.domSize = 0
- return $emptyFragment
- }
- }
- //update
- function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) {
- if (old === vnodes || old == null && vnodes == null) return
- else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
- else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
- else {
- if (old.length === vnodes.length) {
- var isUnkeyed = false
- for (var i = 0; i < vnodes.length; i++) {
- if (vnodes[i] != null && old[i] != null) {
- isUnkeyed = vnodes[i].key == null && old[i].key == null
- break
- }
- }
- if (isUnkeyed) {
- for (let i = 0; i < old.length; i++) {
- if (old[i] === vnodes[i]) continue
- else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling))
- else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
- else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns)
- }
- return
- }
- }
- recycling = recycling || isRecyclable(old, vnodes)
- if (recycling) {
- var pool = old.pool
- old = old.concat(old.pool)
- }
- var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
- while (oldEnd >= oldStart && end >= start) {
- var o = old[oldStart], v = vnodes[start]
- if (o === v && !recycling) oldStart++, start++
- else if (o == null) oldStart++
- else if (v == null) start++
- else if (o.key === v.key) {
- var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
- oldStart++, start++
- updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns)
- if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
- }
- else {
- let o = old[oldEnd]
- if (o === v && !recycling) oldEnd--, start++
- else if (o == null) oldEnd--
- else if (v == null) start++
- else if (o.key === v.key) {
- let shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
- updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
- if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
- oldEnd--, start++
- }
- else break
- }
- }
- while (oldEnd >= oldStart && end >= start) {
- let o = old[oldEnd], v = vnodes[end]
- if (o === v && !recycling) oldEnd--, end--
- else if (o == null) oldEnd--
- else if (v == null) end--
- else if (o.key === v.key) {
- let shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
- updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
- if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
- if (o.dom != null) nextSibling = o.dom
- oldEnd--, end--
- }
- else {
- if (!map) map = getKeyMap(old, oldEnd)
- if (v != null) {
- var oldIndex = map[v.key]
- if (oldIndex != null) {
- let movable = old[oldIndex]
- let shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling)
- updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
- insertNode(parent, toFragment(movable), nextSibling)
- old[oldIndex].skip = true
- if (movable.dom != null) nextSibling = movable.dom
- }
- else {
- var dom = createNode(parent, v, hooks, ns, nextSibling)
- nextSibling = dom
- }
- }
- end--
- }
- if (end < start) break
- }
- createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
- removeNodes(old, oldStart, oldEnd + 1, vnodes)
- }
- }
- function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) {
- var oldTag = old.tag, tag = vnode.tag
- if (oldTag === tag) {
- vnode.state = old.state
- vnode._state = old._state
- vnode.events = old.events
- if (!recycling && shouldNotUpdate(vnode, old)) return
- if (typeof oldTag === "string") {
- if (vnode.attrs != null) {
- if (recycling) {
- vnode.state = {}
- initLifecycle(vnode.attrs, vnode, hooks)
- }
- else updateLifecycle(vnode.attrs, vnode, hooks)
- }
- switch (oldTag) {
- case "#": updateText(old, vnode); break
- case "<": updateHTML(parent, old, vnode, nextSibling); break
- case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
- default: updateElement(old, vnode, recycling, hooks, ns)
- }
- }
- else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
- }
- else {
- removeNode(old, null)
- createNode(parent, vnode, hooks, ns, nextSibling)
- }
- }
- function updateText(old, vnode) {
- if (old.children.toString() !== vnode.children.toString()) {
- old.dom.nodeValue = vnode.children
- }
- vnode.dom = old.dom
- }
- function updateHTML(parent, old, vnode, nextSibling) {
- if (old.children !== vnode.children) {
- toFragment(old)
- createHTML(parent, vnode, nextSibling)
- }
- else vnode.dom = old.dom, vnode.domSize = old.domSize
- }
- function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
- updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
- var domSize = 0, children = vnode.children
- vnode.dom = null
- if (children != null) {
- for (var i = 0; i < children.length; i++) {
- var child = children[i]
- if (child != null && child.dom != null) {
- if (vnode.dom == null) vnode.dom = child.dom
- domSize += child.domSize || 1
- }
- }
- if (domSize !== 1) vnode.domSize = domSize
- }
- }
- function updateElement(old, vnode, recycling, hooks, ns) {
- var element = vnode.dom = old.dom
- ns = getNameSpace(vnode) || ns
- if (vnode.tag === "textarea") {
- if (vnode.attrs == null) vnode.attrs = {}
- if (vnode.text != null) {
- vnode.attrs.value = vnode.text //FIXME handle0 multiple children
- vnode.text = undefined
- }
- }
- updateAttrs(vnode, old.attrs, vnode.attrs, ns)
- if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
- setContentEditable(vnode)
- }
- else if (old.text != null && vnode.text != null && vnode.text !== "") {
- if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text
- }
- else {
- if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
- if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
- updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns)
- }
- }
- function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
- if (recycling) {
- initComponent(vnode, hooks)
- } else {
- vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
- if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
- if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
- updateLifecycle(vnode._state, vnode, hooks)
- }
- if (vnode.instance != null) {
- if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
- else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
- vnode.dom = vnode.instance.dom
- vnode.domSize = vnode.instance.domSize
- }
- else if (old.instance != null) {
- removeNode(old.instance, null)
- vnode.dom = undefined
- vnode.domSize = 0
- }
- else {
- vnode.dom = old.dom
- vnode.domSize = old.domSize
- }
- }
- function isRecyclable(old, vnodes) {
- if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) {
- var oldChildrenLength = old[0] && old[0].children && old[0].children.length || 0
- var poolChildrenLength = old.pool[0] && old.pool[0].children && old.pool[0].children.length || 0
- var vnodesChildrenLength = vnodes[0] && vnodes[0].children && vnodes[0].children.length || 0
- if (Math.abs(poolChildrenLength - vnodesChildrenLength) <= Math.abs(oldChildrenLength - vnodesChildrenLength)) {
- return true
- }
- }
- return false
- }
- function getKeyMap(vnodes, end) {
- var map = {}, i = 0
- for (let i = 0; i < end; i++) {
- let vnode = vnodes[i]
- if (vnode != null) {
- let key2 = vnode.key
- if (key2 != null) map[key2] = i
- }
- }
- return map
- }
- function toFragment(vnode) {
- var count0 = vnode.domSize
- if (count0 != null || vnode.dom == null) {
- var fragment = $doc.createDocumentFragment()
- if (count0 > 0) {
- var dom = vnode.dom
- while (--count0) fragment.appendChild(dom.nextSibling)
- fragment.insertBefore(dom, fragment.firstChild)
- }
- return fragment
- }
- else return vnode.dom
- }
- function getNextSibling(vnodes, i, nextSibling) {
- for (; i < vnodes.length; i++) {
- if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
- }
- return nextSibling
- }
- function insertNode(parent, dom, nextSibling) {
- if (nextSibling && nextSibling.parentNode) parent.insertBefore(dom, nextSibling)
- else parent.appendChild(dom)
- }
- function setContentEditable(vnode) {
- var children = vnode.children
- if (children != null && children.length === 1 && children[0].tag === "<") {
- var content = children[0].children
- if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content
- }
- else if (vnode.text != null || children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted")
- }
- //remove
- function removeNodes(vnodes, start, end, context) {
- for (var i = start; i < end; i++) {
- var vnode = vnodes[i]
- if (vnode != null) {
- if (vnode.skip) vnode.skip = false
- else removeNode(vnode, context)
- }
- }
- }
- function removeNode(vnode, context) {
- var expected = 1, called = 0
- if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
- var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
- if (result != null && typeof result.then === "function") {
- expected++
- result.then(continuation, continuation)
- }
- }
- if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
- let result = vnode._state.onbeforeremove.call(vnode.state, vnode)
- if (result != null && typeof result.then === "function") {
- expected++
- result.then(continuation, continuation)
- }
- }
- continuation()
- function continuation() {
- if (++called === expected) {
- onremove(vnode)
- if (vnode.dom) {
- var count0 = vnode.domSize || 1
- if (count0 > 1) {
- var dom = vnode.dom
- while (--count0) {
- removeNodeFromDOM(dom.nextSibling)
- }
- }
- removeNodeFromDOM(vnode.dom)
- if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements
- if (!context.pool) context.pool = [vnode]
- else context.pool.push(vnode)
- }
- }
- }
- }
- }
- function removeNodeFromDOM(node) {
- var parent = node.parentNode
- if (parent != null) parent.removeChild(node)
- }
- function onremove(vnode) {
- if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode)
- if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
- if (vnode.instance != null) onremove(vnode.instance)
- else {
- var children = vnode.children
- if (Array.isArray(children)) {
- for (var i = 0; i < children.length; i++) {
- var child = children[i]
- if (child != null) onremove(child)
- }
- }
- }
- }
- //attrs2
- function setAttrs(vnode, attrs2, ns) {
- for (var key2 in attrs2) {
- setAttr(vnode, key2, null, attrs2[key2], ns)
- }
- }
- function setAttr(vnode, key2, old, value, ns) {
- var element = vnode.dom
- if (key2 === "key" || key2 === "is" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return
- var nsLastIndex = key2.indexOf(":")
- if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") {
- element.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(nsLastIndex + 1), value)
- }
- else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value)
- else if (key2 === "style") updateStyle(element, old, value)
- else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) {
- if (key2 === "value") {
- var normalized0 = "" + value // eslint-disable-line no-implicit-coercion
- //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
- if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return
- //setting select[value] to same value while having select open blinks select dropdown in Chrome
- if (vnode.tag === "select") {
- if (value === null) {
- if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return
- } else {
- if (old !== null && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return
- }
- }
- //setting option[value] to same value while having select open blinks select dropdown in Chrome
- if (vnode.tag === "option" && old != null && vnode.dom.value === normalized0) return
- }
- // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur.
- if (vnode.tag === "input" && key2 === "type") {
- element.setAttribute(key2, value)
- return
- }
- element[key2] = value
- }
- else {
- if (typeof value === "boolean") {
- if (value) element.setAttribute(key2, "")
- else element.removeAttribute(key2)
- }
- else element.setAttribute(key2 === "className" ? "class" : key2, value)
- }
- }
- function setLateAttrs(vnode) {
- var attrs2 = vnode.attrs
- if (vnode.tag === "select" && attrs2 != null) {
- if ("value" in attrs2) setAttr(vnode, "value", null, attrs2.value, undefined)
- if ("selectedIndex" in attrs2) setAttr(vnode, "selectedIndex", null, attrs2.selectedIndex, undefined)
- }
- }
- function updateAttrs(vnode, old, attrs2, ns) {
- if (attrs2 != null) {
- for (let key2 in attrs2) {
- setAttr(vnode, key2, old && old[key2], attrs2[key2], ns)
- }
- }
- if (old != null) {
- for (var key2 in old) {
- if (attrs2 == null || !(key2 in attrs2)) {
- if (key2 === "className") key2 = "class"
- if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined)
- else if (key2 !== "key") vnode.dom.removeAttribute(key2)
- }
- }
- }
- }
- function isFormAttribute(vnode, attr) {
- return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement
- }
- function isLifecycleMethod(attr) {
- return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"
- }
- function isAttribute(attr) {
- return attr === "href" || attr === "list" || attr === "form" || attr === "width" || attr === "height"// || attr === "type"
- }
- function isCustomElement(vnode){
- return vnode.attrs.is || vnode.tag.indexOf("-") > -1
- }
- function hasIntegrationMethods(source) {
- return source != null && (source.oncreate || source.onupdate || source.onbeforeremove || source.onremove)
- }
- //style
- function updateStyle(element, old, style) {
- if (old === style) element.style.cssText = "", old = null
- if (style == null) element.style.cssText = ""
- else if (typeof style === "string") element.style.cssText = style
- else {
- if (typeof old === "string") element.style.cssText = ""
- for (var key2 in style) {
- element.style[key2] = style[key2]
- }
- if (old != null && typeof old !== "string") {
- for (var key3 in old) {
- if (!(key3 in style)) element.style[key3] = ""
- }
- }
- }
- }
- //event
- function updateEvent(vnode, key2, value) {
- var element = vnode.dom
- var callback = typeof onevent !== "function" ? value : function(e) {
- var result = value.call(element, e)
- onevent.call(element, e)
- return result
- }
- if (key2 in element) element[key2] = typeof value === "function" ? callback : null
- else {
- var eventName = key2.slice(2)
- if (vnode.events === undefined) vnode.events = {}
- if (vnode.events[key2] === callback) return
- if (vnode.events[key2] != null) element.removeEventListener(eventName, vnode.events[key2], false)
- if (typeof value === "function") {
- vnode.events[key2] = callback
- element.addEventListener(eventName, vnode.events[key2], false)
- }
- }
- }
- //lifecycle
- function initLifecycle(source, vnode, hooks) {
- if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
- if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
- }
- function updateLifecycle(source, vnode, hooks) {
- if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
- }
- function shouldNotUpdate(vnode, old) {
- var forceVnodeUpdate, forceComponentUpdate
- if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
- if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old)
- if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
- vnode.dom = old.dom
- vnode.domSize = old.domSize
- vnode.instance = old.instance
- return true
- }
- return false
- }
- function render(dom, vnodes) {
- if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
- var hooks = []
- var active = $doc.activeElement
- var namespace = dom.namespaceURI
- // First time0 rendering into a node clears it out
- if (dom.vnodes == null) dom.textContent = ""
- if (!Array.isArray(vnodes)) vnodes = [vnodes]
- updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace)
- dom.vnodes = vnodes
- for (var i = 0; i < hooks.length; i++) hooks[i]()
- if ($doc.activeElement !== active) active.focus()
- }
- return {render: render, setEventCallback: setEventCallback}
- }
- function throttle(callback) {
- //60fps translates to 16.6ms, round it down since setTimeout requires int
- var time = 16
- var last = 0, pending = null
- var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
- return function() {
- var now = Date.now()
- if (last === 0 || now - last >= time) {
- last = now
- callback()
- }
- else if (pending === null) {
- pending = timeout(function() {
- pending = null
- callback()
- last = Date.now()
- }, time - (now - last))
- }
- }
- }
- var _11 = function($window) {
- var renderService = coreRenderer($window)
- renderService.setEventCallback(function(e) {
- if (e.redraw === false) e.redraw = undefined
- else redraw()
- })
- var callbacks = []
- function subscribe(key1, callback) {
- unsubscribe(key1)
- callbacks.push(key1, throttle(callback))
- }
- function unsubscribe(key1) {
- var index = callbacks.indexOf(key1)
- if (index > -1) callbacks.splice(index, 2)
- }
- function redraw() {
- for (var i = 1; i < callbacks.length; i += 2) {
- callbacks[i]()
- }
- }
- return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
- }
- var redrawService = _11(window)
- requestService.setCompletionCallback(redrawService.redraw)
- var _16 = function(redrawService0) {
- return function(root, component) {
- if (component === null) {
- redrawService0.render(root, [])
- redrawService0.unsubscribe(root)
- return
- }
- if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode")
- var run0 = function() {
- redrawService0.render(root, Vnode(component))
- }
- redrawService0.subscribe(root, run0)
- redrawService0.redraw()
- }
- }
- m.mount = _16(redrawService)
- var Promise = PromisePolyfill
- var parseQueryString = function(string) {
- if (string === "" || string == null) return {}
- if (string.charAt(0) === "?") string = string.slice(1)
- var entries = string.split("&"), data0 = {}, counters = {}
- for (var i = 0; i < entries.length; i++) {
- var entry = entries[i].split("=")
- var key5 = decodeURIComponent(entry[0])
- var value = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
- if (value === "true") value = true
- else if (value === "false") value = false
- var levels = key5.split(/\]\[?|\[/)
- var cursor = data0
- if (key5.indexOf("[") > -1) levels.pop()
- for (var j = 0; j < levels.length; j++) {
- var level = levels[j], nextLevel = levels[j + 1]
- var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
- var isValue = j === levels.length - 1
- if (level === "") {
- var key6 = levels.slice(0, j).join()
- if (counters[key6] == null) counters[key6] = 0
- level = counters[key6]++
- }
- if (cursor[level] == null) {
- cursor[level] = isValue ? value : isNumber ? [] : {}
- }
- cursor = cursor[level]
- }
- }
- return data0
- }
- var coreRouter = function($window) {
- var supportsPushState = typeof $window.history.pushState === "function"
- var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
- function normalize1(fragment0) {
- var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
- if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
- return data
- }
- var asyncId
- function debounceAsync(callback0) {
- return function() {
- if (asyncId != null) return
- asyncId = callAsync0(function() {
- asyncId = null
- callback0()
- })
- }
- }
- function parsePath(path, queryData, hashData) {
- var queryIndex = path.indexOf("?")
- var hashIndex = path.indexOf("#")
- var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length
- if (queryIndex > -1) {
- var queryEnd = hashIndex > -1 ? hashIndex : path.length
- var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd))
- for (var key4 in queryParams) queryData[key4] = queryParams[key4]
- }
- if (hashIndex > -1) {
- var hashParams = parseQueryString(path.slice(hashIndex + 1))
- for (var key5 in hashParams) hashData[key5] = hashParams[key5]
- }
- return path.slice(0, pathEnd)
- }
- var router = {prefix: "#!"}
- router.getPath = function() {
- var type2 = router.prefix.charAt(0)
- switch (type2) {
- case "#": return normalize1("hash").slice(router.prefix.length)
- case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash")
- default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash")
- }
- }
- router.setPath = function(path, data, options) {
- var queryData = {}, hashData = {}
- path = parsePath(path, queryData, hashData)
- if (data != null) {
- for (var key4 in data) queryData[key4] = data[key4]
- path = path.replace(/:([^\/]+)/g, function(match2, token) {
- delete queryData[token]
- return data[token]
- })
- }
- var query = buildQueryString(queryData)
- if (query) path += "?" + query
- var hash = buildQueryString(hashData)
- if (hash) path += "#" + hash
- if (supportsPushState) {
- var state = options ? options.state : null
- var title = options ? options.title : null
- $window.onpopstate()
- if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path)
- else $window.history.pushState(state, title, router.prefix + path)
- }
- else $window.location.href = router.prefix + path
- }
- router.defineRoutes = function(routes, resolve, reject) {
- function resolveRoute() {
- var path = router.getPath()
- var params = {}
- var pathname = parsePath(path, params, params)
- var state = $window.history.state
- if (state != null) {
- for (var k in state) params[k] = state[k]
- }
- for (var route0 in routes) {
- var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
- if (matcher.test(pathname)) {
- pathname.replace(matcher, function() {
- var keys = route0.match(/:[^\/]+/g) || []
- var values = [].slice.call(arguments, 1, -2)
- for (var i = 0; i < keys.length; i++) {
- params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
- }
- resolve(routes[route0], params, path, route0)
- })
- return
- }
- }
- reject(path, params)
- }
- if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
- else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
- resolveRoute()
- }
- return router
- }
- var _20 = function($window, redrawService0) {
- var routeService = coreRouter($window)
- var identity = function(v) {return v}
- var render1, component, attrs3, currentPath, lastUpdate
- var route = function(root, defaultRoute, routes) {
- if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
- var run1 = function() {
- if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
- }
- var bail = function(path) {
- if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
- else throw new Error("Could not resolve default route " + defaultRoute)
- }
- routeService.defineRoutes(routes, function(payload, params, path) {
- var update = lastUpdate = function(routeResolver, comp) {
- if (update !== lastUpdate) return
- component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
- attrs3 = params, currentPath = path, lastUpdate = null
- render1 = (routeResolver.render || identity).bind(routeResolver)
- run1()
- }
- if (payload.view || typeof payload === "function") update({}, payload)
- else {
- if (payload.onmatch) {
- Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
- update(payload, resolved)
- }, bail)
- }
- else update(payload, "div")
- }
- }, bail)
- redrawService0.subscribe(root, run1)
- }
- route.set = function(path, data, options) {
- if (lastUpdate != null) {
- options = options || {}
- options.replace = true
- }
- lastUpdate = null
- routeService.setPath(path, data, options)
- }
- route.get = function() {return currentPath}
- route.prefix = function(prefix0) {routeService.prefix = prefix0}
- route.link = function(vnode1) {
- vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href)
- vnode1.dom.onclick = function(e) {
- if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
- e.preventDefault()
- e.redraw = false
- var href = this.getAttribute("href")
- if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
- route.set(href, undefined, undefined)
- }
- }
- route.param = function(key3) {
- if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3]
- return attrs3
- }
- return route
- }
- m.route = _20(window, redrawService)
- m.withAttr = function(attrName, callback1, context) {
- return function(e) {
- callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
- }
- }
- var _28 = coreRenderer(window)
- m.render = _28.render
- m.redraw = redrawService.redraw
- m.request = requestService.request
- m.jsonp = requestService.jsonp
- m.parseQueryString = parseQueryString
- m.buildQueryString = buildQueryString
- m.version = "1.1.3"
- m.vnode = Vnode
- if ('object' !== "undefined") module["exports"] = m
- else {}
- }());
- });
- const restrictScroll = function(e) {
- const toc = e.currentTarget
- const maxScroll = toc.scrollHeight - toc.offsetHeight
- if (toc.scrollTop + e.deltaY < 0) {
- toc.scrollTop = 0
- e.preventDefault()
- } else if (toc.scrollTop + e.deltaY > maxScroll) {
- toc.scrollTop = maxScroll
- e.preventDefault()
- }
- e.redraw = false
- }
- const TOC = function({ $headings, $activeHeading, onClickHeading }) {
- // $activeHeading.subscribe(activeIndex => {})
- const toTree = function(headings) {
- let i = 0
- let tree = { level: 0, children: [] }
- let stack = [tree]
- const top = arr => arr.slice(-1)[0]
- while (i < headings.length) {
- let { level, isActive } = headings[i]
- if (level === stack.length) {
- const node = {
- heading: headings[i],
- children: []
- }
- top(stack).children.push(node)
- stack.push(node)
- if (isActive) {
- stack.forEach(node => {
- if (node.heading) {
- node.heading.isActive = true
- }
- })
- }
- i++
- } else if (level < stack.length) {
- stack.pop()
- } else if (level > stack.length) {
- const node = {
- heading: null,
- children: []
- }
- top(stack).children.push(node)
- stack.push(node)
- }
- }
- return tree
- }
- const UL = (children, { isRoot = false } = {}) =>
- mithril(
- 'ul',
- { onwheel: isRoot && restrictScroll, onclick: isRoot && onClickHeading },
- children.map(LI)
- )
- const LI = ({ heading, children }, index) =>
- mithril(
- 'li',
- { class: heading && heading.isActive ? 'active' : '', key: index },
- [
- heading &&
- mithril('a', { href: `#${heading.anchor}` }, heading.node.textContent),
- children && children.length && UL(children)
- ].filter(Boolean)
- )
- return {
- oncreate({ dom }) {
- // scroll to heading if out of view
- $activeHeading.subscribe(index => {
- const target = [].slice.apply(dom.querySelectorAll('.active')).pop()
- if (target) {
- const targetRect = target.getBoundingClientRect()
- const containerRect = dom.getBoundingClientRect()
- const outOfView =
- targetRect.top > containerRect.bottom ||
- targetRect.bottom < containerRect.top
- if (outOfView) {
- scrollTo({
- targetElem: target,
- scrollElem: dom,
- maxDuration: 0,
- topMargin: dom.offsetHeight / 2 - target.offsetHeight / 2
- })
- }
- }
- })
- Stream.combine($headings, $activeHeading, () => null).subscribe(_ =>
- mithril.redraw()
- )
- },
- view() {
- $headings().forEach((h, i) => (h.isActive = i === $activeHeading()))
- const tree = toTree($headings())
- return UL(tree.children, { isRoot: true })
- }
- }
- }
- const stop = e => {
- e.stopPropagation()
- e.preventDefault()
- }
- const Handle = function({ $userOffset }) {
- let [sClientX, sClientY] = [0, 0]
- let [sOffsetX, sOffsetY] = [0, 0]
- const onDrag = throttle(e => {
- stop(e)
- let [dX, dY] = [e.clientX - sClientX, e.clientY - sClientY]
- $userOffset([sOffsetX + dX, sOffsetY + dY])
- e.redraw = false
- })
- const onDragEnd = e => {
- window.removeEventListener('mousemove', onDrag)
- window.removeEventListener('mouseup', onDragEnd)
- e.redraw = false
- }
- const onDragStart = e => {
- if (e.button === 0) {
- stop(e)
- sClientX = e.clientX
- sClientY = e.clientY
- sOffsetX = $userOffset()[0]
- sOffsetY = $userOffset()[1]
- window.addEventListener('mousemove', onDrag)
- window.addEventListener('mouseup', onDragEnd)
- }
- e.redraw = false
- }
- return {
- view() {
- return mithril(
- '.handle',
- {
- onmousedown: onDragStart
- },
- 'table of contents'
- )
- }
- }
- }
- const ARTICLE_TOC_GAP = 150
- const makeSticky = function(options) {
- let {
- ref,
- scrollable,
- popper,
- direction,
- gap,
- $refChange,
- $scroll,
- $offset,
- $topMargin
- } = options
- let $refRect = Stream.combine($refChange, () => {
- let refRect = ref.getBoundingClientRect()
- let refStyle = window.getComputedStyle(ref)
- let scrollTop = getScroll(scrollable, 'top')
- let scrollLeft = getScroll(scrollable, 'left')
- let refFullRect = {
- top: refRect.top - scrollTop,
- right: refRect.right - scrollLeft,
- bottom: refRect.bottom - scrollTop,
- left: refRect.left - scrollLeft,
- width: refRect.width,
- height: refRect.height
- }
- if (refStyle['box-sizing'] === 'border-box') {
- refFullRect.left += num(refStyle['padding-left'])
- refFullRect.right -= num(refStyle['padding-right'])
- refFullRect.width -=
- num(refStyle['padding-left']) + num(refStyle['padding-right'])
- }
- return refFullRect
- })
- let popperMetric = popper.getBoundingClientRect()
- const scrollableTop =
- scrollable === document.body ? 0 : scrollable.getBoundingClientRect().top
- return Stream.combine(
- $refRect,
- $scroll,
- $offset,
- $topMargin,
- (ref, [scrollX, scrollY], [offsetX, offsetY], topMargin) => {
- let x =
- direction === 'right'
- ? ref.right + gap
- : ref.left - gap - popperMetric.width
- x = Math.min(Math.max(0, x), window.innerWidth - popperMetric.width) // restrict to visible area
- let y = Math.max(scrollableTop + topMargin, ref.top - scrollY)
- return {
- position: 'fixed',
- left: 0,
- top: 0,
- transform: translate3d(x + offsetX, y + offsetY)
- }
- }
- )
- }
- const getOptimalContainerPos = function(article) {
- const {
- top,
- left,
- right,
- bottom,
- height,
- width
- } = article.getBoundingClientRect()
- const depthOf = function(elem) {
- let depth = 0
- while (elem) {
- elem = elem.parentElement
- depth++
- }
- return depth
- }
- const depthOfPoint = function([x, y]) {
- const elem = document.elementFromPoint(x, y)
- return elem && depthOf(elem)
- }
- const gap = ARTICLE_TOC_GAP
- const testWidth = 200
- const testHeight = 400
- const leftSlotTestPoints = [
- left - gap - testWidth,
- left - gap - testWidth / 2,
- left - gap
- ]
- .map(x => [top, top + testHeight / 2, top + testHeight].map(y => [x, y]))
- .reduce((prev, cur) => prev.concat(cur), [])
- const rightSlotTestPoints = [
- right + gap,
- right + gap + testWidth / 2,
- right + gap + testWidth
- ]
- .map(x => [top, top + testHeight / 2, top + testHeight].map(y => [x, y]))
- .reduce((prev, cur) => prev.concat(cur), [])
- const leftDepths = leftSlotTestPoints.map(depthOfPoint).filter(Boolean)
- const rightDepths = rightSlotTestPoints.map(depthOfPoint).filter(Boolean)
- const leftAvgDepth = leftDepths.length
- ? leftDepths.reduce((a, b) => a + b, 0) / leftDepths.length
- : null
- const rightAvgDepth = rightDepths.length
- ? rightDepths.reduce((a, b) => a + b, 0) / rightDepths.length
- : null
- if (!leftAvgDepth) return { direction: 'right' }
- if (!rightAvgDepth) return { direction: 'left' }
- const spaceDiff = document.documentElement.offsetWidth - right - left
- const scoreDiff =
- spaceDiff * 1 + (rightAvgDepth - leftAvgDepth) * 9 * -10 + 20 // I do like right better
- return scoreDiff > 0 ? { direction: 'right' } : { direction: 'left' }
- }
- const Container = function({
- article,
- scrollable,
- $headings,
- theme,
- $activeHeading,
- $isShow,
- $userOffset,
- $relayout,
- $scroll,
- $topbarHeight,
- onClickHeading
- }) {
- const handle = Handle({ $userOffset })
- const toc = TOC({ $headings, $activeHeading, onClickHeading })
- return {
- oncreate({ dom }) {
- const { direction } = getOptimalContainerPos(article)
- this.$style = makeSticky({
- ref: article,
- scrollable: scrollable,
- popper: dom,
- direction: direction,
- gap: ARTICLE_TOC_GAP,
- $topMargin: $topbarHeight.map(h => (h || 0) + 50),
- $refChange: $relayout,
- $scroll: $scroll,
- $offset: $userOffset
- })
- this.$style.subscribe(_ => mithril.redraw())
- },
- view() {
- return mithril(
- '#smarttoc',
- {
- class: [
- theme || 'light',
- $headings().filter(h => h.level <= 2).length > 50 && 'lengthy',
- $isShow() ? '' : 'hidden'
- ]
- .filter(Boolean)
- .join(' '),
- style: this.$style && this.$style()
- },
- [mithril(handle), mithril(toc)]
- )
- }
- }
- }
- const Extender = function({ $headings, scrollable, $isShow, $relayout }) {
- const $extender = Stream()
- // toc: extend body height so we can scroll to the last heading
- let extender = document.createElement('DIV')
- extender.id = 'smarttoc-extender'
- Stream.combine($isShow, $relayout, $headings, (isShow, _, headings) => {
- setTimeout(() => {
- // some delay to ensure page is stable ?
- let lastHeading = headings.slice(-1)[0].node
- let lastRect = lastHeading.getBoundingClientRect()
- let extenderHeight = 0
- if (scrollable === document.body) {
- let heightBelowLastRect =
- document.documentElement.scrollHeight -
- (lastRect.bottom + document.documentElement.scrollTop) -
- num(extender.style.height) // in case we are there already
- extenderHeight = isShow
- ? Math.max(
- window.innerHeight - lastRect.height - heightBelowLastRect,
- 0
- )
- : 0
- } else {
- let scrollRect = scrollable.getBoundingClientRect()
- let heightBelowLastRect =
- scrollRect.top +
- scrollable.scrollHeight -
- getScroll(scrollable) - // bottom of scrollable relative to viewport
- lastRect.bottom -
- num(extender.style.height) // in case we are there already
- extenderHeight = isShow
- ? Math.max(
- scrollRect.height - lastRect.height - heightBelowLastRect,
- 0
- )
- : 0
- }
- $extender({
- height: extenderHeight
- })
- }, 300)
- })
- $extender.subscribe(style => applyStyle(extender, style))
- return extender
- }
- const relayoutStream = function(article, $resize, $isShow) {
- const readableStyle = function(article) {
- let computed = window.getComputedStyle(article)
- let fontSize = num(computed.fontSize)
- let bestWidth = Math.min(Math.max(fontSize, 12), 16) * 66
- if (computed['box-sizing'] === 'border-box') {
- bestWidth +=
- num(computed['padding-left']) + num(computed['padding-right'])
- }
- return Object.assign(
- num(computed.marginLeft) || num(computed.marginRight)
- ? {}
- : {
- marginLeft: 'auto',
- marginRight: 'auto'
- },
- num(computed.maxWidth)
- ? {}
- : {
- maxWidth: bestWidth
- }
- )
- }
- let oldStyle = article.style.cssText
- let newStyle = readableStyle(article)
- let $relayout = $isShow.map(isShow => {
- if (isShow) {
- applyStyle(article, newStyle)
- return article
- } else {
- applyStyle(article, oldStyle)
- }
- })
- return Stream.combine($relayout, $resize, () => null)
- }
- const addAnchors = function(headings) {
- const anchoredHeadings = headings.map(function({ node, level, anchor }) {
- if (!anchor) {
- anchor =
- node.id ||
- [].slice
- .apply(node.children)
- .filter(elem => elem.tagName === 'A')
- .map(a => {
- let href = a.getAttribute('href') || ''
- return href.startsWith('#') ? href.substr(1) : a.id
- })
- .filter(Boolean)[0]
- if (!anchor) {
- anchor = node.id = unique(safe(node.textContent))
- } else {
- anchor = unique(anchor)
- }
- }
- return { node, level, anchor }
- })
- return anchoredHeadings
- }
- const getScrollParent = function(elem) {
- const canScroll = el =>
- ['auto', 'scroll'].includes(window.getComputedStyle(el).overflowY) &&
- el.clientHeight + 1 < el.scrollHeight
- while (elem && elem !== document.body && !canScroll(elem)) {
- elem = elem.parentElement
- }
- log('scrollable', elem)
- draw(elem, 'purple')
- return elem
- }
- const scrollStream = function(scrollable, $isShow) {
- let $scroll = Stream([getScroll(scrollable, 'left'), getScroll(scrollable)])
- let source = scrollable === document.body ? window : scrollable
- Stream.fromEvent(source, 'scroll')
- .filter(() => $isShow())
- .throttle()
- .subscribe(() => {
- $scroll([getScroll(scrollable, 'left'), getScroll(scrollable)])
- })
- return $scroll
- }
- const activeHeadingStream = function(
- $headings,
- scrollable,
- $scroll,
- $relayout,
- $topbarHeight
- ) {
- const $headingScrollYs = Stream.combine(
- $relayout,
- $headings,
- (_, headings) => {
- const scrollableTop =
- (scrollable === document.body
- ? 0
- : scrollable.getBoundingClientRect().top) -
- getScroll(scrollable, 'top')
- return headings.map(
- ({ node }) => node.getBoundingClientRect().top - scrollableTop
- )
- }
- )
- let $curIndex = Stream.combine(
- $headingScrollYs,
- $scroll,
- $topbarHeight,
- function(headingScrollYs, [scrollX, scrollY], topbarHeight = 0) {
- let i = 0
- for (let len = headingScrollYs.length; i < len; i++) {
- if (headingScrollYs[i] > scrollY + topbarHeight + 20) {
- break
- }
- }
- return Math.max(0, i - 1)
- }
- )
- return $curIndex.unique()
- }
- const scrollToHeading = function(
- { node },
- scrollElem,
- onScrollEnd,
- topMargin = 0
- ) {
- scrollTo({
- targetElem: node,
- scrollElem: scrollElem,
- topMargin: topMargin,
- maxDuration: 300,
- callback: onScrollEnd && onScrollEnd.bind(null, node)
- })
- }
- const getTopBarHeight = function(topElem) {
- const findFixedParent = function(elem) {
- const isFixed = elem => {
- let { position, zIndex } = window.getComputedStyle(elem)
- return position === 'fixed' && zIndex
- }
- while (elem !== document.body && !isFixed(elem)) {
- elem = elem.parentElement
- }
- return elem === document.body ? null : elem
- }
- let { left, right, top } = topElem.getBoundingClientRect()
- let leftTopmost = document.elementFromPoint(left + 1, top + 1)
- let rightTopmost = document.elementFromPoint(right - 1, top + 1)
- if (
- leftTopmost &&
- rightTopmost &&
- leftTopmost !== topElem &&
- rightTopmost !== topElem
- ) {
- let leftFixed = findFixedParent(leftTopmost)
- let rightFixed = findFixedParent(rightTopmost)
- if (leftFixed && leftFixed === rightFixed) {
- return leftFixed.offsetHeight
- } else {
- return 0
- }
- } else {
- return 0
- }
- }
- const getTheme = function(article) {
- let elem = article
- try {
- const parseColor = str =>
- str.replace(/rgba?\(/, '').replace(/\).*/, '').split(/, ?/)
- const getBgColor = elem =>
- parseColor(window.getComputedStyle(elem)['background-color'])
- const isTransparent = ([r, g, b, a]) => a === 0
- const isLight = ([r, g, b, a]) => r + g + b > 255 / 2 * 3
- while (elem && elem.parentElement) {
- const color = getBgColor(elem)
- if (isTransparent(color)) {
- elem = elem.parentElement
- } else {
- return isLight(color) ? 'light' : 'dark'
- }
- }
- return 'light'
- } catch (e) {
- return 'light'
- }
- }
- const getRoot = function() {
- let root = document.getElementById('smarttoc_wrapper')
- if (!root) {
- root = document.body.appendChild(document.createElement('DIV'))
- root.id = 'smarttoc_wrapper'
- }
- return root
- }
- // 生成目录
- function createTOC({
- article,
- $headings: $headings_,
- userOffset = [0, 0]
- }) {
- const $headings = $headings_.map(addAnchors)
- insertCSS(tocCSS, 'smarttoc__css')
- const scrollable = getScrollParent(article)
- const theme = getTheme(article)
- log('theme', theme)
- const $isShow = Stream(true)
- const $topbarHeight = Stream()
- const $resize = Stream.combine(
- Stream.fromEvent(window, 'resize'),
- Stream.fromEvent(document, 'readystatechange'),
- Stream.fromEvent(document, 'load'),
- Stream.fromEvent(document, 'DOMContentLoaded'),
- () => null
- )
- .filter(() => $isShow())
- .throttle()
- const $scroll = scrollStream(scrollable, $isShow)
- const $relayout = relayoutStream(article, $resize, $isShow)
- const $activeHeading = activeHeadingStream(
- $headings,
- scrollable,
- $scroll,
- $relayout,
- $topbarHeight
- )
- const $userOffset = Stream(userOffset)
- /*
- scrollable.appendChild(
- Extender({ $headings, scrollable, $isShow, $relayout })
- )
- */
- const onScrollEnd = function(node) {
- if ($topbarHeight() == null) {
- setTimeout(() => {
- $topbarHeight(getTopBarHeight(node))
- log('topBarHeight', $topbarHeight())
- if ($topbarHeight()) {
- scrollToHeading({ node }, scrollable, null, $topbarHeight() + 10)
- }
- }, 300)
- }
- }
- const onClickHeading = function(e) {
- e.preventDefault()
- e.stopPropagation()
- const anchor = e.target.getAttribute('href').substr(1)
- const heading = $headings().find(heading => heading.anchor === anchor)
- scrollToHeading(
- heading,
- scrollable,
- onScrollEnd,
- ($topbarHeight() || 0) + 10
- )
- }
- mithril.mount(
- getRoot(),
- Container({
- article,
- scrollable,
- $headings,
- theme,
- $activeHeading,
- $isShow,
- $userOffset,
- $relayout,
- $scroll,
- $topbarHeight,
- onClickHeading
- })
- )
- // now show what we've found
- if (article.getBoundingClientRect().top > window.innerHeight - 50) {
- scrollToHeading(
- $headings()[0],
- scrollable,
- onScrollEnd,
- ($topbarHeight() || 0) + 10
- )
- }
- return {
- isValid: () =>
- document.body.contains(article) && article.contains($headings()[0].node),
- isShow: () => $isShow(),
- toggle: () => $isShow(!$isShow()),
- next: () => {
- if ($isShow()) {
- let nextIdx = Math.min($headings().length - 1, $activeHeading() + 1)
- scrollToHeading(
- $headings()[nextIdx],
- scrollable,
- onScrollEnd,
- ($topbarHeight() || 0) + 10
- )
- }
- },
- prev: () => {
- if ($isShow()) {
- let prevIdx = Math.max(0, $activeHeading() - 1)
- scrollToHeading(
- $headings()[prevIdx],
- scrollable,
- onScrollEnd,
- ($topbarHeight() || 0) + 10
- )
- }
- },
- dispose: () => {
- log('dispose')
- $isShow(false)
- mithril.render(getRoot(), mithril(''))
- return { userOffset: $userOffset() }
- }
- }
- }
- const pathToTop = function(elem, maxLvl = -1) {
- assert(elem, 'no element given')
- const path = []
- while (elem && maxLvl--) {
- path.push(elem)
- elem = elem.parentElement
- }
- return path
- }
- const isStrongAlsoHeading = function(rootElement = document) {
- return false
- // return rootElement.querySelectorAll('p > strong:only-child').length > 3
- }
- const extractArticle = function(rootElement = document) {
- log('extracting article')
- const scores = new Map()
- function addScore(elem, inc) {
- scores.set(elem, (scores.get(elem) || 0) + inc)
- }
- function updateScore(elem, weight) {
- let path = pathToTop(elem, weight.length)
- path.forEach((elem, distance) => addScore(elem, weight[distance]))
- }
- // weigh nodes by factor: "selector", "distance from this node"
- const weights = {
- h1: [0, 100, 60, 40, 30, 25, 22].map(s => s * 0.4),
- h2: [0, 100, 60, 40, 30, 25, 22],
- h3: [0, 100, 60, 40, 30, 25, 22].map(s => s * 0.5),
- h4: [0, 100, 60, 40, 30, 25, 22].map(s => s * 0.5 * 0.5),
- h5: [0, 100, 60, 40, 30, 25, 22].map(s => s * 0.5 * 0.5 * 0.5),
- h6: [0, 100, 60, 40, 30, 25, 22].map(s => s * 0.5 * 0.5 * 0.5 * 0.5),
- article: [500],
- '.article': [500],
- '.content': [101],
- sidebar: [-500],
- '.sidebar': [-500],
- aside: [-500],
- '.aside': [-500],
- nav: [-500],
- '.nav': [-500],
- '.navigation': [-500],
- '.toc': [-500],
- '.table-of-contents': [-500]
- }
- const selectors = Object.keys(weights)
- selectors
- .map(selector => ({
- selector: selector,
- elems: [].slice.apply(rootElement.querySelectorAll(selector))
- }))
- .forEach(({ selector, elems }) =>
- elems.forEach(elem => updateScore(elem, weights[selector]))
- )
- const sorted = [...scores].sort((a, b) => b[1] - a[1])
- // reweigh top 5 nodes by factor: "take-lots-vertical-space", "contain-less-links", "too-narrow"
- let candicates = sorted
- .slice(0, 5)
- .filter(Boolean)
- .map(([elem, score]) => ({ elem, score }))
- let isTooNarrow = e => e.scrollWidth < 400 // rule out sidebars
- candicates.forEach(c => {
- if (isTooNarrow(c.elem)) {
- c.isNarrow = true
- candicates.forEach(parent => {
- if (parent.elem.contains(c.elem)) {
- parent.score *= 0.7
- }
- })
- }
- })
- candicates = candicates.filter(c => !c.isNarrow)
- const reweighted = candicates
- .map(({ elem, score }) => [
- elem,
- score *
- Math.log(
- elem.scrollHeight *
- elem.scrollHeight /
- (elem.querySelectorAll('a').length || 1)
- ),
- elem.scrollHeight,
- elem.querySelectorAll('a').length
- ])
- .sort((a, b) => b[1] - a[1])
- const article = reweighted.length ? reweighted[0][0] : null
- return article
- }
- const extractHeadings = function(article) {
- log('extracting heading')
- // what to be considered as headings
- const tags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].concat(
- isStrongAlsoHeading(article) ? 'STRONG' : []
- )
- const tagWeight = tag =>
- ({ H1: 4, H2: 9, H3: 9, H4: 10, H5: 10, H6: 10, STRONG: 10 }[tag])
- const isVisible = elem => elem.offsetHeight !== 0
- const isGroupVisible = headings =>
- headings.filter(isVisible).length >= headings.length * 0.5
- const headingGroup = tags
- .map(tag => [].slice.apply(article.getElementsByTagName(tag)))
- .map((headings, i) => ({
- elems: headings,
- tag: tags[i],
- score: headings.length * tagWeight(tags[i])
- }))
- .filter(heading => heading.score >= 10)
- .filter(heading => isGroupVisible(heading.elems))
- .slice(0, 3)
- // use document sequence
- const validTags = headingGroup.map(headings => headings.tag)
- const acceptNode = node =>
- validTags.includes(node.tagName) && isVisible(node)
- ? NodeFilter.FILTER_ACCEPT
- : NodeFilter.FILTER_SKIP
- const treeWalker = document.createTreeWalker(
- article,
- NodeFilter.SHOW_ELEMENT,
- { acceptNode }
- )
- const headings = []
- while (treeWalker.nextNode()) {
- let node = treeWalker.currentNode
- headings.push({
- node,
- level: validTags.indexOf(node.tagName) + 1
- })
- }
- if (false) {}
- return headings
- }
- function extract() {
- const article = extractArticle(document)
- let $headings
- if (article) {
- $headings = Stream(extractHeadings(article))
- const $articleChange = Stream(null)
- const observer = new MutationObserver(_ => $articleChange(null))
- observer.observe(article, { childList: true })
- $articleChange.throttle(200).subscribe(_ => {
- let headings = extractHeadings(article)
- if (headings && headings.length) {
- $headings(headings)
- }
- })
- }
- return [article, $headings]
- }
- if (isMasterFrame(window)) {
- let toc
- const generate = function(option = {}) {
- let [article, $headings] = extract()
- if (article && $headings && $headings().length) {
- return createTOC(Object.assign({ article, $headings }, option))
- } else {
- toast('No article/headings are detected.')
- return null
- }
- }
- toc = generate()
- setInterval(() => {
- if (toc && !toc.isValid()) {
- let lastState = toc.dispose()
- toc = generate(lastState)
- }
- }, 3000)
- /*chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
- try {
- if (toc) {
- toc[request]()
- } else {
- toc = generate()
- }
- } catch (e) {
- console.error(e)
- }
- sendResponse(true)
- })*/
- }
- }());
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址