├── .gitignore ├── 128.png ├── 16.png ├── 48.png ├── screenshot.png ├── _locales ├── zh_CN │ └── messages.json └── en │ └── messages.json ├── popup.js ├── highlight.css ├── manifest.json ├── README.md ├── PRIVACY.md ├── background.js ├── popup.html └── highLight.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qishibo/highlightCode/HEAD/128.png -------------------------------------------------------------------------------- /16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qishibo/highlightCode/HEAD/16.png -------------------------------------------------------------------------------- /48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qishibo/highlightCode/HEAD/48.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qishibo/highlightCode/HEAD/screenshot.png -------------------------------------------------------------------------------- /_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "message": "高亮关键词" 4 | }, 5 | "description": { 6 | "message": "双击选中某个单词/关键词/关键字,页面内其他位置的该单词自动高亮,比如查找某个function定义,双击该function,页面内所有该function关键词自动高亮,码农看代码神器 ^_^" 7 | }, 8 | "shortcut": { 9 | "message": "选中后高亮" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "message": "Highlight Keyword" 4 | }, 5 | "description": { 6 | "message": "Double-click a word/keyword to automatically highlight the word elsewhere on the page" 7 | }, 8 | "shortcut": { 9 | "message": "Highlight After Selection" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | // 初始化popup展示 2 | chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { 3 | chrome.tabs.sendMessage(tabs[0].id, {action: "init_switch"}, function(data) { 4 | console.log('init switch', data); 5 | if (data == undefined) { 6 | document.getElementById('desc-extra').innerHTML = '抱歉,在该页面无法获得操作权限'; 7 | return; 8 | } 9 | document.getElementById('radio').checked = (data.switch == 1) ? false : true; 10 | }); 11 | }); 12 | 13 | 14 | // 点击时设置开关 15 | document.getElementById("radio").onclick = function () { 16 | var switch_opt = document.getElementById('radio').checked; 17 | 18 | chrome.tabs.query({active: true, currentWindow: true}, function (tabs){ 19 | chrome.tabs.sendMessage(tabs[0].id, {action: "toggle_switch", "switch": switch_opt}); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /highlight.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 高亮词css样式 3 | */ 4 | 5 | .qii404-highlight { 6 | background: #ffff00 !important; 7 | color: black !important; 8 | } 9 | 10 | #qii404-right-summary { 11 | position: fixed; 12 | right: 0; 13 | top: 0; 14 | height: 100%; 15 | width: 12px; 16 | background-color: #F1F1F1; 17 | z-index: 99999; 18 | } 19 | 20 | .qii404-right-total { 21 | position: absolute; 22 | top: 5px; 23 | right: 18px; 24 | width: 90px; 25 | padding: 3px; 26 | font-size: 10px; 27 | border: 2px solid #bdb7b7; 28 | border-radius: 3px; 29 | background-color: #F1F1F1; 30 | } 31 | 32 | .qii404-right-summary-tag { 33 | width: 100%; 34 | height: 3px; 35 | position: absolute; 36 | /*#900 #969646 #ffff00*/ 37 | border: 1px solid #ff9632; 38 | box-sizing: border-box; 39 | background-color: #ffff00; 40 | } 41 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_title__", 3 | "description": "__MSG_description__", 4 | "version": "0.2.0", 5 | "manifest_version": 3, 6 | "permissions" : ["contextMenus", "tabs"], 7 | "background": { 8 | "service_worker": "background.js" 9 | }, 10 | "content_scripts":[{ 11 | "matches":["http://*/*", "http://*/", "https://*/*", "https://*/"], 12 | "js":["highLight.js"], 13 | "css":["highlight.css"], 14 | "all_frames": true 15 | }], 16 | "icons": { 17 | "16": "16.png", 18 | "48": "48.png", 19 | "128": "128.png" 20 | }, 21 | "action": { 22 | "default_popup": "popup.html" 23 | }, 24 | "default_locale": "zh_CN", 25 | "commands" : { 26 | "highlight" : { 27 | "suggested_key" : { 28 | "default" : "Alt+S" 29 | } , 30 | "description" : "__MSG_shortcut__" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # highlightCode 关键词高亮 2 | chrome拓展:双击选中某个词,页面内其他位置的相同词汇自动高亮! 3 | 4 | 比如查找某个function定义,双击该function,页面内所有该function自动高亮,**码农看代码神器**^_^ 5 | 6 | 7 | ![screenshot](screenshot.png) 8 | 9 | ## 安装 10 | 1. [chrome商店](https://chrome.google.com/webstore/detail/%E5%85%B3%E9%94%AE%E8%AF%8D%E5%8F%8C%E5%87%BB%E8%87%AA%E5%8A%A8%E9%AB%98%E4%BA%AE/hiemiigjnmkjedjibioplldlbkhekbjk?gl=CN) 11 | 12 | 2. **源码安装方式** 13 | 14 | `chrome 设置->更多工具->拓展程序->加载已解压的拓展程序 选择你解压之后的文件夹即可` 15 | 16 | ![chrome 设置->更多工具->拓展程序->加载已解压的拓展程序 选择你解压之后的文件夹即可](https://cdn.jsdelivr.net/gh/qishibo/img/202109031631707.jpeg) 17 | 18 | ---------------------------- 19 | 20 | ## 更新日志: 21 | 22 | - 2025-03-07 更新至manifest-v3 23 | - 2021-08-04 右侧增加匹配总数 24 | - 2020-07-03 修复双击时额外选中首尾空格造成的不能匹配 25 | - 2018-06-04 增加右侧位置预览,类似ctrl+F右侧边栏预览 26 | - 2018-05-03 支持iframe内高亮 27 | - 2018.04.13 点击拓展图标,可以设置双击时临时禁用高亮功能,防止在某些大页面内卡顿 28 | - 2018.04.12 选中长语句后,以前只能右键菜单选择高亮,现在通过`Alt+S`快捷键即可实现,再次按下取消高亮 29 | - 2017.11.14 某些情况下插入span会命中原有样式导致样式走形,改为自定义标签 30 | - 2016.11.30 增加右键菜单支持,可以选中多个字词,然后右键选择高亮相同字词 31 | - 2016.11.08 修复特殊字符如. * $等高亮的bug 32 | - 2016.10.02 双击关键词高亮 双击空白处|单击页面取消高亮 33 | - 2016.09.22 更改为遍历dom进行高亮 匹配更加精准 34 | - 2016.04.15 简单的替换body 简单粗暴 35 | 36 | 37 | 38 | ## 快捷键设置 39 | 40 | > 设置`Alt+S`实现选中词句的高亮和取消高亮 41 | 42 | 1. 拓展管理页最底部 43 | ![拓展管理页最底部](https://cdn.jsdelivr.net/gh/qishibo/img/202109031631656.png) 44 | 45 | 2. 设置高亮页面内容热键 46 | ![设置高亮页面内容热键](https://cdn.jsdelivr.net/gh/qishibo/img/202109031632795.png) 47 | 48 | ---------------------------- 49 | 50 | [@齐士博](http://www.weibo.com/shiboooo) 51 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | We takes your privacy seriously. To better protect your privacy we provide this privacy policy notice explaining the way your personal information is collected and used. 4 | 5 | 6 | ## Collection of Routine Information 7 | 8 | This app track basic information about their users. This information includes, but is not limited to, App details, timestamps, bug stacks and referring pages. None of this information can personally identify specific user to this app. The information is tracked for bug fixing and app maintenance purposes. 9 | 10 | And please note that this routine information is stored locally by the user. Unless the user provides it voluntarily, we cannot obtain this information and we won't upload this information to the internet. 11 | 12 | 13 | ## Links to Third Party Websites 14 | 15 | We might included links on this app for your use and reference. we are not responsible for the privacy policies on these websites. You should be aware that the privacy policies of these websites may differ from ours. 16 | 17 | 18 | ## Changes To This Privacy Policy 19 | 20 | This Privacy Policy is effective as of 2020-01-01 and will remain in effect except with respect to any changes in its provisions in the future, which will be in effect immediately after being posted on this page. 21 | 22 | We reserve the right to update or change our Privacy Policy at any time and you should check this Privacy Policy periodically. If we make any material changes to this Privacy Policy, we will notify you either through the email address you have provided us, or by placing a prominent notice on our app. 23 | 24 | 25 | ## Contact Information 26 | 27 | For any questions or concerns regarding the privacy policy, please contact by shiboqi123@gmail.com. 28 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created on : 2016-11-30 14:39:32 星期三 3 | * Encoding : UTF-8 4 | * Description: 后台脚本 5 | * 6 | * @author @齐士博 7 | */ 8 | 9 | var qii404 = { 10 | 11 | /* 12 | * 右键菜单标题 13 | */ 14 | menuContext: '高亮页面内容 %s', 15 | 16 | /* 17 | * 创建右键菜单标识 18 | */ 19 | menuCreated: false, 20 | 21 | /* 22 | * 初始化 23 | */ 24 | init: function() { 25 | this.bindRequest(); 26 | this.bindCommand(); 27 | }, 28 | 29 | /* 30 | * 绑定接受content消息 31 | */ 32 | bindRequest: function() { 33 | 34 | var this_ = this; 35 | 36 | chrome.runtime.onMessage.addListener(function (request, sender, response){ 37 | // chrome.extension.onRequest.addListener(function (request){ 38 | 39 | console.log('receving content message', request); 40 | 41 | if (request['action'] === 'createMenu' && !this_.menuCreated) { 42 | this_.createHighlightMenu(); 43 | this_.menuCreated = true; 44 | } 45 | 46 | response(true); 47 | }); 48 | }, 49 | 50 | /* 51 | * 绑定快捷键命令 52 | */ 53 | bindCommand: function () { 54 | var this_ = this; 55 | chrome.commands.onCommand.addListener(function (command) { 56 | console.log('get command: ', command); 57 | 58 | switch (command) 59 | { 60 | case 'highlight': 61 | this_.contentHighlight(); 62 | break; 63 | } 64 | }); 65 | }, 66 | 67 | /* 68 | * 创建右键菜单 69 | */ 70 | createHighlightMenu: function() { 71 | console.log('creating menu...'); 72 | 73 | var menuProperties = { 74 | 'id': 'highlight_code', 75 | 'title' : this.menuContext, 76 | 'contexts' : ['all'], 77 | // 'onclick' : this.contentHighlight 78 | }; 79 | 80 | chrome.contextMenus.create(menuProperties); 81 | chrome.contextMenus.onClicked.addListener(this.contentHighlight); 82 | }, 83 | 84 | /* 85 | * 被点击 告知content进行高亮操作 86 | */ 87 | contentHighlight: function(clickData, tab) 88 | { 89 | console.log('telling content to highlight...'); 90 | 91 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { 92 | chrome.tabs.sendMessage(tabs[0].id, {'action': 'highlight'}, function(response) { 93 | response && console.log('background reveved from content...', response); 94 | }); 95 | }); 96 | } 97 | } 98 | 99 | qii404.init(); 100 | 101 | // end of file context.js 102 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 高亮设置 6 | 86 | 87 | 88 | 高亮开关 89 | 90 | 95 |

该开关只影响双击时是否高亮,右键和快捷键不受影响

96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /highLight.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created on : 2016-04-15 18:49:33 星期五 3 | * Encoding : UTF-8 4 | * Description: 5 | * chrome拓展 双击选中某个词语 页面内其他相同词语自动高亮 6 | * 码农看代码福利 哈哈 7 | * 8 | * @author @齐士博 9 | */ 10 | 11 | var qii404 = { 12 | 13 | /* 14 | * 自定义高亮标签 15 | */ 16 | highlightTag: 'qii', 17 | 18 | /* 19 | * 高亮css类 20 | */ 21 | highlightClass: 'qii404-highlight', 22 | 23 | /* 24 | * 右侧概览css类 25 | */ 26 | rightSummaryClass: 'qii404-right-summary', 27 | 28 | /* 29 | * 右侧匹配总数css类 30 | */ 31 | rightTotalClass: 'qii404-right-total', 32 | 33 | /* 34 | * 右侧概览标记点css类 35 | */ 36 | rightSummaryTagClass: 'qii404-right-summary-tag', 37 | 38 | /* 39 | * 开关设置存储key 40 | */ 41 | switchKey: 'qii404-highlight-switch', 42 | 43 | /* 44 | * 点击事件 45 | */ 46 | clickEvent: null, 47 | 48 | /* 49 | * 高亮元素高度集合列表 50 | */ 51 | eleTopList: [], 52 | 53 | /** 54 | * 初始化 绑定事件 55 | */ 56 | init: function() { 57 | this.bindClick(); 58 | this.createMenu(); 59 | this.bindMenuMsg(); 60 | }, 61 | 62 | /* 63 | * 绑定点击事件 64 | */ 65 | bindClick: function () { 66 | this.singleClick(); 67 | this.doubleClick(); 68 | }, 69 | 70 | /** 71 | * 创建右键菜单 72 | */ 73 | createMenu: function() { 74 | chrome.runtime.sendMessage({"action": "createMenu"}); 75 | // chrome.extension.sendRequest({"action": "createMenu"}); 76 | }, 77 | 78 | /* 79 | * 高亮开关是否打开 80 | */ 81 | stillHighlight: function () { 82 | return localStorage.getItem(this.switchKey) == 1 ? false : true; 83 | }, 84 | 85 | /** 86 | * 绑定右键菜单点击时的操作 87 | */ 88 | bindMenuMsg: function() { 89 | 90 | var this_ = this; 91 | 92 | chrome.runtime.onMessage.addListener(function(request, sender, response) { 93 | 94 | // 高亮消息 95 | if (request.action === 'highlight') { 96 | // 后台发送消息,如果未高亮则高亮,否则取消高亮 实现toggle 97 | var highlights = document.querySelectorAll(this_.highlightTag + '.' + this_.highlightClass); 98 | // 已经高亮 99 | if (highlights.length > 0) { 100 | this_.removeHighlight(); 101 | return; 102 | } 103 | 104 | // 高亮开关已关闭 返回 105 | // if (!this_.stillHighlight()) return; 106 | 107 | // 进行高亮操作 108 | var selectedText = window.getSelection().toString(); 109 | 110 | console.log(selectedText); 111 | 112 | if(selectedText.length > 0 && selectedText.replace(/(\s)/g, '') != '') { 113 | this_.beginToHighlight(document.body, selectedText); 114 | } 115 | response(true); 116 | } 117 | 118 | // 初始化 popup 119 | else if (request.action === 'init_switch') { 120 | response({switch: localStorage.getItem(this_.switchKey)}); 121 | } 122 | 123 | // 开关切换 124 | else if (request.action === 'toggle_switch') { 125 | localStorage.setItem(this_.switchKey, request.switch ? 0 : 1); 126 | response(true); 127 | } 128 | }); 129 | }, 130 | 131 | /* 132 | * 高亮入口 133 | */ 134 | beginToHighlight: function (node, keyWord, selectText = false) { 135 | // 高亮时去掉首位空格后再匹配 136 | keyWord = keyWord.replace(/(^\s*)|(\s*$)/g, ''); 137 | 138 | this.mapToHighlight(node, keyWord); 139 | selectText && this.selectText(); 140 | this.renderRightSummary(); 141 | }, 142 | 143 | /* 144 | * 遍历节点进行高亮 145 | */ 146 | mapToHighlight: function(node, keyWord) { 147 | 148 | if (node.nodeType === 3) { 149 | if (node.data.replace(/(\s)/g, '') != '') { 150 | this.highlight(node, keyWord); 151 | } 152 | } 153 | 154 | else if ( 155 | (node.nodeType === 1) && 156 | node.childNodes && 157 | !/(script|style)/i.test(node.tagName) && 158 | !(node.tagName === this.highlightTag.toUpperCase() && node.className === this.highlightClass) 159 | ) { 160 | for (var i = 0; i < node.childNodes.length; i++) { 161 | this.mapToHighlight(node.childNodes[i], keyWord); 162 | } 163 | } 164 | }, 165 | 166 | /* 167 | * 根据node进行关键词高亮 168 | */ 169 | highlight: function(node, keyWord) { 170 | 171 | var match = node.data.match(this.initRegex(keyWord)); 172 | 173 | if (match === null) { 174 | return false; 175 | } 176 | 177 | // 高度数据备用 178 | this.addToTopList(node); 179 | 180 | var newNode = node.splitText(match.index); 181 | 182 | newNode.splitText(match[0].length); 183 | 184 | var highlightNode = this.createHighlightNode() 185 | highlightNode.appendChild(newNode.cloneNode(true)); 186 | 187 | newNode.parentNode.replaceChild(highlightNode, newNode); 188 | }, 189 | 190 | /* 191 | * 增加高亮元素节点高度到集合备用 192 | */ 193 | addToTopList: function (node) { 194 | // console.log(node.parentNode.getBoundingClientRect()); 195 | // 相对可视区域左上角距离 196 | var relativeTop = node.parentNode.getBoundingClientRect().y; 197 | 198 | if (relativeTop) { 199 | // 计算出距离真实顶部的绝对距离 200 | relativeTop += document.documentElement.scrollTop; 201 | this.eleTopList.push(relativeTop); 202 | } 203 | }, 204 | 205 | /* 206 | * 初始化正则 207 | */ 208 | initRegex: function(keyWord) { 209 | 210 | // 针对特殊字符的转义 211 | keyWord = keyWord.replace(/(\^|\$|\.|\*|\?|\(|\)|\+|\\)/ig, "\\$1"); 212 | 213 | return RegExp(keyWord, 'i'); 214 | }, 215 | 216 | /* 217 | * 创建高亮的新元素节点 218 | */ 219 | createHighlightNode: function() { 220 | var node = document.createElement(this.highlightTag); 221 | node.className = this.highlightClass; 222 | 223 | return node; 224 | }, 225 | 226 | /* 227 | * 选择文字 228 | */ 229 | selectText: function() { 230 | var e = this.clickEvent; 231 | 232 | var clickX = e.clientX; 233 | var clickY = e.clientY; 234 | 235 | var highlights = e.srcElement.querySelectorAll(this.highlightTag + '.' + this.highlightClass); 236 | 237 | for (var i = 0; i < highlights.length; i++) { 238 | var rect = highlights[i].getBoundingClientRect(); 239 | if (rect.left) { 240 | if ( 241 | (clickX >= rect.left) && 242 | (clickX <= rect.right) && 243 | (clickY >= rect.top) && 244 | (clickY <= rect.bottom) 245 | ) { 246 | this.selectNode(highlights[i]); 247 | break; 248 | } 249 | } 250 | } 251 | }, 252 | 253 | /* 254 | * 渲染右侧预览栏 255 | */ 256 | renderRightSummary: function() { 257 | var eleTops = this.eleTopList; 258 | var rightDiv = document.createElement('div'); 259 | rightDiv.id = this.rightSummaryClass; 260 | 261 | // 匹配总数 262 | var totalDiv = document.createElement('div'); 263 | totalDiv.innerHTML = 'Total: ' + this.eleTopList.length; 264 | totalDiv.className = this.rightTotalClass; 265 | rightDiv.appendChild(totalDiv); 266 | 267 | // 页面高度 268 | var clientHeight = document.body.clientHeight; 269 | // 窗口可见高度 270 | var windowHeight = document.documentElement.clientHeight; 271 | 272 | for (var i = 0; i < eleTops.length; i++) { 273 | var tag = document.createElement('div'); 274 | tag.className = this.rightSummaryTagClass; 275 | 276 | tag.style.top = (eleTops[i] / clientHeight) * windowHeight + 'px'; 277 | rightDiv.appendChild(tag); 278 | } 279 | 280 | window.parent.document.body.appendChild(rightDiv); 281 | }, 282 | 283 | /* 284 | * 选中文字 285 | */ 286 | selectNode: function(node) { 287 | selection = window.getSelection(); 288 | range = document.createRange(); 289 | 290 | range.selectNodeContents(node); 291 | 292 | selection.removeAllRanges(); 293 | selection.addRange(range); 294 | }, 295 | 296 | /* 297 | * 取消所有高亮 298 | */ 299 | removeHighlight: function() { 300 | var highlights = document.querySelectorAll(this.highlightTag + '.' + this.highlightClass); 301 | 302 | for (var i=0; i< highlights.length; i++) { 303 | var highlightNode = highlights[i]; 304 | var parentNode = highlightNode.parentNode; 305 | 306 | parentNode.replaceChild(highlightNode.firstChild, highlightNode); 307 | parentNode.normalize(); 308 | } 309 | 310 | // 取消右侧预览 311 | this.removeRightSummary(); 312 | }, 313 | 314 | /* 315 | * 取消右侧预览 316 | */ 317 | removeRightSummary: function () { 318 | this.eleTopList = []; 319 | 320 | var ele = window.document.getElementById(this.rightSummaryClass); 321 | ele && ele.remove(); 322 | 323 | var ele = window.parent.document.getElementById(this.rightSummaryClass); 324 | ele && ele.remove(); 325 | }, 326 | 327 | /* 328 | * 绑定单击事件 329 | */ 330 | singleClick: function() { 331 | 332 | var this_ = this; 333 | document.body.addEventListener('click', function(e) { 334 | this_.removeHighlight(); 335 | }); 336 | }, 337 | 338 | /* 339 | * 双击绑定 340 | */ 341 | doubleClick: function() { 342 | var this_ = this; 343 | 344 | document.body.addEventListener('dblclick', function(e) { 345 | 346 | // 高亮开关已关闭 返回 347 | if (!this_.stillHighlight()) return; 348 | 349 | if (e.srcElement.className == this_.highlightClass) { 350 | return false; 351 | } 352 | 353 | if (document.activeElement) { 354 | var selectedText = window.getSelection().toString(); 355 | 356 | if(selectedText.length > 0 && selectedText.replace(/(\s)/g, '') != '') { 357 | console.log(selectedText); 358 | this_.clickEvent = e; 359 | // this_.removeHighlight(); 360 | 361 | this_.beginToHighlight(document.body, selectedText, true); 362 | } 363 | } 364 | }, false); 365 | } 366 | } 367 | 368 | qii404.init(); 369 | 370 | // end of file highLight.js 371 | --------------------------------------------------------------------------------