网页高亮关键字

对网页上的文字进行高亮显示,如果对你有帮助,可以随意修改使用

安装此脚本?
作者推荐脚本

您可能也喜欢Greasyfork 糊裱匠

安装此脚本
  1. // ==UserScript==
  2. // @name 网页高亮关键字
  3. // @name:zh-CN 网页高亮关键字
  4. // @namespace https://github.com/ChinaGodMan/UserScripts
  5. // @version 1.1.2.72
  6. // @description 对网页上的文字进行高亮显示,如果对你有帮助,可以随意修改使用
  7. // @description:zh-CN 对网页上的文字进行高亮显示,如果对你有帮助,可以随意修改使用
  8. // @author ma bangde,人民的勤务员 <china.qinwuyuan@gmail.com>
  9. // @include *
  10. // @grant GM_addStyle
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @require https://cdn.jsdelivr.net/npm/vue@2.6.1/dist/vue.min.js
  15. // @icon data:image/png;base64,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
  16. // @iconbak https://github.com/ChinaGodMan/UserScripts/raw/main/docs/icon/Scripts%20Icons/icons8-mark-96.png
  17. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  18. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  19. // @license MIT
  20.  
  21. // ==/UserScript==
  22. (function () {
  23.  
  24.  
  25. // 初始化
  26. function initialize() {
  27. let defaultWords = {
  28. 'key_123': {
  29. limit: ['baidu'],
  30. 'info': '汉字测试',
  31. 'words': ['抖音', '快手', '网页', '平台', '的', '最', '一', '个', '多', '服务', '大'],
  32. 'color': '#85d228',
  33. 'textcolor': '#3467eb'
  34.  
  35.  
  36. },
  37. 'key_124': {
  38. limit: [],
  39. 'info': '数字测试',
  40. 'words': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
  41. 'color': '#48c790',
  42. 'textcolor': '#3467eb'
  43.  
  44. },
  45. 'key_3379656389': {
  46. limit: [],
  47. 'info': '字母测试',
  48. 'words': ['a', 'b', 'c', 'd', 'e', 'f', 't', 'y', 'u', 'i', 'o', 'k', 'j', 'h', 'g', 's', 'z', 'x', 'v', 'n', 'm'],
  49. 'color': '#e33544',
  50. 'textcolor': '#3467eb'
  51. },
  52. 'key_4947181948': {
  53. limit: [],
  54. 'info': '相同的字可以显示各个分组的标题',
  55. 'words': ['的', '最', '一', '个', '多', '服务', '大'],
  56. 'color': '#6e7bdd',
  57. 'textcolor': '#e33544'
  58. }
  59. }
  60. // 设置关键字默认值
  61. if (!GM_getValue('key')) { GM_setValue('key', defaultWords) }
  62. if (Object.keys(GM_getValue('key')).length == 0) { GM_setValue('key', defaultWords) }
  63. // GM_setValue("key",this.defaultWords);
  64.  
  65. let cache = GM_getValue('key')
  66. Object.keys(cache).forEach(key => {
  67. let defult = {
  68. limit: [],
  69. info: '',
  70. words: [],
  71. color: '#85d228'
  72. }
  73. Object.keys(defult).forEach((key2) => {
  74. if (!cache[key][key2]) {
  75. console.log(defult[key2])
  76. cache[key][key2] = defult[key2]
  77. }
  78. })
  79. })
  80.  
  81. GM_setValue('key', cache)
  82. }
  83. /**
  84. * @description: 遍历找出所有文本节点
  85. * @param {*} node
  86. * @return {*} 节点map
  87. */
  88. function textMap(node) {
  89. // 存储文本节点
  90. let nodeMap = new Map()
  91.  
  92. const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, (textNode) => {
  93. if (textNode.parentElement.nodeName === 'SCRIPT' |
  94. textNode.parentElement.nodeName === 'script' |
  95. textNode.parentElement.nodeName === 'style' |
  96. textNode.parentElement.nodeName === 'STYLE' |
  97. textNode.parentElement.className === 'mt_highlight' |
  98. document.querySelector('#mt_seting_box').contains(textNode)
  99. ) {
  100. return NodeFilter.FILTER_SKIP
  101. }
  102.  
  103. if (textNode.data.length < 20) {
  104. return textNode.data.replace(/[\n \t]/g, '').length ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
  105. }
  106.  
  107. return NodeFilter.FILTER_ACCEPT
  108. })
  109.  
  110. while ((textNode = walker.nextNode())) {
  111. nodeMap.set(textNode, textNode.data)
  112. }
  113.  
  114. return nodeMap
  115. }
  116.  
  117. // 高亮
  118. class HIGHTLIGHT {
  119.  
  120. // 需要高亮的关键字
  121. /**通过规则新建关键字列表,解决一个关键字会存在多个分类中
  122. * 将{
  123. * key1{
  124. * words:[word1,word2]
  125. * },
  126. * key2{
  127. * words:[word3,word4]
  128. * }
  129. * }
  130. * 转换为map{
  131. * word1:key1
  132. * word2:key1
  133. * word4:key2
  134. * word3:key2
  135. * }
  136. * @description:
  137. * @return {map}map{
  138. *
  139. * classesKey=>分类标签,类型string
  140. *
  141. * infoList=>提示词,数组["汉字","字符"]
  142. *
  143. * }
  144. */
  145. static words() {
  146.  
  147. // 转换
  148. let newWords = new Map
  149. Object.keys(GM_getValue('key')).forEach(classesKey => {
  150.  
  151. let info = GM_getValue('key')[classesKey].info
  152. let words = GM_getValue('key')[classesKey].words
  153. let color = GM_getValue('key')[classesKey].color
  154. let limit = GM_getValue('key')[classesKey].limit
  155. let textcolor = GM_getValue('key')[classesKey].textcolor
  156.  
  157.  
  158. words.forEach(word => {
  159. let infoList = []
  160.  
  161. // 检测是否被多个类目包含,被多个类目包含的关键字会有对应类目的信息
  162. if (newWords.get(word + '')) {
  163. infoList = newWords.get(word + '').infoList
  164. infoList.push(info)
  165. } else {
  166. infoList = [info]
  167. }
  168.  
  169. newWords.set(word + '', {
  170. classesKey,
  171. infoList: infoList,
  172. textcolor,
  173. color,
  174. limit
  175. })
  176. })
  177. })
  178. return newWords
  179. }
  180.  
  181. // 检测正则
  182. static reg() {
  183. let url = window.location.href
  184. let doIt = false
  185. let wordsList = []
  186. let words = this.words()
  187. words.forEach((value, word) => {
  188. // console.log(value.limit);
  189. // 过滤不匹配的
  190. if (value.limit.length == 0 || url.match(new RegExp(`${value.limit.join('|')}`, 'g'))) {
  191. // 添加要筛选的关键字
  192. wordsList.push(word)
  193. }
  194. })
  195. // 过滤后还需不需要检测
  196. wordsList.length ? doIt = true : doIt = false
  197. // console.log(doIt,wordsList);
  198. return {
  199. rule: new RegExp(`(${wordsList.join('|')})`, 'g'),
  200. doIt
  201. }
  202. }
  203.  
  204.  
  205. // 高亮css
  206. static highlightStyle = `
  207. .mt_highlight{
  208. background-color: rgb(255, 21, 21);
  209. border-radius: 2px;
  210. box-shadow: 0px 0px 1px 1px rgba(0, 0, 0,0.1);
  211. cursor: pointer;
  212. color: white;
  213.  
  214. padding: 1px 1px;
  215. }
  216. `
  217.  
  218. /**
  219. * @description: 返回需要被高亮的节点map{textNode,未来会被修改成目标的值}
  220. * @param {map} nodeMap
  221. * @return {void}
  222. */
  223. static highlight(nodeMap) {
  224. let words = this.words()
  225. let reg = this.reg()
  226. // 没有要高亮的关键字时不执行
  227.  
  228. if (words.size && reg.doIt) {
  229. nodeMap.forEach((value, node) => {
  230. // 正则检测是否符合规则
  231. let newInnerHTML = value.replace(reg.rule, (word) => {
  232. let classesKey = words.get(word).classesKey
  233. let infoList = words.get(word).infoList
  234. let color = words.get(word).color
  235. let textcolor = words.get(word).textcolor
  236.  
  237. // 返回新节点模板
  238. // return `<span class="mt_highlight" classesKey="${classesKey}" title="${infoList.join("\n")}" style="background: ${color};">${word}</span>`
  239.  
  240. return `<span class="mt_highlight" classesKey="${classesKey}" title="${infoList.join('\n')}" style="background: ${color}; color:${textcolor};">${word}</span>`
  241. })
  242. // 是否检测出了
  243. if (value != newInnerHTML) {
  244. // 节点替换
  245. let newNode = document.createElement('span')
  246. newNode.innerHTML = newInnerHTML
  247. node.parentElement.replaceChild(newNode, node)
  248. // 点击复制
  249. newNode.addEventListener('click', (e) => {
  250. navigator.clipboard.writeText(e.target.innerText)
  251. })
  252. }
  253. })
  254. }
  255.  
  256. }
  257.  
  258. }
  259.  
  260. /**
  261. * @description: 动态检测新节点,并执行高亮
  262. * @return {*}
  263. */
  264. function watch() {
  265. // 选择需要观察变动的节点
  266. const targetNode = document.body
  267.  
  268. // 观察器的配置(需要观察什么变动)
  269. const config = { attributes: false, childList: true, subtree: true, characterData: true }
  270.  
  271. // 当观察到变动时执行的回调函数
  272. const callback = function (mutationsList, observer) {
  273. let nodeMap = new Map
  274. setTimeout(() => {
  275. mutationsList.forEach(node => { nodeMap.set(node.target) })
  276. nodeMap.forEach((value, node) => {
  277. doOnce(node)
  278. })
  279. }, 1)
  280. }
  281.  
  282. // 创建一个观察器实例并传入回调函数
  283. const observer = new MutationObserver(callback)
  284.  
  285. // 以上述配置开始观察目标节点
  286. observer.observe(targetNode, config)
  287. }
  288.  
  289.  
  290. // gui
  291. class GUI {
  292. // 模板
  293. static setingTemplate = String.raw`
  294. <div class="seting_box" v-show="showSeting">
  295. <!-- 顶部选项 -->
  296. <div class="option_box">
  297. <div @click="config_in_add">导入添加</div>
  298. <div @click="config_in">导入覆盖</div>
  299.  
  300. <input type="file" class="config_file" accept=".json" @change="file_read($event)">
  301.  
  302. <div @click="config_out">导出配置文件</div>
  303. <div @click="refresh">刷新</div>
  304. <div class="close_seting" @click="close_seting">关闭</div>
  305. </div>
  306.  
  307. <!-- 规则视图 -->
  308. <div class="rule_list_box" v-for="(value,key) in rule">
  309.  
  310. <!-- 展示视图 -->
  311. <div class="show_box" v-show="!edit[key]" >
  312.  
  313. <!-- 左边 -->
  314. <div class="show_left">
  315.  
  316. <!-- 网站作用域 -->
  317. <div class="words_box" @click="editOn(key)">
  318. <span v-for="(word) in value.limit" :style="{'background': value.color, 'color': value.textcolor}">
  319. {{word}}
  320. </span>
  321. <!-- 没有限制 -->
  322. <span v-if="! value.limit.length" :style="{'background': value.color, 'color': value.textcolor}">
  323. 不限制
  324. </span>
  325. </div>
  326.  
  327. <!-- 类目 -->
  328. <div class="info_box" @click="editOn(key)" :style="{'background': value.color, 'color': value.textcolor}">
  329. {{value.info}}
  330. </div>
  331. <!-- 关键字 -->
  332. <div class="words_box" @click="editOn(key)">
  333. <span v-for="(word) in value.words" :style="{'background': value.color, 'color': value.textcolor}">
  334. {{word}}
  335. </span>
  336. </div>
  337. </div>
  338.  
  339. <!-- 分割线 -->
  340. <div class="line"></div>
  341.  
  342. <!-- 修改颜色和删除 -->
  343. <div class="rule_set_box">
  344. <div class="color_box">
  345. <input type="color"
  346. :colorKey="key"
  347. v-model="value.color"
  348. @change="colorChange(key,value.color,value.textcolor)"
  349. >
  350. </div>
  351.  
  352. <div class="textcolor_box">
  353. <input type="color"
  354. :colorKey="key"
  355. v-model="value.textcolor"
  356. @change="colorChange(key,value.color,value.textcolor)"
  357. >
  358. </div>
  359. <div class="del" @click.stop="del_key(key)">删除</div>
  360. </div>
  361. </div>
  362.  
  363. <!-- 编辑视图 -->
  364. <div class="eidt_box" v-show="edit[key]">
  365. <div class="eidt_left">
  366. <!-- 修改作用域 -->
  367. <textarea :limit_key="key" :value="value.limit.toString().replace(/,/g,' ')"></textarea>
  368. <!-- 修改类目信息 -->
  369. <textarea :info_key="key" :value="value.info"></textarea>
  370. <!-- 修改关键字 -->
  371. <textarea :words_key="key" :value="value.words.toString().replace(/,/g,' ')"></textarea>
  372. </div>
  373.  
  374. <!-- 分割线 -->
  375. <div class="line"></div>
  376.  
  377. <!-- 确定 取消 -->
  378. <div class="eidt_right">
  379. <div class="del" @click="editOff(key)">取消</div>
  380. <div class="del" @click="ruleUpdate(key)">确定</div>
  381. </div>
  382.  
  383. </div>
  384.  
  385. </div>
  386.  
  387. <!-- 添加新规则 -->
  388. <div class="add" @click="add_key">+</div>
  389.  
  390. </div>
  391. `
  392. // 模板css
  393. static setingStyle = `
  394. body {
  395. --mian_width: 480px;
  396. --mian_color: #189fd8;
  397. --radius: 5px;
  398. --info_color: #eaeaea;
  399. --font_color: #676767;
  400. }
  401. .seting_box {
  402. width: 500px;
  403. max-height: 800px;
  404. overflow: auto;
  405. background: white;
  406. border-radius: 5px;
  407. position: fixed;
  408. transform: translate(-50%, 0);
  409. top: 50px;
  410. left: 50%;
  411. border: 1px solid rgba(0, 0, 0, 0.1);
  412. padding: 15px 5px;
  413. flex-direction: column;
  414. align-items: center;
  415. z-index: 10000;
  416. display: flex;
  417. box-shadow: 0 1px 5px 5px rgba(0, 0, 0, 0.1);
  418. }
  419. .option_box {
  420. width: var(--mian_width);
  421. display: flex;
  422. justify-content: space-between;
  423. }
  424. .option_box div {
  425. display: flex;
  426. height: 20px;
  427. align-items: center;
  428. padding: 5px 10px;
  429. background: var(--mian_color);
  430. color: white;
  431. border-radius: var(--radius);
  432. cursor: pointer;
  433. }
  434. .rule_list_box {
  435. width: var(--mian_width);
  436. border-radius: var(--radius);
  437. margin-top: 10px;
  438. padding: 5px 0px;
  439. box-shadow: 0 0 5px 0px #e2e2e2;
  440. cursor: pointer;
  441. }
  442. .rule_list_box .show_box {
  443. display: flex;
  444. justify-content: space-between;
  445. }
  446. .rule_list_box .show_box .show_left {
  447. width: 410px;
  448. }
  449. .rule_list_box .show_box .show_left > div {
  450. margin-top: 5px;
  451. }
  452. .rule_list_box .show_box .show_left > div:nth-child(1) {
  453. margin-top: 0px;
  454. }
  455. .rule_list_box .show_box .show_left .info_box {
  456. margin-left: 5px;
  457. margin-right: 5px;
  458. padding: 2px 5px;
  459. min-height: 22px;
  460. color: white;
  461. border-radius: var(--radius);
  462. background-color: var(--mian_color);
  463. display: flex;
  464. align-items: center;
  465. }
  466. .rule_list_box .show_box .show_left .words_box {
  467. margin-top: 0px;
  468. /* border: 1px solid black; */
  469. min-height: 20px;
  470. display: flex;
  471. flex-wrap: wrap;
  472. }
  473. .rule_list_box .show_box .show_left .words_box span {
  474. background-color: var(--info_color);
  475. color: white;
  476. padding: 2px 5px;
  477. border-radius: var(--radius);
  478. margin-left: 5px;
  479. margin-top: 5px;
  480. display: flex;
  481. align-items: center;
  482. height: 20px;
  483. }
  484. .rule_list_box .show_box .rule_set_box {
  485. display: flex;
  486. flex-direction: column;
  487. justify-content: space-around;
  488. padding: 0px 5px;
  489. }
  490. .rule_list_box .show_box .rule_set_box .color_box .textcolor_box {
  491. overflow: hidden;
  492. display: flex;
  493. justify-content: center;
  494. align-items: center;
  495. }
  496. .rule_list_box .show_box .rule_set_box .color_box .textcolor_box input {
  497. width: 50px;
  498. height: 25px;
  499. border-radius: var(--radius) !important;
  500. padding: 0px;
  501. }
  502. .rule_list_box .eidt_box {
  503. padding: 0px 5px;
  504. display: flex;
  505. flex-direction: row;
  506. justify-content: space-between;
  507. }
  508. .rule_list_box .eidt_box .eidt_left {
  509. width: 400px;
  510. }
  511. .rule_list_box .eidt_box .eidt_left textarea {
  512. width: 100% !important;
  513. min-height: 30px !important;
  514. border: none;
  515. outline: none;
  516. color: var(--font_color);
  517. background-color: var(--info_color);
  518. border-radius: var(--radius);
  519. margin-top: 5px;
  520. padding: 5px;
  521. }
  522. .rule_list_box .eidt_box .eidt_left textarea:nth-child(1) {
  523. margin-top: 0px;
  524. }
  525. .rule_list_box .eidt_box .eidt_right {
  526. display: flex;
  527. flex-direction: column;
  528. justify-content: space-around;
  529. }
  530. .rule_list_box .line {
  531. width: 1px;
  532. background-color: rgba(0, 0, 0, 0.1);
  533. }
  534. .rule_list_box .del {
  535. background-color: var(--mian_color);
  536. color: white;
  537. border-radius: var(--radius);
  538. padding: 0px 10px;
  539. font-size: 15px;
  540. display: flex;
  541. justify-content: center;
  542. align-items: center;
  543. cursor: pointer;
  544. }
  545. .add {
  546. width: var(--mian_width);
  547. height: 30px;
  548. background: #189fd8;
  549. color: white;
  550. display: flex;
  551. justify-content: center;
  552. border-radius: 5px;
  553. padding: 5px 0px;
  554. margin-top: 10px;
  555. align-items: center;
  556. font-size: 35px;
  557. font-weight: 100;
  558. cursor: pointer;
  559. }
  560. .bt {
  561. display: flex;
  562. align-items: center;
  563. justify-content: space-around;
  564. }
  565. input {
  566. border: none;
  567. padding: 0px;
  568. border-radius: 5px;
  569. box-shadow: none;
  570. }
  571. .config_file {
  572. display: none;
  573. }
  574. @media (max-width: 768px) {
  575.  
  576. .option_box {
  577. width: 95%;
  578.  
  579. }
  580. .option_box div {
  581. padding: 5px;
  582. font-size: 14px; /* 修改按钮字体大小 */
  583. }
  584.  
  585. .rule_list_box {
  586. width: 95%;
  587. }
  588. .rule_list_box .show_box .show_left {
  589. width: 70%;
  590. }
  591. .rule_list_box .eidt_box .eidt_left {
  592. width: 70%;
  593. }
  594. .rule_list_box .option_box div {
  595. padding: 3px; /* 修改按钮内边距 */
  596. font-size: 12px; /* 修改按钮字体大小 */
  597.  
  598. }
  599. .seting_box {
  600. width: 90%; /* 容器宽度为视口宽度的90% */
  601. max-height: 80vh; /* 最大高度为视口高度的80% */
  602. top: 10%; /* 顶部距离为视口高度的10% */
  603. bottom: 10%; /* 底部距离为视口高度的10% */
  604. transform: translate(-50%, 0); /* 居中显示 */
  605. left: 50%; /* 水平居中 */
  606. z-index: 10000; /* 设置一个较高的层叠顺序 */
  607. }
  608.  
  609. }
  610.  
  611.  
  612. `
  613. // 开发用
  614. static devCss() {
  615. GM_xmlhttpRequest({
  616. method: 'get',
  617. url: 'http://127.0.0.1:1145',
  618. responseType: 'blob',
  619. onload: (res) => {
  620. // console.log(res.responseText);
  621. GM_addStyle(res.responseText)
  622. },
  623. onerror: (error => {
  624. console.log('该页无法链接')
  625. })
  626. })
  627. }
  628.  
  629. static create() {
  630. // 获取根节点
  631. let seting_box = document.querySelector('#mt_seting_box')
  632. seting_box.innerHTML = this.setingTemplate
  633.  
  634.  
  635. // 创建根节点样式
  636. GM_addStyle(this.setingStyle)
  637. // this.devCss()
  638.  
  639. // 创建响应式ui
  640. const mt_Vue = new Vue({
  641. el: '#mt_seting_box',
  642. data() {
  643. return {
  644. rule: GM_getValue('key'),
  645. edit: this.addEdit(GM_getValue('key')),
  646. showSeting: false,
  647. config_add: false
  648. }
  649. },
  650.  
  651. watch: {
  652. showSeting(n) {
  653. // console.log(22333);
  654. }
  655. },
  656.  
  657. methods: {
  658. // 关闭设置
  659. close_seting() {
  660. this.showSeting = false
  661. },
  662.  
  663. // 开启设置
  664. open_seting() {
  665. this.showSeting = true
  666. console.log(2233)
  667. },
  668. // 添加属性开关
  669. addEdit(rules) {
  670. let a = {}
  671. Object.keys(rules).forEach(key => {
  672. a[key] = false
  673. })
  674. return a
  675. },
  676.  
  677. // 打开编辑
  678. editOn(key) {
  679. this.edit[key] = true
  680. },
  681.  
  682. // 关闭编辑
  683. editOff(key) {
  684. this.edit[key] = false
  685. },
  686.  
  687. // 颜色更新
  688. colorChange(key, color, textcolor) {
  689. document.querySelectorAll(`.mt_highlight[classesKey="${key}"]`).forEach(node => {
  690. node.style.background = color
  691. node.style.color = textcolor
  692. })
  693. // 保存到油猴中
  694. GM_setValue('key', this.rule)
  695. },
  696.  
  697. // 更新规则
  698. ruleUpdate(key) {
  699. let newInfo = document.querySelector(`textarea[info_key=${key}]`).value
  700. let newWords = (document.querySelector(`textarea[words_key=${key}]`).value.split(' '))
  701. let newLimit = (document.querySelector(`textarea[limit_key=${key}]`).value.split(' '))
  702.  
  703. // 去除空格
  704. newWords = Array.from(new Set(newWords))
  705. .filter(word => { return word != ' ' & word.length > 0 })
  706. newLimit = Array.from(new Set(newLimit))
  707. .filter(word => { return word != ' ' & word.length > 0 })
  708. // console.log(newInfo,newWords);
  709. this.rule[key].info = newInfo
  710. this.rule[key].words = newWords
  711. this.rule[key].limit = newLimit
  712.  
  713. this.editOff(key)
  714.  
  715. // 保存到油猴中
  716. GM_setValue('key', this.rule)
  717. },
  718.  
  719. // 添加新规则
  720. add_key() {
  721. let key = 'key_' + Math.floor(Math.random() * 10000000000)
  722. this.$set(this.rule, key, {
  723. info: '',
  724. words: [],
  725. color: '#dc6c75',
  726. textcolor: '#3467eb',
  727. limit: []
  728. })
  729.  
  730. this.$set(this.edit, key, false)
  731.  
  732. // 保存到油猴中
  733. GM_setValue('key', this.rule)
  734. console.log(2233)
  735. },
  736.  
  737. // 删除规则
  738. del_key(key) {
  739. let ready = confirm('确认删除,该操作不可恢复')
  740.  
  741. if (ready && Object.keys(this.rule).length > 1) {
  742. this.$delete(this.rule, key)
  743. this.$delete(this.edit, key)
  744. } else if (ready && Object.keys(this.rule).length < 2) {
  745. alert('至少保留一个')
  746. }
  747.  
  748. // 保存到油猴中
  749. GM_setValue('key', this.rule)
  750. },
  751.  
  752. // 复制到粘贴板
  753. copy() {
  754. navigator.clipboard.writeText(JSON.stringify(this.rules))
  755. },
  756.  
  757. // 获取配置覆盖
  758. config_in() {
  759. document.querySelector('.config_file').click()
  760. this.config_add = false
  761. },
  762. // 获取配置添加
  763. config_in_add() {
  764. document.querySelector('.config_file').click()
  765. this.config_add = true
  766. },
  767.  
  768. // 解析配置
  769. importFileJSON(ev) {
  770. return new Promise((resolve, reject) => {
  771. const fileDom = ev.target,
  772. file = fileDom.files[0]
  773.  
  774. // 格式判断
  775. if (file.type !== 'application/json') {
  776. reject('仅允许上传json文件')
  777. }
  778. // 检验是否支持FileRender
  779. if (typeof FileReader === 'undefined') {
  780. reject('当前浏览器不支持FileReader')
  781. }
  782.  
  783. // 执行后清空input的值,防止下次选择同一个文件不会触发onchange事件
  784. ev.target.value = ''
  785.  
  786. // 执行读取json数据操作
  787. const reader = new FileReader()
  788. reader.readAsText(file) // 读取的结果还有其他读取方式,我认为text最为方便
  789.  
  790. reader.onerror = (err) => {
  791. reject('json文件解析失败', err)
  792. }
  793.  
  794. reader.onload = () => {
  795. const resultData = reader.result
  796. if (resultData) {
  797. try {
  798. const importData = JSON.parse(resultData)
  799. resolve(importData)
  800. } catch (error) {
  801. reject('读取数据解析失败', error)
  802. }
  803. } else {
  804. reject('读取数据解析失败', error)
  805. }
  806. }
  807. })
  808. },
  809.  
  810. // 保存配置到本地
  811. file_read(e) {
  812. this.importFileJSON(e).then(res => {
  813. // 合并还是覆盖
  814. if (this.config_add) {
  815. let cache = {}
  816. Object.keys(GM_getValue('key')).forEach(key => {
  817. cache['key_' + Math.floor(Math.random() * 10000000000)] = GM_getValue('key')[key]
  818. })
  819. cache = { ...cache, ...res }
  820. console.log(cache)
  821.  
  822. GM_setValue('key', cache)
  823. } else {
  824. GM_setValue('key', res)
  825. }
  826. initialize()
  827. this.rule = GM_getValue('key')
  828. this.edit = this.addEdit(res)
  829. })
  830. },
  831.  
  832. // 导出配置
  833. config_out() {
  834. function exportJson(name, data) {
  835. let blob = new Blob([data]) // 创建 blob 对象
  836. let link = document.createElement('a')
  837. link.href = URL.createObjectURL(blob) // 创建一个 URL 对象并传给 a 的 href
  838. link.download = name // 设置下载的默认文件名
  839. link.click()
  840. }
  841.  
  842. exportJson('mt_hight_light_config.json', JSON.stringify(this.rule))
  843.  
  844. },
  845.  
  846. // 刷新
  847. refresh() {
  848. location.reload()
  849. }
  850.  
  851. },
  852.  
  853. mounted() {
  854. GM_registerMenuCommand('打开设置', this.open_seting)
  855. // 点击其他区域关闭设置
  856. document.body.addEventListener('click', (e) => {
  857. // 检查是否是移动设备
  858. if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
  859. if (!document.querySelector('#mt_seting_box').contains(e.target)) {
  860. this.close_seting()
  861. }
  862. }
  863. })
  864.  
  865. }
  866. })
  867.  
  868. }
  869. }
  870.  
  871.  
  872. ///////////////////////////////////////////////////////////
  873. // vue根节点
  874. let seting_box = document.createElement('div') // 创建一个节点
  875. seting_box.setAttribute('id', 'mt_seting_box') // 设置一个属性
  876. document.body.appendChild(seting_box)
  877.  
  878. GM_addStyle(HIGHTLIGHT.highlightStyle)
  879.  
  880. // 初始化数据
  881. initialize()
  882. console.log(GM_getValue('key'))
  883.  
  884. // 静态页面的检测
  885. let nodeMap = textMap(document.body)
  886. // console.log(nodeMap);
  887. HIGHTLIGHT.highlight(nodeMap)
  888. nodeMap.clear()
  889.  
  890. // 减少节点的重复检测
  891. let doOnce = ((wait) => {
  892. let timer = null
  893. // 存储动态更新的节点
  894. let elMap = new Map
  895.  
  896. return (el) => {
  897. // 添加节点
  898. elMap.set(el)
  899. if (!timer) {
  900. timer = setTimeout(() => {
  901. // console.log(elMap);
  902. elMap.forEach((value, el) => {
  903. setTimeout(() => {
  904.  
  905. let nodeMap = textMap(el)
  906. HIGHTLIGHT.highlight(nodeMap)
  907. nodeMap = null
  908.  
  909. }, 1)
  910. })
  911. elMap.clear()
  912. timer = null
  913. }, wait)
  914. }
  915. }
  916. })(100)
  917. // 动态更新内容的检测
  918. watch()
  919.  
  920.  
  921. // 创建ui
  922. GUI.create()
  923. }
  924. )()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址