Greasy Fork

A岛引用查看增强

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

目前为 2021-03-23 提交的版本。查看 最新版本

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

}());