// ==UserScript== // @name A岛引用查看增强 // @description 让A岛网页端的引用支持嵌套查看、固定、折叠等功能 // @namespace http://tampermonkey.net/ // @include /^https?://(adnmb\d*.com|tnmb.org)/.*$/ // @homepageURL https://github.com/FToovvr/adnmb-reference-enhancement.user.js // @author FToovvr // @license MIT; https://opensource.org/licenses/MIT // @version 0.2.0 // @grant none // @downloadURL none // ==/UserScript== (function () { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } var ViewHelper = /** @class */ (function () { function ViewHelper() { } ViewHelper.getPosterID = function (elem) { if (!elem.classList.contains('.h-threads-info-uid')) { elem = elem.querySelector('.h-threads-info-uid'); } var uid = elem.textContent; return /^ID:(.*)$/.exec(uid)[1]; }; ViewHelper.getThreadID = function (elem) { if (!elem.classList.contains('.h-threads-info-id')) { elem = elem.querySelector('.h-threads-info-id'); } var link = elem.getAttribute('href'); var id = /^.*\/t\/(\d*).*$/.exec(link)[1]; if (!id.length) { return null; } return Number(id); }; ViewHelper.getPostID = function (elem) { if (!elem.classList.contains('.h-threads-info-id')) { elem = elem.querySelector('.h-threads-info-id'); } return Number(/^No.(\d+)$/.exec(elem.textContent)[1]); }; ViewHelper.hasFetchingRefSucceeded = function (elem) { return !elem.parentElement.querySelector('.fto-ref-view-error'); }; ViewHelper.getRefViewByViewId = function (viewId) { return document.querySelector(".fto-ref-view[data-view-id=\"" + viewId + "\"]"); }; ViewHelper.getRefLinkByViewId = function (viewId) { return document.querySelector(".fto-ref-link[data-view-id=\"" + viewId + "\"]"); }; return ViewHelper; }()); // TODO: 配置决定 // 折叠时保持的高度,低于此高度将不可折叠 var collapsedHeight = 80; // 悬浮时引用内容的不透明度 var floatingOpacity = '100%'; // '90%'; // 悬浮淡入的时长(暂不支持淡出) var fadingDuration = 0; // '80ms'; // 获取引用内容多少毫秒算超时 var refFetchingTimeout = 10000; // = 10 秒 // 在内容成功加载后是否还显示刷新按钮 var showRefreshButtonEvenIfRefContentLoaded = false; var Model = /** @class */ (function () { function Model() { this.refCache = {}; this.refsInFetching = new Set(); this.refSubscriptions = new Map(); } Object.defineProperty(Model.prototype, "isSupported", { get: function () { if (!window.indexedDB || !window.fetch) { return false; } return true; }, enumerable: false, configurable: true }); Model.prototype.getRefCache = function (refId) { return __awaiter(this, void 0, void 0, function () { var elem; return __generator(this, function (_a) { elem = this.refCache[refId]; if (!elem) { return [2 /*return*/, null]; } return [2 /*return*/, elem.cloneNode(true)]; }); }); }; Model.prototype.recordRef = function (refId, rawItem, scope) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { this.refCache[refId] = rawItem.cloneNode(true); return [2 /*return*/]; }); }); }; Model.prototype.subscribeForLoadingItemElement = function (controller, refId, viewId, ignoresCache) { if (ignoresCache === void 0) { ignoresCache = false; } return __awaiter(this, void 0, void 0, function () { var itemCache, _a, item, item_1; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!this.refSubscriptions.has(refId)) { this.refSubscriptions.set(refId, new Set()); } this.refSubscriptions.get(refId).add(viewId); if (!ignoresCache) return [3 /*break*/, 1]; _a = null; return [3 /*break*/, 3]; case 1: return [4 /*yield*/, this.getRefCache(refId)]; case 2: _a = _b.sent(); _b.label = 3; case 3: itemCache = _a; if (!itemCache) return [3 /*break*/, 4]; item = this.processItemElement(itemCache, refId); controller.updateViewContent(viewId, item); return [3 /*break*/, 6]; case 4: if (!!this.refsInFetching.has(refId)) return [3 /*break*/, 6]; this.refsInFetching.add(refId); return [4 /*yield*/, this.fetchItemElement(controller, refId, viewId)]; case 5: item_1 = _b.sent(); item_1 = this.processItemElement(item_1, refId); this.refSubscriptions.get(refId).forEach(function (subscriptedViewId) { controller.updateViewContent(subscriptedViewId, item_1.cloneNode(true)); }); this.refsInFetching["delete"](refId); _b.label = 6; case 6: return [2 /*return*/]; } }); }); }; Model.prototype.fetchItemElement = function (controller, refId, viewId) { return __awaiter(this, void 0, void 0, function () { var itemContainer, abortController, resp, _a, e_1, message, errorSpan, item; var _this = this; return __generator(this, function (_b) { switch (_b.label) { case 0: itemContainer = document.createElement('div'); abortController = new AbortController(); _b.label = 1; case 1: _b.trys.push([1, 4, , 5]); return [4 /*yield*/, Promise.race([ fetch("/Home/Forum/ref?id=" + refId, { signal: abortController.signal }), new Promise(function (_, reject) { var spentMs = 0; var intervalId = setInterval(function () { spentMs += 20; if (!controller.isLoading(viewId)) { clearInterval(intervalId); } else if (spentMs >= refFetchingTimeout) { reject(new Error('Timeout')); abortController.abort(); clearInterval(intervalId); } else { _this.refSubscriptions.get(refId).forEach(function (viewIdToReport) { controller.reportSpentTime(viewIdToReport, spentMs); }); } }, 20); }), ])]; case 2: resp = _b.sent(); _a = itemContainer; return [4 /*yield*/, resp.text()]; case 3: _a.innerHTML = _b.sent(); return [3 /*break*/, 5]; case 4: e_1 = _b.sent(); message = void 0; if (e_1 instanceof Error) { if (e_1.message === 'Timeout') { message = "\u83B7\u53D6\u5F15\u7528\u5185\u5BB9\u8D85\u65F6\uFF01"; } else { message = "\u83B7\u53D6\u5F15\u7528\u5185\u5BB9\u5931\u8D25\uFF1A" + e_1.toString(); } } else { message = "\u83B7\u53D6\u5F15\u7528\u5185\u5BB9\u5931\u8D25\uFF1A" + String(e_1); } errorSpan = document.createElement('span'); errorSpan.classList.add('fto-ref-view-error'); errorSpan.textContent = message; return [2 /*return*/, errorSpan]; case 5: item = itemContainer.firstElementChild; this.recordRef(refId, item, 'global'); return [2 /*return*/, item]; } }); }); }; Model.prototype.processItemElement = function (item, refId) { if (item.querySelector('.fto-ref-view-error')) { return item; } if (!ViewHelper.getThreadID(item)) { var errorSpan = document.createElement('span'); errorSpan.classList.add('fto-ref-view-error'); errorSpan.textContent = "\u5F15\u7528\u5185\u5BB9\u4E0D\u5B58\u5728\uFF01"; this.recordRef(refId, item, 'page'); return errorSpan; } return item; }; return Model; }()); var Utils = /** @class */ (function () { function Utils() { } // https://stackoverflow.com/a/59837035 Utils.generateViewID = function () { Utils.currentGeneratedViewID += 1; return String(Utils.currentGeneratedViewID); }; Utils.insertAfter = function (node, newNode) { node.parentNode.insertBefore(newNode, node.nextSibling); }; // https://stackoverflow.com/a/26230989 Utils.getCoords = function (elem) { var box = elem.getBoundingClientRect(); var body = document.body; var docEl = document.documentElement; var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; var clientTop = docEl.clientTop || body.clientTop || 0; var clientLeft = docEl.clientLeft || body.clientLeft || 0; var top = box.top + scrollTop - clientTop; var left = box.left + scrollLeft - clientLeft; return { top: Math.round(top), left: Math.round(left) }; }; Utils.currentGeneratedViewID = 0; return Utils; }()); var additionalStyleText = ".h-threads-content {\n word-break: break-word;\n}\n\n.fto-ref-view {\n /* 照搬自 h.desktop.css '#h-ref-view .h-threads-item-ref' */\n background: #f0e0d6;\n border: 1px solid #000;\n clear: left;\n position: relative;\n width: max-content;\n max-width: calc(100vw - var(--offset-left) - 35px);\n margin-left: -5px;\n margin-right: -40px;\n}\n\n.h-threads-item-ref .h-threads-content {\n margin: 5px 20px;\n}\n\n/* 修复 h.desktop.css 里 '.h-threads-item .h-threads-content' 这条选择器导致的问题 */\n\n.h-threads-info {\n font-size: 14px;\n line-height: 20px;\n margin: 0px;\n}\n\n.fto-ref-view[data-status=\"closed\"] {\n /* display: none; */\n opacity: 0;\n display: inline-block;\n width: 0;\n height: 0;\n overflow: hidden;\n padding: 0;\n border: 0;\n margin: 0;\n /* transition: opacity ${fadingDuration} ease-out; */\n}\n\n.fto-ref-view[data-status=\"floating\"] {\n position: absolute;\n z-index: 999;\n}\n\n.fto-ref-view[data-status=\"open\"] {\n display: block;\n}\n\n.fto-ref-view[data-status=\"open\"]+br {\n display: none;\n}\n\n.fto-ref-view[data-status=\"collapsed\"] {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.fto-ref-view[data-status=\"collapsed\"]+br {\n display: none;\n}\n\n/* https://stackoverflow.com/a/22809380 */\n\n.fto-ref-view[data-status=\"collapsed\"]:before {\n content: '';\n position: absolute;\n top: 60px;\n height: 20px;\n width: 100%;\n background: linear-gradient(#f0e0d600, #ffeeddcc);\n z-index: 999;\n}\n\n.fto-ref-view-button {\n position: relative;\n font-size: smaller;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.fto-ref-view-pin {\n display: inline-block;\n transform: rotate(-45deg);\n}\n\n/* https://codemyui.com/grayscale-emoji-using-css/ */\n\n.fto-ref-view[data-status=\"floating\"]>.h-threads-item>.h-threads-item-ref>.h-threads-item-reply-main>.h-threads-info>.fto-ref-view-button-list>.fto-ref-view-pin, .fto-ref-view[data-status=\"floating\"]>.fto-ref-view-error>.fto-ref-view-button-list>.fto-ref-view-pin, .fto-ref-view[data-status=\"floating\"]>.fto-ref-view-loading>.fto-ref-view-button-list>.fto-ref-view-pin {\n transform: none;\n filter: grayscale(100%);\n}\n\n.fto-ref-view[data-status=\"collapsed\"]>.h-threads-item>.h-threads-item-ref>.h-threads-item-reply-main>.h-threads-info>.fto-ref-view-button-list>.fto-ref-view-pin:before, .fto-ref-view[data-status=\"collapsed\"]>.fto-ref-view-error>.fto-ref-view-button-list>.fto-ref-view-pin:before, .fto-ref-view[data-status=\"collapsed\"]>.fto-ref-view-loading>.fto-ref-view-button-list>.fto-ref-view-pin:before {\n content: '';\n position: absolute;\n height: 110%;\n width: 100%;\n background: linear-gradient(#f0e0d600, #f0e0d6ff);\n z-index: 999;\n transform: rotate(45deg);\n}\n\n.fto-ref-view-error {\n color: red;\n}"; var additioanVariableStyleText = "\n .fto-ref-view[data-status=\"floating\"] {\n opacity: " + floatingOpacity + ";\n transition: opacity " + fadingDuration + " ease-in;\n }\n\n .fto-ref-view[data-status=\"collapsed\"] {\n max-height: " + collapsedHeight + "px;\n }\n "; var Controller = /** @class */ (function () { function Controller(model) { this.model = model; } Controller.setupStyle = function () { for (var _i = 0, _a = [ [additionalStyleText, 'fto-style-additional-fixed'], [additioanVariableStyleText, 'fto-style-additional-variable'], ]; _i < _a.length; _i++) { var _b = _a[_i], styleText = _b[0], id = _b[1]; var style = document.createElement('style'); style.id = id; style.classList.add('fto-style'); // TODO: fade out style.append(styleText); document.head.append(style); } }; Controller.prototype.setupContent = function (root) { var _this = this; if (root === document.body) { root.querySelectorAll('.h-threads-item').forEach(function (threadItemElem) { _this.setupThreadContent(threadItemElem); }); } else if (ViewHelper.hasFetchingRefSucceeded(root)) { var repliesElem = root.closest('.h-threads-item-replys'); var threadElem = void 0; if (repliesElem) { // 在串的回应中 threadElem = repliesElem.closest('.h-threads-item'); } else { // 在串首中 threadElem = root.closest('.h-threads-item-main'); } var threadID = ViewHelper.getThreadID(threadElem); var po = ViewHelper.getPosterID(threadElem); this.setupRefContent(root, threadID, po); } else { this.setupErrorRefContent(root); return; } root.querySelectorAll('font[color="#789922"]').forEach(function (linkElem) { if (!linkElem.textContent.startsWith('>>')) { return; } _this.setupRefLink(linkElem); }); }; Controller.prototype.setupThreadContent = function (threadItemElem) { var _this = this; var threadID = ViewHelper.getThreadID(threadItemElem); { // 将串首加入缓存 var originalItemMainElem = threadItemElem.querySelector('.h-threads-item-main'); var itemDiv = document.createElement('div'); itemDiv.classList.add('h-threads-item'); var itemRefDiv = document.createElement('div'); itemRefDiv.classList.add('h-threads-item-reply', 'h-threads-item-ref'); itemDiv.append(itemRefDiv); var itemMainDiv = originalItemMainElem.cloneNode(true); itemMainDiv.className = ''; itemMainDiv.classList.add('h-threads-item-reply-main'); itemRefDiv.append(itemMainDiv); var infoDiv = itemMainDiv.querySelector('.h-threads-info'); try { // 尝试修正几个按钮的位置。以后如果A岛自己修正了这里就会抛异常 var messedUpDiv = infoDiv.querySelector('.h-admin-tool').closest('.h-threads-info-report-btn'); if (!messedUpDiv) { // 版块页面里的各个按钮没搞砸 infoDiv.querySelectorAll('.h-threads-info-report-btn a').forEach(function (aElem) { if (aElem.textContent !== "举报") { aElem.closest('.h-threads-info-report-btn').remove(); } }); infoDiv.querySelector('.h-threads-info-reply-btn').remove(); } else { // 串内容页面的各个按钮搞砸了 infoDiv.append('', messedUpDiv.querySelector('.h-threads-info-id'), '', messedUpDiv.querySelector('.h-admin-tool')); messedUpDiv.remove(); } } catch (e) { console.log(e); } this.model.recordRef(threadID, itemDiv, 'global'); } // 将各回应加入缓存 threadItemElem.querySelectorAll('.h-threads-item-replys .h-threads-item-reply').forEach(function (originalItemElem) { var div = document.createElement('div'); div.classList.add('h-threads-item'); var itemElem = originalItemElem.cloneNode(true); itemElem.classList.add('h-threads-item-ref'); itemElem.querySelector('.h-threads-item-reply-icon').remove(); for (var _i = 0, _a = itemElem.querySelector('.h-threads-item-reply-main').children; _i < _a.length; _i++) { var child = _a[_i]; if (!child.classList.contains('h-threads-info') && !child.classList.contains('h-threads-content')) { child.remove(); } } itemElem.querySelectorAll('.uk-text-primary').forEach(function (labelElem) { if (labelElem.textContent === "(PO主)") { labelElem.remove(); } }); div.append(itemElem); _this.model.recordRef(ViewHelper.getPostID(itemElem), div, 'global'); }); }; Controller.prototype.setupRefContent = function (elem, threadID, po) { var infoElem = elem.querySelector('.h-threads-info'); // 补标 PO if (ViewHelper.getPosterID(infoElem) === po) { var poLabel = document.createElement('span'); poLabel.textContent = "(PO主)"; poLabel.classList.add('uk-text-primary', 'uk-text-small', 'fto-po-label'); var uidElem = infoElem.querySelector('.h-threads-info-uid'); Utils.insertAfter(uidElem, poLabel); Utils.insertAfter(uidElem, document.createTextNode(' ')); } // 标「外串」 if (ViewHelper.getThreadID(infoElem) !== threadID) { var outerThreadLabel = document.createElement('span'); outerThreadLabel.textContent = "(外串)"; outerThreadLabel.classList.add('uk-text-secondary', 'uk-text-small', 'fto-outer-thread-label'); var idElem = infoElem.querySelector('.h-threads-info-id'); idElem.append(' ', outerThreadLabel); } this.setupButtons(infoElem); }; Controller.prototype.setupErrorRefContent = function (elem) { this.setupButtons(elem); }; Controller.prototype.setupButtons = function (elem) { var _this = this; var viewDiv = elem.closest('.fto-ref-view'); var linkElem = ViewHelper.getRefLinkByViewId(viewDiv.dataset.viewId); var buttonListSpan = document.createElement('span'); buttonListSpan.classList.add('fto-ref-view-button-list'); // 图钉📌按钮 var pinSpan = document.createElement('span'); pinSpan.classList.add('fto-ref-view-pin', 'fto-ref-view-button'); pinSpan.textContent = "📌"; pinSpan.addEventListener('click', function () { if (viewDiv.dataset.status === 'floating') { linkElem.dataset.status = 'open'; viewDiv.dataset.status = 'open'; } else { linkElem.dataset.status = 'closed'; viewDiv.dataset.status = 'floating'; } }); buttonListSpan.append(pinSpan); if (!viewDiv.dataset.isLoading && (!ViewHelper.hasFetchingRefSucceeded(elem) || showRefreshButtonEvenIfRefContentLoaded)) { // 刷新🔄按钮 var refreshSpan = document.createElement('span'); refreshSpan.classList.add('fto-ref-view-refresh', 'fto-ref-view-button'); refreshSpan.textContent = "🔄"; refreshSpan.addEventListener('click', function () { _this.startLoadingViewContent(viewDiv, Number(linkElem.dataset.refId), true); }); Utils.insertAfter(pinSpan, refreshSpan); buttonListSpan.append(refreshSpan); } elem.prepend(buttonListSpan); }; Controller.prototype.setupRefLink = function (linkElem) { var _this = this; linkElem.classList.add('fto-ref-link'); // closed: 无固定显示 view; open: 有固定显示 view linkElem.dataset.status = 'closed'; var r = /^>>No.(\d+)$/.exec(linkElem.textContent); if (!r) { return; } var refId = Number(r[1]); linkElem.dataset.refId = String(refId); var viewId = Utils.generateViewID(); linkElem.dataset.viewId = viewId; var viewDiv = document.createElement('div'); viewDiv.classList.add('fto-ref-view'); // closed: 不显示; floating: 悬浮显示; open: 完整固定显示; collapsed: 折叠固定显示 viewDiv.dataset.status = 'closed'; viewDiv.dataset.viewId = viewId; viewDiv.style.setProperty('--offset-left', Utils.getCoords(linkElem).left + "px"); Utils.insertAfter(linkElem, viewDiv); // 处理悬浮 linkElem.addEventListener('mouseenter', function () { if (viewDiv.dataset.status !== 'closed') { viewDiv.dataset.isHovering = '1'; return; } viewDiv.dataset.status = 'floating'; viewDiv.dataset.isHovering = '1'; _this.startLoadingViewContent(viewDiv, refId); }); viewDiv.addEventListener('mouseenter', function () { viewDiv.dataset.isHovering = '1'; }); for (var _i = 0, _a = [linkElem, viewDiv]; _i < _a.length; _i++) { var elem = _a[_i]; elem.addEventListener('mouseleave', function () { if (viewDiv.dataset.status !== 'floating') { return; } delete viewDiv.dataset.isHovering; (function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { setTimeout(function () { if (!viewDiv.dataset.isHovering) { viewDiv.dataset.status = 'closed'; } }, 200); return [2 /*return*/]; }); }); })(); }); } // 处理折叠 linkElem.addEventListener('click', function () { if (linkElem.dataset.status === 'closed' || ['collapsed', 'floating'].includes(viewDiv.dataset.status)) { linkElem.dataset.status = 'open'; viewDiv.dataset.status = 'open'; } else if (viewDiv.clientHeight > collapsedHeight) { viewDiv.dataset.status = 'collapsed'; } }); viewDiv.addEventListener('click', function () { if (viewDiv.dataset.status === 'collapsed') { viewDiv.dataset.status = 'open'; } }); }; Controller.prototype.startLoadingViewContent = function (viewDiv, refId, forced) { if (forced === void 0) { forced = false; } if (!forced && viewDiv.hasChildNodes()) { return; } else if (viewDiv.dataset.isLoading) { // TODO: 也可以强制从头重新加载? return; } this.setupLoading(viewDiv); this.model.subscribeForLoadingItemElement(this, refId, viewDiv.dataset.viewId, forced); }; Controller.prototype.setupLoading = function (viewDiv) { viewDiv.dataset.isLoading = '1'; var loadingSpan = document.createElement('span'); loadingSpan.classList.add('fto-ref-view-loading'); var loadingTextSpan = document.createElement('span'); loadingTextSpan.classList.add('fto-ref-view-loading-text'); loadingTextSpan.dataset.waitedMilliseconds = '0'; loadingTextSpan.textContent = "加载中…"; loadingSpan.append(loadingTextSpan); viewDiv.innerHTML = ''; viewDiv.append(loadingSpan); this.setupButtons(loadingSpan); }; Controller.prototype.isLoading = function (viewId) { return !!ViewHelper.getRefViewByViewId(viewId).dataset.isLoading; }; Controller.prototype.reportSpentTime = function (viewId, spentMs) { var viewDiv = ViewHelper.getRefViewByViewId(viewId); if (!this.isLoading(viewId)) { this.setupLoading(viewDiv); } viewDiv.querySelector('.fto-ref-view-loading-text') .textContent = "\u52A0\u8F7D\u4E2D\u2026 " + (spentMs / 1000.0).toFixed(2) + "s"; }; Controller.prototype.updateViewContent = function (viewId, itemElement) { var viewDiv = ViewHelper.getRefViewByViewId(viewId); delete viewDiv.dataset.isLoading; viewDiv.innerHTML = ''; viewDiv.append(itemElement); this.setupContent(itemElement); }; return Controller; }()); function entry() { if (window.disableAdnmbReferenceViewerEnhancementUserScript) { console.log("「A岛引用查看增强」用户脚本被禁用(设有变量 `window.disableAdnmbReferenceViewerEnhancementUserScript`),将终止。"); return; } var model = new Model(); if (!model.isSupported) { console.log("浏览器功能不支持「A岛引用查看增强」用户脚本,将终止。"); return; } // 销掉原先的预览方法 document.querySelectorAll('font[color="#789922"]').forEach(function (elem) { if (elem.textContent.startsWith('>>')) { var newElem = elem.cloneNode(true); elem.parentElement.replaceChild(newElem, elem); } }); Controller.setupStyle(); var controller = new Controller(model); controller.setupContent(document.body); } entry(); }());