auto-toc

可以为任何网站生成TOC网站目录大纲, 默认是不打开的, 需要去插件菜单里为想要打开 toc 的网站开启开关, 插件会记住这个开关, 下回再打开这个网站会自动根据开关来生成 toc 与否. 高级技巧: 单击TOC拖动栏可以自动折叠 TOC, 双击TOC拖动栏可以关闭 TOC .

目前为 2023-10-20 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name auto-toc
  3. // @name:zh-CN auto-toc
  4. // @namespace EX
  5. // @version 1.16
  6. // @license MIT
  7. // @description Generate table of contents for any website. By default, it is not open. You need to go to the plug-in menu to open the switch for the website that wants to open the toc. The plug-in will remember this switch, and the toc will be generated automatically according to the switch when you open the website the next time.
  8. // @description:zh-cn 可以为任何网站生成TOC网站目录大纲, 默认是不打开的, 需要去插件菜单里为想要打开 toc 的网站开启开关, 插件会记住这个开关, 下回再打开这个网站会自动根据开关来生成 toc 与否. 高级技巧: 单击TOC拖动栏可以自动折叠 TOC, 双击TOC拖动栏可以关闭 TOC .
  9. // @include http://*
  10. // @include https://*
  11. // @grant GM_registerMenuCommand
  12. // @grant GM.registerMenuCommand
  13. // @grant GM_unregisterMenuCommand
  14. // @grant GM.unregisterMenuCommand
  15. // @grant GM_setValue
  16. // @grant GM.setValue
  17. // @grant GM_getValue
  18. // @grant GM.getValue
  19. // @grant GM_addStyle
  20. // @grant GM.addStyle
  21. // ==/UserScript==
  22.  
  23. (function () {
  24. "use strict";
  25.  
  26. function getRootWindow() {
  27. let w = window;
  28. while (w !== w.parent) {
  29. w = w.parent;
  30. }
  31. return w;
  32. }
  33.  
  34. function getMaster(root) {
  35. const iframes = [].slice.apply(
  36. root.document.getElementsByTagName("iframe")
  37. );
  38.  
  39. if (iframes.length === 0) {
  40. return root;
  41. } else {
  42. const largestChild = iframes
  43. .map((f) => ({
  44. elem: f,
  45. area: f.offsetWidth * f.offsetHeight,
  46. }))
  47. .sort((a, b) => b.area - a.area)[0];
  48. const html = root.document.documentElement;
  49. return largestChild.area / (html.offsetWidth * html.offsetHeight) >
  50. 0.5
  51. ? largestChild.elem.contentWindow
  52. : root;
  53. }
  54. }
  55.  
  56. function isMasterFrame(w) {
  57. const root = getRootWindow();
  58. const master = getMaster(root);
  59. return w === master;
  60. }
  61.  
  62. var toastCSS = `
  63. #smarttoc-toast {
  64. all: initial;
  65. }
  66. #smarttoc-toast * {
  67. all: unset;
  68. }
  69. #smarttoc-toast {
  70. display: none;
  71. position: fixed;
  72. left: 50%;
  73. transform: translateX(-50%);
  74. top: 0;
  75. margin: 1em 2em;
  76. min-width: 16em;
  77. text-align: center;
  78. padding: 1em;
  79. z-index: 10000;
  80. box-sizing: border-box;
  81. background-color: #017afe;
  82. border: 1px solid rgba(158, 158, 158, 0.22);
  83. color: #ffffff;
  84. font-size: calc(12px + 0.15vw);
  85. font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  86. font-weight: normal;
  87. -webkit-font-smoothing: subpixel-antialiased;
  88. font-smoothing: subpixel-antialiased;
  89. transition: opacity 200ms ease-out, transform 200ms ease-out;
  90. border-radius: 18px;
  91. box-shadow: 0px 0px 0px 0px rgb(0 0 0 / 20%), 0px 0px 8px 0 rgb(0 0 0 / 19%);
  92. }
  93. #smarttoc-toast.enter {
  94. display: block;
  95. opacity: 0.01;
  96. transform: translate3d(-50%, -2em, 0);
  97. }
  98. #smarttoc-toast.enter.enter-active {
  99. display: block;
  100. opacity: 1;
  101. transform: translate3d(-50%, 0, 0);
  102. }
  103. #smarttoc-toast.leave {
  104. display: block;
  105. opacity: 1;
  106. transform: translate3d(-50%, 0, 0);
  107. }
  108. #smarttoc-toast.leave.leave-active {
  109. display: block;
  110. opacity: 0.01;
  111. transform: translate3d(-50%, -2em, 0);
  112. }
  113. `;
  114.  
  115. function log() {
  116. if (false) {
  117. }
  118. }
  119.  
  120. function draw(elem, color = "red") {
  121. if (false && elem) {
  122. }
  123. }
  124.  
  125. function assert(condition, error) {
  126. if (!condition) {
  127. throw new Error(error);
  128. }
  129. }
  130.  
  131. // '12px' => 12
  132. const num = (size = "0") =>
  133. typeof size === "number" ? size : +size.replace(/px/, "");
  134.  
  135. // '12px' <= 12
  136. const px = (size = 0) => num(size) + "px";
  137.  
  138. function throttle(fn, delay) {
  139. if (delay) {
  140. let timer;
  141. return function timerThrottled(...args) {
  142. clearTimeout(timer);
  143. timer = setTimeout(function () {
  144. fn(...args);
  145. }, delay);
  146. };
  147. } else {
  148. let request;
  149. return function rafThrottled(...args) {
  150. cancelAnimationFrame(request);
  151. request = requestAnimationFrame(function () {
  152. fn(...args);
  153. });
  154. };
  155. }
  156. }
  157.  
  158. const safe = (str) => str.replace(/\s+/g, "-");
  159.  
  160. const unique = (function uniqueGenerator() {
  161. let set = new Set();
  162. return function unique(str) {
  163. let id = 1;
  164. while (set.has(str)) {
  165. str = str.replace(/(\$\d+)?$/, "") + "$" + id;
  166. id++;
  167. }
  168. set.add(str);
  169. return str;
  170. };
  171. })();
  172.  
  173. const getScroll = (elem, direction = "top") => {
  174. if (elem === document.body) {
  175. return direction === "top"
  176. ? document.documentElement.scrollTop || document.body.scrollTop
  177. : document.documentElement.scrollLeft ||
  178. document.body.scrollLeft;
  179. } else {
  180. return direction === "top" ? elem.scrollTop : elem.scrollLeft;
  181. }
  182. };
  183.  
  184. const setScroll = (elem, val, direction = "top") => {
  185. if (elem === document.body) {
  186. if (direction === "top") {
  187. document.documentElement.scrollTop = val;
  188. window.scrollTo(window.scrollX, val);
  189. } else {
  190. document.documentElement.scrollLeft = val;
  191. window.scrollTo(val, window.scrollY);
  192. }
  193. } else {
  194. if (direction === "top") {
  195. elem.scrollTop = val;
  196. } else {
  197. elem.scrollLeft = val;
  198. }
  199. }
  200. };
  201.  
  202. const scrollTo = (function scrollToFactory() {
  203. let request;
  204. const easeOutQuad = function (t, b, c, d) {
  205. t /= d;
  206. return -c * t * (t - 2) + b;
  207. };
  208. return function scrollTo({
  209. targetElem,
  210. scrollElem,
  211. topMargin = 0,
  212. maxDuration = 300,
  213. easeFn,
  214. callback,
  215. }) {
  216. cancelAnimationFrame(request);
  217. let rect = targetElem.getBoundingClientRect();
  218. let endScrollTop =
  219. rect.top -
  220. (scrollElem === document.body
  221. ? 0
  222. : scrollElem.getBoundingClientRect().top) +
  223. getScroll(scrollElem) -
  224. topMargin;
  225. let startScrollTop = getScroll(scrollElem);
  226. let distance = endScrollTop - startScrollTop;
  227. let startTime;
  228. let ease = easeFn || easeOutQuad;
  229. let distanceRatio = Math.min(Math.abs(distance) / 10000, 1);
  230. let duration = Math.max(
  231. maxDuration * distanceRatio * (2 - distanceRatio),
  232. 10
  233. );
  234. if (!maxDuration) {
  235. setScroll(scrollElem, endScrollTop);
  236. if (callback) {
  237. callback();
  238. }
  239. } else {
  240. requestAnimationFrame(update);
  241. }
  242.  
  243. function update(timestamp) {
  244. if (!startTime) {
  245. startTime = timestamp;
  246. }
  247. let progress = (timestamp - startTime) / duration;
  248. if (progress < 1) {
  249. setScroll(
  250. scrollElem,
  251. ease(
  252. timestamp - startTime,
  253. startScrollTop,
  254. distance,
  255. duration
  256. )
  257. );
  258. requestAnimationFrame(update);
  259. } else {
  260. setScroll(scrollElem, endScrollTop);
  261. if (callback) {
  262. callback();
  263. }
  264. }
  265. }
  266. };
  267. })();
  268.  
  269. function toDash(str) {
  270. return str.replace(/([A-Z])/g, (match, p1) => "-" + p1.toLowerCase());
  271. }
  272.  
  273. function applyStyle(elem, style = {}, reset = false) {
  274. if (reset) {
  275. elem.style = "";
  276. }
  277. if (typeof style === "string") {
  278. elem.style = style;
  279. } else {
  280. for (let prop in style) {
  281. if (typeof style[prop] === "number") {
  282. elem.style.setProperty(
  283. toDash(prop),
  284. px(style[prop]),
  285. "important"
  286. );
  287. } else {
  288. elem.style.setProperty(
  289. toDash(prop),
  290. style[prop],
  291. "important"
  292. );
  293. }
  294. }
  295. }
  296. }
  297.  
  298. function translate3d(x = 0, y = 0, z = 0) {
  299. return `translate3d(${Math.round(x)}px, ${Math.round(
  300. y
  301. )}px, ${Math.round(z)}px)`; // 0.5px => blurred text
  302. }
  303.  
  304. function setClass(elem, names, delay) {
  305. if (delay === undefined) {
  306. elem.classList = names;
  307. } else {
  308. return setTimeout(() => {
  309. elem.classList = names;
  310. }, delay);
  311. }
  312. }
  313.  
  314. const toast = (function toastFactory() {
  315. let timers = [];
  316. return function toast(msg, display_duration = 1600 /* ms */) {
  317. let toast;
  318. insertCSS(toastCSS, "smarttoc-toast__css");
  319. if (document.getElementById("smarttoc-toast")) {
  320. toast = document.getElementById("smarttoc-toast");
  321. } else {
  322. toast = document.createElement("DIV");
  323. toast.id = "smarttoc-toast";
  324. document.body.appendChild(toast);
  325. }
  326. toast.textContent = msg;
  327.  
  328. timers.forEach(clearTimeout);
  329. toast.classList = "";
  330.  
  331. const set = setClass.bind(null, toast);
  332.  
  333. toast.classList = "enter";
  334. timers = [
  335. set("enter enter-active", 0),
  336. set("leave", display_duration),
  337. set("leave leave-active", display_duration),
  338. set("", display_duration + 200),
  339. ];
  340. };
  341. })();
  342.  
  343. const insertCSS = function (css, id) {
  344. // if (!document.getElementById(id)) {
  345. let style = document.createElement("STYLE");
  346. style.type = "text/css";
  347. style.id = id;
  348. style.textContent = css;
  349. document.head.appendChild(style);
  350. // return
  351. // }
  352. };
  353.  
  354. const removeCSS = function (id) {
  355. let styleElement = document.querySelector(`#${id}`);
  356. if (styleElement) {
  357. styleElement.parentNode.removeChild(styleElement);
  358. }
  359. };
  360.  
  361. function shouldCollapseToc() {
  362. const domain2isCollapse = GM_getValue(
  363. "menu_GAEEScript_auto_collapse_toc"
  364. );
  365. // console.log('[shouldCollapseToc cccccccccccccccccccccccccccccc]', domain2isCollapse);
  366. // alert(domain2isCollapse[window.location.host])
  367. return domain2isCollapse[window.location.host];
  368. }
  369.  
  370. let toc_dom = null;
  371.  
  372. function getTocCss() {
  373. const shouldCollapse = shouldCollapseToc();
  374. console.log("[getTocCss]", shouldCollapse);
  375. return (
  376. `
  377. @media (prefers-color-scheme: dark) {
  378. #smarttoc.dark-scheme {
  379. background-color: rgb(48, 52, 54);
  380. }
  381. #smarttoc.dark-scheme .handle {
  382. color: #ffffff;
  383. }
  384. #smarttoc.dark-scheme a {
  385. color: #ccc;
  386. }
  387. #smarttoc.dark-scheme a:hover,
  388. #smarttoc.dark-scheme a:active {
  389. border-left-color: #f6f6f6;
  390. color: #fff;
  391. }
  392. #smarttoc.dark-scheme li.active>a {
  393. border-left-color: rgb(46, 82, 154);
  394. color: rgb(131, 174, 218)
  395. }
  396. }
  397. #smarttoc {
  398. all: initial;
  399. }
  400. #smarttoc * {
  401. all: unset;
  402. }
  403. /* container */
  404. #smarttoc {
  405. display: flex;
  406. flex-direction: column;
  407. align-items: stretch;
  408. position: fixed;
  409. min-width: 12em;
  410. resize: horizontal;
  411. width: 18em;
  412. ` +
  413. (shouldCollapse
  414. ? "max-height: 22px;"
  415. : "max-height: calc(100vh - 100px);") +
  416. `
  417. z-index: 10000;
  418. box-sizing: border-box;
  419. background-color: #fff;
  420. color: gray;
  421. font-size: calc(12px + 0.1vw);
  422. font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;
  423. line-height: 1.5;
  424. font-weight: normal;
  425. border: 1px solid rgba(158, 158, 158, 0.22);
  426. -webkit-font-smoothing: subpixel-antialiased;
  427. font-smoothing: subpixel-antialiased;
  428. overflow: hidden;
  429. contain: content;
  430. box-shadow: 0px 0px 0px 0px rgb(0 0 0 / 20%), 0px 0px 8px 0 rgb(0 0 0 / 19%);
  431. border-radius: 6px;
  432. transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
  433. ` +
  434. (shouldCollapse ? "opacity: 0.6;" : "opacity: 1;") +
  435. `
  436. }
  437. #smarttoc:hover {
  438. ` +
  439. (shouldCollapse
  440. ? "max-height: calc(100vh - 100px); opacity: 1"
  441. : "") +
  442. `
  443. }
  444. #smarttoc.hidden {
  445. display: none;
  446. }
  447. #smarttoc .handle {
  448. -webkit-user-select: none;
  449. user-select: none;
  450. border-bottom: 1px solid rgba(158, 158, 158, 0.22);
  451. padding: 0.1em 0.7em;
  452. font-variant-caps: inherit;
  453. font-variant: small-caps;
  454. font-size: 0.9em;
  455. color: rgb(0, 0, 0);
  456. cursor: pointer;
  457. text-align: center;
  458. opacity: 1;
  459. }
  460. #smarttoc .handle:hover,
  461. #smarttoc .handle:active {
  462. cursor: move;
  463. }
  464. #smarttoc>ul {
  465. flex-grow: 1;
  466. padding: 1em 1.3em 1.3em 1em;
  467. overflow-y: auto;
  468. overflow-x: hidden;
  469. }
  470. /* all headings */
  471. #smarttoc ul,
  472. #smarttoc li {
  473. list-style: none;
  474. display: block;
  475. }
  476. #smarttoc a {
  477. text-decoration: none;
  478. color: gray;
  479. display: block;
  480. line-height: 1.3;
  481. padding-top: 0.2em;
  482. padding-bottom: 0.2em;
  483. text-overflow: ellipsis;
  484. overflow-x: hidden;
  485. white-space: nowrap;
  486. margin-bottom: 0.8px;
  487. margin-top: 0.8px;
  488. }
  489. #smarttoc a:hover,
  490. #smarttoc a:active {
  491. border-left-color: rgba(86, 61, 124, 0.5);
  492. color: #563d7c;
  493. }
  494. #smarttoc li.active>a {
  495. border-left-color: #563d7c;
  496. color: #563d7c;
  497. }
  498. /* heading level: 1 */
  499. #smarttoc ul {
  500. line-height: 2;
  501. }
  502. #smarttoc ul a {
  503. font-size: 1em;
  504. padding-left: 1.3em;
  505. cursor: pointer
  506. }
  507. #smarttoc ul a:hover,
  508. #smarttoc ul a:active,
  509. #smarttoc ul li.active>a {
  510. border-left-width: 6px;
  511. border-left-style: solid;
  512. padding-left: calc(1.3em - 3px);
  513. }
  514. #smarttoc ul li.active>a {
  515. font-weight: 700;
  516. }
  517. /* heading level: 2 (hidden only when there are too many headings) */
  518. #smarttoc ul ul {
  519. line-height: 1.8;
  520. }
  521. #smarttoc.lengthy ul ul {
  522. display: none;
  523. }
  524. #smarttoc.lengthy ul li.active>ul {
  525. display: block;
  526. }
  527. #smarttoc ul ul a {
  528. font-size: 1em;
  529. padding-left: 2.7em;
  530. }
  531. #smarttoc ul ul a:hover,
  532. #smarttoc ul ul a:active,
  533. #smarttoc ul ul li.active>a {
  534. border-left-width: 4.6px;
  535. border-left-style: solid;
  536. padding-left: calc(2.7em - 2px);
  537. font-weight: normal;
  538. }
  539. /* heading level: 3 */
  540. #smarttoc ul ul ul {
  541. line-height: 1.7;
  542. /* display: none; */ /* (hidden unless parent is active) */
  543. }
  544. #smarttoc ul ul li.active>ul {
  545. display: block;
  546. }
  547. #smarttoc ul ul ul a {
  548. font-size: 1em;
  549. padding-left: 4em;
  550. }
  551. #smarttoc ul ul ul a:hover,
  552. #smarttoc ul ul ul a:active,
  553. #smarttoc ul ul ul li.active>a {
  554. border-left-width: 3px;
  555. border-left-style: solid;
  556. padding-left: calc(4em - 1px);
  557. font-weight: normal;
  558. }
  559. /* heading level: 4 */
  560. #smarttoc ul ul ul ul {
  561. line-height: 1.7;
  562. /* display: none; */ /* (hidden unless parent is active) */
  563. }
  564. #smarttoc ul ul ul li.active>ul {
  565. display: block;
  566. }
  567. #smarttoc ul ul ul ul a {
  568. font-size: 1em;
  569. padding-left: 5em;
  570. }
  571. #smarttoc ul ul ul ul a:hover,
  572. #smarttoc ul ul ul ul a:active,
  573. #smarttoc ul ul ul ul li.active>a {
  574. border-left-width: 2.2px;
  575. border-left-style: solid;
  576. padding-left: calc(5em - 0.5px);
  577. font-weight: normal;
  578. }
  579. /* heading level: 5 */
  580. #smarttoc ul ul ul ul ul {
  581. line-height: 1.7;
  582. /* display: none; */ /* (hidden unless parent is active) */
  583. }
  584. #smarttoc ul ul ul ul li.active>ul {
  585. display: block;
  586. }
  587. #smarttoc ul ul ul ul ul a {
  588. font-size: 1em;
  589. padding-left: 6em;
  590. }
  591. #smarttoc ul ul ul ul ul a:hover,
  592. #smarttoc ul ul ul ul ul a:active,
  593. #smarttoc ul ul ul ul ul li.active>a {
  594. border-left-width: 1.6px;
  595. border-left-style: solid;
  596. padding-left: calc(6em - 0.25px);
  597. font-weight: normal;
  598. }
  599. /* heading level: 6 */
  600. #smarttoc ul ul ul ul ul ul {
  601. line-height: 1.7;
  602. /* display: none; */ /* (hidden unless parent is active) */
  603. }
  604. #smarttoc ul ul ul ul ul li.active>ul {
  605. display: block;
  606. }
  607. #smarttoc ul ul ul ul ul ul a {
  608. font-size: 1em;
  609. padding-left: 7em;
  610. }
  611. #smarttoc ul ul ul ul ul ul a:hover,
  612. #smarttoc ul ul ul ul ul ul a:active,
  613. #smarttoc ul ul ul ul ul ul li.active>a {
  614. border-left-width: 0.8px;
  615. border-left-style: solid;
  616. padding-left: calc(7em - 0.1px);
  617. font-weight: normal;
  618. }
  619. `
  620. );
  621. }
  622.  
  623. const proto = {
  624. subscribe(cb, emitOnSubscribe = true) {
  625. if (emitOnSubscribe && this.value !== undefined) {
  626. cb(this.value);
  627. }
  628. this.listeners.push(cb);
  629. },
  630. addDependent(dependent) {
  631. this.dependents.push(dependent);
  632. },
  633. update(val) {
  634. this.value = val;
  635. this.changed = true;
  636. this.dependents.forEach((dep) => dep.update(val));
  637. },
  638. flush() {
  639. if (this.changed) {
  640. this.changed = false;
  641. this.listeners.forEach((l) => l(this.value));
  642. this.dependents.forEach((dep) => dep.flush());
  643. }
  644. },
  645. unique() {
  646. let lastValue = this.value;
  647. let $unique = Stream(lastValue);
  648. this.subscribe((val) => {
  649. if (val !== lastValue) {
  650. $unique(val);
  651. lastValue = val;
  652. }
  653. });
  654. return $unique;
  655. },
  656. map(f) {
  657. return Stream.combine(this, f);
  658. },
  659. filter(f) {
  660. return this.map((output) => (f(output) ? output : undefined));
  661. },
  662. throttle(delay) {
  663. let $throttled = Stream(this.value);
  664. const emit = throttle($throttled, delay);
  665. this.subscribe(emit);
  666. return $throttled;
  667. },
  668. log(name) {
  669. this.subscribe((e) => console.log(`[${name}]: `, e));
  670. return this;
  671. },
  672. };
  673.  
  674. function Stream(init) {
  675. let s = function (val) {
  676. if (val === undefined) return s.value;
  677. s.update(val);
  678. s.flush(val);
  679. };
  680.  
  681. s.value = init;
  682. s.changed = false;
  683. s.listeners = [];
  684. s.dependents = [];
  685.  
  686. return Object.assign(s, proto);
  687. }
  688.  
  689. Stream.combine = function (...streams) {
  690. const combiner = streams.pop();
  691. let cached = streams.map((s) => s());
  692. const combined = Stream(combiner(...cached));
  693.  
  694. streams.forEach((s, i) => {
  695. const dependent = {
  696. update(val) {
  697. cached[i] = val;
  698. combined.update(combiner(...cached));
  699. },
  700. flush() {
  701. combined.flush();
  702. },
  703. };
  704. s.addDependent(dependent);
  705. });
  706.  
  707. return combined;
  708. };
  709.  
  710. Stream.interval = function (int) {
  711. let $interval = Stream();
  712. setInterval(() => $interval(null), int);
  713. return $interval;
  714. };
  715.  
  716. Stream.fromEvent = function (elem, type) {
  717. let $event = Stream();
  718. elem.addEventListener(type, $event);
  719. return $event;
  720. };
  721.  
  722. var commonjsGlobal =
  723. typeof window !== "undefined"
  724. ? window
  725. : typeof global !== "undefined"
  726. ? global
  727. : typeof self !== "undefined"
  728. ? self
  729. : {};
  730.  
  731. function createCommonjsModule(fn, module) {
  732. return (
  733. (module = { exports: {} }),
  734. fn(module, module.exports),
  735. module.exports
  736. );
  737. }
  738.  
  739. var mithril = createCommonjsModule(function (module) {
  740. (function () {
  741. "use strict";
  742. function Vnode(tag, key, attrs0, children, text, dom) {
  743. return {
  744. tag: tag,
  745. key: key,
  746. attrs: attrs0,
  747. children: children,
  748. text: text,
  749. dom: dom,
  750. domSize: undefined,
  751. state: undefined,
  752. _state: undefined,
  753. events: undefined,
  754. instance: undefined,
  755. skip: false,
  756. };
  757. }
  758. Vnode.normalize = function (node) {
  759. if (Array.isArray(node))
  760. return Vnode(
  761. "[",
  762. undefined,
  763. undefined,
  764. Vnode.normalizeChildren(node),
  765. undefined,
  766. undefined
  767. );
  768. if (node != null && typeof node !== "object")
  769. return Vnode(
  770. "#",
  771. undefined,
  772. undefined,
  773. node === false ? "" : node,
  774. undefined,
  775. undefined
  776. );
  777. return node;
  778. };
  779. Vnode.normalizeChildren = function normalizeChildren(children) {
  780. for (var i = 0; i < children.length; i++) {
  781. children[i] = Vnode.normalize(children[i]);
  782. }
  783. return children;
  784. };
  785. var selectorParser =
  786. /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g;
  787. var selectorCache = {};
  788. var hasOwn = {}.hasOwnProperty;
  789. function compileSelector(selector) {
  790. var match,
  791. tag = "div",
  792. classes = [],
  793. attrs = {};
  794. while ((match = selectorParser.exec(selector))) {
  795. var type = match[1],
  796. value = match[2];
  797. if (type === "" && value !== "") tag = value;
  798. else if (type === "#") attrs.id = value;
  799. else if (type === ".") classes.push(value);
  800. else if (match[3][0] === "[") {
  801. var attrValue = match[6];
  802. if (attrValue)
  803. attrValue = attrValue
  804. .replace(/\\(["'])/g, "$1")
  805. .replace(/\\\\/g, "\\");
  806. if (match[4] === "class") classes.push(attrValue);
  807. else
  808. attrs[match[4]] =
  809. attrValue === ""
  810. ? attrValue
  811. : attrValue || true;
  812. }
  813. }
  814. if (classes.length > 0) attrs.className = classes.join(" ");
  815. return (selectorCache[selector] = { tag: tag, attrs: attrs });
  816. }
  817. function execSelector(state, attrs, children) {
  818. var hasAttrs = false,
  819. childList,
  820. text;
  821. var className = attrs.className || attrs.class;
  822. for (var key in state.attrs) {
  823. if (hasOwn.call(state.attrs, key)) {
  824. attrs[key] = state.attrs[key];
  825. }
  826. }
  827. if (className !== undefined) {
  828. if (attrs.class !== undefined) {
  829. attrs.class = undefined;
  830. attrs.className = className;
  831. }
  832. if (state.attrs.className != null) {
  833. attrs.className =
  834. state.attrs.className + " " + className;
  835. }
  836. }
  837. for (let key in attrs) {
  838. if (hasOwn.call(attrs, key) && key !== "key") {
  839. hasAttrs = true;
  840. break;
  841. }
  842. }
  843. if (
  844. Array.isArray(children) &&
  845. children.length === 1 &&
  846. children[0] != null &&
  847. children[0].tag === "#"
  848. ) {
  849. text = children[0].children;
  850. } else {
  851. childList = children;
  852. }
  853. return Vnode(
  854. state.tag,
  855. attrs.key,
  856. hasAttrs ? attrs : undefined,
  857. childList,
  858. text
  859. );
  860. }
  861. function hyperscript(selector) {
  862. // Because sloppy mode sucks
  863. var attrs = arguments[1],
  864. start = 2,
  865. children;
  866. if (
  867. selector == null ||
  868. (typeof selector !== "string" &&
  869. typeof selector !== "function" &&
  870. typeof selector.view !== "function")
  871. ) {
  872. throw Error(
  873. "The selector must be either a string or a component."
  874. );
  875. }
  876. if (typeof selector === "string") {
  877. var cached =
  878. selectorCache[selector] || compileSelector(selector);
  879. }
  880. if (attrs == null) {
  881. attrs = {};
  882. } else if (
  883. typeof attrs !== "object" ||
  884. attrs.tag != null ||
  885. Array.isArray(attrs)
  886. ) {
  887. attrs = {};
  888. start = 1;
  889. }
  890. if (arguments.length === start + 1) {
  891. children = arguments[start];
  892. if (!Array.isArray(children)) children = [children];
  893. } else {
  894. children = [];
  895. while (start < arguments.length)
  896. children.push(arguments[start++]);
  897. }
  898. var normalized = Vnode.normalizeChildren(children);
  899. if (typeof selector === "string") {
  900. return execSelector(cached, attrs, normalized);
  901. } else {
  902. return Vnode(selector, attrs.key, attrs, normalized);
  903. }
  904. }
  905. hyperscript.trust = function (html) {
  906. if (html == null) html = "";
  907. return Vnode(
  908. "<",
  909. undefined,
  910. undefined,
  911. html,
  912. undefined,
  913. undefined
  914. );
  915. };
  916. hyperscript.fragment = function (attrs1, children) {
  917. return Vnode(
  918. "[",
  919. attrs1.key,
  920. attrs1,
  921. Vnode.normalizeChildren(children),
  922. undefined,
  923. undefined
  924. );
  925. };
  926. var m = hyperscript;
  927. /** @constructor */
  928. var PromisePolyfill = function (executor) {
  929. if (!(this instanceof PromisePolyfill))
  930. throw new Error("Promise must be called with `new`");
  931. if (typeof executor !== "function")
  932. throw new TypeError("executor must be a function");
  933. var self = this,
  934. resolvers = [],
  935. rejectors = [],
  936. resolveCurrent = handler(resolvers, true),
  937. rejectCurrent = handler(rejectors, false);
  938. var instance = (self._instance = {
  939. resolvers: resolvers,
  940. rejectors: rejectors,
  941. });
  942. var callAsync =
  943. typeof setImmediate === "function"
  944. ? setImmediate
  945. : setTimeout;
  946. function handler(list, shouldAbsorb) {
  947. return function execute(value) {
  948. var then;
  949. try {
  950. if (
  951. shouldAbsorb &&
  952. value != null &&
  953. (typeof value === "object" ||
  954. typeof value === "function") &&
  955. typeof (then = value.then) === "function"
  956. ) {
  957. if (value === self)
  958. throw new TypeError(
  959. "Promise can't be resolved w/ itself"
  960. );
  961. executeOnce(then.bind(value));
  962. } else {
  963. callAsync(function () {
  964. if (!shouldAbsorb && list.length === 0)
  965. console.error(
  966. "Possible unhandled promise rejection:",
  967. value
  968. );
  969. for (var i = 0; i < list.length; i++)
  970. list[i](value);
  971. (resolvers.length = 0),
  972. (rejectors.length = 0);
  973. instance.state = shouldAbsorb;
  974. instance.retry = function () {
  975. execute(value);
  976. };
  977. });
  978. }
  979. } catch (e) {
  980. rejectCurrent(e);
  981. }
  982. };
  983. }
  984. function executeOnce(then) {
  985. var runs = 0;
  986. function run(fn) {
  987. return function (value) {
  988. if (runs++ > 0) return;
  989. fn(value);
  990. };
  991. }
  992. var onerror = run(rejectCurrent);
  993. try {
  994. then(run(resolveCurrent), onerror);
  995. } catch (e) {
  996. onerror(e);
  997. }
  998. }
  999. executeOnce(executor);
  1000. };
  1001. PromisePolyfill.prototype.then = function (
  1002. onFulfilled,
  1003. onRejection
  1004. ) {
  1005. var self = this,
  1006. instance = self._instance;
  1007. function handle(callback, list, next, state) {
  1008. list.push(function (value) {
  1009. if (typeof callback !== "function") next(value);
  1010. else
  1011. try {
  1012. resolveNext(callback(value));
  1013. } catch (e) {
  1014. if (rejectNext) rejectNext(e);
  1015. }
  1016. });
  1017. if (
  1018. typeof instance.retry === "function" &&
  1019. state === instance.state
  1020. )
  1021. instance.retry();
  1022. }
  1023. var resolveNext, rejectNext;
  1024. var promise = new PromisePolyfill(function (resolve, reject) {
  1025. (resolveNext = resolve), (rejectNext = reject);
  1026. });
  1027. handle(onFulfilled, instance.resolvers, resolveNext, true),
  1028. handle(onRejection, instance.rejectors, rejectNext, false);
  1029. return promise;
  1030. };
  1031. PromisePolyfill.prototype.catch = function (onRejection) {
  1032. return this.then(null, onRejection);
  1033. };
  1034. PromisePolyfill.resolve = function (value) {
  1035. if (value instanceof PromisePolyfill) return value;
  1036. return new PromisePolyfill(function (resolve) {
  1037. resolve(value);
  1038. });
  1039. };
  1040. PromisePolyfill.reject = function (value) {
  1041. return new PromisePolyfill(function (resolve, reject) {
  1042. reject(value);
  1043. });
  1044. };
  1045. PromisePolyfill.all = function (list) {
  1046. return new PromisePolyfill(function (resolve, reject) {
  1047. var total = list.length,
  1048. count = 0,
  1049. values = [];
  1050. if (list.length === 0) resolve([]);
  1051. else
  1052. for (var i = 0; i < list.length; i++) {
  1053. (function (i) {
  1054. function consume(value) {
  1055. count++;
  1056. values[i] = value;
  1057. if (count === total) resolve(values);
  1058. }
  1059. if (
  1060. list[i] != null &&
  1061. (typeof list[i] === "object" ||
  1062. typeof list[i] === "function") &&
  1063. typeof list[i].then === "function"
  1064. ) {
  1065. list[i].then(consume, reject);
  1066. } else consume(list[i]);
  1067. })(i);
  1068. }
  1069. });
  1070. };
  1071. PromisePolyfill.race = function (list) {
  1072. return new PromisePolyfill(function (resolve, reject) {
  1073. for (var i = 0; i < list.length; i++) {
  1074. list[i].then(resolve, reject);
  1075. }
  1076. });
  1077. };
  1078. if (typeof window !== "undefined") {
  1079. if (typeof window.Promise === "undefined")
  1080. window.Promise = PromisePolyfill;
  1081. let PromisePolyfill = window.Promise;
  1082. } else if (typeof commonjsGlobal !== "undefined") {
  1083. if (typeof commonjsGlobal.Promise === "undefined")
  1084. commonjsGlobal.Promise = PromisePolyfill;
  1085. let PromisePolyfill = commonjsGlobal.Promise;
  1086. } else {
  1087. }
  1088. var buildQueryString = function (object) {
  1089. if (
  1090. Object.prototype.toString.call(object) !== "[object Object]"
  1091. )
  1092. return "";
  1093. var args = [];
  1094. for (var key0 in object) {
  1095. destructure(key0, object[key0]);
  1096. }
  1097. return args.join("&");
  1098. function destructure(key0, value) {
  1099. if (Array.isArray(value)) {
  1100. for (var i = 0; i < value.length; i++) {
  1101. destructure(key0 + "[" + i + "]", value[i]);
  1102. }
  1103. } else if (
  1104. Object.prototype.toString.call(value) ===
  1105. "[object Object]"
  1106. ) {
  1107. for (let i in value) {
  1108. destructure(key0 + "[" + i + "]", value[i]);
  1109. }
  1110. } else
  1111. args.push(
  1112. encodeURIComponent(key0) +
  1113. (value != null && value !== ""
  1114. ? "=" + encodeURIComponent(value)
  1115. : "")
  1116. );
  1117. }
  1118. };
  1119. var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i");
  1120. var _8 = function ($window, Promise) {
  1121. var callbackCount = 0;
  1122. var oncompletion;
  1123. function setCompletionCallback(callback) {
  1124. oncompletion = callback;
  1125. }
  1126. function finalizer() {
  1127. var count = 0;
  1128. function complete() {
  1129. if (--count === 0 && typeof oncompletion === "function")
  1130. oncompletion();
  1131. }
  1132. return function finalize(promise0) {
  1133. var then0 = promise0.then;
  1134. promise0.then = function () {
  1135. count++;
  1136. var next = then0.apply(promise0, arguments);
  1137. next.then(complete, function (e) {
  1138. complete();
  1139. if (count === 0) throw e;
  1140. });
  1141. return finalize(next);
  1142. };
  1143. return promise0;
  1144. };
  1145. }
  1146. function normalize(args, extra) {
  1147. if (typeof args === "string") {
  1148. var url = args;
  1149. args = extra || {};
  1150. if (args.url == null) args.url = url;
  1151. }
  1152. return args;
  1153. }
  1154. function request(args, extra) {
  1155. var finalize = finalizer();
  1156. args = normalize(args, extra);
  1157. var promise0 = new Promise(function (resolve, reject) {
  1158. if (args.method == null) args.method = "GET";
  1159. args.method = args.method.toUpperCase();
  1160. var useBody =
  1161. args.method === "GET" || args.method === "TRACE"
  1162. ? false
  1163. : typeof args.useBody === "boolean"
  1164. ? args.useBody
  1165. : true;
  1166. if (typeof args.serialize !== "function")
  1167. args.serialize =
  1168. typeof FormData !== "undefined" &&
  1169. args.data instanceof FormData
  1170. ? function (value) {
  1171. return value;
  1172. }
  1173. : JSON.stringify;
  1174. if (typeof args.deserialize !== "function")
  1175. args.deserialize = deserialize;
  1176. if (typeof args.extract !== "function")
  1177. args.extract = extract;
  1178. args.url = interpolate(args.url, args.data);
  1179. if (useBody) args.data = args.serialize(args.data);
  1180. else args.url = assemble(args.url, args.data);
  1181. var xhr = new $window.XMLHttpRequest(),
  1182. aborted = false,
  1183. _abort = xhr.abort;
  1184. xhr.abort = function abort() {
  1185. aborted = true;
  1186. _abort.call(xhr);
  1187. };
  1188. xhr.open(
  1189. args.method,
  1190. args.url,
  1191. typeof args.async === "boolean" ? args.async : true,
  1192. typeof args.user === "string"
  1193. ? args.user
  1194. : undefined,
  1195. typeof args.password === "string"
  1196. ? args.password
  1197. : undefined
  1198. );
  1199. if (args.serialize === JSON.stringify && useBody) {
  1200. xhr.setRequestHeader(
  1201. "Content-Type",
  1202. "application/json; charset=utf-8"
  1203. );
  1204. }
  1205. if (args.deserialize === deserialize) {
  1206. xhr.setRequestHeader(
  1207. "Accept",
  1208. "application/json, text/*"
  1209. );
  1210. }
  1211. if (args.withCredentials)
  1212. xhr.withCredentials = args.withCredentials;
  1213. for (var key in args.headers)
  1214. if ({}.hasOwnProperty.call(args.headers, key)) {
  1215. xhr.setRequestHeader(key, args.headers[key]);
  1216. }
  1217. if (typeof args.config === "function")
  1218. xhr = args.config(xhr, args) || xhr;
  1219. xhr.onreadystatechange = function () {
  1220. // Don't throw errors on xhr.abort().
  1221. if (aborted) return;
  1222. if (xhr.readyState === 4) {
  1223. try {
  1224. var response =
  1225. args.extract !== extract
  1226. ? args.extract(xhr, args)
  1227. : args.deserialize(
  1228. args.extract(xhr, args)
  1229. );
  1230. if (
  1231. (xhr.status >= 200 &&
  1232. xhr.status < 300) ||
  1233. xhr.status === 304 ||
  1234. FILE_PROTOCOL_REGEX.test(args.url)
  1235. ) {
  1236. resolve(cast(args.type, response));
  1237. } else {
  1238. var error = new Error(xhr.responseText);
  1239. for (var key in response)
  1240. error[key] = response[key];
  1241. reject(error);
  1242. }
  1243. } catch (e) {
  1244. reject(e);
  1245. }
  1246. }
  1247. };
  1248. if (useBody && args.data != null) xhr.send(args.data);
  1249. else xhr.send();
  1250. });
  1251. return args.background === true
  1252. ? promise0
  1253. : finalize(promise0);
  1254. }
  1255. function jsonp(args, extra) {
  1256. var finalize = finalizer();
  1257. args = normalize(args, extra);
  1258. var promise0 = new Promise(function (resolve, reject) {
  1259. var callbackName =
  1260. args.callbackName ||
  1261. "_mithril_" +
  1262. Math.round(Math.random() * 1e16) +
  1263. "_" +
  1264. callbackCount++;
  1265. var script = $window.document.createElement("script");
  1266. $window[callbackName] = function (data) {
  1267. script.parentNode.removeChild(script);
  1268. resolve(cast(args.type, data));
  1269. delete $window[callbackName];
  1270. };
  1271. script.onerror = function () {
  1272. script.parentNode.removeChild(script);
  1273. reject(new Error("JSONP request failed"));
  1274. delete $window[callbackName];
  1275. };
  1276. if (args.data == null) args.data = {};
  1277. args.url = interpolate(args.url, args.data);
  1278. args.data[args.callbackKey || "callback"] =
  1279. callbackName;
  1280. script.src = assemble(args.url, args.data);
  1281. $window.document.documentElement.appendChild(script);
  1282. });
  1283. return args.background === true
  1284. ? promise0
  1285. : finalize(promise0);
  1286. }
  1287. function interpolate(url, data) {
  1288. if (data == null) return url;
  1289. var tokens = url.match(/:[^\/]+/gi) || [];
  1290. for (var i = 0; i < tokens.length; i++) {
  1291. var key = tokens[i].slice(1);
  1292. if (data[key] != null) {
  1293. url = url.replace(tokens[i], data[key]);
  1294. }
  1295. }
  1296. return url;
  1297. }
  1298. function assemble(url, data) {
  1299. var querystring = buildQueryString(data);
  1300. if (querystring !== "") {
  1301. var prefix = url.indexOf("?") < 0 ? "?" : "&";
  1302. url += prefix + querystring;
  1303. }
  1304. return url;
  1305. }
  1306. function deserialize(data) {
  1307. try {
  1308. return data !== "" ? JSON.parse(data) : null;
  1309. } catch (e) {
  1310. throw new Error(data);
  1311. }
  1312. }
  1313. function extract(xhr) {
  1314. return xhr.responseText;
  1315. }
  1316. function cast(type0, data) {
  1317. if (typeof type0 === "function") {
  1318. if (Array.isArray(data)) {
  1319. for (var i = 0; i < data.length; i++) {
  1320. data[i] = new type0(data[i]);
  1321. }
  1322. } else return new type0(data);
  1323. }
  1324. return data;
  1325. }
  1326. return {
  1327. request: request,
  1328. jsonp: jsonp,
  1329. setCompletionCallback: setCompletionCallback,
  1330. };
  1331. };
  1332. var requestService = _8(window, PromisePolyfill);
  1333. var coreRenderer = function ($window) {
  1334. var $doc = $window.document;
  1335. var $emptyFragment = $doc.createDocumentFragment();
  1336. var nameSpace = {
  1337. svg: "http://www.w3.org/2000/svg",
  1338. math: "http://www.w3.org/1998/Math/MathML",
  1339. };
  1340. var onevent;
  1341. function setEventCallback(callback) {
  1342. return (onevent = callback);
  1343. }
  1344. function getNameSpace(vnode) {
  1345. return (
  1346. (vnode.attrs && vnode.attrs.xmlns) ||
  1347. nameSpace[vnode.tag]
  1348. );
  1349. }
  1350. //create
  1351. function createNodes(
  1352. parent,
  1353. vnodes,
  1354. start,
  1355. end,
  1356. hooks,
  1357. nextSibling,
  1358. ns
  1359. ) {
  1360. for (var i = start; i < end; i++) {
  1361. var vnode = vnodes[i];
  1362. if (vnode != null) {
  1363. createNode(parent, vnode, hooks, ns, nextSibling);
  1364. }
  1365. }
  1366. }
  1367. function createNode(parent, vnode, hooks, ns, nextSibling) {
  1368. var tag = vnode.tag;
  1369. if (typeof tag === "string") {
  1370. vnode.state = {};
  1371. if (vnode.attrs != null)
  1372. initLifecycle(vnode.attrs, vnode, hooks);
  1373. switch (tag) {
  1374. case "#":
  1375. return createText(parent, vnode, nextSibling);
  1376. case "<":
  1377. return createHTML(parent, vnode, nextSibling);
  1378. case "[":
  1379. return createFragment(
  1380. parent,
  1381. vnode,
  1382. hooks,
  1383. ns,
  1384. nextSibling
  1385. );
  1386. default:
  1387. return createElement(
  1388. parent,
  1389. vnode,
  1390. hooks,
  1391. ns,
  1392. nextSibling
  1393. );
  1394. }
  1395. } else
  1396. return createComponent(
  1397. parent,
  1398. vnode,
  1399. hooks,
  1400. ns,
  1401. nextSibling
  1402. );
  1403. }
  1404. function createText(parent, vnode, nextSibling) {
  1405. vnode.dom = $doc.createTextNode(vnode.children);
  1406. insertNode(parent, vnode.dom, nextSibling);
  1407. return vnode.dom;
  1408. }
  1409. function createHTML(parent, vnode, nextSibling) {
  1410. var match1 = vnode.children.match(/^\s*?<(\w+)/im) || [];
  1411. var parent1 =
  1412. {
  1413. caption: "table",
  1414. thead: "table",
  1415. tbody: "table",
  1416. tfoot: "table",
  1417. tr: "tbody",
  1418. th: "tr",
  1419. td: "tr",
  1420. colgroup: "table",
  1421. col: "colgroup",
  1422. }[match1[1]] || "div";
  1423. var temp = $doc.createElement(parent1);
  1424. temp.innerHTML = vnode.children;
  1425. vnode.dom = temp.firstChild;
  1426. vnode.domSize = temp.childNodes.length;
  1427. var fragment = $doc.createDocumentFragment();
  1428. var child;
  1429. while ((child = temp.firstChild)) {
  1430. fragment.appendChild(child);
  1431. }
  1432. insertNode(parent, fragment, nextSibling);
  1433. return fragment;
  1434. }
  1435. function createFragment(parent, vnode, hooks, ns, nextSibling) {
  1436. var fragment = $doc.createDocumentFragment();
  1437. if (vnode.children != null) {
  1438. var children = vnode.children;
  1439. createNodes(
  1440. fragment,
  1441. children,
  1442. 0,
  1443. children.length,
  1444. hooks,
  1445. null,
  1446. ns
  1447. );
  1448. }
  1449. vnode.dom = fragment.firstChild;
  1450. vnode.domSize = fragment.childNodes.length;
  1451. insertNode(parent, fragment, nextSibling);
  1452. return fragment;
  1453. }
  1454. function createElement(parent, vnode, hooks, ns, nextSibling) {
  1455. var tag = vnode.tag;
  1456. var attrs2 = vnode.attrs;
  1457. var is = attrs2 && attrs2.is;
  1458. ns = getNameSpace(vnode) || ns;
  1459. var element = ns
  1460. ? is
  1461. ? $doc.createElementNS(ns, tag, { is: is })
  1462. : $doc.createElementNS(ns, tag)
  1463. : is
  1464. ? $doc.createElement(tag, { is: is })
  1465. : $doc.createElement(tag);
  1466. vnode.dom = element;
  1467. if (attrs2 != null) {
  1468. setAttrs(vnode, attrs2, ns);
  1469. }
  1470. insertNode(parent, element, nextSibling);
  1471. if (
  1472. vnode.attrs != null &&
  1473. vnode.attrs.contenteditable != null
  1474. ) {
  1475. setContentEditable(vnode);
  1476. } else {
  1477. if (vnode.text != null) {
  1478. if (vnode.text !== "")
  1479. element.textContent = vnode.text;
  1480. else
  1481. vnode.children = [
  1482. Vnode(
  1483. "#",
  1484. undefined,
  1485. undefined,
  1486. vnode.text,
  1487. undefined,
  1488. undefined
  1489. ),
  1490. ];
  1491. }
  1492. if (vnode.children != null) {
  1493. var children = vnode.children;
  1494. createNodes(
  1495. element,
  1496. children,
  1497. 0,
  1498. children.length,
  1499. hooks,
  1500. null,
  1501. ns
  1502. );
  1503. setLateAttrs(vnode);
  1504. }
  1505. }
  1506. return element;
  1507. }
  1508. function initComponent(vnode, hooks) {
  1509. var sentinel;
  1510. if (typeof vnode.tag.view === "function") {
  1511. vnode.state = Object.create(vnode.tag);
  1512. sentinel = vnode.state.view;
  1513. if (sentinel.$$reentrantLock$$ != null)
  1514. return $emptyFragment;
  1515. sentinel.$$reentrantLock$$ = true;
  1516. } else {
  1517. vnode.state = void 0;
  1518. sentinel = vnode.tag;
  1519. if (sentinel.$$reentrantLock$$ != null)
  1520. return $emptyFragment;
  1521. sentinel.$$reentrantLock$$ = true;
  1522. vnode.state =
  1523. vnode.tag.prototype != null &&
  1524. typeof vnode.tag.prototype.view === "function"
  1525. ? new vnode.tag(vnode)
  1526. : vnode.tag(vnode);
  1527. }
  1528. vnode._state = vnode.state;
  1529. if (vnode.attrs != null)
  1530. initLifecycle(vnode.attrs, vnode, hooks);
  1531. initLifecycle(vnode._state, vnode, hooks);
  1532. vnode.instance = Vnode.normalize(
  1533. vnode._state.view.call(vnode.state, vnode)
  1534. );
  1535. if (vnode.instance === vnode)
  1536. throw Error(
  1537. "A view cannot return the vnode it received as argument"
  1538. );
  1539. sentinel.$$reentrantLock$$ = null;
  1540. }
  1541. function createComponent(
  1542. parent,
  1543. vnode,
  1544. hooks,
  1545. ns,
  1546. nextSibling
  1547. ) {
  1548. initComponent(vnode, hooks);
  1549. if (vnode.instance != null) {
  1550. var element = createNode(
  1551. parent,
  1552. vnode.instance,
  1553. hooks,
  1554. ns,
  1555. nextSibling
  1556. );
  1557. vnode.dom = vnode.instance.dom;
  1558. vnode.domSize =
  1559. vnode.dom != null ? vnode.instance.domSize : 0;
  1560. insertNode(parent, element, nextSibling);
  1561. return element;
  1562. } else {
  1563. vnode.domSize = 0;
  1564. return $emptyFragment;
  1565. }
  1566. }
  1567. //update
  1568. function updateNodes(
  1569. parent,
  1570. old,
  1571. vnodes,
  1572. recycling,
  1573. hooks,
  1574. nextSibling,
  1575. ns
  1576. ) {
  1577. if (old === vnodes || (old == null && vnodes == null))
  1578. return;
  1579. else if (old == null)
  1580. createNodes(
  1581. parent,
  1582. vnodes,
  1583. 0,
  1584. vnodes.length,
  1585. hooks,
  1586. nextSibling,
  1587. ns
  1588. );
  1589. else if (vnodes == null)
  1590. removeNodes(old, 0, old.length, vnodes);
  1591. else {
  1592. if (old.length === vnodes.length) {
  1593. var isUnkeyed = false;
  1594. for (var i = 0; i < vnodes.length; i++) {
  1595. if (vnodes[i] != null && old[i] != null) {
  1596. isUnkeyed =
  1597. vnodes[i].key == null &&
  1598. old[i].key == null;
  1599. break;
  1600. }
  1601. }
  1602. if (isUnkeyed) {
  1603. for (let i = 0; i < old.length; i++) {
  1604. if (old[i] === vnodes[i]) continue;
  1605. else if (
  1606. old[i] == null &&
  1607. vnodes[i] != null
  1608. )
  1609. createNode(
  1610. parent,
  1611. vnodes[i],
  1612. hooks,
  1613. ns,
  1614. getNextSibling(
  1615. old,
  1616. i + 1,
  1617. nextSibling
  1618. )
  1619. );
  1620. else if (vnodes[i] == null)
  1621. removeNodes(old, i, i + 1, vnodes);
  1622. else
  1623. updateNode(
  1624. parent,
  1625. old[i],
  1626. vnodes[i],
  1627. hooks,
  1628. getNextSibling(
  1629. old,
  1630. i + 1,
  1631. nextSibling
  1632. ),
  1633. recycling,
  1634. ns
  1635. );
  1636. }
  1637. return;
  1638. }
  1639. }
  1640. recycling = recycling || isRecyclable(old, vnodes);
  1641. if (recycling) {
  1642. var pool = old.pool;
  1643. old = old.concat(old.pool);
  1644. }
  1645. var oldStart = 0,
  1646. start = 0,
  1647. oldEnd = old.length - 1,
  1648. end = vnodes.length - 1,
  1649. map;
  1650. while (oldEnd >= oldStart && end >= start) {
  1651. var o = old[oldStart],
  1652. v = vnodes[start];
  1653. if (o === v && !recycling) oldStart++, start++;
  1654. else if (o == null) oldStart++;
  1655. else if (v == null) start++;
  1656. else if (o.key === v.key) {
  1657. var shouldRecycle =
  1658. (pool != null &&
  1659. oldStart >= old.length - pool.length) ||
  1660. (pool == null && recycling);
  1661. oldStart++, start++;
  1662. updateNode(
  1663. parent,
  1664. o,
  1665. v,
  1666. hooks,
  1667. getNextSibling(old, oldStart, nextSibling),
  1668. shouldRecycle,
  1669. ns
  1670. );
  1671. if (recycling && o.tag === v.tag)
  1672. insertNode(
  1673. parent,
  1674. toFragment(o),
  1675. nextSibling
  1676. );
  1677. } else {
  1678. let o = old[oldEnd];
  1679. if (o === v && !recycling) oldEnd--, start++;
  1680. else if (o == null) oldEnd--;
  1681. else if (v == null) start++;
  1682. else if (o.key === v.key) {
  1683. let shouldRecycle =
  1684. (pool != null &&
  1685. oldEnd >=
  1686. old.length - pool.length) ||
  1687. (pool == null && recycling);
  1688. updateNode(
  1689. parent,
  1690. o,
  1691. v,
  1692. hooks,
  1693. getNextSibling(
  1694. old,
  1695. oldEnd + 1,
  1696. nextSibling
  1697. ),
  1698. shouldRecycle,
  1699. ns
  1700. );
  1701. if (recycling || start < end)
  1702. insertNode(
  1703. parent,
  1704. toFragment(o),
  1705. getNextSibling(
  1706. old,
  1707. oldStart,
  1708. nextSibling
  1709. )
  1710. );
  1711. oldEnd--, start++;
  1712. } else break;
  1713. }
  1714. }
  1715. while (oldEnd >= oldStart && end >= start) {
  1716. let o = old[oldEnd],
  1717. v = vnodes[end];
  1718. if (o === v && !recycling) oldEnd--, end--;
  1719. else if (o == null) oldEnd--;
  1720. else if (v == null) end--;
  1721. else if (o.key === v.key) {
  1722. let shouldRecycle =
  1723. (pool != null &&
  1724. oldEnd >= old.length - pool.length) ||
  1725. (pool == null && recycling);
  1726. updateNode(
  1727. parent,
  1728. o,
  1729. v,
  1730. hooks,
  1731. getNextSibling(
  1732. old,
  1733. oldEnd + 1,
  1734. nextSibling
  1735. ),
  1736. shouldRecycle,
  1737. ns
  1738. );
  1739. if (recycling && o.tag === v.tag)
  1740. insertNode(
  1741. parent,
  1742. toFragment(o),
  1743. nextSibling
  1744. );
  1745. if (o.dom != null) nextSibling = o.dom;
  1746. oldEnd--, end--;
  1747. } else {
  1748. if (!map) map = getKeyMap(old, oldEnd);
  1749. if (v != null) {
  1750. var oldIndex = map[v.key];
  1751. if (oldIndex != null) {
  1752. let movable = old[oldIndex];
  1753. let shouldRecycle =
  1754. (pool != null &&
  1755. oldIndex >=
  1756. old.length - pool.length) ||
  1757. (pool == null && recycling);
  1758. updateNode(
  1759. parent,
  1760. movable,
  1761. v,
  1762. hooks,
  1763. getNextSibling(
  1764. old,
  1765. oldEnd + 1,
  1766. nextSibling
  1767. ),
  1768. recycling,
  1769. ns
  1770. );
  1771. insertNode(
  1772. parent,
  1773. toFragment(movable),
  1774. nextSibling
  1775. );
  1776. old[oldIndex].skip = true;
  1777. if (movable.dom != null)
  1778. nextSibling = movable.dom;
  1779. } else {
  1780. var dom = createNode(
  1781. parent,
  1782. v,
  1783. hooks,
  1784. ns,
  1785. nextSibling
  1786. );
  1787. nextSibling = dom;
  1788. }
  1789. }
  1790. end--;
  1791. }
  1792. if (end < start) break;
  1793. }
  1794. createNodes(
  1795. parent,
  1796. vnodes,
  1797. start,
  1798. end + 1,
  1799. hooks,
  1800. nextSibling,
  1801. ns
  1802. );
  1803. removeNodes(old, oldStart, oldEnd + 1, vnodes);
  1804. }
  1805. }
  1806. function updateNode(
  1807. parent,
  1808. old,
  1809. vnode,
  1810. hooks,
  1811. nextSibling,
  1812. recycling,
  1813. ns
  1814. ) {
  1815. var oldTag = old.tag,
  1816. tag = vnode.tag;
  1817. if (oldTag === tag) {
  1818. vnode.state = old.state;
  1819. vnode._state = old._state;
  1820. vnode.events = old.events;
  1821. if (!recycling && shouldNotUpdate(vnode, old)) return;
  1822. if (typeof oldTag === "string") {
  1823. if (vnode.attrs != null) {
  1824. if (recycling) {
  1825. vnode.state = {};
  1826. initLifecycle(vnode.attrs, vnode, hooks);
  1827. } else
  1828. updateLifecycle(vnode.attrs, vnode, hooks);
  1829. }
  1830. switch (oldTag) {
  1831. case "#":
  1832. updateText(old, vnode);
  1833. break;
  1834. case "<":
  1835. updateHTML(parent, old, vnode, nextSibling);
  1836. break;
  1837. case "[":
  1838. updateFragment(
  1839. parent,
  1840. old,
  1841. vnode,
  1842. recycling,
  1843. hooks,
  1844. nextSibling,
  1845. ns
  1846. );
  1847. break;
  1848. default:
  1849. updateElement(
  1850. old,
  1851. vnode,
  1852. recycling,
  1853. hooks,
  1854. ns
  1855. );
  1856. }
  1857. } else
  1858. updateComponent(
  1859. parent,
  1860. old,
  1861. vnode,
  1862. hooks,
  1863. nextSibling,
  1864. recycling,
  1865. ns
  1866. );
  1867. } else {
  1868. removeNode(old, null);
  1869. createNode(parent, vnode, hooks, ns, nextSibling);
  1870. }
  1871. }
  1872. function updateText(old, vnode) {
  1873. if (old.children.toString() !== vnode.children.toString()) {
  1874. old.dom.nodeValue = vnode.children;
  1875. }
  1876. vnode.dom = old.dom;
  1877. }
  1878. function updateHTML(parent, old, vnode, nextSibling) {
  1879. if (old.children !== vnode.children) {
  1880. toFragment(old);
  1881. createHTML(parent, vnode, nextSibling);
  1882. } else (vnode.dom = old.dom), (vnode.domSize = old.domSize);
  1883. }
  1884. function updateFragment(
  1885. parent,
  1886. old,
  1887. vnode,
  1888. recycling,
  1889. hooks,
  1890. nextSibling,
  1891. ns
  1892. ) {
  1893. updateNodes(
  1894. parent,
  1895. old.children,
  1896. vnode.children,
  1897. recycling,
  1898. hooks,
  1899. nextSibling,
  1900. ns
  1901. );
  1902. var domSize = 0,
  1903. children = vnode.children;
  1904. vnode.dom = null;
  1905. if (children != null) {
  1906. for (var i = 0; i < children.length; i++) {
  1907. var child = children[i];
  1908. if (child != null && child.dom != null) {
  1909. if (vnode.dom == null) vnode.dom = child.dom;
  1910. domSize += child.domSize || 1;
  1911. }
  1912. }
  1913. if (domSize !== 1) vnode.domSize = domSize;
  1914. }
  1915. }
  1916. function updateElement(old, vnode, recycling, hooks, ns) {
  1917. var element = (vnode.dom = old.dom);
  1918. ns = getNameSpace(vnode) || ns;
  1919. if (vnode.tag === "textarea") {
  1920. if (vnode.attrs == null) vnode.attrs = {};
  1921. if (vnode.text != null) {
  1922. vnode.attrs.value = vnode.text; //FIXME handle0 multiple children
  1923. vnode.text = undefined;
  1924. }
  1925. }
  1926. updateAttrs(vnode, old.attrs, vnode.attrs, ns);
  1927. if (
  1928. vnode.attrs != null &&
  1929. vnode.attrs.contenteditable != null
  1930. ) {
  1931. setContentEditable(vnode);
  1932. } else if (
  1933. old.text != null &&
  1934. vnode.text != null &&
  1935. vnode.text !== ""
  1936. ) {
  1937. if (old.text.toString() !== vnode.text.toString())
  1938. old.dom.firstChild.nodeValue = vnode.text;
  1939. } else {
  1940. if (old.text != null)
  1941. old.children = [
  1942. Vnode(
  1943. "#",
  1944. undefined,
  1945. undefined,
  1946. old.text,
  1947. undefined,
  1948. old.dom.firstChild
  1949. ),
  1950. ];
  1951. if (vnode.text != null)
  1952. vnode.children = [
  1953. Vnode(
  1954. "#",
  1955. undefined,
  1956. undefined,
  1957. vnode.text,
  1958. undefined,
  1959. undefined
  1960. ),
  1961. ];
  1962. updateNodes(
  1963. element,
  1964. old.children,
  1965. vnode.children,
  1966. recycling,
  1967. hooks,
  1968. null,
  1969. ns
  1970. );
  1971. }
  1972. }
  1973. function updateComponent(
  1974. parent,
  1975. old,
  1976. vnode,
  1977. hooks,
  1978. nextSibling,
  1979. recycling,
  1980. ns
  1981. ) {
  1982. if (recycling) {
  1983. initComponent(vnode, hooks);
  1984. } else {
  1985. vnode.instance = Vnode.normalize(
  1986. vnode._state.view.call(vnode.state, vnode)
  1987. );
  1988. if (vnode.instance === vnode)
  1989. throw Error(
  1990. "A view cannot return the vnode it received as argument"
  1991. );
  1992. if (vnode.attrs != null)
  1993. updateLifecycle(vnode.attrs, vnode, hooks);
  1994. updateLifecycle(vnode._state, vnode, hooks);
  1995. }
  1996. if (vnode.instance != null) {
  1997. if (old.instance == null)
  1998. createNode(
  1999. parent,
  2000. vnode.instance,
  2001. hooks,
  2002. ns,
  2003. nextSibling
  2004. );
  2005. else
  2006. updateNode(
  2007. parent,
  2008. old.instance,
  2009. vnode.instance,
  2010. hooks,
  2011. nextSibling,
  2012. recycling,
  2013. ns
  2014. );
  2015. vnode.dom = vnode.instance.dom;
  2016. vnode.domSize = vnode.instance.domSize;
  2017. } else if (old.instance != null) {
  2018. removeNode(old.instance, null);
  2019. vnode.dom = undefined;
  2020. vnode.domSize = 0;
  2021. } else {
  2022. vnode.dom = old.dom;
  2023. vnode.domSize = old.domSize;
  2024. }
  2025. }
  2026. function isRecyclable(old, vnodes) {
  2027. if (
  2028. old.pool != null &&
  2029. Math.abs(old.pool.length - vnodes.length) <=
  2030. Math.abs(old.length - vnodes.length)
  2031. ) {
  2032. var oldChildrenLength =
  2033. (old[0] &&
  2034. old[0].children &&
  2035. old[0].children.length) ||
  2036. 0;
  2037. var poolChildrenLength =
  2038. (old.pool[0] &&
  2039. old.pool[0].children &&
  2040. old.pool[0].children.length) ||
  2041. 0;
  2042. var vnodesChildrenLength =
  2043. (vnodes[0] &&
  2044. vnodes[0].children &&
  2045. vnodes[0].children.length) ||
  2046. 0;
  2047. if (
  2048. Math.abs(
  2049. poolChildrenLength - vnodesChildrenLength
  2050. ) <=
  2051. Math.abs(oldChildrenLength - vnodesChildrenLength)
  2052. ) {
  2053. return true;
  2054. }
  2055. }
  2056. return false;
  2057. }
  2058. function getKeyMap(vnodes, end) {
  2059. var map = {},
  2060. i = 0;
  2061. for (let i = 0; i < end; i++) {
  2062. let vnode = vnodes[i];
  2063. if (vnode != null) {
  2064. let key2 = vnode.key;
  2065. if (key2 != null) map[key2] = i;
  2066. }
  2067. }
  2068. return map;
  2069. }
  2070. function toFragment(vnode) {
  2071. var count0 = vnode.domSize;
  2072. if (count0 != null || vnode.dom == null) {
  2073. var fragment = $doc.createDocumentFragment();
  2074. if (count0 > 0) {
  2075. var dom = vnode.dom;
  2076. while (--count0)
  2077. fragment.appendChild(dom.nextSibling);
  2078. fragment.insertBefore(dom, fragment.firstChild);
  2079. }
  2080. return fragment;
  2081. } else return vnode.dom;
  2082. }
  2083. function getNextSibling(vnodes, i, nextSibling) {
  2084. for (; i < vnodes.length; i++) {
  2085. if (vnodes[i] != null && vnodes[i].dom != null)
  2086. return vnodes[i].dom;
  2087. }
  2088. return nextSibling;
  2089. }
  2090. function insertNode(parent, dom, nextSibling) {
  2091. if (nextSibling && nextSibling.parentNode)
  2092. parent.insertBefore(dom, nextSibling);
  2093. else parent.appendChild(dom);
  2094. }
  2095. function setContentEditable(vnode) {
  2096. var children = vnode.children;
  2097. if (
  2098. children != null &&
  2099. children.length === 1 &&
  2100. children[0].tag === "<"
  2101. ) {
  2102. var content = children[0].children;
  2103. if (vnode.dom.innerHTML !== content)
  2104. vnode.dom.innerHTML = content;
  2105. } else if (
  2106. vnode.text != null ||
  2107. (children != null && children.length !== 0)
  2108. )
  2109. throw new Error(
  2110. "Child node of a contenteditable must be trusted"
  2111. );
  2112. }
  2113. //remove
  2114. function removeNodes(vnodes, start, end, context) {
  2115. for (var i = start; i < end; i++) {
  2116. var vnode = vnodes[i];
  2117. if (vnode != null) {
  2118. if (vnode.skip) vnode.skip = false;
  2119. else removeNode(vnode, context);
  2120. }
  2121. }
  2122. }
  2123. function removeNode(vnode, context) {
  2124. var expected = 1,
  2125. called = 0;
  2126. if (
  2127. vnode.attrs &&
  2128. typeof vnode.attrs.onbeforeremove === "function"
  2129. ) {
  2130. var result = vnode.attrs.onbeforeremove.call(
  2131. vnode.state,
  2132. vnode
  2133. );
  2134. if (
  2135. result != null &&
  2136. typeof result.then === "function"
  2137. ) {
  2138. expected++;
  2139. result.then(continuation, continuation);
  2140. }
  2141. }
  2142. if (
  2143. typeof vnode.tag !== "string" &&
  2144. typeof vnode._state.onbeforeremove === "function"
  2145. ) {
  2146. let result = vnode._state.onbeforeremove.call(
  2147. vnode.state,
  2148. vnode
  2149. );
  2150. if (
  2151. result != null &&
  2152. typeof result.then === "function"
  2153. ) {
  2154. expected++;
  2155. result.then(continuation, continuation);
  2156. }
  2157. }
  2158. continuation();
  2159. function continuation() {
  2160. if (++called === expected) {
  2161. onremove(vnode);
  2162. if (vnode.dom) {
  2163. var count0 = vnode.domSize || 1;
  2164. if (count0 > 1) {
  2165. var dom = vnode.dom;
  2166. while (--count0) {
  2167. removeNodeFromDOM(dom.nextSibling);
  2168. }
  2169. }
  2170. removeNodeFromDOM(vnode.dom);
  2171. if (
  2172. context != null &&
  2173. vnode.domSize == null &&
  2174. !hasIntegrationMethods(vnode.attrs) &&
  2175. typeof vnode.tag === "string"
  2176. ) {
  2177. //TODO test custom elements
  2178. if (!context.pool) context.pool = [vnode];
  2179. else context.pool.push(vnode);
  2180. }
  2181. }
  2182. }
  2183. }
  2184. }
  2185. function removeNodeFromDOM(node) {
  2186. var parent = node.parentNode;
  2187. if (parent != null) parent.removeChild(node);
  2188. }
  2189. function onremove(vnode) {
  2190. if (
  2191. vnode.attrs &&
  2192. typeof vnode.attrs.onremove === "function"
  2193. )
  2194. vnode.attrs.onremove.call(vnode.state, vnode);
  2195. if (
  2196. typeof vnode.tag !== "string" &&
  2197. typeof vnode._state.onremove === "function"
  2198. )
  2199. vnode._state.onremove.call(vnode.state, vnode);
  2200. if (vnode.instance != null) onremove(vnode.instance);
  2201. else {
  2202. var children = vnode.children;
  2203. if (Array.isArray(children)) {
  2204. for (var i = 0; i < children.length; i++) {
  2205. var child = children[i];
  2206. if (child != null) onremove(child);
  2207. }
  2208. }
  2209. }
  2210. }
  2211. //attrs2
  2212. function setAttrs(vnode, attrs2, ns) {
  2213. for (var key2 in attrs2) {
  2214. setAttr(vnode, key2, null, attrs2[key2], ns);
  2215. }
  2216. }
  2217. function setAttr(vnode, key2, old, value, ns) {
  2218. var element = vnode.dom;
  2219. if (
  2220. key2 === "key" ||
  2221. key2 === "is" ||
  2222. (old === value &&
  2223. !isFormAttribute(vnode, key2) &&
  2224. typeof value !== "object") ||
  2225. typeof value === "undefined" ||
  2226. isLifecycleMethod(key2)
  2227. )
  2228. return;
  2229. var nsLastIndex = key2.indexOf(":");
  2230. if (
  2231. nsLastIndex > -1 &&
  2232. key2.substr(0, nsLastIndex) === "xlink"
  2233. ) {
  2234. element.setAttributeNS(
  2235. "http://www.w3.org/1999/xlink",
  2236. key2.slice(nsLastIndex + 1),
  2237. value
  2238. );
  2239. } else if (
  2240. key2[0] === "o" &&
  2241. key2[1] === "n" &&
  2242. typeof value === "function"
  2243. )
  2244. updateEvent(vnode, key2, value);
  2245. else if (key2 === "style") updateStyle(element, old, value);
  2246. else if (
  2247. key2 in element &&
  2248. !isAttribute(key2) &&
  2249. ns === undefined &&
  2250. !isCustomElement(vnode)
  2251. ) {
  2252. if (key2 === "value") {
  2253. var normalized0 = "" + value; // eslint-disable-line no-implicit-coercion
  2254. //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
  2255. if (
  2256. (vnode.tag === "input" ||
  2257. vnode.tag === "textarea") &&
  2258. vnode.dom.value === normalized0 &&
  2259. vnode.dom === $doc.activeElement
  2260. )
  2261. return;
  2262. //setting select[value] to same value while having select open blinks select dropdown in Chrome
  2263. if (vnode.tag === "select") {
  2264. if (value === null) {
  2265. if (
  2266. vnode.dom.selectedIndex === -1 &&
  2267. vnode.dom === $doc.activeElement
  2268. )
  2269. return;
  2270. } else {
  2271. if (
  2272. old !== null &&
  2273. vnode.dom.value === normalized0 &&
  2274. vnode.dom === $doc.activeElement
  2275. )
  2276. return;
  2277. }
  2278. }
  2279. //setting option[value] to same value while having select open blinks select dropdown in Chrome
  2280. if (
  2281. vnode.tag === "option" &&
  2282. old != null &&
  2283. vnode.dom.value === normalized0
  2284. )
  2285. return;
  2286. }
  2287. // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur.
  2288. if (vnode.tag === "input" && key2 === "type") {
  2289. element.setAttribute(key2, value);
  2290. return;
  2291. }
  2292. element[key2] = value;
  2293. } else {
  2294. if (typeof value === "boolean") {
  2295. if (value) element.setAttribute(key2, "");
  2296. else element.removeAttribute(key2);
  2297. } else
  2298. element.setAttribute(
  2299. key2 === "className" ? "class" : key2,
  2300. value
  2301. );
  2302. }
  2303. }
  2304. function setLateAttrs(vnode) {
  2305. var attrs2 = vnode.attrs;
  2306. if (vnode.tag === "select" && attrs2 != null) {
  2307. if ("value" in attrs2)
  2308. setAttr(
  2309. vnode,
  2310. "value",
  2311. null,
  2312. attrs2.value,
  2313. undefined
  2314. );
  2315. if ("selectedIndex" in attrs2)
  2316. setAttr(
  2317. vnode,
  2318. "selectedIndex",
  2319. null,
  2320. attrs2.selectedIndex,
  2321. undefined
  2322. );
  2323. }
  2324. }
  2325. function updateAttrs(vnode, old, attrs2, ns) {
  2326. if (attrs2 != null) {
  2327. for (let key2 in attrs2) {
  2328. setAttr(
  2329. vnode,
  2330. key2,
  2331. old && old[key2],
  2332. attrs2[key2],
  2333. ns
  2334. );
  2335. }
  2336. }
  2337. if (old != null) {
  2338. for (var key2 in old) {
  2339. if (attrs2 == null || !(key2 in attrs2)) {
  2340. if (key2 === "className") key2 = "class";
  2341. if (
  2342. key2[0] === "o" &&
  2343. key2[1] === "n" &&
  2344. !isLifecycleMethod(key2)
  2345. )
  2346. updateEvent(vnode, key2, undefined);
  2347. else if (key2 !== "key")
  2348. vnode.dom.removeAttribute(key2);
  2349. }
  2350. }
  2351. }
  2352. }
  2353. function isFormAttribute(vnode, attr) {
  2354. return (
  2355. attr === "value" ||
  2356. attr === "checked" ||
  2357. attr === "selectedIndex" ||
  2358. (attr === "selected" &&
  2359. vnode.dom === $doc.activeElement)
  2360. );
  2361. }
  2362. function isLifecycleMethod(attr) {
  2363. return (
  2364. attr === "oninit" ||
  2365. attr === "oncreate" ||
  2366. attr === "onupdate" ||
  2367. attr === "onremove" ||
  2368. attr === "onbeforeremove" ||
  2369. attr === "onbeforeupdate"
  2370. );
  2371. }
  2372. function isAttribute(attr) {
  2373. return (
  2374. attr === "href" ||
  2375. attr === "list" ||
  2376. attr === "form" ||
  2377. attr === "width" ||
  2378. attr === "height"
  2379. ); // || attr === "type"
  2380. }
  2381. function isCustomElement(vnode) {
  2382. return vnode.attrs.is || vnode.tag.indexOf("-") > -1;
  2383. }
  2384. function hasIntegrationMethods(source) {
  2385. return (
  2386. source != null &&
  2387. (source.oncreate ||
  2388. source.onupdate ||
  2389. source.onbeforeremove ||
  2390. source.onremove)
  2391. );
  2392. }
  2393. //style
  2394. function updateStyle(element, old, style) {
  2395. if (old === style)
  2396. (element.style.cssText = ""), (old = null);
  2397. if (style == null) element.style.cssText = "";
  2398. else if (typeof style === "string")
  2399. element.style.cssText = style;
  2400. else {
  2401. if (typeof old === "string") element.style.cssText = "";
  2402. for (var key2 in style) {
  2403. element.style[key2] = style[key2];
  2404. }
  2405. if (old != null && typeof old !== "string") {
  2406. for (var key3 in old) {
  2407. if (!(key3 in style)) element.style[key3] = "";
  2408. }
  2409. }
  2410. }
  2411. }
  2412. //event
  2413. function updateEvent(vnode, key2, value) {
  2414. var element = vnode.dom;
  2415. var callback =
  2416. typeof onevent !== "function"
  2417. ? value
  2418. : function (e) {
  2419. var result = value.call(element, e);
  2420. onevent.call(element, e);
  2421. return result;
  2422. };
  2423. if (key2 in element)
  2424. element[key2] =
  2425. typeof value === "function" ? callback : null;
  2426. else {
  2427. var eventName = key2.slice(2);
  2428. if (vnode.events === undefined) vnode.events = {};
  2429. if (vnode.events[key2] === callback) return;
  2430. if (vnode.events[key2] != null)
  2431. element.removeEventListener(
  2432. eventName,
  2433. vnode.events[key2],
  2434. false
  2435. );
  2436. if (typeof value === "function") {
  2437. vnode.events[key2] = callback;
  2438. element.addEventListener(
  2439. eventName,
  2440. vnode.events[key2],
  2441. false
  2442. );
  2443. }
  2444. }
  2445. }
  2446. //lifecycle
  2447. function initLifecycle(source, vnode, hooks) {
  2448. if (typeof source.oninit === "function")
  2449. source.oninit.call(vnode.state, vnode);
  2450. if (typeof source.oncreate === "function")
  2451. hooks.push(source.oncreate.bind(vnode.state, vnode));
  2452. }
  2453. function updateLifecycle(source, vnode, hooks) {
  2454. if (typeof source.onupdate === "function")
  2455. hooks.push(source.onupdate.bind(vnode.state, vnode));
  2456. }
  2457. function shouldNotUpdate(vnode, old) {
  2458. var forceVnodeUpdate, forceComponentUpdate;
  2459. if (
  2460. vnode.attrs != null &&
  2461. typeof vnode.attrs.onbeforeupdate === "function"
  2462. )
  2463. forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(
  2464. vnode.state,
  2465. vnode,
  2466. old
  2467. );
  2468. if (
  2469. typeof vnode.tag !== "string" &&
  2470. typeof vnode._state.onbeforeupdate === "function"
  2471. )
  2472. forceComponentUpdate = vnode._state.onbeforeupdate.call(
  2473. vnode.state,
  2474. vnode,
  2475. old
  2476. );
  2477. if (
  2478. !(
  2479. forceVnodeUpdate === undefined &&
  2480. forceComponentUpdate === undefined
  2481. ) &&
  2482. !forceVnodeUpdate &&
  2483. !forceComponentUpdate
  2484. ) {
  2485. vnode.dom = old.dom;
  2486. vnode.domSize = old.domSize;
  2487. vnode.instance = old.instance;
  2488. return true;
  2489. }
  2490. return false;
  2491. }
  2492. function render(dom, vnodes) {
  2493. let lastWidth = "";
  2494. if (toc_dom) {
  2495. lastWidth = toc_dom.getBoundingClientRect().width;
  2496. }
  2497. if (!dom)
  2498. throw new Error(
  2499. "Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined."
  2500. );
  2501. var hooks = [];
  2502. var active = $doc.activeElement;
  2503. var namespace = dom.namespaceURI;
  2504. // First time0 rendering into a node clears it out
  2505. if (dom.vnodes == null) dom.textContent = "";
  2506. if (!Array.isArray(vnodes)) vnodes = [vnodes];
  2507. updateNodes(
  2508. dom,
  2509. dom.vnodes,
  2510. Vnode.normalizeChildren(vnodes),
  2511. false,
  2512. hooks,
  2513. null,
  2514. namespace === "http://www.w3.org/1999/xhtml"
  2515. ? undefined
  2516. : namespace
  2517. );
  2518. dom.vnodes = vnodes;
  2519. for (var i = 0; i < hooks.length; i++) hooks[i]();
  2520. if ($doc.activeElement !== active) active.focus();
  2521.  
  2522. // 保证toc拉宽了之后, 当点击标题或滚动页面的时候不会恢复原来的宽度
  2523. if (toc_dom) {
  2524. toc_dom.style.width = lastWidth + "px";
  2525. }
  2526. }
  2527. return { render: render, setEventCallback: setEventCallback };
  2528. };
  2529. function throttle(callback) {
  2530. //60fps translates to 16.6ms, round it down since setTimeout requires int
  2531. var time = 16;
  2532. var last = 0,
  2533. pending = null;
  2534. var timeout =
  2535. typeof requestAnimationFrame === "function"
  2536. ? requestAnimationFrame
  2537. : setTimeout;
  2538. return function () {
  2539. var now = Date.now();
  2540. if (last === 0 || now - last >= time) {
  2541. last = now;
  2542. callback();
  2543. } else if (pending === null) {
  2544. pending = timeout(function () {
  2545. pending = null;
  2546. callback();
  2547. last = Date.now();
  2548. }, time - (now - last));
  2549. }
  2550. };
  2551. }
  2552. var _11 = function ($window) {
  2553. var renderService = coreRenderer($window);
  2554. renderService.setEventCallback(function (e) {
  2555. if (e.redraw === false) e.redraw = undefined;
  2556. else redraw();
  2557. });
  2558. var callbacks = [];
  2559. function subscribe(key1, callback) {
  2560. unsubscribe(key1);
  2561. callbacks.push(key1, throttle(callback));
  2562. }
  2563. function unsubscribe(key1) {
  2564. var index = callbacks.indexOf(key1);
  2565. if (index > -1) callbacks.splice(index, 2);
  2566. }
  2567. function redraw() {
  2568. for (var i = 1; i < callbacks.length; i += 2) {
  2569. callbacks[i]();
  2570. }
  2571. }
  2572. return {
  2573. subscribe: subscribe,
  2574. unsubscribe: unsubscribe,
  2575. redraw: redraw,
  2576. render: renderService.render,
  2577. };
  2578. };
  2579. var redrawService = _11(window);
  2580. requestService.setCompletionCallback(redrawService.redraw);
  2581. var _16 = function (redrawService0) {
  2582. return function (root, component) {
  2583. if (component === null) {
  2584. redrawService0.render(root, []);
  2585. redrawService0.unsubscribe(root);
  2586. return;
  2587. }
  2588.  
  2589. if (
  2590. component.view == null &&
  2591. typeof component !== "function"
  2592. )
  2593. throw new Error(
  2594. "m.mount(element, component) expects a component, not a vnode"
  2595. );
  2596.  
  2597. var run0 = function () {
  2598. redrawService0.render(root, Vnode(component));
  2599. };
  2600. redrawService0.subscribe(root, run0);
  2601. redrawService0.redraw();
  2602. };
  2603. };
  2604. m.mount = _16(redrawService);
  2605. var Promise = PromisePolyfill;
  2606. var parseQueryString = function (string) {
  2607. if (string === "" || string == null) return {};
  2608. if (string.charAt(0) === "?") string = string.slice(1);
  2609. var entries = string.split("&"),
  2610. data0 = {},
  2611. counters = {};
  2612. for (var i = 0; i < entries.length; i++) {
  2613. var entry = entries[i].split("=");
  2614. var key5 = decodeURIComponent(entry[0]);
  2615. var value =
  2616. entry.length === 2 ? decodeURIComponent(entry[1]) : "";
  2617. if (value === "true") value = true;
  2618. else if (value === "false") value = false;
  2619. var levels = key5.split(/\]\[?|\[/);
  2620. var cursor = data0;
  2621. if (key5.indexOf("[") > -1) levels.pop();
  2622. for (var j = 0; j < levels.length; j++) {
  2623. var level = levels[j],
  2624. nextLevel = levels[j + 1];
  2625. var isNumber =
  2626. nextLevel == "" || !isNaN(parseInt(nextLevel, 10));
  2627. var isValue = j === levels.length - 1;
  2628. if (level === "") {
  2629. var key6 = levels.slice(0, j).join();
  2630. if (counters[key6] == null) counters[key6] = 0;
  2631. level = counters[key6]++;
  2632. }
  2633. if (cursor[level] == null) {
  2634. cursor[level] = isValue
  2635. ? value
  2636. : isNumber
  2637. ? []
  2638. : {};
  2639. }
  2640. cursor = cursor[level];
  2641. }
  2642. }
  2643. return data0;
  2644. };
  2645. var coreRouter = function ($window) {
  2646. var supportsPushState =
  2647. typeof $window.history.pushState === "function";
  2648. var callAsync0 =
  2649. typeof setImmediate === "function"
  2650. ? setImmediate
  2651. : setTimeout;
  2652. function normalize1(fragment0) {
  2653. var data = $window.location[fragment0].replace(
  2654. /(?:%[a-f89][a-f0-9])+/gim,
  2655. decodeURIComponent
  2656. );
  2657. if (fragment0 === "pathname" && data[0] !== "/")
  2658. data = "/" + data;
  2659. return data;
  2660. }
  2661. var asyncId;
  2662. function debounceAsync(callback0) {
  2663. return function () {
  2664. if (asyncId != null) return;
  2665. asyncId = callAsync0(function () {
  2666. asyncId = null;
  2667. callback0();
  2668. });
  2669. };
  2670. }
  2671. function parsePath(path, queryData, hashData) {
  2672. var queryIndex = path.indexOf("?");
  2673. var hashIndex = path.indexOf("#");
  2674. var pathEnd =
  2675. queryIndex > -1
  2676. ? queryIndex
  2677. : hashIndex > -1
  2678. ? hashIndex
  2679. : path.length;
  2680. if (queryIndex > -1) {
  2681. var queryEnd = hashIndex > -1 ? hashIndex : path.length;
  2682. var queryParams = parseQueryString(
  2683. path.slice(queryIndex + 1, queryEnd)
  2684. );
  2685. for (var key4 in queryParams)
  2686. queryData[key4] = queryParams[key4];
  2687. }
  2688. if (hashIndex > -1) {
  2689. var hashParams = parseQueryString(
  2690. path.slice(hashIndex + 1)
  2691. );
  2692. for (var key5 in hashParams)
  2693. hashData[key5] = hashParams[key5];
  2694. }
  2695. return path.slice(0, pathEnd);
  2696. }
  2697. var router = { prefix: "#!" };
  2698. router.getPath = function () {
  2699. var type2 = router.prefix.charAt(0);
  2700. switch (type2) {
  2701. case "#":
  2702. return normalize1("hash").slice(
  2703. router.prefix.length
  2704. );
  2705. case "?":
  2706. return (
  2707. normalize1("search").slice(
  2708. router.prefix.length
  2709. ) + normalize1("hash")
  2710. );
  2711. default:
  2712. return (
  2713. normalize1("pathname").slice(
  2714. router.prefix.length
  2715. ) +
  2716. normalize1("search") +
  2717. normalize1("hash")
  2718. );
  2719. }
  2720. };
  2721. router.setPath = function (path, data, options) {
  2722. var queryData = {},
  2723. hashData = {};
  2724. path = parsePath(path, queryData, hashData);
  2725. if (data != null) {
  2726. for (var key4 in data) queryData[key4] = data[key4];
  2727. path = path.replace(
  2728. /:([^\/]+)/g,
  2729. function (match2, token) {
  2730. delete queryData[token];
  2731. return data[token];
  2732. }
  2733. );
  2734. }
  2735. var query = buildQueryString(queryData);
  2736. if (query) path += "?" + query;
  2737. var hash = buildQueryString(hashData);
  2738. if (hash) path += "#" + hash;
  2739. if (supportsPushState) {
  2740. var state = options ? options.state : null;
  2741. var title = options ? options.title : null;
  2742. $window.onpopstate();
  2743. if (options && options.replace)
  2744. $window.history.replaceState(
  2745. state,
  2746. title,
  2747. router.prefix + path
  2748. );
  2749. else
  2750. $window.history.pushState(
  2751. state,
  2752. title,
  2753. router.prefix + path
  2754. );
  2755. } else $window.location.href = router.prefix + path;
  2756. };
  2757. router.defineRoutes = function (routes, resolve, reject) {
  2758. function resolveRoute() {
  2759. var path = router.getPath();
  2760. var params = {};
  2761. var pathname = parsePath(path, params, params);
  2762. var state = $window.history.state;
  2763. if (state != null) {
  2764. for (var k in state) params[k] = state[k];
  2765. }
  2766. for (var route0 in routes) {
  2767. var matcher = new RegExp(
  2768. "^" +
  2769. route0
  2770. .replace(/:[^\/]+?\.{3}/g, "(.*?)")
  2771. .replace(/:[^\/]+/g, "([^\\/]+)") +
  2772. "/?$"
  2773. );
  2774. if (matcher.test(pathname)) {
  2775. pathname.replace(matcher, function () {
  2776. var keys = route0.match(/:[^\/]+/g) || [];
  2777. var values = [].slice.call(
  2778. arguments,
  2779. 1,
  2780. -2
  2781. );
  2782. for (var i = 0; i < keys.length; i++) {
  2783. params[keys[i].replace(/:|\./g, "")] =
  2784. decodeURIComponent(values[i]);
  2785. }
  2786. resolve(
  2787. routes[route0],
  2788. params,
  2789. path,
  2790. route0
  2791. );
  2792. });
  2793. return;
  2794. }
  2795. }
  2796. reject(path, params);
  2797. }
  2798. if (supportsPushState)
  2799. $window.onpopstate = debounceAsync(resolveRoute);
  2800. else if (router.prefix.charAt(0) === "#")
  2801. $window.onhashchange = resolveRoute;
  2802. resolveRoute();
  2803. };
  2804. return router;
  2805. };
  2806. var _20 = function ($window, redrawService0) {
  2807. var routeService = coreRouter($window);
  2808. var identity = function (v) {
  2809. return v;
  2810. };
  2811. var render1, component, attrs3, currentPath, lastUpdate;
  2812. var route = function (root, defaultRoute, routes) {
  2813. if (root == null)
  2814. throw new Error(
  2815. "Ensure the DOM element that was passed to `m.route` is not undefined"
  2816. );
  2817. var run1 = function () {
  2818. if (render1 != null)
  2819. redrawService0.render(
  2820. root,
  2821. render1(Vnode(component, attrs3.key, attrs3))
  2822. );
  2823. };
  2824. var bail = function (path) {
  2825. if (path !== defaultRoute)
  2826. routeService.setPath(defaultRoute, null, {
  2827. replace: true,
  2828. });
  2829. else
  2830. throw new Error(
  2831. "Could not resolve default route " +
  2832. defaultRoute
  2833. );
  2834. };
  2835. routeService.defineRoutes(
  2836. routes,
  2837. function (payload, params, path) {
  2838. var update = (lastUpdate = function (
  2839. routeResolver,
  2840. comp
  2841. ) {
  2842. if (update !== lastUpdate) return;
  2843. component =
  2844. comp != null &&
  2845. (typeof comp.view === "function" ||
  2846. typeof comp === "function")
  2847. ? comp
  2848. : "div";
  2849. (attrs3 = params),
  2850. (currentPath = path),
  2851. (lastUpdate = null);
  2852. render1 = (
  2853. routeResolver.render || identity
  2854. ).bind(routeResolver);
  2855. run1();
  2856. });
  2857. if (payload.view || typeof payload === "function")
  2858. update({}, payload);
  2859. else {
  2860. if (payload.onmatch) {
  2861. Promise.resolve(
  2862. payload.onmatch(params, path)
  2863. ).then(function (resolved) {
  2864. update(payload, resolved);
  2865. }, bail);
  2866. } else update(payload, "div");
  2867. }
  2868. },
  2869. bail
  2870. );
  2871. redrawService0.subscribe(root, run1);
  2872. };
  2873. route.set = function (path, data, options) {
  2874. if (lastUpdate != null) {
  2875. options = options || {};
  2876. options.replace = true;
  2877. }
  2878. lastUpdate = null;
  2879. routeService.setPath(path, data, options);
  2880. };
  2881. route.get = function () {
  2882. return currentPath;
  2883. };
  2884. route.prefix = function (prefix0) {
  2885. routeService.prefix = prefix0;
  2886. };
  2887. route.link = function (vnode1) {
  2888. vnode1.dom.setAttribute(
  2889. "href",
  2890. routeService.prefix + vnode1.attrs.href
  2891. );
  2892. vnode1.dom.onclick = function (e) {
  2893. if (
  2894. e.ctrlKey ||
  2895. e.metaKey ||
  2896. e.shiftKey ||
  2897. e.which === 2
  2898. )
  2899. return;
  2900. e.preventDefault();
  2901. e.redraw = false;
  2902. var href = this.getAttribute("href");
  2903. if (href.indexOf(routeService.prefix) === 0)
  2904. href = href.slice(routeService.prefix.length);
  2905. route.set(href, undefined, undefined);
  2906. };
  2907. };
  2908. route.param = function (key3) {
  2909. if (
  2910. typeof attrs3 !== "undefined" &&
  2911. typeof key3 !== "undefined"
  2912. )
  2913. return attrs3[key3];
  2914. return attrs3;
  2915. };
  2916. return route;
  2917. };
  2918. m.route = _20(window, redrawService);
  2919. m.withAttr = function (attrName, callback1, context) {
  2920. return function (e) {
  2921. callback1.call(
  2922. context || this,
  2923. attrName in e.currentTarget
  2924. ? e.currentTarget[attrName]
  2925. : e.currentTarget.getAttribute(attrName)
  2926. );
  2927. };
  2928. };
  2929. var _28 = coreRenderer(window);
  2930. m.render = _28.render;
  2931. m.redraw = redrawService.redraw;
  2932. m.request = requestService.request;
  2933. m.jsonp = requestService.jsonp;
  2934. m.parseQueryString = parseQueryString;
  2935. m.buildQueryString = buildQueryString;
  2936. m.version = "1.1.3";
  2937. m.vnode = Vnode;
  2938. if ("object" !== "undefined") module["exports"] = m;
  2939. else {
  2940. }
  2941. })();
  2942. });
  2943.  
  2944. const restrictScroll = function (e) {
  2945. const toc = e.currentTarget;
  2946. const maxScroll = toc.scrollHeight - toc.offsetHeight;
  2947. if (toc.scrollTop + e.deltaY < 0) {
  2948. toc.scrollTop = 0;
  2949. e.preventDefault();
  2950. } else if (toc.scrollTop + e.deltaY > maxScroll) {
  2951. toc.scrollTop = maxScroll;
  2952. e.preventDefault();
  2953. }
  2954. e.redraw = false;
  2955. };
  2956.  
  2957. const TOC = function ({ $headings, $activeHeading, onClickHeading }) {
  2958. // $activeHeading.subscribe(activeIndex => {})
  2959. const toTree = function (headings) {
  2960. let i = 0;
  2961. let tree = { level: 0, children: [] };
  2962. let stack = [tree];
  2963. const top = (arr) => arr.slice(-1)[0];
  2964.  
  2965. while (i < headings.length) {
  2966. let { level, isActive } = headings[i];
  2967. if (level === stack.length) {
  2968. const node = {
  2969. heading: headings[i],
  2970. children: [],
  2971. };
  2972. top(stack).children.push(node);
  2973. stack.push(node);
  2974. if (isActive) {
  2975. stack.forEach((node) => {
  2976. if (node.heading) {
  2977. node.heading.isActive = true;
  2978. }
  2979. });
  2980. }
  2981. i++;
  2982. } else if (level < stack.length) {
  2983. stack.pop();
  2984. } else if (level > stack.length) {
  2985. const node = {
  2986. heading: null,
  2987. children: [],
  2988. };
  2989. top(stack).children.push(node);
  2990. stack.push(node);
  2991. }
  2992. }
  2993. return tree;
  2994. };
  2995.  
  2996. const UL = (children, { isRoot = false } = {}) =>
  2997. mithril(
  2998. "ul",
  2999. {
  3000. onwheel: isRoot && restrictScroll,
  3001. onclick: isRoot && onClickHeading,
  3002. },
  3003. children.map(LI)
  3004. );
  3005.  
  3006. const LI = ({ heading, children }, index) =>
  3007. mithril(
  3008. "li",
  3009. {
  3010. class: heading && heading.isActive ? "active" : "",
  3011. key: index,
  3012. },
  3013. [
  3014. heading &&
  3015. mithril(
  3016. "a",
  3017. {
  3018. href: `#${heading.anchor}`,
  3019. title: heading.node.textContent,
  3020. },
  3021. heading.node.textContent
  3022. ),
  3023. children && children.length && UL(children),
  3024. ].filter(Boolean)
  3025. );
  3026.  
  3027. return {
  3028. oncreate({ dom }) {
  3029. // scroll to heading if out of view
  3030. $activeHeading.subscribe((index) => {
  3031. const target = [].slice
  3032. .apply(dom.querySelectorAll(".active"))
  3033. .pop();
  3034. if (target) {
  3035. const targetRect = target.getBoundingClientRect();
  3036. const containerRect = dom.getBoundingClientRect();
  3037. const outOfView =
  3038. targetRect.top > containerRect.bottom ||
  3039. targetRect.bottom < containerRect.top;
  3040. if (outOfView) {
  3041. scrollTo({
  3042. targetElem: target,
  3043. scrollElem: dom,
  3044. maxDuration: 0,
  3045. topMargin:
  3046. dom.offsetHeight / 2 -
  3047. target.offsetHeight / 2,
  3048. });
  3049. }
  3050. }
  3051. });
  3052. Stream.combine($headings, $activeHeading, () => null).subscribe(
  3053. (_) => mithril.redraw()
  3054. );
  3055. },
  3056. view() {
  3057. $headings().forEach(
  3058. (h, i) => (h.isActive = i === $activeHeading())
  3059. );
  3060. const tree = toTree($headings());
  3061. // console.log("tree begin aaa")
  3062. // console.log(tree)
  3063. // console.log("tree end bbb")
  3064. return UL(tree.children, { isRoot: true });
  3065. },
  3066. };
  3067. };
  3068.  
  3069. const stop = (e) => {
  3070. e.stopPropagation();
  3071. e.preventDefault();
  3072. };
  3073.  
  3074. let multi_click_cnt = 0;
  3075. let last_click_ts = 0;
  3076.  
  3077. const Handle = function ({ $userOffset }) {
  3078. let [sClientX, sClientY] = [0, 0];
  3079. let [sOffsetX, sOffsetY] = [0, 0];
  3080.  
  3081. const onDrag = throttle((e) => {
  3082. stop(e);
  3083. let [dX, dY] = [e.clientX - sClientX, e.clientY - sClientY];
  3084. $userOffset([sOffsetX + dX, sOffsetY + dY]);
  3085. e.redraw = false;
  3086. });
  3087.  
  3088. const onDragEnd = (e) => {
  3089. window.removeEventListener("mousemove", onDrag);
  3090. window.removeEventListener("mouseup", onDragEnd);
  3091. e.redraw = false;
  3092.  
  3093. var domain2offset = GM_getValue(
  3094. "menu_GAEEScript_auto_toc_domain_2_offset"
  3095. );
  3096. // 判断之前toc 的位置和现在的, 如果相等的话, 说明只是原地点击
  3097. if (
  3098. sOffsetX === $userOffset()[0] &&
  3099. sOffsetY === $userOffset()[1]
  3100. ) {
  3101. console.log(
  3102. "[auto-toc, 原地点击, multi_click_cnt:]",
  3103. multi_click_cnt
  3104. );
  3105. if (Date.now() - last_click_ts < 233) {
  3106. // 说明是双击, 走关闭 toc 逻辑
  3107. console.log("[auto-toc, double click handle section]");
  3108. menuSwitch("menu_GAEEScript_auto_open_toc");
  3109. handleToc();
  3110. return;
  3111. }
  3112. // 单击逻辑, 走折叠 toc 逻辑
  3113. console.log("[auto-toc, click handle section]");
  3114. menuSwitch("menu_GAEEScript_auto_collapse_toc");
  3115. handleToc();
  3116. last_click_ts = Date.now();
  3117.  
  3118. ////////////////////////////////////////// 以下这种实现方案导致单击有延迟, 故不采用
  3119. // if (multi_click_cnt > 0) {
  3120. // // setInterval 已经启动, 所以我们记录单击次数
  3121. // multi_click_cnt += 1;
  3122. // return;
  3123. // }
  3124. // multi_click_cnt = 1;
  3125. // setTimeout(() => {
  3126. // if (multi_click_cnt === 1) {
  3127. // // 单击逻辑, 走折叠 toc 逻辑
  3128. // console.log("[auto-toc, click handle section]");
  3129. // menuSwitch("menu_GAEEScript_auto_collapse_toc");
  3130. // } else if (multi_click_cnt === 2) {
  3131. // // 说明是双击, 走关闭 toc 逻辑
  3132. // console.log("[auto-toc, double click handle section]");
  3133. // menuSwitch("menu_GAEEScript_auto_open_toc");
  3134. // }
  3135. // handleToc();
  3136. // multi_click_cnt = 0;
  3137. // }, 222);
  3138. return;
  3139. }
  3140. domain2offset[window.location.host] = $userOffset();
  3141. GM_setValue(
  3142. "menu_GAEEScript_auto_toc_domain_2_offset",
  3143. domain2offset
  3144. );
  3145. console.log(
  3146. "[auto-toc, update domain offset]",
  3147. domain2offset[window.location.host]
  3148. );
  3149. console.log("[auto-toc, $userOffset()]", $userOffset());
  3150. console.log(
  3151. "[auto-toc, update domain offset, domain2offset]",
  3152. domain2offset
  3153. );
  3154. };
  3155.  
  3156. const onDragStart = (e) => {
  3157. if (e.button === 0) {
  3158. stop(e);
  3159. sClientX = e.clientX;
  3160. sClientY = e.clientY;
  3161. sOffsetX = $userOffset()[0];
  3162. sOffsetY = $userOffset()[1];
  3163. window.addEventListener("mousemove", onDrag);
  3164. window.addEventListener("mouseup", onDragEnd);
  3165. }
  3166. e.redraw = false;
  3167. };
  3168.  
  3169. const onDoubleClick = (e) => {
  3170. console.log("[auto-toc, onDoubleClick]");
  3171. menuSwitch("menu_GAEEScript_auto_open_toc");
  3172. handleToc();
  3173. };
  3174.  
  3175. return {
  3176. view() {
  3177. return mithril(
  3178. ".handle",
  3179. {
  3180. onmousedown: onDragStart,
  3181. // ondblclick: onDoubleClick,
  3182. },
  3183. "○ ○ ○"
  3184. );
  3185. },
  3186. };
  3187. };
  3188.  
  3189. const ARTICLE_TOC_GAP = 150;
  3190. const TOP_MARGIN = 88;
  3191.  
  3192. const makeSticky = function (options) {
  3193. let {
  3194. ref,
  3195. scrollable,
  3196. popper,
  3197. direction,
  3198. gap,
  3199. $refChange,
  3200. $scroll,
  3201. $offset,
  3202. $topMargin,
  3203. } = options;
  3204.  
  3205. let $refRect = Stream.combine($refChange, () => {
  3206. let refRect = ref.getBoundingClientRect();
  3207. let refStyle = window.getComputedStyle(ref);
  3208. let scrollTop = getScroll(scrollable, "top");
  3209. let scrollLeft = getScroll(scrollable, "left");
  3210. let refFullRect = {
  3211. top: refRect.top - scrollTop,
  3212. right: refRect.right - scrollLeft,
  3213. bottom: refRect.bottom - scrollTop,
  3214. left: refRect.left - scrollLeft,
  3215. width: refRect.width,
  3216. height: refRect.height,
  3217. };
  3218. if (refStyle["box-sizing"] === "border-box") {
  3219. refFullRect.left += num(refStyle["padding-left"]);
  3220. refFullRect.right -= num(refStyle["padding-right"]);
  3221. refFullRect.width -=
  3222. num(refStyle["padding-left"]) +
  3223. num(refStyle["padding-right"]);
  3224. }
  3225. return refFullRect;
  3226. });
  3227. let popperMetric = popper.getBoundingClientRect();
  3228. const scrollableTop =
  3229. scrollable === document.body
  3230. ? 0
  3231. : scrollable.getBoundingClientRect().top;
  3232. return Stream.combine(
  3233. $refRect,
  3234. $scroll,
  3235. $offset,
  3236. $topMargin,
  3237. (ref, [scrollX, scrollY], [offsetX, offsetY], topMargin) => {
  3238. // console.log("[makeSticky, direction]", direction)
  3239. // let x =
  3240. // direction === 'right'
  3241. // ? ref.right + gap
  3242. // : ref.left - gap - popperMetric.width
  3243. // let y = Math.max(scrollableTop + topMargin, ref.top - scrollY)
  3244. // let y = Math.max(scrollableTop + topMargin, 288 - scrollY)
  3245.  
  3246. // 我们假定 topMargin 为 TOP_MARGIN (88), 方便固定 toc 在网页的位置
  3247. let y = scrollableTop + TOP_MARGIN;
  3248. let final_y = y + offsetY;
  3249. // let y = Math.max((scrollableTop + TOP_MARGIN), 888 - scrollY)
  3250. // let final_y = Math.max(TOP_MARGIN, offsetY + Math.max((scrollableTop + TOP_MARGIN), 288 - scrollY))
  3251. // let final_y = Math.max(TOP_MARGIN, offsetY + Math.max((scrollableTop + TOP_MARGIN), 288 - scrollY))
  3252.  
  3253. // 把 window.innerWidth 换成 window.outerWidth: 解决 safari 双指缩放导致 toc 居中遮挡网页内容的问题
  3254. // popperMetric.width 是 toc 挂件的宽度
  3255. // x = Math.min(Math.max(0, x), window.outerWidth - popperMetric.width) // restrict to visible area
  3256.  
  3257. // 放在右侧
  3258. // 我们假定 popperMetric.width 为 288, 方便固定 toc 在网页的位置
  3259. // 我们假定用户都开启了Edge浏览器侧边栏, 所以往左多移 36
  3260. let final_x =
  3261. offsetX + Math.max(0, window.outerWidth - (288 + 36)); // restrict to visible area
  3262.  
  3263. // // 放在左侧, 多加 36, 免得靠浏览器左侧太近
  3264. // let final_x = offsetX + 36; // restrict to visible area
  3265.  
  3266. // console.log('[auto-toc, makeSticky, final_y]', Math.max((scrollableTop + TOP_MARGIN), 888 - scrollY), final_y)
  3267. // console.log('[auto-toc, makeSticky, scrollableTop, topMargin]',scrollableTop, topMargin)
  3268. // console.log('[auto-toc, makeSticky, window.outerWidth, popperMetric.width]',window.outerWidth, popperMetric.width)
  3269. // console.log('[auto-toc, makeSticky, ref.right, gap]',ref.right, gap)
  3270. // console.log('[auto-toc, makeSticky, x, window.outerWidth - popperMetric.width]', x, window.outerWidth - popperMetric.width)
  3271. // console.log('[auto-toc, makeSticky, x, y, offsetX, offsetY]', x, y, offsetX, offsetY)
  3272. // console.log('[auto-toc, makeSticky, scrollableTop, topMargin, ref.top, scrollY)', scrollableTop, topMargin, ref.top, scrollY)
  3273. // // console.log('[auto-toc, makeSticky, scrollableTop + topMargin, ref.top - scrollY)', scrollableTop + topMargin, ref.top - scrollY)
  3274. // console.log('[auto-toc, makeSticky, 3*(scrollableTop + TOP_MARGIN), 888 - scrollY', 3*(scrollableTop + TOP_MARGIN), 888 - scrollY)
  3275. // console.log('[auto-toc, makeSticky, x + offsetX, y + offsetY]',x + offsetX, y + offsetY)
  3276. // console.log('[auto-toc, makeSticky, ref.top, gap]',ref.top, gap)
  3277. return {
  3278. position: "fixed",
  3279. left: 0,
  3280. top: 0,
  3281. // transform: translate3d(x + offsetX, y + offsetY)
  3282. transform: translate3d(final_x, final_y),
  3283. };
  3284. }
  3285. );
  3286. };
  3287.  
  3288. const getOptimalContainerPos = function (article) {
  3289. const { top, left, right, bottom, height, width } =
  3290. article.getBoundingClientRect();
  3291.  
  3292. const depthOf = function (elem) {
  3293. let depth = 0;
  3294. while (elem) {
  3295. elem = elem.parentElement;
  3296. depth++;
  3297. }
  3298. return depth;
  3299. };
  3300. const depthOfPoint = function ([x, y]) {
  3301. const elem = document.elementFromPoint(x, y);
  3302. return elem && depthOf(elem);
  3303. };
  3304. const gap = ARTICLE_TOC_GAP;
  3305. const testWidth = 200;
  3306. const testHeight = 400;
  3307. const leftSlotTestPoints = [
  3308. left - gap - testWidth,
  3309. left - gap - testWidth / 2,
  3310. left - gap,
  3311. ]
  3312. .map((x) =>
  3313. [top, top + testHeight / 2, top + testHeight].map((y) => [x, y])
  3314. )
  3315. .reduce((prev, cur) => prev.concat(cur), []);
  3316. const rightSlotTestPoints = [
  3317. right + gap,
  3318. right + gap + testWidth / 2,
  3319. right + gap + testWidth,
  3320. ]
  3321. .map((x) =>
  3322. [top, top + testHeight / 2, top + testHeight].map((y) => [x, y])
  3323. )
  3324. .reduce((prev, cur) => prev.concat(cur), []);
  3325. const leftDepths = leftSlotTestPoints.map(depthOfPoint).filter(Boolean);
  3326. const rightDepths = rightSlotTestPoints
  3327. .map(depthOfPoint)
  3328. .filter(Boolean);
  3329. const leftAvgDepth = leftDepths.length
  3330. ? leftDepths.reduce((a, b) => a + b, 0) / leftDepths.length
  3331. : null;
  3332. const rightAvgDepth = rightDepths.length
  3333. ? rightDepths.reduce((a, b) => a + b, 0) / rightDepths.length
  3334. : null;
  3335.  
  3336. if (!leftAvgDepth) return { direction: "right" };
  3337. if (!rightAvgDepth) return { direction: "left" };
  3338. const spaceDiff = document.documentElement.offsetWidth - right - left;
  3339. const scoreDiff =
  3340. spaceDiff * 1 + (rightAvgDepth - leftAvgDepth) * 9 * -10 + 20; // I do like right better
  3341. return scoreDiff > 0 ? { direction: "right" } : { direction: "left" };
  3342. };
  3343.  
  3344. const Container = function ({
  3345. article,
  3346. scrollable,
  3347. $headings,
  3348. theme,
  3349. $activeHeading,
  3350. $isShow,
  3351. $userOffset,
  3352. $relayout,
  3353. $scroll,
  3354. $topbarHeight,
  3355. onClickHeading,
  3356. }) {
  3357. const handle = Handle({ $userOffset });
  3358. const toc = TOC({ $headings, $activeHeading, onClickHeading });
  3359. return {
  3360. oncreate({ dom }) {
  3361. toc_dom = dom;
  3362. const { direction } = getOptimalContainerPos(article);
  3363. this.$style = makeSticky({
  3364. ref: article,
  3365. scrollable: scrollable,
  3366. popper: dom,
  3367. direction: direction,
  3368. gap: ARTICLE_TOC_GAP,
  3369. // $topMargin: $topbarHeight.map(h => (h || 0) + 50),
  3370. $topMargin: $topbarHeight.map((h) => TOP_MARGIN),
  3371. $refChange: $relayout,
  3372. $scroll: $scroll,
  3373. $offset: $userOffset,
  3374. });
  3375. this.$style.subscribe((_) => mithril.redraw());
  3376. },
  3377. view() {
  3378. return mithril(
  3379. "#smarttoc.dark-scheme",
  3380. {
  3381. class: [
  3382. theme || "light",
  3383. $headings().filter((h) => h.level <= 2).length >
  3384. 50 && "lengthy",
  3385. $isShow() ? "" : "hidden",
  3386. ]
  3387. .filter(Boolean)
  3388. .join(" "),
  3389. style: this.$style && this.$style(),
  3390. },
  3391. [mithril(handle), mithril(toc)]
  3392. );
  3393. },
  3394. };
  3395. };
  3396.  
  3397. const Extender = function ({ $headings, scrollable, $isShow, $relayout }) {
  3398. const $extender = Stream();
  3399. // toc: extend body height so we can scroll to the last heading
  3400. let extender = document.createElement("DIV");
  3401. extender.id = "smarttoc-extender";
  3402. Stream.combine($isShow, $relayout, $headings, (isShow, _, headings) => {
  3403. setTimeout(() => {
  3404. // some delay to ensure page is stable ?
  3405. let lastHeading = headings.slice(-1)[0].node;
  3406. let lastRect = lastHeading.getBoundingClientRect();
  3407. let extenderHeight = 0;
  3408. if (scrollable === document.body) {
  3409. let heightBelowLastRect =
  3410. document.documentElement.scrollHeight -
  3411. (lastRect.bottom + document.documentElement.scrollTop) -
  3412. num(extender.style.height); // in case we are there already
  3413. extenderHeight = isShow
  3414. ? Math.max(
  3415. window.innerHeight -
  3416. lastRect.height -
  3417. heightBelowLastRect,
  3418. 0
  3419. )
  3420. : 0;
  3421. } else {
  3422. let scrollRect = scrollable.getBoundingClientRect();
  3423. let heightBelowLastRect =
  3424. scrollRect.top +
  3425. scrollable.scrollHeight -
  3426. getScroll(scrollable) - // bottom of scrollable relative to viewport
  3427. lastRect.bottom -
  3428. num(extender.style.height); // in case we are there already
  3429. extenderHeight = isShow
  3430. ? Math.max(
  3431. scrollRect.height -
  3432. lastRect.height -
  3433. heightBelowLastRect,
  3434. 0
  3435. )
  3436. : 0;
  3437. }
  3438. $extender({
  3439. height: extenderHeight,
  3440. });
  3441. }, 300);
  3442. });
  3443. $extender.subscribe((style) => applyStyle(extender, style));
  3444. return extender;
  3445. };
  3446.  
  3447. const relayoutStream = function (article, $resize, $isShow) {
  3448. const readableStyle = function (article) {
  3449. let computed = window.getComputedStyle(article);
  3450. let fontSize = num(computed.fontSize);
  3451. let bestWidth = Math.min(Math.max(fontSize, 12), 16) * 66;
  3452. if (computed["box-sizing"] === "border-box") {
  3453. bestWidth +=
  3454. num(computed["padding-left"]) +
  3455. num(computed["padding-right"]);
  3456. }
  3457.  
  3458. return Object.assign(
  3459. num(computed.marginLeft) || num(computed.marginRight)
  3460. ? {}
  3461. : {
  3462. marginLeft: "auto",
  3463. marginRight: "auto",
  3464. },
  3465. num(computed.maxWidth)
  3466. ? {}
  3467. : {
  3468. maxWidth: bestWidth,
  3469. }
  3470. );
  3471. };
  3472. let oldStyle = article.style.cssText;
  3473. let newStyle = readableStyle(article);
  3474. let $relayout = $isShow.map((isShow) => {
  3475. if (isShow) {
  3476. // 注释掉了下面这两行, 免得生成 toc 的时候导致页面重排, 很丑
  3477. // applyStyle(article, newStyle)
  3478. // return article
  3479. } else {
  3480. // applyStyle(article, oldStyle)
  3481. }
  3482. });
  3483. return Stream.combine($relayout, $resize, () => null);
  3484. };
  3485.  
  3486. const addAnchors = function (headings) {
  3487. const anchoredHeadings = headings.map(function ({
  3488. node,
  3489. level,
  3490. anchor,
  3491. }) {
  3492. if (!anchor) {
  3493. anchor =
  3494. node.id ||
  3495. [].slice
  3496. .apply(node.children)
  3497. .filter((elem) => elem.tagName === "A")
  3498. .map((a) => {
  3499. let href = a.getAttribute("href") || "";
  3500. return href.startsWith("#") ? href.substr(1) : a.id;
  3501. })
  3502. .filter(Boolean)[0];
  3503. if (!anchor) {
  3504. anchor = node.id = unique(safe(node.textContent));
  3505. } else {
  3506. anchor = unique(anchor);
  3507. }
  3508. }
  3509. return { node, level, anchor };
  3510. });
  3511.  
  3512. // console.log("anchoredHeadings begin aaa")
  3513. // console.log(anchoredHeadings)
  3514. // console.log("anchoredHeadings end bbb")
  3515. return anchoredHeadings;
  3516. };
  3517.  
  3518. const getScrollParent = function (elem) {
  3519. const canScroll = (el) =>
  3520. ["auto", "scroll"].includes(
  3521. window.getComputedStyle(el).overflowY
  3522. ) && el.clientHeight + 1 < el.scrollHeight;
  3523. while (elem && elem !== document.body && !canScroll(elem)) {
  3524. elem = elem.parentElement;
  3525. }
  3526. log("scrollable", elem);
  3527. draw(elem, "purple");
  3528. return elem;
  3529. };
  3530.  
  3531. const scrollStream = function (scrollable, $isShow) {
  3532. let $scroll = Stream([
  3533. getScroll(scrollable, "left"),
  3534. getScroll(scrollable),
  3535. ]);
  3536. let source = scrollable === document.body ? window : scrollable;
  3537. Stream.fromEvent(source, "scroll")
  3538. .filter(() => $isShow())
  3539. .throttle()
  3540. .subscribe(() => {
  3541. $scroll([getScroll(scrollable, "left"), getScroll(scrollable)]);
  3542. });
  3543. return $scroll;
  3544. };
  3545.  
  3546. const activeHeadingStream = function (
  3547. $headings,
  3548. scrollable,
  3549. $scroll,
  3550. $relayout,
  3551. $topbarHeight
  3552. ) {
  3553. const $headingScrollYs = Stream.combine(
  3554. $relayout,
  3555. $headings,
  3556. (_, headings) => {
  3557. const scrollableTop =
  3558. (scrollable === document.body
  3559. ? 0
  3560. : scrollable.getBoundingClientRect().top) -
  3561. getScroll(scrollable, "top");
  3562. return headings.map(
  3563. ({ node }) =>
  3564. node.getBoundingClientRect().top - scrollableTop
  3565. );
  3566. }
  3567. );
  3568.  
  3569. let $curIndex = Stream.combine(
  3570. $headingScrollYs,
  3571. $scroll,
  3572. $topbarHeight,
  3573. function (headingScrollYs, [scrollX, scrollY], topbarHeight = 0) {
  3574. let i = 0;
  3575. for (let len = headingScrollYs.length; i < len; i++) {
  3576. if (headingScrollYs[i] > scrollY + topbarHeight + 20) {
  3577. break;
  3578. }
  3579. }
  3580. return Math.max(0, i - 1);
  3581. }
  3582. );
  3583.  
  3584. return $curIndex.unique();
  3585. };
  3586.  
  3587. const scrollToHeading = function (
  3588. { node },
  3589. scrollElem,
  3590. onScrollEnd,
  3591. topMargin = 0
  3592. ) {
  3593. scrollTo({
  3594. targetElem: node,
  3595. scrollElem: scrollElem,
  3596. topMargin: topMargin,
  3597. maxDuration: 188,
  3598. callback: onScrollEnd && onScrollEnd.bind(null, node),
  3599. });
  3600. };
  3601.  
  3602. const getTopBarHeight = function (topElem) {
  3603. // 默认网页的顶部有个 bar, 而且默认这个 bar 的高度是 88, 保证点击 toc 的时候跳转可以网页多往下移一点, 免得被各种检测不出来的 bar 挡住
  3604. return TOP_MARGIN;
  3605.  
  3606. const findFixedParent = function (elem) {
  3607. const isFixed = (elem) => {
  3608. let { position, zIndex } = window.getComputedStyle(elem);
  3609. return position === "fixed" && zIndex;
  3610. };
  3611. while (elem !== document.body && !isFixed(elem)) {
  3612. elem = elem.parentElement;
  3613. }
  3614. return elem === document.body ? null : elem;
  3615. };
  3616. let { left, right, top } = topElem.getBoundingClientRect();
  3617. let leftTopmost = document.elementFromPoint(left + 1, top + 1);
  3618. let rightTopmost = document.elementFromPoint(right - 1, top + 1);
  3619. if (
  3620. leftTopmost &&
  3621. rightTopmost &&
  3622. leftTopmost !== topElem &&
  3623. rightTopmost !== topElem
  3624. ) {
  3625. let leftFixed = findFixedParent(leftTopmost);
  3626. let rightFixed = findFixedParent(rightTopmost);
  3627. if (leftFixed && leftFixed === rightFixed) {
  3628. return leftFixed.offsetHeight;
  3629. } else {
  3630. return 0;
  3631. }
  3632. } else {
  3633. return 0;
  3634. }
  3635. };
  3636.  
  3637. const getTheme = function (article) {
  3638. let elem = article;
  3639. try {
  3640. const parseColor = (str) =>
  3641. str
  3642. .replace(/rgba?\(/, "")
  3643. .replace(/\).*/, "")
  3644. .split(/, ?/);
  3645. const getBgColor = (elem) =>
  3646. parseColor(window.getComputedStyle(elem)["background-color"]);
  3647. const isTransparent = ([r, g, b, a]) => a === 0;
  3648. const isLight = ([r, g, b, a]) => r + g + b > (255 / 2) * 3;
  3649. while (elem && elem.parentElement) {
  3650. const color = getBgColor(elem);
  3651. if (isTransparent(color)) {
  3652. elem = elem.parentElement;
  3653. } else {
  3654. return isLight(color) ? "light" : "dark";
  3655. }
  3656. }
  3657. return "light";
  3658. } catch (e) {
  3659. return "light";
  3660. }
  3661. };
  3662.  
  3663. const getRoot = function () {
  3664. let root = document.getElementById("smarttoc_wrapper");
  3665. if (!root) {
  3666. root = document.body.appendChild(document.createElement("DIV"));
  3667. root.id = "smarttoc_wrapper";
  3668. }
  3669. return root;
  3670. };
  3671.  
  3672. // 生成目录
  3673. function createTOC({
  3674. article,
  3675. $headings: $headings_,
  3676. userOffset = [0, 0],
  3677. }) {
  3678. var domain2offset = GM_getValue(
  3679. "menu_GAEEScript_auto_toc_domain_2_offset"
  3680. );
  3681. var lastOffset = domain2offset[window.location.host];
  3682. console.log("[auto-toc, lastOffset]", lastOffset);
  3683. if (lastOffset != null) {
  3684. userOffset = lastOffset;
  3685. }
  3686. console.log("[auto-toc, init userOffset]", userOffset);
  3687.  
  3688. const $headings = $headings_.map(addAnchors);
  3689. insertCSS(getTocCss(), "smarttoc__css");
  3690.  
  3691. const scrollable = getScrollParent(article);
  3692. const theme = getTheme(article);
  3693. log("theme", theme);
  3694.  
  3695. const $isShow = Stream(true);
  3696. const $topbarHeight = Stream();
  3697. const $resize = Stream.combine(
  3698. Stream.fromEvent(window, "resize"),
  3699. Stream.fromEvent(document, "readystatechange"),
  3700. Stream.fromEvent(document, "load"),
  3701. Stream.fromEvent(document, "DOMContentLoaded"),
  3702. () => null
  3703. )
  3704. .filter(() => $isShow())
  3705. .throttle();
  3706. const $scroll = scrollStream(scrollable, $isShow);
  3707. const $relayout = relayoutStream(article, $resize, $isShow);
  3708. const $activeHeading = activeHeadingStream(
  3709. $headings,
  3710. scrollable,
  3711. $scroll,
  3712. $relayout,
  3713. $topbarHeight
  3714. );
  3715. const $userOffset = Stream(userOffset);
  3716.  
  3717. // scrollable.appendChild(
  3718. // Extender({ $headings, scrollable, $isShow, $relayout })
  3719. // )
  3720.  
  3721. const onScrollEnd = function (node) {
  3722. if ($topbarHeight() == null) {
  3723. setTimeout(() => {
  3724. $topbarHeight(getTopBarHeight(node));
  3725. log("topBarHeight", $topbarHeight());
  3726. if ($topbarHeight()) {
  3727. scrollToHeading(
  3728. { node },
  3729. scrollable,
  3730. null,
  3731. $topbarHeight() + 10
  3732. );
  3733. }
  3734. }, 300);
  3735. }
  3736. };
  3737.  
  3738. const onClickHeading = function (e) {
  3739. e.redraw = false;
  3740. e.preventDefault();
  3741. e.stopPropagation();
  3742. const temp = e.target.getAttribute("href");
  3743. if (!temp) return;
  3744. const anchor = temp.substr(1);
  3745. const heading = $headings().find(
  3746. (heading) => heading.anchor === anchor
  3747. );
  3748. scrollToHeading(
  3749. heading,
  3750. scrollable,
  3751. onScrollEnd,
  3752. ($topbarHeight() || 0) + 10
  3753. );
  3754. };
  3755.  
  3756. mithril.mount(
  3757. getRoot(),
  3758. Container({
  3759. article,
  3760. scrollable,
  3761. $headings,
  3762. theme,
  3763. $activeHeading,
  3764. $isShow,
  3765. $userOffset,
  3766. $relayout,
  3767. $scroll,
  3768. $topbarHeight,
  3769. onClickHeading,
  3770. })
  3771. );
  3772.  
  3773. // // now show what we've found
  3774. // if (article.getBoundingClientRect().top > window.innerHeight - 50) {
  3775. // scrollToHeading(
  3776. // $headings()[0],
  3777. // scrollable,
  3778. // onScrollEnd,
  3779. // ($topbarHeight() || 0) + 10
  3780. // );
  3781. // }
  3782.  
  3783. return {
  3784. isValid: () =>
  3785. document.body.contains(article) &&
  3786. article.contains($headings()[0].node),
  3787.  
  3788. isShow: () => $isShow(),
  3789.  
  3790. toggle: () => $isShow(!$isShow()),
  3791.  
  3792. next: () => {
  3793. if ($isShow()) {
  3794. let nextIdx = Math.min(
  3795. $headings().length - 1,
  3796. $activeHeading() + 1
  3797. );
  3798. scrollToHeading(
  3799. $headings()[nextIdx],
  3800. scrollable,
  3801. onScrollEnd,
  3802. ($topbarHeight() || 0) + 10
  3803. );
  3804. }
  3805. },
  3806.  
  3807. prev: () => {
  3808. if ($isShow()) {
  3809. let prevIdx = Math.max(0, $activeHeading() - 1);
  3810. scrollToHeading(
  3811. $headings()[prevIdx],
  3812. scrollable,
  3813. onScrollEnd,
  3814. ($topbarHeight() || 0) + 10
  3815. );
  3816. }
  3817. },
  3818.  
  3819. dispose: () => {
  3820. log("dispose");
  3821. $isShow(false);
  3822. mithril.render(getRoot(), mithril(""));
  3823. return { userOffset: $userOffset() };
  3824. },
  3825. };
  3826. }
  3827.  
  3828. const pathToTop = function (elem, maxLvl = -1) {
  3829. assert(elem, "no element given");
  3830. const path = [];
  3831. while (elem && maxLvl--) {
  3832. path.push(elem);
  3833. elem = elem.parentElement;
  3834. }
  3835. return path;
  3836. };
  3837.  
  3838. const isStrongAlsoHeading = function (rootElement = document) {
  3839. // return false
  3840. return true;
  3841. // return rootElement.querySelectorAll('p > strong:only-child').length >= 2
  3842. };
  3843.  
  3844. //////////////////////////////// 以下是新版提取文章和标题的部分(目前测出某些网站会导致页面排版错乱比如谷歌和https://www.163.com/dy/article/GJKFUO4105119NPR.html) //////////////////////////////////////////////////////////////////////
  3845. //////////////////////////////// 所以退回后面的旧版的代码了 //////////////////////////////////////////////////////////////////////
  3846.  
  3847. var toArray = function (arr) {
  3848. return [].slice.apply(arr);
  3849. };
  3850.  
  3851. // var getAncestors = function (elem, maxDepth) {
  3852. // if (maxDepth === void 0) { maxDepth = -1; }
  3853. // var ancestors = [];
  3854. // var cur = elem;
  3855. // while (cur && maxDepth--) {
  3856. // ancestors.push(cur);
  3857. // cur = cur.parentElement;
  3858. // }
  3859. // return ancestors;
  3860. // };
  3861.  
  3862. // var canScroll = function (el) {
  3863. // return (['auto', 'scroll'].includes(window.getComputedStyle(el).overflowY) &&
  3864. // el.clientHeight + 1 < el.scrollHeight);
  3865. // };
  3866.  
  3867. // var ARTICLE_TAG_WEIGHTS = {
  3868. // h1: [0, 100, 60, 40, 30, 25, 22, 18].map(function (s) { return s * 0.4; }),
  3869. // h2: [0, 100, 60, 40, 30, 25, 22, 18],
  3870. // h3: [0, 100, 60, 40, 30, 25, 22, 18].map(function (s) { return s * 0.5; }),
  3871. // h4: [0, 100, 60, 40, 30, 25, 22, 18].map(function (s) { return s * 0.5 * 0.5; }),
  3872. // h5: [0, 100, 60, 40, 30, 25, 22, 18].map(function (s) { return s * 0.5 * 0.5 * 0.5; }),
  3873. // h6: [0, 100, 60, 40, 30, 25, 22, 18].map(function (s) { return s * 0.5 * 0.5 * 0.5 * 0.5; }),
  3874. // strong: [0, 100, 60, 40, 30, 25, 22, 18].map(function (s) { return s * 0.5 * 0.5 * 0.5; }),
  3875. // article: [500],
  3876. // '.article': [500],
  3877. // '#article': [500],
  3878. // '.content': [101],
  3879. // sidebar: [-500, -100, -50],
  3880. // '.sidebar': [-500, -100, -50],
  3881. // '#sidebar': [-500, -100, -50],
  3882. // aside: [-500, -100, -50],
  3883. // '.aside': [-500, -100, -50],
  3884. // '#aside': [-500, -100, -50],
  3885. // nav: [-500, -100, -50],
  3886. // '.nav': [-500, -100, -50],
  3887. // '.navigation': [-500, -100, -50],
  3888. // '.toc': [-500, -100, -50],
  3889. // '.table-of-contents': [-500, -100, -50],
  3890. // '.comment': [-500, -100, -50]
  3891. // };
  3892.  
  3893. // 拿到离页面左边边缘最近的标题的距离
  3894. var getElemsCommonLeft = function (elems) {
  3895. if (!elems.length) {
  3896. return undefined;
  3897. }
  3898. var lefts = {};
  3899. elems.forEach(function (el) {
  3900. var left = el.getBoundingClientRect().left;
  3901. if (!lefts[left]) {
  3902. lefts[left] = 0;
  3903. }
  3904. lefts[left]++;
  3905. });
  3906. var count = elems.length;
  3907. var isAligned = Object.keys(lefts).length <= Math.ceil(0.3 * count);
  3908. if (!isAligned) {
  3909. return undefined;
  3910. }
  3911. var sortedByCount = Object.keys(lefts).sort(function (a, b) {
  3912. return lefts[b] - lefts[a];
  3913. });
  3914. var most = Number(sortedByCount[0]);
  3915. return most;
  3916. };
  3917.  
  3918. // const extractArticle = function (rootElement = document) {
  3919. // var elemScores = new Map();
  3920. // // weigh nodes by factor: "selector" "distance from this node"
  3921. // Object.keys(ARTICLE_TAG_WEIGHTS).forEach(function (selector) {
  3922. // var elems = (0, toArray)(rootElement.querySelectorAll(selector));
  3923. // if (selector.toLowerCase() === 'strong') {
  3924. // // for <strong> elements, only take them as heading when they align at left
  3925. // var commonLeft_1 = getElemsCommonLeft(elems);
  3926. // if (commonLeft_1 === undefined || commonLeft_1 > window.innerWidth / 2) {
  3927. // elems = [];
  3928. // }
  3929. // else {
  3930. // elems = elems.filter(function (elem) { return elem.getBoundingClientRect().left === commonLeft_1; });
  3931. // }
  3932. // }
  3933. // elems.forEach(function (elem) {
  3934. // var weights = ARTICLE_TAG_WEIGHTS[selector];
  3935. // var ancestors = getAncestors(elem, weights.length);
  3936. // ancestors.forEach(function (elem, distance) {
  3937. // elemScores.set(elem, (elemScores.get(elem) || 0) + weights[distance] || 0);
  3938. // });
  3939. // });
  3940. // });
  3941. // var sortedByScore = [...elemScores].sort(function (a, b) { return b[1] - a[1]; });
  3942. // // pick top 5 node to re-weigh
  3943. // var candicates = sortedByScore
  3944. // .slice(0, 5)
  3945. // .filter(Boolean)
  3946. // .map(function (_a) {
  3947. // var elem = _a[0], score = _a[1];
  3948. // return { elem: elem, score: score };
  3949. // });
  3950. // // re-weigh by factor: "take-lots-vertical-space", "contain-less-links", "not-too-narrow", "cannot-scroll"
  3951. // var isTooNarrow = function (e) { return e.scrollWidth < 400; }; // rule out sidebars
  3952. // candicates.forEach(function (candicate) {
  3953. // if (isTooNarrow(candicate.elem)) {
  3954. // candicate.score = 0;
  3955. // candicates.forEach(function (parent) {
  3956. // if (parent.elem.contains(candicate.elem)) {
  3957. // parent.score *= 0.7;
  3958. // }
  3959. // });
  3960. // }
  3961. // if ((0, canScroll)(candicate.elem) && candicate.elem !== rootElement.body) {
  3962. // candicate.score *= 0.5;
  3963. // }
  3964. // });
  3965. // var reweighted = candicates
  3966. // .map(function (_a) {
  3967. // var elem = _a.elem, score = _a.score;
  3968. // return {
  3969. // elem: elem,
  3970. // score: score *
  3971. // Math.log((elem.scrollHeight * elem.scrollHeight) /
  3972. // (elem.querySelectorAll('a').length || 1))
  3973. // };
  3974. // })
  3975. // .sort(function (a, b) { return b.score - a.score; });
  3976. // var article = reweighted.length ? reweighted[0].elem : undefined;
  3977.  
  3978. // console.log('[extract]', {
  3979. // elemScores: elemScores,
  3980. // sortedByScore: sortedByScore,
  3981. // candicates: candicates,
  3982. // reweighted: reweighted
  3983. // });
  3984.  
  3985. // return article;
  3986. // }
  3987.  
  3988. // var HEADING_TAG_WEIGHTS = {
  3989. // H1: 4,
  3990. // H2: 9,
  3991. // H3: 9,
  3992. // H4: 10,
  3993. // H5: 10,
  3994. // H6: 10,
  3995. // STRONG: 5
  3996. // };
  3997. // var extractHeadings = function (articleDom) {
  3998. // var isVisible = function (elem) { return elem.offsetHeight !== 0; };
  3999. // var isHeadingGroupVisible = function (group) {
  4000. // return group.elems.filter(isVisible).length >= group.elems.length * 0.5;
  4001. // };
  4002. // var headingTagGroups = Object.keys(HEADING_TAG_WEIGHTS)
  4003. // .map(function (tag) {
  4004. // var elems = (0, toArray)(articleDom.getElementsByTagName(tag));
  4005. // if (tag.toLowerCase() === 'strong') {
  4006. // // for <strong> elements, only take them as heading when they align at left
  4007. // var commonLeft_2 = getElemsCommonLeft(elems);
  4008. // if (commonLeft_2 === undefined || commonLeft_2 > window.innerWidth / 2) {
  4009. // elems = [];
  4010. // }
  4011. // else {
  4012. // elems = elems.filter(function (elem) { return elem.getBoundingClientRect().left === commonLeft_2; });
  4013. // }
  4014. // }
  4015. // return {
  4016. // tag: tag,
  4017. // elems: elems,
  4018. // score: elems.length * HEADING_TAG_WEIGHTS[tag]
  4019. // };
  4020. // })
  4021. // .filter(function (group) { return group.score >= 10 && group.elems.length > 0; })
  4022. // .filter(function (group) { return isHeadingGroupVisible(group); })
  4023. // .slice(0, 3);
  4024. // // use document sequence
  4025. // var headingTags = headingTagGroups.map(function (headings) { return headings.tag; });
  4026. // var acceptNode = function (node) {
  4027. // var group = headingTagGroups.find(function (g) { return g.tag === node.tagName; });
  4028. // if (!group) {
  4029. // return NodeFilter.FILTER_SKIP;
  4030. // }
  4031. // return group.elems.includes(node) && isVisible(node)
  4032. // ? NodeFilter.FILTER_ACCEPT
  4033. // : NodeFilter.FILTER_SKIP;
  4034. // };
  4035. // var treeWalker = document.createTreeWalker(articleDom, NodeFilter.SHOW_ELEMENT, { acceptNode: acceptNode });
  4036. // var headings = [];
  4037. // var id = 0;
  4038. // while (treeWalker.nextNode()) {
  4039. // var node = treeWalker.currentNode;
  4040. // // var anchor = node.id ||
  4041. // // (0, toArray)(node.querySelectorAll('a'))
  4042. // // .map(function (a) {
  4043. // // var href = a.getAttribute('href') || '';
  4044. // // return href.startsWith('#') ? href.substr(1) : a.id;
  4045. // // })
  4046. // // .filter(Boolean)[0];
  4047. // // headings.push({
  4048. // // node: node,
  4049. // // text: node.textContent || '',
  4050. // // level: headingTags.indexOf(node.tagName) + 1,
  4051. // // id: id,
  4052. // // anchor: anchor
  4053. // // });
  4054. // headings.push({
  4055. // node,
  4056. // level: headingTags.indexOf(node.tagName) + 1,
  4057. // })
  4058. // id++;
  4059. // }
  4060. // console.log("headingsssss new begin")
  4061. // console.log(headings)
  4062. // console.log("headingsssss new end")
  4063. // return headings;
  4064. // };
  4065.  
  4066. ///////////////////////////////////////////////// 上面的是新版, 下面的是旧版 ////////////////////////
  4067.  
  4068. const extractArticle = function (rootElement = document) {
  4069. log("extracting article");
  4070.  
  4071. const scores = new Map();
  4072.  
  4073. function addScore(elem, inc) {
  4074. scores.set(elem, (scores.get(elem) || 0) + inc);
  4075. }
  4076.  
  4077. function updateScore(elem, weight) {
  4078. let path = pathToTop(elem, weight.length);
  4079. path.forEach((elem, distance) => addScore(elem, weight[distance]));
  4080. }
  4081.  
  4082. // weigh nodes by factor: "selector", "distance from this node"
  4083. const weights = {
  4084. h1: [0, 100, 60, 40, 30, 25, 22].map((s) => s * 0.4),
  4085. h2: [0, 100, 60, 40, 30, 25, 22],
  4086. h3: [0, 100, 60, 40, 30, 25, 22].map((s) => s * 0.5),
  4087. h4: [0, 100, 60, 40, 30, 25, 22].map((s) => s * 0.5 * 0.5),
  4088. h5: [0, 100, 60, 40, 30, 25, 22].map((s) => s * 0.5 * 0.5 * 0.5),
  4089. h6: [0, 100, 60, 40, 30, 25, 22].map(
  4090. (s) => s * 0.5 * 0.5 * 0.5 * 0.5
  4091. ),
  4092. article: [500],
  4093. ".article": [500],
  4094. ".content": [101],
  4095. sidebar: [-500],
  4096. ".sidebar": [-500],
  4097. aside: [-500],
  4098. ".aside": [-500],
  4099. nav: [-500],
  4100. ".nav": [-500],
  4101. ".navigation": [-500],
  4102. ".toc": [-500],
  4103. ".table-of-contents": [-500],
  4104. };
  4105. const selectors = Object.keys(weights);
  4106. selectors
  4107. .map((selector) => ({
  4108. selector: selector,
  4109. elems: [].slice.apply(rootElement.querySelectorAll(selector)),
  4110. }))
  4111. .forEach(({ selector, elems }) =>
  4112. elems.forEach((elem) => updateScore(elem, weights[selector]))
  4113. );
  4114. const sorted = [...scores].sort((a, b) => b[1] - a[1]);
  4115.  
  4116. // reweigh top 5 nodes by factor: "take-lots-vertical-space", "contain-less-links", "too-narrow"
  4117. let candicates = sorted
  4118. .slice(0, 5)
  4119. .filter(Boolean)
  4120. .map(([elem, score]) => ({ elem, score }));
  4121.  
  4122. let isTooNarrow = (e) => e.scrollWidth < 400; // rule out sidebars
  4123.  
  4124. candicates.forEach((c) => {
  4125. if (isTooNarrow(c.elem)) {
  4126. c.isNarrow = true;
  4127. candicates.forEach((parent) => {
  4128. if (parent.elem.contains(c.elem)) {
  4129. parent.score *= 0.7;
  4130. }
  4131. });
  4132. }
  4133. });
  4134. candicates = candicates.filter((c) => !c.isNarrow);
  4135.  
  4136. const reweighted = candicates
  4137. .map(({ elem, score }) => [
  4138. elem,
  4139. score *
  4140. Math.log(
  4141. (elem.scrollHeight * elem.scrollHeight) /
  4142. (elem.querySelectorAll("a").length || 1)
  4143. ),
  4144. elem.scrollHeight,
  4145. elem.querySelectorAll("a").length,
  4146. ])
  4147. .sort((a, b) => b[1] - a[1]);
  4148.  
  4149. const article = reweighted.length ? reweighted[0][0] : null;
  4150.  
  4151. // console.log('[extracttttttttttt]', {
  4152. // scores: scores,
  4153. // sorted: sorted,
  4154. // candicates: candicates,
  4155. // reweighted: reweighted
  4156. // });
  4157. return article;
  4158. };
  4159.  
  4160. const extractHeadings = function (article) {
  4161. log("extracting heading");
  4162.  
  4163. // what to be considered as headings
  4164. // const tags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].concat(
  4165. // isStrongAlsoHeading(article) ? 'STRONG' : []
  4166. // )
  4167. const header_tags = ["H1", "H2", "H3", "H4", "H5", "H6"];
  4168. const tags = header_tags.concat(["STRONG", "B"]);
  4169. const tagWeight = (tag) =>
  4170. ({ H1: 4, H2: 9, H3: 9, H4: 10, H5: 10, H6: 10, STRONG: 10, B: 10 }[
  4171. tag
  4172. ]);
  4173. const isVisible = (elem) => elem.offsetHeight !== 0;
  4174. const isGroupVisible = (headings) =>
  4175. headings.filter(isVisible).length >= headings.length * 0.5;
  4176. // var headingGroup = tags
  4177. // .map(tag => [].slice.apply(article.getElementsByTagName(tag)))
  4178. // const mm1 = headingGroup
  4179. // .map((headings, i) => ({
  4180. // elems: headings,
  4181. // tag: tags[i],
  4182. // score: headings.length * tagWeight(tags[i])
  4183. // }))
  4184.  
  4185. // const mm2 = mm1
  4186. // .filter(heading => heading.score >= 10)
  4187. // const mm3 = mm2
  4188. // .filter(heading => isGroupVisible(heading.elems))
  4189. // const mm4 = mm3
  4190. // .slice(0, 3)
  4191. // headingGroup = mm4
  4192.  
  4193. var headingGroup = tags.map(function (tag) {
  4194. var elems = (0, toArray)(article.getElementsByTagName(tag));
  4195. if (tag.toLowerCase() === "strong" || tag.toLowerCase() === "b") {
  4196. // for <strong> elements, only take them as heading when they align at left
  4197. var commonLeft_2 = getElemsCommonLeft(elems);
  4198. // console.log("commonLeft_2 old begin")
  4199. // console.log(commonLeft_2)
  4200. // console.log(window.innerWidth / 2)
  4201. // console.log(elems)
  4202. // if (commonLeft_2 === undefined || commonLeft_2 > window.innerWidth / 2) {
  4203. if (commonLeft_2 === undefined) {
  4204. elems = [];
  4205. } else {
  4206. elems = elems.filter(function (elem) {
  4207. // 当前 elem 离左边距离得和 commonLeft_2 一样, 并且当前 elem 不能是正经标题的子元素, 否则会重复; 当加粗的文字后面还有普通不加粗的文字则不识别为标题
  4208. return (
  4209. elem.getBoundingClientRect().left ===
  4210. commonLeft_2 &&
  4211. !header_tags.includes(elem.parentElement.tagName) &&
  4212. elem.parentElement.childNodes.length == 1
  4213. );
  4214. });
  4215. }
  4216. // console.log(elems)
  4217. // console.log("commonLeft_2 old end")
  4218. }
  4219. return {
  4220. tag: tag,
  4221. elems: elems,
  4222. score: elems.length * tagWeight(tag),
  4223. };
  4224. });
  4225. var mm1 = headingGroup
  4226. // 注释下面这三行代码(用于筛选score小于10的标题), 免得被认为是不显示某些标题的bug(比如这种情况: https://github.com/no5ix/auto-toc/issues/5)
  4227. // .filter(function (group) {
  4228. // return group.score >= 10 && group.elems.length > 0;
  4229. // })
  4230. .filter(function (group) {
  4231. return isVisible(group);
  4232. })
  4233. .slice(0, 8);
  4234. headingGroup = mm1;
  4235.  
  4236. // use document sequence
  4237. const validTags = headingGroup.map((headings) => headings.tag);
  4238. // 记录一下已经找到的要显示的标题名字最终放到 finalInnerHTML 里
  4239. const valid_innerHTML = headingGroup.map((headings) =>
  4240. headings.elems.map((node) => node.innerHTML)
  4241. );
  4242. var finalInnerHTML = [];
  4243. valid_innerHTML.forEach(function (arr) {
  4244. finalInnerHTML = finalInnerHTML.concat(arr);
  4245. });
  4246. // 筛选页面上想要遍历的 node
  4247. const acceptNode = (node) =>
  4248. validTags.includes(node.tagName) &&
  4249. isVisible(node) &&
  4250. finalInnerHTML.includes(node.innerHTML)
  4251. ? NodeFilter.FILTER_ACCEPT
  4252. : NodeFilter.FILTER_SKIP;
  4253. const treeWalker = document.createTreeWalker(
  4254. article,
  4255. NodeFilter.SHOW_ELEMENT,
  4256. { acceptNode }
  4257. );
  4258. const headings = [];
  4259.  
  4260. while (treeWalker.nextNode()) {
  4261. // 按照页面上的显示顺序遍历
  4262. let node = treeWalker.currentNode;
  4263. headings.push({
  4264. node,
  4265. // strong 粗体字类型的标题那 level 就直接是 1 就好了, 免得显示不出来
  4266. level:
  4267. node.tagName.toLowerCase() === "strong" ||
  4268. node.tagName.toLowerCase() === "b"
  4269. ? 1
  4270. : validTags.indexOf(node.tagName) + 1,
  4271. });
  4272. }
  4273.  
  4274. // console.log("headingsssss old begin")
  4275. // console.log(validTags)
  4276. // console.log(headings)
  4277. // console.log("headingsssss old end")
  4278.  
  4279. return headings;
  4280. };
  4281.  
  4282. ////////////////////////////////////////////////////////////////////////////////
  4283.  
  4284. function extract() {
  4285. const article = extractArticle(document);
  4286. let $headings;
  4287. if (article) {
  4288. $headings = Stream(extractHeadings(article));
  4289.  
  4290. const $articleChange = Stream(null);
  4291. const observer = new MutationObserver((_) => $articleChange(null));
  4292. observer.observe(article, { childList: true });
  4293.  
  4294. $articleChange.throttle(200).subscribe((_) => {
  4295. let headings = extractHeadings(article);
  4296. if (headings && headings.length) {
  4297. $headings(headings);
  4298. }
  4299. });
  4300. }
  4301.  
  4302. return [article, $headings];
  4303. }
  4304.  
  4305. ////////////////////////////////
  4306.  
  4307. let toc;
  4308.  
  4309. const doGenerateToc = function (option = {}) {
  4310. let [article, $headings] = extract();
  4311. if (article && $headings && $headings().length) {
  4312. console.log("createTOC before old begin aaa");
  4313. console.log($headings());
  4314. console.log("createTOC before old end bbb");
  4315.  
  4316. return createTOC(Object.assign({ article, $headings }, option));
  4317. } else {
  4318. return null;
  4319. }
  4320. };
  4321.  
  4322. function handleToc() {
  4323. var domain2shouldShow = GM_getValue("menu_GAEEScript_auto_open_toc");
  4324. console.log("[handleToc domain2shouldShow]", domain2shouldShow);
  4325. console.log("[handleToc window.location.host]", window.location.host);
  4326. console.log(
  4327. "[domain2shouldShow[window.location.host]]",
  4328. domain2shouldShow[window.location.host]
  4329. );
  4330.  
  4331. var timerId = setInterval(() => {
  4332. // console.log('[handleToc regen toc window.location.host]', window.location.host);
  4333. // clearInterval(timerId);
  4334. if (!domain2shouldShow[window.location.host]) {
  4335. // 防止正在循环尝试生成 toc 的时候用户关闭了 toc 开关
  4336. return;
  4337. }
  4338. if (toc && !toc.isValid()) {
  4339. let lastState = toc.dispose();
  4340. toc = doGenerateToc(lastState);
  4341. } else if (toc == null) {
  4342. toc = doGenerateToc();
  4343. }
  4344. }, 1600);
  4345.  
  4346. if (domain2shouldShow[window.location.host]) {
  4347. toc = doGenerateToc();
  4348. console.log("[handleToc toc]", toc);
  4349. // 如果生成的toc有问题或者toc没生成出来, 那就 n 秒之后再生成一次(比如掘金的很多文章得过几秒钟再生成才行)
  4350. // toast('Will generate TOC in 2.8 seconds ...', 1600);
  4351. setTimeout(() => {
  4352. if ((toc && !toc.isValid()) || toc == null) {
  4353. toast("No article/headings are detected.");
  4354. }
  4355. }, 3800);
  4356. } else {
  4357. console.log("[handleToc should not show]", toc);
  4358. if (toc) {
  4359. toc.dispose();
  4360. }
  4361. }
  4362. }
  4363.  
  4364. //////////////////////////////////////// 所有网站-缩小图片
  4365. function shrink_img(from_menu_switch=false) {
  4366. var domain2shouldShrinkImg = GM_getValue("menu_GAEEScript_shrink_img");
  4367. var shouldShrinkImg = domain2shouldShrinkImg[window.location.host];
  4368. // console.log(
  4369. // "[shrink_img] begin"
  4370. // );
  4371. let shouldNotShrink = shouldShrinkImg == null || !shouldShrinkImg
  4372. if (!from_menu_switch && shouldNotShrink) {
  4373. return;
  4374. }
  4375. let cssTxt = '';
  4376. const shrinkWidth = "88";
  4377. const shrinkWidthStr = shrinkWidth + "px";
  4378. Array.from(document.getElementsByTagName('*')).forEach(ele=>{
  4379. if (ele.tagName === 'IMG') {
  4380. if (shouldNotShrink) {
  4381. ele.style.width = ele.style.originalWidth;
  4382. // ele.style.height = ele.style.originalHeight;
  4383. ele.style.maxHeight = ele.style.originalMaxHeight;
  4384. ele.style.minHeight = ele.style.originalMinHeight;
  4385. ele.style.maxWidth = ele.style.originalMaxWidth;
  4386. ele.style.minWidth = ele.style.originalMinWidth;
  4387. } else {
  4388. if (ele.width > shrinkWidth) { // 防止多次缩小同一个图片, 也防止放大本身就很小的图片
  4389. const genCSSSelector = (ele)=>{
  4390. if (ele.id)
  4391. return `img[id="${ele.id}"]:hover`
  4392. else {
  4393. // if(ele.src.startsWith('data:')) return `img[src="${ele.src}"]:hover`;//base64的src
  4394. if(ele.src.startsWith('data:')) return "";//base64的src
  4395. else{
  4396. const the_src = ele.src || ele.getAttribute('_src') || '找不到可用选择器';
  4397. //http的src
  4398. try {
  4399. const url = new URL(the_src)//_src是一些网站懒加载的
  4400. return `img[src="${url.pathname + url.search}"]:hover,img[src="${the_src}"]:hover`;
  4401. } catch(e) {
  4402. console.log(
  4403. "[shrink_img] ERROR: " + e.message
  4404. );
  4405. return ""
  4406. }
  4407. }
  4408. }
  4409. }
  4410. let cssSelectorStr = genCSSSelector(ele)
  4411. if (cssSelectorStr != "" ) {
  4412. if (!ele.style.originalWidth || from_menu_switch) { // 防止不是打开开关导致的多次缩小同一个图片
  4413. ele.style.originalWidth = ele.width + "px";
  4414. // ele.style.originalHeight = ele.height + "px"; // 不记录这个了, 时不时拿到的是0
  4415. ele.style.originalMaxHeight = ele.style.maxHeight;
  4416. ele.style.originalMinHeight = ele.style.minHeight;
  4417. ele.style.originalMaxWidth = ele.style.maxWidth;
  4418. ele.style.originalMinWidth = ele.style.minWidth;
  4419. ele.style.cssSelectorStr = cssSelectorStr;
  4420. cssTxt += cssSelectorStr +
  4421. `{` +
  4422. // `width:${ele.width}px !important;height:${ele.height}px !important;` +
  4423. // `width:${ele.width}px !important;height:auto !important;` +
  4424. `width:${ele.width}px !important;` +
  4425. `}`;
  4426. ele.style.width = shrinkWidthStr;
  4427. ele.style.height = "";
  4428. ele.style.maxHeight = "";
  4429. ele.style.minHeight = "";
  4430. ele.style.maxWidth = "";
  4431. ele.style.minWidth = "";
  4432. }
  4433. }
  4434. }
  4435. }
  4436. }
  4437. }
  4438. )
  4439.  
  4440. if (shouldNotShrink) {
  4441. removeCSS("shrinkimg__css");
  4442. } else {
  4443. // removeCSS("shrinkimg__css");
  4444. insertCSS(cssTxt, "shrinkimg__css");
  4445. }
  4446. // console.log(
  4447. // "[shrink_img] end"
  4448. // );
  4449. }
  4450.  
  4451. var menu_ALL = [
  4452. [
  4453. "menu_GAEEScript_auto_open_toc",
  4454. "Enable TOC on current site(当前网站TOC开关)",
  4455. {},
  4456. ],
  4457. [
  4458. "menu_GAEEScript_auto_collapse_toc",
  4459. "Collapse TOC on current site(当前网站TOC自动折叠开关)",
  4460. {},
  4461. ],
  4462. [
  4463. "menu_GAEEScript_shrink_img",
  4464. "Touch Fish on current site(当前网站摸鱼开关)",
  4465. {},
  4466. ],
  4467. ],
  4468. menu_ID = [];
  4469.  
  4470. function handleMenu() {
  4471. // console.log("")
  4472. for (let i = 0; i < menu_ALL.length; i++) {
  4473. // 如果读取到的值为 null 就写入默认值
  4474. // console.log("debug ssss")
  4475. if (GM_getValue(menu_ALL[i][0]) == null) {
  4476. // console.log("debug ssss 11")
  4477. GM_setValue(menu_ALL[i][0], menu_ALL[i][2]);
  4478. }
  4479. }
  4480. registerMenuCommand();
  4481. }
  4482.  
  4483. // 注册(不可用)脚本菜单
  4484. function registerMenuCommand() {
  4485. for (let i = 0; i < menu_ID.length; i++) {
  4486. // console.log("debug ssss 22, aa")
  4487. // console.log(menu_ID)
  4488.  
  4489. // 因为 safari 的各个油猴平台都还没支持好 GM_unregisterMenuCommand , 所以先只让非 safari 的跑, 这会导致 safari 里用户关闭显示 toc 开关的时候, 相关菜单的✅不会变成❎
  4490. if (
  4491. !(
  4492. /Safari/.test(navigator.userAgent) &&
  4493. !/Chrome/.test(navigator.userAgent)
  4494. )
  4495. ) {
  4496. // alert("非safari");
  4497. GM_unregisterMenuCommand(menu_ID[i]);
  4498. }
  4499. // console.log("debug ssss 22, bb")
  4500. }
  4501. for (let i = 0; i < menu_ALL.length; i++) {
  4502. // 循环注册(不可用)脚本菜单
  4503. var currLocalStorage = GM_getValue(menu_ALL[i][0]);
  4504. menu_ID[menu_ID.length + 1] = GM_registerMenuCommand(
  4505. `${currLocalStorage[window.location.host] ? "✅" : "❎"} ${
  4506. menu_ALL[i][1]
  4507. }`,
  4508. // `${menu_ALL[i][1]}`,
  4509. function () {
  4510. menuSwitch(`${menu_ALL[i][0]}`);
  4511. }
  4512. );
  4513. // menu_ID[menu_ID.length + 1] = GM_registerMenuCommand(
  4514. // `${currLocalStorage[window.location.host] ? '✅' : '❎'} ${window.location.host}`,
  4515. // function () {
  4516. // menuSwitch(`${menu_ALL[i][0]}`)
  4517. // }
  4518. // );
  4519.  
  4520. // console.log("debug ssss , aa")
  4521. // console.log(menu_ID)
  4522. // console.log("debug ssss , bb")
  4523. }
  4524. // menu_ID[menu_ID.length] = GM_registerMenuCommand(`🏁 当前版本 ${version}`);
  4525. //menu_ID[menu_ID.length] = GM_registerMenuCommand('💬 反馈 & 建议', function () {window.GM_openInTab('', {active: true,insert: true,setParent: true});});
  4526. }
  4527.  
  4528. //切换选项
  4529. function menuSwitch(localStorageKeyName) {
  4530. // console.log("debug ssss 33")
  4531. var domain2isCollapse = GM_getValue(
  4532. "menu_GAEEScript_auto_collapse_toc"
  4533. );
  4534. if (localStorageKeyName === "menu_GAEEScript_auto_open_toc") {
  4535. var domain2isShow = GM_getValue(`${localStorageKeyName}`);
  4536. var domain2offset = GM_getValue(
  4537. "menu_GAEEScript_auto_toc_domain_2_offset"
  4538. );
  4539. console.log(
  4540. "[menuSwitch menu_GAEEScript_auto_open_toc]",
  4541. domain2isShow
  4542. );
  4543. var isCurrShow = domain2isShow[window.location.host];
  4544. if (isCurrShow == null || !isCurrShow) {
  4545. domain2isShow[window.location.host] = true;
  4546. toast("Turn On TOC.");
  4547. } else {
  4548. delete domain2isShow[window.location.host];
  4549. delete domain2offset[window.location.host];
  4550. delete domain2isCollapse[window.location.host];
  4551.  
  4552. toast("Turn Off TOC.");
  4553. }
  4554. GM_setValue(`${localStorageKeyName}`, domain2isShow);
  4555. GM_setValue(
  4556. "menu_GAEEScript_auto_toc_domain_2_offset",
  4557. domain2offset
  4558. );
  4559. GM_setValue("menu_GAEEScript_auto_collapse_toc", domain2isCollapse);
  4560. handleToc();
  4561. } else if (localStorageKeyName === "menu_GAEEScript_auto_collapse_toc") {
  4562. console.log(
  4563. "[menuSwitch menu_GAEEScript_auto_collapse_toc]",
  4564. domain2isCollapse
  4565. );
  4566. var isCurrCollapse = domain2isCollapse[window.location.host];
  4567. if (isCurrCollapse == null || !isCurrCollapse) {
  4568. domain2isCollapse[window.location.host] = true;
  4569. toast("Turn On TOC Auto Collapse.");
  4570. } else {
  4571. delete domain2isCollapse[window.location.host];
  4572. toast("Turn Off TOC Auto Collapse.");
  4573. }
  4574. GM_setValue(`${localStorageKeyName}`, domain2isCollapse);
  4575. handleToc();
  4576. } else if (localStorageKeyName === "menu_GAEEScript_shrink_img") {
  4577. var domain2shouldShrinkImg = GM_getValue("menu_GAEEScript_shrink_img");
  4578. console.log(
  4579. "[menuSwitch menu_GAEEScript_shrink_img]",
  4580. domain2shouldShrinkImg
  4581. );
  4582. var shouldShrinkImg = domain2shouldShrinkImg[window.location.host];
  4583. if (shouldShrinkImg == null || !shouldShrinkImg) {
  4584. domain2shouldShrinkImg[window.location.host] = true;
  4585. toast("Turn On Shrink IMG.");
  4586. } else {
  4587. delete domain2shouldShrinkImg[window.location.host];
  4588. toast("Turn Off Shrink IMG.");
  4589. }
  4590. GM_setValue(`${localStorageKeyName}`, domain2shouldShrinkImg);
  4591. shrink_img(true);
  4592. }
  4593. // if((/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent))) {
  4594. // alert("这是safari")
  4595. // }
  4596. // 因为 safari 的各个油猴平台都还没支持好 GM_unregisterMenuCommand , 所以先只让非 safari 的跑, 这会导致 safari 里用户关闭显示 toc 开关的时候, 相关菜单的✅不会变成❎
  4597. if (
  4598. !(
  4599. /Safari/.test(navigator.userAgent) &&
  4600. !/Chrome/.test(navigator.userAgent)
  4601. )
  4602. ) {
  4603. // alert("非safari");
  4604. registerMenuCommand(); // 重新注册(不可用)脚本菜单
  4605. }
  4606. // location.reload(); // 刷新网页
  4607. }
  4608.  
  4609. // if (isMasterFrame(window)) {
  4610. // if (true) {
  4611. console.log("auto_toc running !!!");
  4612. // 貌似无用
  4613. // 可以检查pageshow 事件的persisted属性,当页面初始化加载的时候,persisted被设置为false,当页面从缓存中加载的时候,persisted被设置为true。因此,上面代码的意思就是:
  4614. // 如果页面是从缓存中加载的,那么页面重新加载。
  4615. // window.onpageshow = function(event) {
  4616. // if (event.persisted) {
  4617. // // window.location.reload()
  4618. // console.log("ex-smart-toc handle toc when open web from cache !!!")
  4619. // handleToc()
  4620. // }
  4621. // };
  4622.  
  4623. // if( ('onhashchange' in window) && ((typeof document.documentMode==='undefined') || document.documentMode==8)) {
  4624. // // 浏览器支持onhashchange事件
  4625. // console.log("ex-smart-toc register window.onhashchange to handleToc !!!")
  4626. // // window.onhashchange = handleToc; // 对应新的hash执行的操作函数
  4627. // window.onhashchange = function(event) {
  4628. // console.log("ex-smart-toc window.onhashchange trigger handleToc !!!")
  4629. // handleToc()
  4630. // }
  4631. // } else {
  4632. // 不支持则用定时器检测的办法
  4633. // setInterval(function() {
  4634. // // 检测hash值或其中某一段是否更改的函数, 在低版本的iE浏览器中通过window.location.hash取出的指和其它的浏览器不同,要注意
  4635. //      var ischanged = isHashChanged();
  4636. // if(ischanged) {
  4637. // handleToc(); // 对应新的hash执行的操作函数
  4638. // }
  4639. // }, 150);
  4640. // }
  4641.  
  4642. // console.log("ex-smart-toc innerWidth", window.innerWidth)
  4643. // console.log("ex-smart-toc outerWidth", window.outerWidth)
  4644.  
  4645. handleMenu();
  4646.  
  4647. if (GM_getValue("menu_GAEEScript_auto_toc_domain_2_offset") == null) {
  4648. GM_setValue("menu_GAEEScript_auto_toc_domain_2_offset", {});
  4649. }
  4650. if (GM_getValue("menu_GAEEScript_auto_collapse_toc") == null) {
  4651. GM_setValue("menu_GAEEScript_auto_collapse_toc", {});
  4652. }
  4653. handleToc();
  4654.  
  4655. const urlObj = new URL(window.location.href);
  4656. if (urlObj.host === "www.zhihu.com") {
  4657. //////////////////////////////////////// 知乎-向下翻时自动隐藏顶栏
  4658. console.log(
  4659. "[hide-top-bar-when-scroll-down]"
  4660. );
  4661. let style = "";
  4662. let style_3 = `/* 向下翻时自动隐藏顶栏*/
  4663. header.is-hidden {display: none;}
  4664. `
  4665. style += style_3;
  4666. let style_Add = document.createElement('style');
  4667.  
  4668. if (document.lastChild) {
  4669. document.lastChild.appendChild(style_Add).textContent = style;
  4670. } else {
  4671. // 避免网站加载速度太慢的备用措施
  4672. let timer1 = setInterval(function () {
  4673. // 每 10 毫秒检查一下 html 是否已存在
  4674. if (document.lastChild) {
  4675. clearInterval(timer1); // 取消定时器
  4676. document.lastChild.appendChild(style_Add).textContent =
  4677. style;
  4678. }
  4679. });
  4680. }
  4681. } else if (urlObj.host === "www.google.com") {
  4682. //////////////////////////////////////// google-禁止重定向
  4683. console.log(
  4684. "[anti-google-redirect]"
  4685. );
  4686. var url = window.location.href.toLowerCase();
  4687. if (url.indexOf("/search") >= 0 || url.indexOf("/url") >= 0) {
  4688. function clean() {
  4689. // 获取id为"center_col"的div元素
  4690. const centerCol = document.getElementById('center_col');
  4691.  
  4692. // 获取所有超链接
  4693. var links = centerCol.getElementsByTagName('a');
  4694.  
  4695. // 遍历超链接并移除不必要的属性
  4696. let url
  4697. for (let i = 0; i < links.length; i++) {
  4698. const link = links[i];
  4699. // 移除不必要的属性
  4700. if (link.hasAttribute('button')) {
  4701. continue;
  4702. //alert("found!");
  4703. }
  4704. url = links[i].getAttribute('href');
  4705. var match = /url=(.*?)&/.exec(url);
  4706. if (match) {
  4707. links[i].setAttribute('href', decodeURIComponent(match[1]));
  4708. }
  4709. link.removeAttribute('data-jsarwt');
  4710. }
  4711. }
  4712. setTimeout(clean, 10);
  4713. setTimeout(clean, 500);
  4714. for (let i = 1; i <= 240; i++) {
  4715. setTimeout(clean, 1000 * i);
  4716. }
  4717. }
  4718. }
  4719.  
  4720. //////////////////////////////////////// 所有网站-缩小图片
  4721. console.log(
  4722. "[shrink_img]"
  4723. );
  4724. setTimeout(shrink_img, 10);
  4725. setTimeout(shrink_img, 500);
  4726. for (let i = 1; i <= 6666; i++) {
  4727. setTimeout(shrink_img, 1000 * i);
  4728. }
  4729.  
  4730. //////////////////////////////////////// 所有网站-anti-jump-warning
  4731. console.log(
  4732. "[anti-jump-warning]"
  4733. );
  4734. const parameters = {
  4735. 'link.zhihu.com': 'target',
  4736. 'link.csdn.net': 'target',
  4737. 'link.juejin.cn': 'target',
  4738. 'www.jianshu.com': 'url',
  4739. 'www.oschina.net': 'url',
  4740. 'gitee.com': 'target',
  4741. 'weibo.cn': 'u',
  4742. };
  4743. const selectors = {
  4744. 'jump2.bdimg.com': 'body > div > div.warning_info > p.link',
  4745. };
  4746. if (parameters[urlObj.host]) {
  4747. location.replace(urlObj.searchParams.get(parameters[urlObj.host]));
  4748. } else if (selectors[urlObj.host]) {
  4749. location.replace(document.querySelector(selectors[urlObj.host]).innerHTML);
  4750. }
  4751.  
  4752. // }
  4753. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址