// ==UserScript== // @name yudao_doc_unlock // @namespace https://doc.iocoder.cn/ // @version 2.0.0 // @description 解除 doc.iocoder.cn 文档的 VIP 遮罩与会员弹窗(适配 2026-10 新版 doc-access 机制) // @author mensorigos // @match *://doc.iocoder.cn/* // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function () { 'use strict'; var BLOCKED_STATES = ['guest', 'pending', 'error']; var OVERLAY_TAGS = ['doc-access-layer', 'doc-access-pending', 'doc-access-status']; /* ── A. 样式覆盖 ─────────────────────────────────────────────── */ var CSS = /* 选择器权重 (0,1,2) 高于站点的 (0,1,1),样式表先后顺序不影响结果 */ 'html[data-doc-access] body .content-wrapper,\n' + 'html[data-doc-access] body .content-wrapper .theme-vdoing-content,\n' + 'html[data-doc-access] body .content-wrapper .content__default {\n' + ' visibility: visible !important;\n' + '}\n' + /* 兜底:即便元素被插进来也不参与布局 */ 'doc-access-layer, doc-access-pending, doc-access-status,\n' + '#doc-access-error { display: none !important; }\n'; function injectCss() { var host = document.head || document.documentElement; if (!host) return; var old = document.getElementById('yd-unlock-style'); if (old && old.parentNode === host) return; if (old && old.parentNode) old.parentNode.removeChild(old); var style = document.createElement('style'); style.id = 'yd-unlock-style'; style.textContent = CSS; host.appendChild(style); } /* ── B. 状态位归零 ───────────────────────────────────────────── */ function normalizeAccess() { var root = document.documentElement; if (!root) return; var state = root.getAttribute('data-doc-access'); if (state && BLOCKED_STATES.indexOf(state) !== -1) { root.setAttribute('data-doc-access', 'public'); } } /* ── C. 遮罩元素拦截 ─────────────────────────────────────────── */ function isOverlay(node) { return !!node && node.nodeType === 1 && typeof node.tagName === 'string' && OVERLAY_TAGS.indexOf(node.tagName.toLowerCase()) !== -1; } function releaseScroll() { if (document.body && document.body.style.overflow === 'hidden') { document.body.style.overflow = ''; } } function dropOverlay(node) { if (node && node.parentNode) node.parentNode.removeChild(node); releaseScroll(); } var rawAppendChild = Node.prototype.appendChild; Node.prototype.appendChild = function (node) { if (isOverlay(node)) { /* 站点在 append 之后才会锁 body 滚动,延后一个宏任务再解除 */ setTimeout(releaseScroll, 0); return node; } return rawAppendChild.call(this, node); }; var rawInsertBefore = Node.prototype.insertBefore; Node.prototype.insertBefore = function (node, ref) { if (isOverlay(node)) { setTimeout(releaseScroll, 0); return node; } return rawInsertBefore.call(this, node, ref); }; /* 兜底:脚本注入前已存在的节点 */ function sweepAll() { var root = document.body || document.documentElement; if (!root || !root.querySelectorAll) return; var list = root.querySelectorAll(OVERLAY_TAGS.join(',')); for (var i = list.length - 1; i >= 0; i--) dropOverlay(list[i]); } /* ── D. 监听器 ───────────────────────────────────────────────── */ function boot() { injectCss(); normalizeAccess(); sweepAll(); } if (document.documentElement) { new MutationObserver(function (records) { /* 出现后把样式表迁进去;被站点清掉时自动补回 */ injectCss(); for (var i = 0; i < records.length; i++) { var r = records[i]; if (r.type === 'attributes') { normalizeAccess(); continue; } /* 只判定新增节点本身,避免在热路径上做整棵子树查询 */ for (var j = 0; j < r.addedNodes.length; j++) { if (isOverlay(r.addedNodes[j])) dropOverlay(r.addedNodes[j]); } } }).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-doc-access'] }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot, { once: true }); } else { boot(); } /* 路由切换会重新触发校验,首屏后再补一次清理 */ window.addEventListener('load', function () { setTimeout(boot, 300); }); })();