跳转到内容
主菜单
主菜单
移至侧栏
隐藏
导航
首页
最近更改
随机页面
MediaWiki帮助
中医百科
搜索
搜索
登录
个人工具
登录
深色模式
查看“MediaWiki:Gadget-FloatTOC.js”的源代码
系统消息
讨论
English
阅读
查看源代码
查看历史
工具
工具
移至侧栏
隐藏
操作
阅读
查看源代码
查看历史
清除缓存
常规
链入页面
相关更改
特殊页面
页面信息
页面值
←
MediaWiki:Gadget-FloatTOC.js
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
此页面为本wiki上的软件提供界面文本,并受到保护以防止滥用。 如欲修改所有wiki的翻译,请访问
translatewiki.net
上的MediaWiki本地化项目。
您无权编辑此JavaScript页面,因为编辑此页面可能会影响所有访问者。
您必须确认您的电子邮件地址才能编辑页面。请通过
参数设置
设置并确认您的电子邮件地址。
您可以查看和复制此页面的源代码。
/** * SPDX-License-Identifier: GPL-3.0-or-later * _addText: '{{Gadget Header|license=GPL-3.0-or-later}}' * * @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/FloatTOC} * @author 安忆 <i@anyi.in> * @license GPL-3.0-or-later {@link https://www.qiuwenbaike.cn/wiki/H:GPL-3.0} */ /** * Copyright (C) 安忆 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ /** * +------------------------------------------------------------+ * | === WARNING: GLOBAL GADGET FILE === | * +------------------------------------------------------------+ * | All changes should be made in the repository, | * | otherwise they will be lost. | * +------------------------------------------------------------+ * | Changes to this page may affect many users. | * | Please discuss changes by opening an issue before editing. | * +------------------------------------------------------------+ */ /* <nowiki> */ (() => { "use strict"; var __getOwnPropNames = Object.getOwnPropertyNames; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; // node_modules/.pnpm/@mrhenry+core-web@1.2.3/node_modules/@mrhenry/core-web/modules/IntersectionObserver.js var require_IntersectionObserver = __commonJS({ "node_modules/.pnpm/@mrhenry+core-web@1.2.3/node_modules/@mrhenry/core-web/modules/IntersectionObserver.js"() { (function(undefined) { if (!("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype)) { (function(window2, document2) { "use strict"; var supportedNatively = "IntersectionObserver" in window2 && "IntersectionObserverEntry" in window2 && "intersectionRatio" in window2.IntersectionObserverEntry.prototype; if (supportedNatively) { return; } var registry = []; function IntersectionObserverEntry(entry) { this.time = entry.time; this.target = entry.target; this.rootBounds = entry.rootBounds; this.boundingClientRect = entry.boundingClientRect; this.intersectionRect = entry.intersectionRect || getEmptyRect(); try { this.isIntersecting = !!entry.intersectionRect; } catch (err) { } var targetRect = this.boundingClientRect; var targetArea = targetRect.width * targetRect.height; var intersectionRect = this.intersectionRect; var intersectionArea = intersectionRect.width * intersectionRect.height; if (targetArea) { this.intersectionRatio = Number((intersectionArea / targetArea).toFixed(4)); } else { this.intersectionRatio = this.isIntersecting ? 1 : 0; } } IntersectionObserverEntry.prototype.intersectionRatio = 0; function IntersectionObserver2(callback, opt_options) { var options = opt_options || {}; if (typeof callback != "function") { throw new Error("callback must be a function"); } if (options.root && options.root.nodeType != 1) { throw new Error("root must be an Element"); } this._checkForIntersections = throttle( this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT ); this._callback = callback; this._observationTargets = []; this._queuedEntries = []; this._rootMarginValues = this._parseRootMargin(options.rootMargin); this.thresholds = this._initThresholds(options.threshold); this.root = options.root || null; this.rootMargin = this._rootMarginValues.map(function(margin) { return margin.value + margin.unit; }).join(" "); } IntersectionObserver2.prototype.THROTTLE_TIMEOUT = 100; IntersectionObserver2.prototype.POLL_INTERVAL = null; IntersectionObserver2.prototype.USE_MUTATION_OBSERVER = true; IntersectionObserver2.prototype.observe = function(target) { var isTargetAlreadyObserved = this._observationTargets.some(function(item) { return item.element == target; }); if (isTargetAlreadyObserved) { return; } if (!(target && target.nodeType == 1)) { throw new Error("target must be an Element"); } this._registerInstance(); this._observationTargets.push({ element: target, entry: null }); this._monitorIntersections(); this._checkForIntersections(); }; IntersectionObserver2.prototype.unobserve = function(target) { this._observationTargets = this._observationTargets.filter(function(item) { return item.element != target; }); if (!this._observationTargets.length) { this._unmonitorIntersections(); this._unregisterInstance(); } }; IntersectionObserver2.prototype.disconnect = function() { this._observationTargets = []; this._unmonitorIntersections(); this._unregisterInstance(); }; IntersectionObserver2.prototype.takeRecords = function() { var records = this._queuedEntries.slice(); this._queuedEntries = []; return records; }; IntersectionObserver2.prototype._initThresholds = function(opt_threshold) { var threshold = opt_threshold || [0]; if (!Array.isArray(threshold)) threshold = [threshold]; return threshold.sort().filter(function(t, i, a) { if (typeof t != "number" || isNaN(t) || t < 0 || t > 1) { throw new Error("threshold must be a number between 0 and 1 inclusively"); } return t !== a[i - 1]; }); }; IntersectionObserver2.prototype._parseRootMargin = function(opt_rootMargin) { var marginString = opt_rootMargin || "0px"; var margins = marginString.split(/\s+/).map(function(margin) { var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin); if (!parts) { throw new Error("rootMargin must be specified in pixels or percent"); } return { value: parseFloat(parts[1]), unit: parts[2] }; }); margins[1] = margins[1] || margins[0]; margins[2] = margins[2] || margins[0]; margins[3] = margins[3] || margins[1]; return margins; }; IntersectionObserver2.prototype._monitorIntersections = function() { if (!this._monitoringIntersections) { this._monitoringIntersections = true; if (this.POLL_INTERVAL) { this._monitoringInterval = setInterval( this._checkForIntersections, this.POLL_INTERVAL ); } else { addEvent(window2, "resize", this._checkForIntersections, true); addEvent(document2, "scroll", this._checkForIntersections, true); if (this.USE_MUTATION_OBSERVER && "MutationObserver" in window2) { this._domObserver = new MutationObserver(this._checkForIntersections); this._domObserver.observe(document2, { attributes: true, childList: true, characterData: true, subtree: true }); } } } }; IntersectionObserver2.prototype._unmonitorIntersections = function() { if (this._monitoringIntersections) { this._monitoringIntersections = false; clearInterval(this._monitoringInterval); this._monitoringInterval = null; removeEvent(window2, "resize", this._checkForIntersections, true); removeEvent(document2, "scroll", this._checkForIntersections, true); if (this._domObserver) { this._domObserver.disconnect(); this._domObserver = null; } } }; IntersectionObserver2.prototype._checkForIntersections = function() { var rootIsInDom = this._rootIsInDom(); var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect(); this._observationTargets.forEach(function(item) { var target = item.element; var targetRect = getBoundingClientRect(target); var rootContainsTarget = this._rootContainsTarget(target); var oldEntry = item.entry; var intersectionRect = rootIsInDom && rootContainsTarget && this._computeTargetAndRootIntersection(target, rootRect); var newEntry = item.entry = new IntersectionObserverEntry({ time: now(), target, boundingClientRect: targetRect, rootBounds: rootRect, intersectionRect }); if (!oldEntry) { this._queuedEntries.push(newEntry); } else if (rootIsInDom && rootContainsTarget) { if (this._hasCrossedThreshold(oldEntry, newEntry)) { this._queuedEntries.push(newEntry); } } else { if (oldEntry && oldEntry.isIntersecting) { this._queuedEntries.push(newEntry); } } }, this); if (this._queuedEntries.length) { this._callback(this.takeRecords(), this); } }; IntersectionObserver2.prototype._computeTargetAndRootIntersection = function(target, rootRect) { if (window2.getComputedStyle(target).display == "none") return; var targetRect = getBoundingClientRect(target); var intersectionRect = targetRect; var parent = getParentNode(target); var atRoot = false; while (!atRoot) { var parentRect = null; var parentComputedStyle = parent.nodeType == 1 ? window2.getComputedStyle(parent) : {}; if (parentComputedStyle.display == "none") return; if (parent == this.root || parent == document2) { atRoot = true; parentRect = rootRect; } else { if (parent != document2.body && parent != document2.documentElement && parentComputedStyle.overflow != "visible") { parentRect = getBoundingClientRect(parent); } } if (parentRect) { intersectionRect = computeRectIntersection(parentRect, intersectionRect); if (!intersectionRect) break; } parent = getParentNode(parent); } return intersectionRect; }; IntersectionObserver2.prototype._getRootRect = function() { var rootRect; if (this.root) { rootRect = getBoundingClientRect(this.root); } else { var html = document2.documentElement; var body = document2.body; rootRect = { x: 0, y: 0, top: 0, left: 0, right: html.clientWidth || body.clientWidth, width: html.clientWidth || body.clientWidth, bottom: html.clientHeight || body.clientHeight, height: html.clientHeight || body.clientHeight }; } return this._expandRectByRootMargin(rootRect); }; IntersectionObserver2.prototype._expandRectByRootMargin = function(rect) { var margins = this._rootMarginValues.map(function(margin, i) { return margin.unit == "px" ? margin.value : margin.value * (i % 2 ? rect.width : rect.height) / 100; }); var newRect = { top: rect.top - margins[0], right: rect.right + margins[1], bottom: rect.bottom + margins[2], left: rect.left - margins[3] }; newRect.width = newRect.right - newRect.left; newRect.height = newRect.bottom - newRect.top; newRect.x = newRect.left; newRect.y = newRect.top; return newRect; }; IntersectionObserver2.prototype._hasCrossedThreshold = function(oldEntry, newEntry) { var oldRatio = oldEntry && oldEntry.isIntersecting ? oldEntry.intersectionRatio || 0 : -1; var newRatio = newEntry.isIntersecting ? newEntry.intersectionRatio || 0 : -1; if (oldRatio === newRatio) return; for (var i = 0; i < this.thresholds.length; i++) { var threshold = this.thresholds[i]; if (threshold == oldRatio || threshold == newRatio || threshold < oldRatio !== threshold < newRatio) { return true; } } }; IntersectionObserver2.prototype._rootIsInDom = function() { return !this.root || containsDeep(document2, this.root); }; IntersectionObserver2.prototype._rootContainsTarget = function(target) { return containsDeep(this.root || document2, target); }; IntersectionObserver2.prototype._registerInstance = function() { if (registry.indexOf(this) < 0) { registry.push(this); } }; IntersectionObserver2.prototype._unregisterInstance = function() { var index = registry.indexOf(this); if (index != -1) registry.splice(index, 1); }; function now() { return window2.performance && performance.now && performance.now(); } function throttle(fn, timeout) { var timer = null; return function() { if (!timer) { timer = setTimeout(function() { fn(); timer = null; }, timeout); } }; } function addEvent(node, event, fn, opt_useCapture) { if (typeof node.addEventListener == "function") { node.addEventListener(event, fn, opt_useCapture || false); } else if (typeof node.attachEvent == "function") { node.attachEvent("on" + event, fn); } } function removeEvent(node, event, fn, opt_useCapture) { if (typeof node.removeEventListener == "function") { node.removeEventListener(event, fn, opt_useCapture || false); } else if (typeof node.detatchEvent == "function") { node.detatchEvent("on" + event, fn); } } function computeRectIntersection(rect1, rect2) { var top = Math.max(rect1.top, rect2.top); var bottom = Math.min(rect1.bottom, rect2.bottom); var left = Math.max(rect1.left, rect2.left); var right = Math.min(rect1.right, rect2.right); var width = right - left; var height = bottom - top; return width >= 0 && height >= 0 && { x: left, y: top, top, bottom, left, right, width, height }; } function getBoundingClientRect(el) { var rect; try { rect = el.getBoundingClientRect(); } catch (err) { } if (!rect) return getEmptyRect(); if (!(rect.width && rect.height && rect.x && rect.y)) { rect = { x: rect.left, y: rect.top, top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left, width: rect.right - rect.left, height: rect.bottom - rect.top }; } return rect; } function getEmptyRect() { return { x: 0, y: 0, top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0 }; } function containsDeep(parent, child) { var node = child; while (node) { if (node == parent) return true; node = getParentNode(node); } return false; } function getParentNode(node) { var parent = node.parentNode; if (parent && parent.nodeType == 11 && parent.host) { return parent.host; } if (parent && parent.assignedSlot) { return parent.assignedSlot.parentNode; } return parent; } window2.IntersectionObserver = IntersectionObserver2; window2.IntersectionObserverEntry = IntersectionObserverEntry; })(window, document); } }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {}); } }); // dist/FloatTOC/FloatTOC.js require_IntersectionObserver(); //! src/FloatTOC/options.json var elementId = "floatTOC"; //! src/FloatTOC/modules/core.ts var import_ext_gadget2 = require("ext.gadget.Util"); var import_ext_gadget3 = require("ext.gadget.FilterAlteredClicks"); //! src/FloatTOC/modules/i18n.ts var import_ext_gadget = require("ext.gadget.i18n"); var getI18nMessages = () => { return { Close: (0, import_ext_gadget.localize)({ en: "Close", ja: "閉じる", "zh-hans": "关闭", "zh-hant": "關閉" }), Contents: (0, import_ext_gadget.localize)({ en: "Contents", ja: "目次", zh: "目录" }), Collapse: (0, import_ext_gadget.localize)({ en: "Collapse", ja: "折り畳み", "zh-hans": "折叠", "zh-hant": "摺叠" }), Expand: (0, import_ext_gadget.localize)({ en: "Expand", ja: "展開", "zh-hans": "展开", "zh-hant": "展開" }) }; }; var i18nMessages = getI18nMessages(); var getMessage = (key) => { return i18nMessages[key] || key; }; //! src/FloatTOC/modules/util/generateElements.ts var generateElements = (originToc) => { var _toc$querySelector, _toc$querySelector2; const toc = originToc.cloneNode(true); (_toc$querySelector = toc.querySelector("input")) === null || _toc$querySelector === void 0 || _toc$querySelector.remove(); (_toc$querySelector2 = toc.querySelector(".toctogglespan")) === null || _toc$querySelector2 === void 0 || _toc$querySelector2.remove(); const $toc = $(toc); const $floatToc = $toc.clone().removeAttr("id").prepend($("<span>").addClass("oo-ui-indicatorElement-indicator oo-ui-icon-close").attr({ id: "close", title: getMessage("Close"), role: "button", tabindex: "0" })); const $floatTocOpener = $("<div>").addClass("noprint").attr({ id: "floatToc-opener", title: getMessage("Contents"), role: "button", tabindex: "0" }).append($("<span>").addClass("oo-ui-indicatorElement-indicator oo-ui-icon-reference"), $("<span>").text(getMessage("Contents"))); return { $floatToc, $floatTocOpener }; }; //! src/FloatTOC/modules/util/generateTogglerElement.ts var generateTogglerElement = (isCollapse) => { let $toggler = $("<span>").addClass("oo-ui-indicatorElement-indicator oo-ui-icon-downTriangle"); $toggler = isCollapse ? $toggler.attr("title", getMessage("Expand")) : $toggler.attr("title", getMessage("Collapse")).addClass("collapse"); return $toggler; }; //! src/FloatTOC/modules/getConfig.ts var getConfig = (id) => { let config = mw.storage.getObject(id); config || (config = { floatTOC: window.outerHeight < window.outerWidth ? "open" : "close", originTOC: "open" }); return config; }; //! src/FloatTOC/modules/setMwNotifyStyle.ts var setMwNotifyStyle = () => { const style = mw.loader.addStyleTag(".mw-notification-area{right:unset;width:auto;max-width:20em}.mw-notification{-webkit-transform:translateX(-999px);-moz-transform:translateX(-999px);transform:translateX(-999px)}.mw-notification-visible{-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translateX(0)}"); style.disabled = true; return style; }; //! src/FloatTOC/modules/core.ts var floatTOC = ($originToc) => { const { skin } = mw.config.get(); const originToc = $originToc.get(0); const $body = $originToc.parents("body"); const { $floatToc, $floatTocOpener } = generateElements(originToc); $floatTocOpener.hide().appendTo($body); const config = getConfig(elementId); const mwNotifyStyle = setMwNotifyStyle(); let isShow = false; const storeState = (target, state) => { config[target] = state; mw.storage.setObject(elementId, config); }; let disableMwNotifyStyleTimer; const disableMwNotifyStyle = () => { if (disableMwNotifyStyleTimer) { clearTimeout(disableMwNotifyStyleTimer); } disableMwNotifyStyleTimer = setTimeout(() => { if (!isShow) { mwNotifyStyle.disabled = true; } }, 5 * 1e3); }; let notification; const closeNotification = (currentNotification) => { currentNotification.close(); $floatTocOpener.fadeIn(); storeState("floatTOC", "close"); disableMwNotifyStyle(); }; const smoothScroll = (event) => { if (skin === "citizen") { return; } const target = event.target; const $target = $(target).parent(); const href = $target.attr("href"); if (!href) { return; } const anchorOffset = $(href).offset(); if (!anchorOffset) { return; } event.preventDefault(); (0, import_ext_gadget2.scrollTop)("".concat(anchorOffset.top, "px")); }; const toggleToc = (currentIsShow = true, preNotification = void 0) => { var _preNotification; (_preNotification = preNotification) === null || _preNotification === void 0 || _preNotification.close(); isShow = !!currentIsShow; switch (currentIsShow) { case true: if (config.floatTOC === "close") { $floatTocOpener.fadeIn(); return; } break; case "open": $floatTocOpener.fadeOut(); storeState("floatTOC", "open"); break; default: $floatTocOpener.fadeOut(); disableMwNotifyStyle(); return; } mwNotifyStyle.disabled = false; if (preNotification) { preNotification.start(); } else { preNotification = mw.notification.notify($floatToc, { classes: "noprint", id: elementId, autoHide: false }); const notificationListener = (event) => { event.stopPropagation(); if (!(0, import_ext_gadget2.checkA11yConfirmKey)(event)) { return; } const target = event.target; if (target.id === "close") { closeNotification(preNotification); } else { smoothScroll(event); } }; preNotification.$notification.on("click", (0, import_ext_gadget3.filterAlteredClicks)((event) => { void notificationListener(event); })); preNotification.$notification.on("keydown", notificationListener); } return preNotification; }; const observerCallback = (entries) => { const [entry] = entries; if (!entry) { return; } const { intersectionRatio } = entry; notification = toggleToc(intersectionRatio === 0, notification); }; const intersectionObserver = new IntersectionObserver(observerCallback); intersectionObserver.observe(originToc); const openerListener = (event) => { event.preventDefault(); if (!(0, import_ext_gadget2.checkA11yConfirmKey)(event)) { return; } notification = toggleToc("open"); }; $floatTocOpener.on("click", openerListener); $floatTocOpener.on("keydown", openerListener); const collapseOriginToc = () => { if (skin !== "citizen") { return; } const isCollapse = config.originTOC === "close"; const $originTocTitle = $body.find("#toc .toctitle"); const $originTocItem = $body.find("#toc ul"); const $toggler = generateTogglerElement(isCollapse); $originTocTitle.append($toggler); const collapseToggle = () => { const $element = $originTocTitle.find(".oo-ui-indicatorElement-indicator"); $element.toggleClass("collapse"); if (isCollapse) { $element.attr("title", getMessage("Expand")); } else { $element.attr("title", getMessage("Collapse")); } }; $originTocTitle.on("click", () => { storeState("originTOC", isCollapse ? "open" : "close"); collapseToggle(); $originTocItem.fadeToggle(); }); if (isCollapse) { $originTocItem.fadeOut(); } }; collapseOriginToc(); }; //! src/FloatTOC/FloatTOC.ts var import_ext_gadget4 = require("ext.gadget.Util"); void (0, import_ext_gadget4.getBody)().then(($body) => { const $originToc = $body.find("#toc"); if (!$originToc.length) { return; } floatTOC($originToc); }); })();
该页面使用的模板:
Template:Gadget Header
(
查看源代码
)
返回
MediaWiki:Gadget-FloatTOC.js
。
开关有限宽度模式