Greasy Fork is available in English.

网页高亮关键字

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

Stan na 31-07-2024. Zobacz najnowsza wersja.

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