网页高亮关键字

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

От 11.09.2024. Виж последната версия.

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