auto-toc

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

目前为 2024-12-29 提交的版本。查看 最新版本

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

QingJ © 2025

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