A岛引用查看增强

让A岛网页端的引用支持嵌套查看、固定、折叠等功能

目前為 2021-03-23 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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
// ==/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();

}());