您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
高亮关键词,可设置关键词的样式,支持正则匹配
// ==UserScript== // @name highlight-keywords // @namespace https://github.com/mudssky/highlight-keywords // @version 2.0.4 // @author mudssky // @description 高亮关键词,可设置关键词的样式,支持正则匹配 // @license MIT // @icon https://vitejs.dev/logo.svg // @homepage https://github.com/mudssky/highlight-keywords // @homepageURL https://github.com/mudssky/highlight-keywords // @supportURL https://github.com/mudssky/highlight-keywords/issues // @match *://*/* // @exclude *://element-plus* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js // @require data:application/javascript,window.Vue%3DVue%3B // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/index.full.min.js // @resource element-plus/dist/index.css https://cdn.jsdelivr.net/npm/[email protected]/dist/index.css // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_setClipboard // @grant GM_setValue // @run-at document-end // ==/UserScript== (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const r=document.createElement("style");r.textContent=t,document.head.append(r)})(" .dialog-footer button[data-v-aead4c22]:first-child{margin-right:10px}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.min-h-\\[400px\\]{min-height:400px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)} "); (function (vue, ElementPlus) { 'use strict'; var zhCn = { name: "zh-cn", el: { colorpicker: { confirm: "确定", clear: "清空" }, datepicker: { now: "此刻", today: "今天", cancel: "取消", clear: "清空", confirm: "确定", selectDate: "选择日期", selectTime: "选择时间", startDate: "开始日期", startTime: "开始时间", endDate: "结束日期", endTime: "结束时间", prevYear: "前一年", nextYear: "后一年", prevMonth: "上个月", nextMonth: "下个月", year: "年", month1: "1 月", month2: "2 月", month3: "3 月", month4: "4 月", month5: "5 月", month6: "6 月", month7: "7 月", month8: "8 月", month9: "9 月", month10: "10 月", month11: "11 月", month12: "12 月", weeks: { sun: "日", mon: "一", tue: "二", wed: "三", thu: "四", fri: "五", sat: "六" }, months: { jan: "一月", feb: "二月", mar: "三月", apr: "四月", may: "五月", jun: "六月", jul: "七月", aug: "八月", sep: "九月", oct: "十月", nov: "十一月", dec: "十二月" } }, select: { loading: "加载中", noMatch: "无匹配数据", noData: "无数据", placeholder: "请选择" }, cascader: { noMatch: "无匹配数据", loading: "加载中", placeholder: "请选择", noData: "暂无数据" }, pagination: { goto: "前往", pagesize: "条/页", total: "共 {total} 条", pageClassifier: "页", page: "页", prev: "上一页", next: "下一页", currentPage: "第 {pager} 页", prevPages: "向前 {pager} 页", nextPages: "向后 {pager} 页", deprecationWarning: "你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档" }, messagebox: { title: "提示", confirm: "确定", cancel: "取消", error: "输入的数据不合法!" }, upload: { deleteTip: "按 delete 键可删除", delete: "删除", preview: "查看图片", continue: "继续上传" }, table: { emptyText: "暂无数据", confirmFilter: "筛选", resetFilter: "重置", clearFilter: "全部", sumText: "合计" }, tree: { emptyText: "暂无数据" }, transfer: { noMatch: "无匹配数据", noData: "无数据", titles: ["列表 1", "列表 2"], filterPlaceholder: "请输入搜索内容", noCheckedFormat: "共 {total} 项", hasCheckedFormat: "已选 {checked}/{total} 项" }, image: { error: "加载失败" }, pageHeader: { title: "返回" }, popconfirm: { confirmButtonText: "确定", cancelButtonText: "取消" } } }; var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)(); var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)(); var _GM_setClipboard = /* @__PURE__ */ (() => typeof GM_setClipboard != "undefined" ? GM_setClipboard : void 0)(); var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)(); function highlightKeyword(node, pattern, index) { var _a; let exposeCount = 0; if (node.nodeType === Node.TEXT_NODE && node instanceof Text) { const matchResult = node.data.match(pattern); if (matchResult) { const highlightEl = document.createElement("span"); highlightEl.dataset.highlight = "yes"; highlightEl.dataset.highlightMatch = matchResult[0]; if (index ?? false) { highlightEl.dataset.highlightIndex = index; } const matchNode = node.splitText((matchResult == null ? void 0 : matchResult.index) ?? 0); matchNode.splitText(matchResult[0].length); var highlightTextNode = document.createTextNode(matchNode.data); highlightEl.appendChild(highlightTextNode); (_a = matchNode.parentNode) == null ? void 0 : _a.replaceChild(highlightEl, matchNode); exposeCount++; } } else if (node.nodeType === Node.ELEMENT_NODE && !/script|style/.test(node.tagName.toLowerCase())) { if (node.dataset.highlight === "yes") { if ((index ?? null) === null) { return; } if (node.dataset.highlightIndex === (index ?? "").toString()) { return; } } let childNodes = node.childNodes; for (var i = 0; i < childNodes.length; i++) { highlightKeyword(childNodes[i], pattern, index); } } return exposeCount; } function closeHighlight(pattern, index = null) { var highlightNodeList = document.querySelectorAll("[data-highlight=yes]"); for (var n = 0; n < highlightNodeList.length; n++) { const dataset = highlightNodeList[n].dataset; if (index === null || dataset.highlightIndex !== index.toString()) { return; } if (pattern.test(dataset.highlightMatch)) { var parentNode = highlightNodeList[n].parentNode; var childNodes = highlightNodeList[n].childNodes; var childNodesLen = childNodes.length; var nextSibling = highlightNodeList[n].nextSibling; for (var k = 0; k < childNodesLen; k++) { parentNode.insertBefore(childNodes[0], nextSibling); } var flagNode = document.createTextNode(""); parentNode.replaceChild(flagNode, highlightNodeList[n]); parentNode.normalize(); } } } function cleanKeywords(keywords) { let wordMatchString = ""; const words = [...keywords]; words.forEach((item) => { let transformString = item.replace(/[.[*?+^$|()/]|\]|\\/g, "\\$&"); wordMatchString += `|(${transformString})`; }); wordMatchString = wordMatchString.substring(1); const wholePattern = new RegExp(`^${wordMatchString}$`, "i"); const pattern = new RegExp(wordMatchString, "i"); return [pattern, wholePattern]; } const configName = "hightlight-config"; const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({ __name: "index", setup(__props) { const ruleFormRef = vue.ref(); const dialogVisible = vue.ref(false); const ruleList = vue.ref([]); const pageState = vue.reactive({ globalStyle: void 0 }); const form = vue.reactive({ configJson: "", defaultHightlightStyle: "background:gold;color:black;", highlightStyle: "background:gold;color:black;", placeholder: `//示例: [ { "keywords": ["成年コミック"], "matchUrl": "sukebei.nyaa.si", }, ] ` }); const matchedRuleList = vue.computed(() => { return ruleList.value.filter((rule) => { var urlPattern = new RegExp(rule.matchUrl); return urlPattern.test(window.location.href); }); }); const matchedKeywords = vue.computed(() => { const keywordsLists = matchedRuleList.value.map((item) => { return item.keywords; }); return [...new Set(keywordsLists.flat())]; }); function generateHighlightStyle(styleText) { return `[data-highlight="yes"]{${styleText}}`; } function loadGlobalStyle() { let style2 = document.createElement("style"); style2.textContent = generateHighlightStyle(form.defaultHightlightStyle); document.head.appendChild(style2); pageState.globalStyle = style2; } function updateHighlightStyle(styleText) { if (pageState.globalStyle) { pageState.globalStyle.textContent = generateHighlightStyle(styleText); } } function handleCopyJson() { _GM_setClipboard(form.configJson, "text"); } function loadRuleList() { const vv = _GM_getValue(configName, []); ruleList.value = vv; form.configJson = JSON.stringify(ruleList.value); } function handleOpenPanel() { dialogVisible.value = true; } function handleClose() { dialogVisible.value = false; } function validateConfig(configList) { const res = [false, "配置项格式不对"]; if (!Array.isArray(configList)) { return res; } if (configList.some((item) => { return typeof item !== "object"; })) { return res; } for (const property of ["keywords", "matchUrl"]) { if (configList.some((item) => { return !((item == null ? void 0 : item[property]) ?? false); })) { res[1] = `${property} 属性是必须的`; return res; } } for (const item of configList) { if (typeof item.matchUrl !== "string") { res[1] = "matchUrl类型错误"; return res; } if (!Array.isArray(item.keywords)) { res[1] = "keywords类型错误"; return res; } for (const keyword of item.keywords) { if (typeof keyword !== "string") { res[1] = "keywords类型错误"; return res; } if (keyword.trim() === "") { console.log("空字符串"); res[1] = "keywords不能为空"; return res; } } } return [true, res[1]]; } async function handleUpdateConfig() { var _a; await ((_a = ruleFormRef.value) == null ? void 0 : _a.validate((valid2, fields) => { if (valid2) { console.log("submit!"); } else { console.log("error submit!", fields); } })); let list; try { list = JSON.parse(form.configJson); } catch (error) { ElementPlus.ElMessage({ type: "warning", message: "json解析错误" }); return; } const [valid, errorMessage] = validateConfig(list); if (!valid) { ElementPlus.ElMessage({ type: "warning", message: errorMessage }); return; } ruleList.value = list; updateHighlightStyle(form.highlightStyle); _GM_setValue(configName, list); highlightMatchedKeywords(); ElementPlus.ElMessage({ type: "success", message: "配置更新成功" }); handleClose(); } function highlightMatchedKeywords() { if (matchedKeywords.value.length < 1) { return; } const [pattern, _] = cleanKeywords(matchedKeywords.value); closeHighlight(document.body, pattern); highlightKeyword(document.body, pattern); } vue.onMounted(() => { loadRuleList(); if (matchedKeywords.value.length > 0) { loadGlobalStyle(); highlightMatchedKeywords(); } _GM_registerMenuCommand("打开配置面板", handleOpenPanel); }); return (_ctx, _cache) => { const _component_el_input = vue.resolveComponent("el-input"); const _component_el_form_item = vue.resolveComponent("el-form-item"); const _component_el_form = vue.resolveComponent("el-form"); const _component_el_button = vue.resolveComponent("el-button"); const _component_el_space = vue.resolveComponent("el-space"); const _component_el_row = vue.resolveComponent("el-row"); const _component_el_dialog = vue.resolveComponent("el-dialog"); return vue.openBlock(), vue.createBlock(_component_el_dialog, { modelValue: dialogVisible.value, "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => dialogVisible.value = $event), title: "配置面板", width: "30%", "before-close": handleClose, class: "min-h-[400px]" }, { default: vue.withCtx(() => [ vue.createVNode(_component_el_form, { model: form, ref_key: "ruleFormRef", ref: ruleFormRef }, { default: vue.withCtx(() => [ vue.createVNode(_component_el_form_item, { label: "高亮样式", prop: "highlightStyle", rules: [ { required: true, whitespace: true, message: "请输入高亮样式", trigger: "change" } ] }, { default: vue.withCtx(() => [ vue.createVNode(_component_el_input, { modelValue: form.highlightStyle, "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => form.highlightStyle = $event) }, null, 8, ["modelValue"]) ]), _: 1 }), vue.createVNode(_component_el_form_item, { label: "配置" }, { default: vue.withCtx(() => [ vue.createVNode(_component_el_input, { modelValue: form.configJson, "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => form.configJson = $event), placeholder: form.placeholder, type: "textarea", autosize: { minRows: 5, maxRows: 10 } }, null, 8, ["modelValue", "placeholder"]) ]), _: 1 }) ]), _: 1 }, 8, ["model"]), vue.createVNode(_component_el_row, { justify: "end" }, { default: vue.withCtx(() => [ vue.createVNode(_component_el_space, null, { default: vue.withCtx(() => [ vue.createVNode(_component_el_button, { onClick: handleCopyJson }, { default: vue.withCtx(() => [ vue.createTextVNode("复制json") ]), _: 1 }), vue.createVNode(_component_el_button, { onClick: handleUpdateConfig, type: "primary" }, { default: vue.withCtx(() => [ vue.createTextVNode("更新配置") ]), _: 1 }) ]), _: 1 }) ]), _: 1 }) ]), _: 1 }, 8, ["modelValue"]); }; } }); const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const app$1 = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-aead4c22"]]); const cssLoader = (e) => { const t = GM_getResourceText(e); return GM_addStyle(t), t; }; cssLoader("element-plus/dist/index.css"); const _sfc_main = /* @__PURE__ */ vue.defineComponent({ __name: "App", setup(__props) { const config = vue.reactive({ zIndex: 100 }); return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.unref(ElementPlus.ElConfigProvider), { locale: vue.unref(zhCn), zIndex: config.zIndex }, { default: vue.withCtx(() => [ vue.createVNode(app$1) ]), _: 1 }, 8, ["locale", "zIndex"]); }; } }); const app = vue.createApp(_sfc_main); const appContainer = (() => { const app2 = document.createElement("div"); document.documentElement.append(app2); return app2; })(); app.use(ElementPlus); app.mount(appContainer); })(Vue, ElementPlus);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址