// ==UserScript==
// @name 简书优化
// @namespace https://github.com/WhiteSevs/TamperMonkeyScript
// @version 2025.8.21
// @author WhiteSevs
// @description 支持手机端和PC端、屏蔽广告、优化浏览体验、重定向链接、全文居中、自动展开全文、允许复制文字、劫持唤醒/跳转App、自定义屏蔽元素等
// @license GPL-3.0-only
// @icon data:image/png;base64,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
// @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues
// @match *://*.jianshu.com/*
// @match *://*.jianshu.io/*
// @require https://fastly.jsdelivr.net/gh/WhiteSevs/TamperMonkeyScript@86be74b83fca4fa47521cded28377b35e1d7d2ac/lib/CoverUMD/index.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/[email protected]/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/[email protected]/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/@whitesev/[email protected]/dist/index.umd.js
// @require https://fastly.jsdelivr.net/npm/[email protected]/dist/index.umd.js
// @connect *
// @grant GM_deleteValue
// @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_info
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_unregisterMenuCommand
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(function (Qmsg, DOMUtils, Utils, pops) {
'use strict';
const blockCSS = `.download-app-guidance,\r
.call-app-btn,\r
.collapse-tips,\r
.note-graceful-button,\r
.app-open,\r
.header-wrap,\r
.recommend-wrap.recommend-ad,\r
.call-app-Ad-bottom,\r
#recommended-notes p.top-title span.more,\r
#homepage .modal,\r
button.index_call-app-btn,\r
span.note__flow__download,\r
.download-guide,\r
#footer,\r
.comment-open-app-btn-wrap,\r
.nav.navbar-nav + div,\r
.self-flow-ad,\r
#free-reward-panel,\r
div[id*='AdFive'],\r
#index-aside-download-qrbox,\r
.baidu-app-download-2eIkf_1,\r
/* 底部的"小礼物走一走,来简书关注我"、赞赏支持和更多精彩内容,就在简书APP */\r
div[role="main"] > div > section:first-child > div:nth-last-child(2),\r
/* 它的内部是script标签,可能影响部分评论之间的高度问题 */\r
div.adad_container ,\r
/* 顶部导航栏的【下载App】 */\r
#__next nav a[href*="navbar-app"] {\r
display: none !important;\r
}\r
body.reader-day-mode.normal-size {\r
overflow: auto !important;\r
}\r
.collapse-free-content {\r
height: auto !important;\r
}\r
.copyright {\r
color: #000 !important;\r
}\r
#note-show .content .show-content-free .collapse-free-content:after {\r
background-image: none !important;\r
}\r
footer > div > div {\r
justify-content: center;\r
}\r
/* 修复底部最后编辑于:。。。在某些套壳浏览器上的错位问题 */\r
#note-show .content .show-content-free .note-meta-time {\r
margin-top: 0px !important;\r
}\r
`;
var _GM_deleteValue = /* @__PURE__ */ (() => typeof GM_deleteValue != "undefined" ? GM_deleteValue : void 0)();
var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : void 0)();
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)();
var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
var _monkeyWindow = /* @__PURE__ */ (() => window)();
const KEY = "GM_Panel";
const ATTRIBUTE_INIT = "data-init";
const ATTRIBUTE_KEY = "data-key";
const ATTRIBUTE_DEFAULT_VALUE = "data-default-value";
const ATTRIBUTE_INIT_MORE_VALUE = "data-init-more-value";
const PROPS_STORAGE_API = "data-storage-api";
const PanelUISize = {
/**
* 一般设置界面的尺寸
*/
setting: {
get width() {
if (window.innerWidth < 550) {
return "88vw";
} else if (window.innerWidth < 700) {
return "550px";
} else {
return "700px";
}
},
get height() {
if (window.innerHeight < 450) {
return "70vh";
} else if (window.innerHeight < 550) {
return "450px";
} else {
return "550px";
}
}
},
/**
* 中等的设置界面
*/
settingMiddle: {
get width() {
return window.innerWidth < 350 ? "88vw" : "350px";
}
}
};
class StorageUtils {
/** 存储的键名 */
storageKey;
listenerData;
/**
* 存储的键名,可以是多层的,如:a.b.c
*
* 那就是
* {
* "a": {
* "b": {
* "c": {
* ...你的数据
* }
* }
* }
* }
* @param key
*/
constructor(key) {
if (typeof key === "string") {
let trimKey = key.trim();
if (trimKey == "") {
throw new Error("key参数不能为空字符串");
}
this.storageKey = trimKey;
} else {
throw new Error("key参数类型错误,必须是字符串");
}
this.listenerData = new Utils.Dictionary();
}
/**
* 获取本地值
*/
getLocalValue() {
let localValue = _GM_getValue(this.storageKey);
if (localValue == null) {
localValue = {};
this.setLocalValue(localValue);
}
return localValue;
}
/**
* 设置本地值
* @param value
*/
setLocalValue(value) {
_GM_setValue(this.storageKey, value);
}
/**
* 设置值
* @param key 键
* @param value 值
*/
set(key, value) {
let oldValue = this.get(key);
let localValue = this.getLocalValue();
Reflect.set(localValue, key, value);
this.setLocalValue(localValue);
this.triggerValueChangeListener(key, oldValue, value);
}
/**
* 获取值
* @param key 键
* @param defaultValue 默认值
*/
get(key, defaultValue) {
let localValue = this.getLocalValue();
return Reflect.get(localValue, key) ?? defaultValue;
}
/**
* 获取所有值
*/
getAll() {
let localValue = this.getLocalValue();
return localValue;
}
/**
* 删除值
* @param key 键
*/
delete(key) {
let oldValue = this.get(key);
let localValue = this.getLocalValue();
Reflect.deleteProperty(localValue, key);
this.setLocalValue(localValue);
this.triggerValueChangeListener(key, oldValue, void 0);
}
/**
* 判断是否存在该值
*/
has(key) {
let localValue = this.getLocalValue();
return Reflect.has(localValue, key);
}
/**
* 获取所有键
*/
keys() {
let localValue = this.getLocalValue();
return Reflect.ownKeys(localValue);
}
/**
* 获取所有值
*/
values() {
let localValue = this.getLocalValue();
return Reflect.ownKeys(localValue).map(
(key) => Reflect.get(localValue, key)
);
}
/**
* 清空所有值
*/
clear() {
_GM_deleteValue(this.storageKey);
}
/**
* 监听值改变
* + .set
* + .delete
* @param key 监听的键
* @param callback 值改变的回调函数
*/
addValueChangeListener(key, callback) {
let listenerId = Math.random();
let listenerData = this.listenerData.get(key) || [];
listenerData.push({
id: listenerId,
key,
callback
});
this.listenerData.set(key, listenerData);
return listenerId;
}
/**
* 移除监听
* @param listenerId 监听的id或键名
*/
removeValueChangeListener(listenerId) {
let flag = false;
for (const [key, listenerData] of this.listenerData.entries()) {
for (let index = 0; index < listenerData.length; index++) {
const value = listenerData[index];
if (typeof listenerId === "string" && value.key === listenerId || typeof listenerId === "number" && value.id === listenerId) {
listenerData.splice(index, 1);
index--;
flag = true;
}
}
this.listenerData.set(key, listenerData);
}
return flag;
}
/**
* 主动触发监听器
* @param key 键
* @param oldValue (可选)旧值
* @param newValue (可选)新值
*/
triggerValueChangeListener(key, oldValue, newValue) {
if (!this.listenerData.has(key)) {
return;
}
let listenerData = this.listenerData.get(key);
for (let index = 0; index < listenerData.length; index++) {
const data = listenerData[index];
if (typeof data.callback === "function") {
let value = this.get(key);
let __newValue;
let __oldValue;
if (typeof oldValue !== "undefined" && arguments.length >= 2) {
__oldValue = oldValue;
} else {
__oldValue = value;
}
if (typeof newValue !== "undefined" && arguments.length > 2) {
__newValue = newValue;
} else {
__newValue = value;
}
data.callback(key, __oldValue, __newValue);
}
}
}
}
const PopsPanelStorageApi = new StorageUtils(KEY);
const PanelContent = {
$data: {
/**
* @private
*/
__contentConfig: null,
get contentConfig() {
if (this.__contentConfig == null) {
this.__contentConfig = new utils.Dictionary();
}
return this.__contentConfig;
}
},
/**
* 设置所有配置项,用于初始化默认的值
*
* 如果是第一组添加的话,那么它默认就是设置菜单打开的配置
* @param configList 配置项
*/
addContentConfig(configList) {
if (!Array.isArray(configList)) {
configList = [configList];
}
let index = this.$data.contentConfig.keys().length;
this.$data.contentConfig.set(index, configList);
},
/**
* 获取所有的配置内容,用于初始化默认的值
*/
getAllContentConfig() {
return this.$data.contentConfig.values().flat();
},
/**
* 获取配置内容
* @param index 配置索引
*/
getConfig(index = 0) {
return this.$data.contentConfig.get(index) ?? [];
},
/**
* 获取默认左侧底部的配置项
*/
getDefaultBottomContentConfig() {
return [
{
id: "script-version",
title: `版本:${_GM_info?.script?.version || "未知"}`,
isBottom: true,
forms: [],
clickFirstCallback(event, rightHeaderElement, rightContainerElement) {
let supportURL = _GM_info?.script?.supportURL || _GM_info?.script?.namespace;
if (typeof supportURL === "string" && utils.isNotNull(supportURL)) {
window.open(supportURL, "_blank");
}
return false;
}
}
];
}
};
const PanelMenu = {
$data: {
__menuOption: [
{
key: "show_pops_panel_setting",
text: "⚙ 设置",
autoReload: false,
isStoreValue: false,
showText(text) {
return text;
},
callback: () => {
Panel.showPanel(PanelContent.getConfig(0));
}
}
],
get menuOption() {
return this.__menuOption;
}
},
init() {
this.initExtensionsMenu();
},
/**
* 初始化菜单项
*/
initExtensionsMenu() {
if (!Panel.isTopWindow()) {
return;
}
GM_Menu.add(this.$data.menuOption);
},
/**
* 添加菜单项
* @param option 菜单配置
*/
addMenuOption(option) {
if (!Array.isArray(option)) {
option = [option];
}
this.$data.menuOption.push(...option);
},
/**
* 更新菜单项
* @param option 菜单配置
*/
updateMenuOption(option) {
if (!Array.isArray(option)) {
option = [option];
}
option.forEach((optionItem) => {
let findIndex = this.$data.menuOption.findIndex((it) => {
return it.key === optionItem.key;
});
if (findIndex !== -1) {
this.$data.menuOption[findIndex] = optionItem;
}
});
},
/**
* 获取菜单项
* @param [index=0] 索引
*/
getMenuOption(index = 0) {
return this.$data.menuOption[index];
},
/**
* 删除菜单项
* @param [index=0] 索引
*/
deleteMenuOption(index = 0) {
this.$data.menuOption.splice(index, 1);
}
};
const CommonUtil = {
/**
* 移除元素(未出现也可以等待出现)
* @param selector 元素选择器
*/
waitRemove(...args) {
args.forEach((selector) => {
if (typeof selector !== "string") {
return;
}
utils.waitNodeList(selector).then((nodeList) => {
nodeList.forEach(($el) => $el.remove());
});
});
},
/**
* 添加屏蔽CSS
* @param args
* @example
* addBlockCSS("")
* addBlockCSS("","")
* addBlockCSS(["",""])
*/
addBlockCSS(...args) {
let selectorList = [];
if (args.length === 0) {
return;
}
if (args.length === 1 && typeof args[0] === "string" && args[0].trim() === "") {
return;
}
args.forEach((selector) => {
if (Array.isArray(selector)) {
selectorList = selectorList.concat(selector);
} else {
selectorList.push(selector);
}
});
return addStyle(`${selectorList.join(",\n")}{display: none !important;}`);
},
/**
* 设置GM_getResourceText的style内容
* @param resourceMapData 资源数据
* @example
* setGMResourceCSS({
* keyName: "ViewerCSS",
* url: "https://example.com/example.css",
* })
*/
setGMResourceCSS(resourceMapData) {
let cssText = typeof _GM_getResourceText === "function" ? _GM_getResourceText(resourceMapData.keyName) : null;
if (typeof cssText === "string" && cssText) {
addStyle(cssText);
} else {
CommonUtil.loadStyleLink(resourceMapData.url);
}
},
/**
* 添加<link>标签
* @param url
* @example
* loadStyleLink("https://example.com/example.css")
*/
async loadStyleLink(url) {
let $link = document.createElement("link");
$link.rel = "stylesheet";
$link.type = "text/css";
$link.href = url;
DOMUtils.ready(() => {
document.head.appendChild($link);
});
},
/**
* 添加<script>标签
* @param url
* @example
* loadStyleLink("https://example.com/example.js")
*/
async loadScript(url) {
let $script = document.createElement("script");
$script.src = url;
return new Promise((resolve) => {
$script.onload = () => {
resolve(null);
};
(document.head || document.documentElement).appendChild($script);
});
},
/**
* 将url修复,例如只有search的链接修复为完整的链接
*
* 注意:不包括http转https
* @param url 需要修复的链接
* @example
* 修复前:`/xxx/xxx?ss=ssss`
* 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
* @example
* 修复前:`//xxx/xxx?ss=ssss`
* 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
* @example
* 修复前:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
* 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
* @example
* 修复前:`xxx/xxx?ss=ssss`
* 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
*/
fixUrl(url) {
url = url.trim();
if (url.match(/^http(s|):\/\//i)) {
return url;
} else if (url.startsWith("//")) {
if (url.startsWith("///")) ;
else {
url = window.location.protocol + url;
}
return url;
} else {
if (!url.startsWith("/")) {
url += "/";
}
url = window.location.origin + url;
return url;
}
},
/**
* http转https
* @param url 需要修复的链接
* @example
* 修复前:
* 修复后:
* @example
* 修复前:
* 修复后:
*/
fixHttps(url) {
if (url.startsWith("https://")) {
return url;
}
if (!url.startsWith("http://")) {
return url;
}
let urlInstance = new URL(url);
urlInstance.protocol = "https:";
return urlInstance.toString();
},
/**
* 禁止页面滚动,默认锁定html和body
* @example
* lockScroll();
* @example
* lockScroll(document.body);
*/
lockScroll(...args) {
let $hidden = document.createElement("style");
$hidden.innerHTML = /*css*/
`
.pops-overflow-hidden-important {
overflow: hidden !important;
}
`;
let $elList = [document.documentElement, document.body].concat(...args || []);
$elList.forEach(($el) => {
$el.classList.add("pops-overflow-hidden-important");
});
(document.head || document.documentElement).appendChild($hidden);
return {
/**
* 解除锁定
*/
recovery() {
$elList.forEach(($el) => {
$el.classList.remove("pops-overflow-hidden-important");
});
$hidden.remove();
}
};
},
/**
* 获取剪贴板文本
*/
async getClipboardText() {
function readClipboardText(resolve) {
navigator.clipboard.readText().then((clipboardText) => {
resolve(clipboardText);
}).catch((error) => {
log.error("读取剪贴板内容失败👉", error);
resolve("");
});
}
function requestPermissionsWithClipboard(resolve) {
navigator.permissions.query({
// @ts-ignore
name: "clipboard-read"
}).then((permissionStatus) => {
readClipboardText(resolve);
}).catch((error) => {
log.error("申请剪贴板权限失败,尝试直接读取👉", error.message ?? error.name ?? error.stack);
readClipboardText(resolve);
});
}
function checkClipboardApi() {
if (typeof navigator?.clipboard?.readText !== "function") {
return false;
}
if (typeof navigator?.permissions?.query !== "function") {
return false;
}
return true;
}
return new Promise((resolve) => {
if (!checkClipboardApi()) {
resolve("");
return;
}
if (document.hasFocus()) {
requestPermissionsWithClipboard(resolve);
} else {
window.addEventListener(
"focus",
() => {
requestPermissionsWithClipboard(resolve);
},
{
once: true
}
);
}
});
},
/**
* html转义
* @param unsafe
*/
escapeHtml(unsafe) {
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/©/g, "©").replace(/®/g, "®").replace(/™/g, "™").replace(/→/g, "→").replace(/←/g, "←").replace(/↑/g, "↑").replace(/↓/g, "↓").replace(/—/g, "—").replace(/–/g, "–").replace(/…/g, "…").replace(/ /g, " ").replace(/\r\n/g, "<br>").replace(/\r/g, "<br>").replace(/\n/g, "<br>").replace(/\t/g, " ");
},
/**
* 在规定时间内循环,如果超时或返回false则取消循环
* @param fn 循环的函数
* @param intervalTime 循环间隔时间
* @param [timeout=5000] 循环超时时间
*/
interval(fn, intervalTime, timeout = 5e3) {
let timeId;
let maxTimeout = timeout - intervalTime;
let intervalTimeCount = intervalTime;
let loop = async (isTimeout) => {
let result = await fn(isTimeout);
if (typeof result === "boolean" && !result || isTimeout) {
utils.workerClearTimeout(timeId);
return;
}
intervalTimeCount += intervalTime;
if (intervalTimeCount > maxTimeout) {
loop(true);
return;
}
timeId = utils.workerSetTimeout(() => {
loop(false);
}, intervalTime);
};
loop(false);
},
/**
* 找到对应的上层元素
*/
findParentNode($el, selector, parentSelector) {
if (parentSelector) {
let $parent = DOMUtils.closest($el, parentSelector);
if ($parent) {
let $target = $parent.querySelector(selector);
return $target;
}
} else {
if (DOMUtils.matches($el, selector)) {
return $el;
}
let $parent = DOMUtils.closest($el, selector);
return $parent;
}
}
};
const Panel = {
/** 数据 */
$data: {
/**
* @private
*/
__contentConfigInitDefaultValue: null,
/**
* @private
*/
__onceExecMenuData: null,
/**
* @private
*/
__onceExecData: null,
/**
* @private
*/
__panelConfig: {},
/**
* 面板
*/
$panel: null,
/**
* 面板配置
*/
panelContent: [],
/**
* 菜单项初始化的默认值
*/
get contentConfigInitDefaultValue() {
if (this.__contentConfigInitDefaultValue == null) {
this.__contentConfigInitDefaultValue = new utils.Dictionary();
}
return this.__contentConfigInitDefaultValue;
},
/**
* 菜单项初始化时禁用的键
*/
contentConfigInitDisabledKeys: [],
/**
* 成功只执行了一次的菜单项
*
* + .exec
* + .execMenu
* + .execMenuOnce
*/
get onceExecMenuData() {
if (this.__onceExecMenuData == null) {
this.__onceExecMenuData = new utils.Dictionary();
}
return this.__onceExecMenuData;
},
/**
* 成功只执行了一次的项
*
* + .onceExec
*/
get onceExecData() {
if (this.__onceExecData == null) {
this.__onceExecData = new utils.Dictionary();
}
return this.__onceExecData;
},
/** 脚本名,一般用在设置的标题上 */
get scriptName() {
return SCRIPT_NAME;
},
/**
* pops.panel的默认配置
*/
get panelConfig() {
return this.__panelConfig;
},
set panelConfig(value) {
this.__panelConfig = value;
},
/** 菜单项的总值在本地数据配置的键名 */
key: KEY,
/** 菜单项在attributes上配置的菜单键 */
attributeKeyName: ATTRIBUTE_KEY,
/** 菜单项在attributes上配置的菜单默认值 */
attributeDefaultValueName: ATTRIBUTE_DEFAULT_VALUE
},
init() {
this.initContentDefaultValue();
PanelMenu.init();
},
/** 判断是否是顶层窗口 */
isTopWindow() {
return _unsafeWindow.top === _unsafeWindow.self;
},
/** 初始化菜单项的默认值保存到本地数据中 */
initContentDefaultValue() {
const initDefaultValue = (config) => {
if (!config.attributes) {
return;
}
if (config.type === "button" || config.type === "forms" || config.type === "deepMenu") {
return;
}
let menuDefaultConfig = /* @__PURE__ */ new Map();
let key = config.attributes[ATTRIBUTE_KEY];
if (key != null) {
const defaultValue = config.attributes[ATTRIBUTE_DEFAULT_VALUE];
menuDefaultConfig.set(key, defaultValue);
}
let moreMenuDefaultConfig = config.attributes[ATTRIBUTE_INIT_MORE_VALUE];
if (typeof moreMenuDefaultConfig === "object" && moreMenuDefaultConfig) {
Object.keys(moreMenuDefaultConfig).forEach((key2) => {
menuDefaultConfig.set(key2, moreMenuDefaultConfig[key2]);
});
}
if (!menuDefaultConfig.size) {
log.warn(["请先配置键", config]);
return;
}
let __attr_init__ = config.attributes[ATTRIBUTE_INIT];
if (typeof __attr_init__ === "function") {
let __attr_result__ = __attr_init__();
if (typeof __attr_result__ === "boolean" && !__attr_result__) {
return;
}
}
if (config.type === "switch") {
let disabled = typeof config.disabled === "function" ? config.disabled() : config.disabled;
if (typeof disabled === "boolean" && disabled) {
this.$data.contentConfigInitDisabledKeys.push(...menuDefaultConfig.keys());
}
}
for (const [__key, __defaultValue] of menuDefaultConfig.entries()) {
this.setDefaultValue(__key, __defaultValue);
}
};
const loopInitDefaultValue = (configList) => {
for (let index = 0; index < configList.length; index++) {
let configItem = configList[index];
initDefaultValue(configItem);
let child_forms = configItem.forms;
if (child_forms && Array.isArray(child_forms)) {
loopInitDefaultValue(child_forms);
}
}
};
const contentConfigList = [...PanelContent.getAllContentConfig()];
for (let index = 0; index < contentConfigList.length; index++) {
let leftContentConfigItem = contentConfigList[index];
if (!leftContentConfigItem.forms) {
continue;
}
const rightContentConfigList = leftContentConfigItem.forms;
if (rightContentConfigList && Array.isArray(rightContentConfigList)) {
loopInitDefaultValue(rightContentConfigList);
}
}
this.$data.contentConfigInitDisabledKeys = [...new Set(this.$data.contentConfigInitDisabledKeys)];
},
/**
* 设置初始化使用的默认值
* @param key 键
* @param defaultValue 默认值
*/
setDefaultValue(key, defaultValue) {
if (this.$data.contentConfigInitDefaultValue.has(key)) {
log.warn("请检查该key(已存在): " + key);
}
this.$data.contentConfigInitDefaultValue.set(key, defaultValue);
},
/**
* 设置值
* @param key 键
* @param value 值
*/
setValue(key, value) {
PopsPanelStorageApi.set(key, value);
},
/**
* 获取值
* @param key 键
* @param defaultValue 默认值
*/
getValue(key, defaultValue) {
let localValue = PopsPanelStorageApi.get(key);
if (localValue == null) {
if (this.$data.contentConfigInitDefaultValue.has(key)) {
return this.$data.contentConfigInitDefaultValue.get(key);
}
return defaultValue;
}
return localValue;
},
/**
* 删除值
* @param key 键
*/
deleteValue(key) {
PopsPanelStorageApi.delete(key);
},
/**
* 判断该键是否存在
* @param key 键
*/
hasKey(key) {
return PopsPanelStorageApi.has(key);
},
/**
* 监听调用setValue、deleteValue
* @param key 需要监听的键
* @param callback
*/
addValueChangeListener(key, callback) {
let listenerId = PopsPanelStorageApi.addValueChangeListener(key, (__key, __newValue, __oldValue) => {
callback(key, __oldValue, __newValue);
});
return listenerId;
},
/**
* 移除监听
* @param listenerId 监听的id
*/
removeValueChangeListener(listenerId) {
PopsPanelStorageApi.removeValueChangeListener(listenerId);
},
/**
* 主动触发菜单值改变的回调
* @param key 菜单键
* @param newValue 想要触发的新值,默认使用当前值
* @param oldValue 想要触发的旧值,默认使用当前值
*/
triggerMenuValueChange(key, newValue, oldValue) {
PopsPanelStorageApi.triggerValueChangeListener(key, oldValue, newValue);
},
/**
* 执行菜单
*
* @param queryKey 判断的键,如果是字符串列表,那么它们的判断处理方式是与关系
* @param callback 执行的回调函数
* @param checkExec 判断是否执行回调
*
* (默认)如果想要每个菜单是`与`关系,即每个菜单都判断为开启,那么就判断它们的值&就行
*
* 如果想要任意菜单存在true再执行,那么判断它们的值|就行
*
* + 返回值都为`true`,执行回调,如果回调返回了<style>元素,该元素会在监听到值改变时被移除掉
* + 返回值有一个为`false`,则不执行回调,且移除之前回调函数返回的<style>元素
* @param once 是否只执行一次,默认true
*
* + true (默认)只执行一次,且会监听键的值改变
* + false 不会监听键的值改变
*/
exec(queryKey, callback, checkExec, once = true) {
const that = this;
let queryKeyFn;
if (typeof queryKey === "string" || Array.isArray(queryKey)) {
queryKeyFn = () => queryKey;
} else {
queryKeyFn = queryKey;
}
let isArrayKey = false;
let queryKeyResult = queryKeyFn();
let keyList = [];
if (Array.isArray(queryKeyResult)) {
isArrayKey = true;
keyList = queryKeyResult;
} else {
keyList.push(queryKeyResult);
}
let findNotInDataKey = keyList.find((it) => !this.$data.contentConfigInitDefaultValue.has(it));
if (findNotInDataKey) {
log.warn(`${findNotInDataKey} 键不存在`);
return;
}
let storageKey = JSON.stringify(keyList);
if (once) {
if (this.$data.onceExecMenuData.has(storageKey)) {
return;
}
this.$data.onceExecMenuData.set(storageKey, 1);
}
let storeValueList = [];
let listenerIdList = [];
let dynamicAddStyleNodeCallback = (value, $style) => {
let dynamicResultList = [];
if (!Array.isArray($style)) {
$style = [$style];
}
$style.forEach(($styleItem) => {
if ($styleItem == null) {
return;
}
if ($styleItem instanceof HTMLStyleElement) {
dynamicResultList.push($styleItem);
return;
}
});
{
storeValueList = storeValueList.concat(dynamicResultList);
}
};
let getMenuValue = (key) => {
let value = this.getValue(key);
return value;
};
let clearBeforeStoreValue = () => {
for (let index = 0; index < storeValueList.length; index++) {
let $css = storeValueList[index];
$css.remove();
storeValueList.splice(index, 1);
index--;
}
};
let checkMenuExec = () => {
let flag = false;
if (typeof checkExec === "function") {
flag = checkExec(keyList);
} else {
flag = keyList.every((key) => getMenuValue(key));
}
return flag;
};
let valueChangeCallback = (valueOption) => {
let execFlag = checkMenuExec();
let resultList = [];
if (execFlag) {
let valueList = keyList.map((key) => this.getValue(key));
let callbackResult = callback({
value: isArrayKey ? valueList : valueList[0],
addStyleElement: (...args) => {
return dynamicAddStyleNodeCallback(true, ...args);
}
});
if (!Array.isArray(callbackResult)) {
callbackResult = [callbackResult];
}
callbackResult.forEach((it) => {
if (it == null) {
return;
}
if (it instanceof HTMLStyleElement) {
resultList.push(it);
return;
}
});
}
clearBeforeStoreValue();
storeValueList = [...resultList];
};
once && keyList.forEach((key) => {
let listenerId = this.addValueChangeListener(key, (key2, newValue, oldValue) => {
valueChangeCallback();
});
listenerIdList.push(listenerId);
});
valueChangeCallback();
let result = {
/**
* 清空菜单执行情况
*
* + 清空存储的元素列表
* + 清空值改变的监听器
* + 清空存储的一次执行的键
*/
clear() {
this.clearStoreStyleElements();
this.removeValueChangeListener();
once && that.$data.onceExecMenuData.delete(storageKey);
},
/**
* 清空存储的元素列表
*/
clearStoreStyleElements: () => {
return clearBeforeStoreValue();
},
/**
* 移除值改变的监听器
*/
removeValueChangeListener: () => {
listenerIdList.forEach((listenerId) => {
this.removeValueChangeListener(listenerId);
});
}
};
return result;
},
/**
* 自动判断菜单是否启用,然后执行回调
* @param key 判断的键,如果是字符串列表,那么它们的判断处理方式是与关系
* @param callback 回调
* @param isReverse 逆反判断菜单启用,默认false
* @param once 是否是只执行一次,默认false
*/
execMenu(key, callback, isReverse = false, once = false) {
return this.exec(
key,
(option) => {
return callback(option);
},
(keyList) => {
let execFlag = keyList.every((__key__) => {
let flag = !!this.getValue(__key__);
let disabled = Panel.$data.contentConfigInitDisabledKeys.includes(__key__);
if (disabled) {
flag = false;
log.warn(`.execMenu${once ? "Once" : ""} ${__key__} 被禁用`);
}
isReverse && (flag = !flag);
return flag;
});
return execFlag;
},
once
);
},
/**
* 自动判断菜单是否启用,然后执行回调,只会执行一次
*
* 它会自动监听值改变(设置中的修改),改变后如果未执行,则执行一次
* @param key 判断的键,如果是字符串列表,那么它们的判断处理方式是与关系
* @param callback 回调
* @param isReverse 逆反判断菜单启用,默认false
*/
execMenuOnce(key, callback, isReverse = false) {
return this.execMenu(key, callback, isReverse, true);
},
/**
* 移除已执行的仅执行一次的菜单
* + .exec
* + .execMenu
* + .execMenuOnce
* @param key 键
*/
deleteExecMenuOnce(key) {
this.$data.onceExecMenuData.delete(key);
let flag = PopsPanelStorageApi.removeValueChangeListener(key);
return flag;
},
/**
* 根据key执行一次,该key不会和execMenu|exec|execMenuOnce已执行的key冲突
* @param key 键
* @param callback 回调
*/
onceExec(key, callback) {
key = this.transformKey(key);
if (typeof key !== "string") {
throw new TypeError("key 必须是字符串");
}
if (this.$data.onceExecData.has(key)) {
return;
}
callback();
this.$data.onceExecData.set(key, 1);
},
/**
* 移除已执行的仅执行一次的菜单
* + .onceExec
* @param key 键
*/
deleteOnceExec(key) {
key = this.transformKey(key);
this.$data.onceExecData.delete(key);
},
/**
* 显示设置面板
* @param content 显示的内容配置
* @param [title] 标题
* @param [preventDefaultContentConfig=false] 是否阻止默认添加内容配置(版本号),默认false
* @param [preventRegisterSearchPlugin=false] 是否阻止默认添加搜索组件,默认false
*/
showPanel(content, title = `${SCRIPT_NAME}-设置`, preventDefaultContentConfig = false, preventRegisterSearchPlugin = false) {
this.$data.$panel = null;
this.$data.panelContent = [];
let checkHasBottomVersionContentConfig = content.findIndex((it) => {
let isBottom = typeof it.isBottom === "function" ? it.isBottom() : Boolean(it.isBottom);
return isBottom && it.id === "script-version";
}) !== -1;
if (!preventDefaultContentConfig && !checkHasBottomVersionContentConfig) {
content.push(...PanelContent.getDefaultBottomContentConfig());
}
let $panel = __pops.panel({
...{
title: {
text: title,
position: "center",
html: false,
style: ""
},
content,
btn: {
close: {
enable: true,
callback: (details, event) => {
details.close();
this.$data.$panel = null;
}
}
},
mask: {
enable: true,
clickEvent: {
toClose: true,
toHide: false
},
clickCallBack: (originalRun, config) => {
originalRun();
this.$data.$panel = null;
}
},
width: PanelUISize.setting.width,
height: PanelUISize.setting.height,
drag: true,
only: true
},
...this.$data.panelConfig
});
this.$data.$panel = $panel;
this.$data.panelContent = content;
if (!preventRegisterSearchPlugin) {
this.registerConfigSearch({ $panel, content });
}
},
/**
* 注册设置面板的搜索功能(双击左侧选项第一个)
* @param config 配置项
*/
registerConfigSearch(config) {
const { $panel, content } = config;
let asyncQueryProperty = async (target, handler) => {
if (target == null) {
return;
}
let handleResult = await handler(target);
if (handleResult && typeof handleResult.isFind === "boolean" && handleResult.isFind) {
return handleResult.data;
}
return await asyncQueryProperty(handleResult.data, handler);
};
let scrollToElementAndListen = ($el, callback) => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
callback?.();
observer.disconnect();
}
});
},
{
root: null,
// 使用视口作为根
threshold: 1
// 元素完全进入视口时触发
}
);
observer.observe($el);
$el.scrollIntoView({ behavior: "smooth", block: "center" });
};
let addFlashingClass = ($el) => {
const flashingClassName = "pops-flashing";
domUtils.animationend($el, () => {
$el.classList.remove(flashingClassName);
});
$el.classList.add(flashingClassName);
};
let dbclick_event = (evt, selectorTarget) => {
utils.preventEvent(evt);
let $alert = __pops.alert({
title: {
text: "搜索配置",
position: "center"
},
content: {
text: (
/*html*/
`
<div class="search-wrapper">
<input class="search-config-text" name="search-config" type="text" placeholder="请输入需要搜素的配置名称">
</div>
<div class="search-result-wrapper"></div>
`
),
html: true
},
btn: {
ok: { enable: false }
},
mask: {
clickEvent: {
toClose: true
}
},
width: PanelUISize.settingMiddle.width,
height: "auto",
drag: true,
style: (
/*css*/
`
${__pops.config.cssText.panelCSS}
.search-wrapper{
border-bottom: 1px solid rgb(235, 238, 245, 1);
}
.pops-content:has(.search-result-wrapper:empty) .search-wrapper{
border-bottom: 0;
}
.search-config-text{
width: 100%;
border: 0;
height: 32px;
padding: 0px 10px;
outline: none;
}
.search-result-wrapper{
max-height: 400px;
overflow: auto;
}
.search-result-item{
cursor: pointer;
padding: 5px 10px;
display: flex;
flex-direction: column;
}
.search-result-item:hover{
background-color: #D8F1FD;
}
.search-result-item-path{
display: flex;
align-items: center;
}
.search-result-item-description{
font-size: 0.8rem;
color: #6c6c6c;
}
${config.searchDialogStyle ?? ""}
`
)
});
$alert.$shadowRoot.querySelector(".search-wrapper");
let $searchInput = $alert.$shadowRoot.querySelector(".search-config-text");
let $searchResultWrapper = $alert.$shadowRoot.querySelector(".search-result-wrapper");
$searchInput.focus();
let clearSearchResult = () => {
domUtils.empty($searchResultWrapper);
};
let createSearchResultItem = (pathInfo) => {
const searchPath = utils.queryProperty(pathInfo, (target) => {
if (target?.next) {
return {
isFind: false,
data: target.next
};
} else {
return {
isFind: true,
data: target
};
}
});
let $item = domUtils.createElement("div", {
className: "search-result-item",
innerHTML: (
/*html*/
`
<div class="search-result-item-path">${searchPath.matchedData?.path}</div>
<div class="search-result-item-description">${searchPath.matchedData?.description ?? ""}</div>
`
)
});
domUtils.on($item, "click", (clickItemEvent) => {
let $asideItems = $panel.$shadowRoot.querySelectorAll(
"aside.pops-panel-aside .pops-panel-aside-top-container li"
);
let $targetAsideItem = $asideItems[pathInfo.index];
if (!$targetAsideItem) {
Qmsg.error(`左侧项下标${pathInfo.index}不存在`);
return;
}
$targetAsideItem.scrollIntoView({
behavior: "smooth",
block: "center"
});
$targetAsideItem.click();
asyncQueryProperty(pathInfo.next, async (target) => {
if (target?.next) {
let $findDeepMenu = await utils.waitNode(() => {
return Array.from(
$panel.$shadowRoot.querySelectorAll(".pops-panel-deepMenu-nav-item")
).find(($deepMenu) => {
const __formConfig__ = Reflect.get($deepMenu, "__formConfig__");
return typeof __formConfig__ === "object" && __formConfig__ != null && __formConfig__.text === target.name;
});
}, 2500);
if ($findDeepMenu) {
$findDeepMenu.click();
} else {
Qmsg.error("未找到对应的二级菜单");
return {
isFind: true,
data: target
};
}
return {
isFind: false,
data: target.next
};
} else {
let $findTargetMenu = await utils.waitNode(() => {
return Array.from(
$panel.$shadowRoot.querySelectorAll(`li:not(.pops-panel-deepMenu-nav-item)`)
).find(($menuItem) => {
const __formConfig__ = Reflect.get($menuItem, "__formConfig__");
return __formConfig__ === target.matchedData?.formConfig;
});
}, 2500);
if ($findTargetMenu) {
scrollToElementAndListen($findTargetMenu);
let $fold = $findTargetMenu.closest(`.pops-panel-forms-fold[data-fold-enable]`);
if ($fold) {
let $foldWrapper = $fold.querySelector(".pops-panel-forms-fold-container");
$foldWrapper.click();
await utils.sleep(500);
}
scrollToElementAndListen($findTargetMenu, () => {
addFlashingClass($findTargetMenu);
});
} else {
Qmsg.error("未找到对应的菜单项");
}
return {
isFind: true,
data: target
};
}
});
});
return $item;
};
let execSearch = (searchText) => {
const searchTextRegExp = new RegExp(searchText, "i");
const searchConfigResult = [];
const loopContentConfig = (configList, path) => {
for (let index = 0; index < configList.length; index++) {
const configItem = configList[index];
let child_forms = configItem.forms;
if (child_forms && Array.isArray(child_forms)) {
const deepMenuPath = utils.deepClone(path);
if (configItem.type === "deepMenu") {
const deepNext = utils.queryProperty(deepMenuPath, (target) => {
if (target?.next) {
return {
isFind: false,
data: target.next
};
} else {
return {
isFind: true,
data: target
};
}
});
deepNext.next = {
name: configItem.text
};
}
loopContentConfig(child_forms, deepMenuPath);
} else {
let text = Reflect.get(configItem, "text");
let description = Reflect.get(configItem, "description");
const delayMatchedTextList = [text, description];
let matchedIndex = delayMatchedTextList.findIndex((configText) => {
if (typeof configText !== "string") {
return;
}
return configText.match(searchTextRegExp);
});
if (matchedIndex !== -1) {
const matchedPath = utils.deepClone(path);
const deepNext = utils.queryProperty(matchedPath, (target) => {
if (target?.next) {
return {
isFind: false,
data: target.next
};
} else {
return {
isFind: true,
data: target
};
}
});
deepNext.next = {
name: text,
matchedData: {
path: "",
formConfig: configItem,
matchedText: delayMatchedTextList[matchedIndex],
description
}
};
const pathList = [];
utils.queryProperty(matchedPath, (target) => {
const name = target?.name;
if (typeof name === "string" && name.trim() !== "") {
pathList.push(name);
}
if (target?.next) {
return {
isFind: false,
data: target.next
};
} else {
return {
isFind: true,
data: target
};
}
});
const pathStr = pathList.join(CommonUtil.escapeHtml(" - "));
deepNext.next.matchedData.path = pathStr;
searchConfigResult.push(matchedPath);
}
}
}
};
for (let index = 0; index < content.length; index++) {
const leftContentConfigItem = content[index];
if (!leftContentConfigItem.forms) {
continue;
}
if (leftContentConfigItem.isBottom && leftContentConfigItem.id === "script-version") {
continue;
}
const rightContentConfigList = leftContentConfigItem.forms;
if (rightContentConfigList && Array.isArray(rightContentConfigList)) {
let text = leftContentConfigItem.title;
if (typeof text === "function") {
text = text();
}
loopContentConfig(rightContentConfigList, {
index,
name: text
});
}
}
let fragment = document.createDocumentFragment();
for (const pathInfo of searchConfigResult) {
let $resultItem = createSearchResultItem(pathInfo);
fragment.appendChild($resultItem);
}
clearSearchResult();
$searchResultWrapper.append(fragment);
};
domUtils.on(
$searchInput,
"input",
utils.debounce((evt2) => {
utils.preventEvent(evt2);
let searchText = domUtils.val($searchInput).trim();
if (searchText === "") {
clearSearchResult();
return;
}
execSearch(searchText);
}, 200)
);
};
let clickElement = null;
let isDoubleClick = false;
let timer = void 0;
domUtils.on(
$panel.$shadowRoot,
"dblclick",
`aside.pops-panel-aside .pops-panel-aside-item:not(#script-version)`,
dbclick_event
);
domUtils.on(
$panel.$shadowRoot,
"touchend",
`aside.pops-panel-aside .pops-panel-aside-item:not(#script-version)`,
(evt, selectorTarget) => {
clearTimeout(timer);
timer = void 0;
if (isDoubleClick && clickElement === selectorTarget) {
isDoubleClick = false;
dbclick_event(evt);
} else {
timer = setTimeout(() => {
isDoubleClick = false;
}, 200);
clickElement = selectorTarget;
isDoubleClick = true;
}
},
{
capture: true
}
);
$panel.$shadowRoot.appendChild(
domUtils.createElement("style", {
type: "text/css",
textContent: (
/*css*/
`
.pops-flashing{
animation: double-blink 1.5s ease-in-out;
}
@keyframes double-blink {
0% {
background-color: initial;
}
25% {
background-color: yellow;
}
50% {
background-color: initial;
}
75% {
background-color: yellow;
}
100% {
background-color: initial;
}
}
`
)
})
);
},
/**
* 把key:string[]转为string
*/
transformKey(key) {
if (Array.isArray(key)) {
const keyArray = key.sort();
return JSON.stringify(keyArray);
} else {
return key;
}
}
};
const PanelSettingConfig = {
/** Toast位置 */
qmsg_config_position: {
key: "qmsg-config-position",
defaultValue: "bottom"
},
/** 最多显示的数量 */
qmsg_config_maxnums: {
key: "qmsg-config-maxnums",
defaultValue: 3
},
/** 逆序弹出 */
qmsg_config_showreverse: {
key: "qmsg-config-showreverse",
defaultValue: false
}
};
const utils = Utils.noConflict();
const domUtils = DOMUtils.noConflict();
const __pops = pops;
const log = new utils.Log(
_GM_info,
_unsafeWindow.console || _monkeyWindow.console
);
let SCRIPT_NAME = _GM_info?.script?.name || void 0;
pops.config.Utils.AnyTouch();
const DEBUG = false;
log.config({
debug: DEBUG,
logMaxCount: 1e3,
autoClearConsole: true,
tag: true
});
Qmsg.config({
isHTML: true,
autoClose: true,
showClose: false,
consoleLogContent(qmsgInst) {
const qmsgType = qmsgInst.getSetting().type;
if (qmsgType === "loading") {
return false;
}
const content = qmsgInst.getSetting().content;
if (qmsgType === "warning") {
log.warn(content);
} else if (qmsgType === "error") {
log.error(content);
} else {
log.info(content);
}
return true;
},
get position() {
return Panel.getValue(
PanelSettingConfig.qmsg_config_position.key,
PanelSettingConfig.qmsg_config_position.defaultValue
);
},
get maxNums() {
return Panel.getValue(
PanelSettingConfig.qmsg_config_maxnums.key,
PanelSettingConfig.qmsg_config_maxnums.defaultValue
);
},
get showReverse() {
return Panel.getValue(
PanelSettingConfig.qmsg_config_showreverse.key,
PanelSettingConfig.qmsg_config_showreverse.defaultValue
);
},
get zIndex() {
let maxZIndex = Utils.getMaxZIndex();
let popsMaxZIndex = pops.config.InstanceUtils.getPopsMaxZIndex().zIndex;
return Utils.getMaxValue(maxZIndex, popsMaxZIndex) + 100;
}
});
__pops.GlobalConfig.setGlobalConfig({
zIndex: () => {
let maxZIndex = Utils.getMaxZIndex(void 0, void 0, ($ele) => {
if ($ele?.classList?.contains("qmsg-shadow-container")) {
return false;
}
if ($ele?.closest("qmsg") && $ele.getRootNode() instanceof ShadowRoot) {
return false;
}
});
let popsMaxZIndex = pops.config.InstanceUtils.getPopsMaxZIndex().zIndex;
return Utils.getMaxValue(maxZIndex, popsMaxZIndex) + 100;
},
mask: {
// 开启遮罩层
enable: true,
// 取消点击遮罩层的事件
clickEvent: {
toClose: false,
toHide: false
}
},
drag: true
});
const GM_Menu = new utils.GM_Menu({
GM_getValue: _GM_getValue,
GM_setValue: _GM_setValue,
GM_registerMenuCommand: _GM_registerMenuCommand,
GM_unregisterMenuCommand: _GM_unregisterMenuCommand
});
const httpx = new utils.Httpx({
xmlHttpRequest: _GM_xmlhttpRequest,
logDetails: DEBUG
});
httpx.interceptors.request.use((data) => {
return data;
});
httpx.interceptors.response.use(void 0, (data) => {
log.error("拦截器-请求错误", data);
if (data.type === "onabort") {
Qmsg.warning("请求取消", { consoleLogContent: true });
} else if (data.type === "onerror") {
Qmsg.error("请求异常", { consoleLogContent: true });
} else if (data.type === "ontimeout") {
Qmsg.error("请求超时", { consoleLogContent: true });
} else {
Qmsg.error("其它错误", { consoleLogContent: true });
}
return data;
});
({
Object: {
defineProperty: _unsafeWindow.Object.defineProperty
},
Function: {
apply: _unsafeWindow.Function.prototype.apply,
call: _unsafeWindow.Function.prototype.call
},
Element: {
appendChild: _unsafeWindow.Element.prototype.appendChild
},
setTimeout: _unsafeWindow.setTimeout
});
const addStyle = utils.addStyle.bind(utils);
DOMUtils.selector.bind(DOMUtils);
DOMUtils.selectorAll.bind(DOMUtils);
new utils.GM_Cookie();
const JianshuRouter = {
/**
* 简书拦截跳转的网址
*/
isGoWild() {
return window.location.pathname === "/go-wild";
}
};
const waitForElementToRemove = function(selectorText = "") {
utils.waitNodeList(selectorText).then((nodeList) => {
nodeList.forEach((item) => item.remove());
});
};
const Jianshu = {
init() {
this.addCSS();
Panel.execMenu("JianShuAutoJumpRedirect_PC", () => {
this.jumpRedirect();
});
Panel.execMenu("JianShuRemoveClipboardHijacking", () => {
this.removeClipboardHijacking();
});
Panel.execMenu("JianShuAutoExpandFullText", () => {
this.autoExpandFullText();
});
Panel.execMenu("JianShuArticleCenter", () => {
return this.articleCenter();
});
Panel.execMenu("JianShuShieldRelatedArticles", () => {
return this.blockRelatedArticles();
});
Panel.execMenu("jianshu-shieldClientDialog", () => {
this.blockClientDialog();
});
Panel.execMenuOnce("JianShuShieldUserComments", () => {
return this.blockUserComments();
});
Panel.execMenuOnce("JianShuShieldRecommendedReading", () => {
return this.blockRecommendedReading();
});
Panel.execMenuOnce("jianshu-shieldTopNav", () => {
return this.blockTopNav();
});
Panel.execMenuOnce("jianshu-shieldBottomToolbar", () => {
return this.blockBottomToolbar();
});
},
/**
* 添加屏蔽CSS
*/
addCSS() {
log.info("添加屏蔽CSS");
return addStyle(blockCSS);
},
/**
* 全文居中
*/
articleCenter() {
log.info("全文居中");
let result = [];
result.push(
CommonUtil.addBlockCSS("div[role=main] aside", "div._3Pnjry"),
addStyle(
/*css*/
`
div[role=main] aside,
div._3Pnjry{
display: none !important;
}
div._gp-ck{
width: 100% !important;
}`
)
);
waitForElementToRemove("div[role=main] aside");
waitForElementToRemove("div._3Pnjry");
utils.waitNodeList("div._gp-ck").then((nodeList) => {
nodeList.forEach((item) => {
item.style["width"] = "100%";
});
});
return result;
},
/**
* 去除剪贴板劫持
*/
removeClipboardHijacking() {
log.info("去除剪贴板劫持");
const stopNativePropagation = (event) => {
event.stopPropagation();
};
window.addEventListener("copy", stopNativePropagation, true);
document.addEventListener("copy", stopNativePropagation, true);
},
/**
* 自动展开全文
*/
autoExpandFullText() {
utils.waitNode(`div#homepage div[class*="dialog-"]`).then((element) => {
element.style["visibility"] = "hidden";
log.info("自动展开全文");
utils.mutationObserver(element, {
callback: (mutations) => {
if (mutations.length == 0) {
return;
}
mutations.forEach((mutationItem) => {
if (mutationItem.target.style["display"] != "none") {
log.success("自动展开全文-自动点击");
document.querySelector(
'div#homepage div[class*="dialog-"] .cancel'
)?.click();
}
});
},
config: {
/* 子节点的变动(新增、删除或者更改) */
childList: false,
/* 属性的变动 */
attributes: true,
/* 节点内容或节点文本的变动 */
characterData: true,
/* 是否将观察器应用于该节点的所有后代节点 */
subtree: true
}
});
});
},
/**
* 去除简书拦截其它网址的url并自动跳转
*/
jumpRedirect() {
if (JianshuRouter.isGoWild()) {
log.success("去除简书拦截其它网址的url并自动跳转");
window.stop();
let search = window.location.href.replace(
window.location.origin + "/",
""
);
search = decodeURIComponent(search);
let newURL = search.replace(/^go-wild\?ac=2&url=/gi, "").replace(/^https:\/\/link.zhihu.com\/\?target\=/gi, "");
window.location.href = newURL;
}
},
/**
* 屏蔽相关文章
*/
blockRelatedArticles() {
log.info("屏蔽相关文章");
return CommonUtil.addBlockCSS(
'div[role="main"] > div > section:nth-child(2)'
);
},
/**
* 【屏蔽】客户端弹窗
*/
blockClientDialog() {
log.info("【屏蔽】客户端弹窗");
CommonUtil.addBlockCSS(
'div:has(>div[class*="-mask"]:not([class*="-mask-hidden"]) + div[tabindex="-1"][role="dialog"])'
);
utils.waitNode(
`div[class*="-mask"]:not([class*="-mask-hidden"]) + div[tabindex="-1"][role="dialog"]`
).then((element) => {
log.success("弹窗出现");
utils.waitPropertyByInterval(
element,
() => {
let react = utils.getReactObj(element);
return react?.reactInternalInstance?.return?.return?.memoizedProps?.onClose;
},
250,
1e4
).then(() => {
let react = utils.getReactObj(element);
react.reactInternalInstance.return.return.memoizedProps.onClose(
new Event("click")
);
log.success("调用函数关闭弹窗");
});
});
},
/**
* 屏蔽评论区
*/
blockUserComments() {
log.info("屏蔽评论区");
return CommonUtil.addBlockCSS("div#note-page-comment");
},
/**
* 屏蔽底部推荐阅读
*/
blockRecommendedReading() {
log.info("屏蔽底部推荐阅读");
return CommonUtil.addBlockCSS(
'div[role="main"] > div > section:last-child'
);
},
/**
* 【屏蔽】顶部导航栏
*/
blockTopNav() {
log.info("【屏蔽】顶部导航栏");
return CommonUtil.addBlockCSS("header");
},
/**
* 【屏蔽】底部工具栏
*/
blockBottomToolbar() {
log.info("【屏蔽】底部工具栏");
return CommonUtil.addBlockCSS("footer");
}
};
const M_Jianshu = {
init() {
this.addCSS();
Panel.execMenu("JianShuAutoJumpRedirect_Mobile", () => {
Jianshu.jumpRedirect();
});
Panel.execMenu("JianShuHijackSchemeScriptLabel_Mobile", () => {
this.handlePrototype();
});
Panel.execMenu("JianShuRemoveClipboardHijacking_Mobile", () => {
Jianshu.removeClipboardHijacking();
});
Panel.execMenu("JianShuAutoExpandFullText_Mobile", () => {
Jianshu.autoExpandFullText();
});
Panel.execMenuOnce("JianShuremoveFooterRecommendRead", () => {
return this.blockeFooterRecommendRead();
});
Panel.execMenu("JianShuShieldUserCommentsMobile", () => {
return this.blockUserComments();
});
},
/**
* 添加屏蔽CSS
*/
addCSS() {
Jianshu.addCSS();
},
/**
* 手机-屏蔽底部推荐阅读
*/
blockeFooterRecommendRead() {
log.info("屏蔽底部推荐阅读");
return CommonUtil.addBlockCSS("#recommended-notes");
},
/**
* 处理原型
*/
handlePrototype() {
log.info("处理原型添加script标签");
let originalAppendChild = Node.prototype.appendChild;
_unsafeWindow.Node.prototype.appendChild = function(element) {
let allowElementLocalNameList = ["img"];
if (element.src && !element.src.includes("jianshu.io") && !allowElementLocalNameList.includes(element.localName)) {
log.success(["禁止添加的元素", element]);
return null;
} else {
return originalAppendChild.call(this, element);
}
};
},
/**
* 屏蔽评论区
*/
blockUserComments() {
log.info("屏蔽评论区");
return CommonUtil.addBlockCSS("#comment-main");
}
};
const PanelComponents = {
$data: {
__storeApiFn: null,
get storeApiValue() {
if (!this.__storeApiFn) {
this.__storeApiFn = new Utils.Dictionary();
}
return this.__storeApiFn;
}
},
/**
* 获取自定义的存储接口
* @param type 组件类型
*/
getStorageApi(type) {
if (!this.hasStorageApi(type)) {
return;
}
return this.$data.storeApiValue.get(type);
},
/**
* 判断是否存在自定义的存储接口
* @param type 组件类型
*/
hasStorageApi(type) {
return this.$data.storeApiValue.has(type);
},
/**
* 设置自定义的存储接口
* @param type 组件类型
* @param storageApiValue 存储接口
*/
setStorageApi(type, storageApiValue) {
this.$data.storeApiValue.set(type, storageApiValue);
},
/**
* 初始化组件的存储接口属性
*
* @param type 组件类型
* @param config 组件配置,必须包含prop属性
* @param storageApiValue 存储接口
*/
initComponentsStorageApi(type, config, storageApiValue) {
let propsStorageApi;
if (this.hasStorageApi(type)) {
propsStorageApi = this.getStorageApi(type);
} else {
propsStorageApi = storageApiValue;
}
this.setComponentsStorageApiProperty(config, propsStorageApi);
},
/**
* 设置组件的存储接口属性
* @param config 组件配置,必须包含prop属性
* @param storageApiValue 存储接口
*/
setComponentsStorageApiProperty(config, storageApiValue) {
Reflect.set(config.props, PROPS_STORAGE_API, storageApiValue);
}
};
const UISwitch = function(text, key, defaultValue, clickCallBack, description, afterAddToUListCallBack, disabled, valueChangeCallBack) {
let result = {
text,
type: "switch",
description,
disabled,
attributes: {},
props: {},
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
let value = storageApiValue.get(key, defaultValue);
return value;
},
callback(event, __value) {
let value = Boolean(__value);
log.success(`${value ? "开启" : "关闭"} ${text}`);
let storageApiValue = this.props[PROPS_STORAGE_API];
storageApiValue.set(key, value);
},
afterAddToUListCallBack
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"switch",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
const SettingUIMobile = {
id: "jianshu-panel-config-mobile",
title: "移动端",
forms: [
{
text: "",
type: "forms",
forms: [
{
text: "功能",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"自动展开全文",
"JianShuAutoExpandFullText_Mobile",
true
),
UISwitch(
"重定向链接",
"JianShuAutoJumpRedirect_Mobile",
true,
void 0,
"自动跳转简书拦截的Url链接"
)
]
}
]
},
{
text: "屏蔽",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"【屏蔽】底部推荐阅读",
"JianShuremoveFooterRecommendRead",
false
),
UISwitch(
"【屏蔽】评论区",
"JianShuShieldUserCommentsMobile",
false
)
]
}
]
},
{
text: "劫持/拦截",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"拦截-剪贴板",
"JianShuRemoveClipboardHijacking_Mobile",
true,
void 0,
"去除禁止复制"
),
UISwitch(
"劫持-唤醒/跳转App",
"JianShuHijackSchemeScriptLabel_Mobile",
true,
void 0,
"去除简书唤醒调用App"
)
]
}
]
}
]
}
]
};
const UISelect = function(text, key, defaultValue, data, selectCallBack, description, valueChangeCallBack) {
let selectData = [];
if (typeof data === "function") {
selectData = data();
} else {
selectData = data;
}
let result = {
text,
type: "select",
description,
attributes: {},
props: {},
getValue() {
let storageApiValue = this.props[PROPS_STORAGE_API];
return storageApiValue.get(key, defaultValue);
},
callback(event, isSelectedValue, isSelectedText) {
let value = isSelectedValue;
log.info(`选择:${isSelectedText}`);
if (typeof selectCallBack === "function") {
let result2 = selectCallBack(event, value, isSelectedText);
if (result2) {
return;
}
}
let storageApiValue = this.props[PROPS_STORAGE_API];
storageApiValue.set(key, value);
},
data: selectData
};
Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
PanelComponents.initComponentsStorageApi(
"select",
result,
{
get(key2, defaultValue2) {
return Panel.getValue(key2, defaultValue2);
},
set(key2, value) {
Panel.setValue(key2, value);
}
}
);
return result;
};
const SettingUICommon = {
id: "jianshu-panel-common",
title: "通用",
forms: [
{
text: "Toast配置",
type: "forms",
forms: [
UISelect(
"Toast位置",
"qmsg-config-position",
"bottom",
[
{
value: "topleft",
text: "左上角"
},
{
value: "top",
text: "顶部"
},
{
value: "topright",
text: "右上角"
},
{
value: "left",
text: "左边"
},
{
value: "center",
text: "中间"
},
{
value: "right",
text: "右边"
},
{
value: "bottomleft",
text: "左下角"
},
{
value: "bottom",
text: "底部"
},
{
value: "bottomright",
text: "右下角"
}
],
(event, isSelectValue, isSelectText) => {
log.info("设置当前Qmsg弹出位置" + isSelectText);
},
"Toast显示在页面九宫格的位置"
),
UISelect(
"最多显示的数量",
"qmsg-config-maxnums",
3,
[
{
value: 1,
text: "1"
},
{
value: 2,
text: "2"
},
{
value: 3,
text: "3"
},
{
value: 4,
text: "4"
},
{
value: 5,
text: "5"
}
],
void 0,
"限制Toast显示的数量"
),
UISwitch(
"逆序弹出",
"qmsg-config-showreverse",
false,
void 0,
"修改Toast弹出的顺序"
)
]
}
]
};
const SettingUIPC = {
id: "jianshu-panel-config-pc",
title: "桌面端",
forms: [
{
text: "",
type: "forms",
forms: [
{
text: "功能",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch("全文居中", "JianShuArticleCenter", true),
UISwitch("自动展开全文", "JianShuAutoExpandFullText", true),
UISwitch(
"重定向链接",
"JianShuAutoJumpRedirect_PC",
true,
void 0,
"自动跳转简书拦截的Url链接"
)
]
}
]
},
{
text: "屏蔽",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"【屏蔽】底部推荐阅读",
"JianShuShieldRecommendedReading",
false
),
UISwitch("【屏蔽】评论区", "JianShuShieldUserComments", false),
UISwitch(
"【屏蔽】相关文章",
"JianShuShieldRelatedArticles",
false
),
UISwitch(
"【屏蔽】客户端弹窗",
"jianshu-shieldClientDialog",
true,
void 0,
"弹出的【扫码安装简书客户端 畅享全文阅读体验】"
),
UISwitch("【屏蔽】顶部导航栏", "jianshu-shieldTopNav", false),
UISwitch(
"【屏蔽】底部工具栏",
"jianshu-shieldBottomToolbar",
false,
void 0,
"屏蔽掉底部悬浮的评论输入框、评论、点赞..."
)
]
}
]
},
{
text: "劫持/拦截",
type: "deepMenu",
forms: [
{
text: "",
type: "forms",
forms: [
UISwitch(
"拦截-剪贴板",
"JianShuRemoveClipboardHijacking",
true,
void 0,
"去除禁止复制"
)
]
}
]
}
]
}
]
};
PanelContent.addContentConfig([SettingUICommon, SettingUIPC, SettingUIMobile]);
Panel.init();
let isMobile = utils.isPhone();
let CHANGE_ENV_SET_KEY = "change_env_set";
let chooseMode = _GM_getValue(CHANGE_ENV_SET_KEY);
GM_Menu.add({
key: CHANGE_ENV_SET_KEY,
text: `⚙ 自动: ${isMobile ? "移动端" : "PC端"}`,
autoReload: false,
isStoreValue: false,
showText(text) {
if (chooseMode == null) {
return text;
}
return text + ` 手动: ${chooseMode == 1 ? "移动端" : chooseMode == 2 ? "PC端" : "未知"}`;
},
callback: () => {
let allowValue = [0, 1, 2];
let chooseText = window.prompt(
"请输入当前脚本环境判定\n\n自动判断: 0\n移动端: 1\nPC端: 2",
"0"
);
if (!chooseText) {
return;
}
let chooseMode2 = parseInt(chooseText);
if (isNaN(chooseMode2)) {
Qmsg.error("输入的不是规范的数字");
return;
}
if (!allowValue.includes(chooseMode2)) {
Qmsg.error("输入的值必须是0或1或2");
return;
}
if (chooseMode2 == 0) {
_GM_deleteValue(CHANGE_ENV_SET_KEY);
} else {
_GM_setValue(CHANGE_ENV_SET_KEY, chooseMode2);
}
}
});
if (chooseMode != null) {
log.info(`手动判定为${chooseMode === 1 ? "移动端" : "PC端"}`);
if (chooseMode == 1) {
M_Jianshu.init();
} else if (chooseMode == 2) {
Jianshu.init();
} else {
Qmsg.error("意外,手动判定的值不在范围内");
_GM_deleteValue(CHANGE_ENV_SET_KEY);
}
} else {
if (isMobile) {
log.info("自动判定为移动端");
M_Jianshu.init();
} else {
log.info("自动判定为PC端");
Jianshu.init();
}
}
})(Qmsg, DOMUtils, Utils, pops);