网页高亮关键字

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

Stan na 06-09-2024. Zobacz najnowsza wersja.

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