├── .gitignore ├── Assets ├── Icon.sketch └── Screenshot.png ├── Info.plist ├── LICENSE ├── README.md ├── Update.plist ├── dist.sh ├── github-highlight-selected.safariextz ├── icon-128.png ├── icon-48.png ├── icon-64.png ├── manifest.json └── src └── highlight-selected.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /Assets/Icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuclides/github-highlight-selected/f21457feb8c408fbc47670e1cb913825ff33fa1b/Assets/Icon.sketch -------------------------------------------------------------------------------- /Assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuclides/github-highlight-selected/f21457feb8c408fbc47670e1cb913825ff33fa1b/Assets/Screenshot.png -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Author 6 | CAO Shan 7 | Builder Version 8 | 9537.85.10.17.1 9 | CFBundleDisplayName 10 | GitHub Highlight Selected 11 | CFBundleIdentifier 12 | com.Nuclides.github-highlight-selected 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleShortVersionString 16 | 1.3 17 | CFBundleVersion 18 | 5 19 | Chrome 20 | 21 | Content 22 | 23 | Scripts 24 | 25 | End 26 | 27 | src/highlight-selected.js 28 | 29 | Start 30 | 31 | src/jquery-2.1.1.min.js 32 | 33 | 34 | Stylesheets 35 | 36 | src/style.css 37 | 38 | Whitelist 39 | 40 | https://github.com/* 41 | 42 | 43 | Description 44 | Safari extension for highlighting selected word in GitHub source view like Sublime Text. 45 | DeveloperIdentifier 46 | 7676B49VAW 47 | ExtensionInfoDictionaryVersion 48 | 1.0 49 | Permissions 50 | 51 | Website Access 52 | 53 | Allowed Domains 54 | 55 | github.com 56 | 57 | Include Secure Pages 58 | 59 | Level 60 | Some 61 | 62 | 63 | Update Manifest URL 64 | https://raw.githubusercontent.com/Nuclides/github-highlight-selected/master/Update.plist 65 | Website 66 | https://github.com/Nuclides/github-highlight-selected 67 | 68 | 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 LIU Dongyuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Highlight Selected 2 | 3 | [Download from Chrome App Store](https://chrome.google.com/webstore/detail/github-highlight-selected/lhiklbgjcblimmjjflobpncgihagcmbj) 4 | 5 | [Download Safari Extension](https://github.com/Nuclides/github-highlight-selected/blob/master/github-highlight-selected.safariextz?raw=true) 6 | 7 | Chrome extension for highlighting selected word in GitHub source view like Sublime Text. 8 | 9 | Screenshot 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /Update.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Extension Updates 6 | 7 | 8 | CFBundleIdentifier 9 | com.Nuclides.github-highlight-selected 10 | Developer Identifier 11 | 7676B49VAW 12 | CFBundleVersion 13 | 5 14 | CFBundleShortVersionString 15 | 1.3 16 | URL 17 | https://github.com/Nuclides/github-highlight-selected/blob/master/github-highlight-selected.safariextz?raw=true 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo Distributing the extension... 4 | zip -r ~/Desktop/github-highlight-selected.zip . -x '.*/*' '.*' 'Assets/*' 5 | echo Distribution file created at ~/Desktop/github-highlight-selected.zip 6 | -------------------------------------------------------------------------------- /github-highlight-selected.safariextz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuclides/github-highlight-selected/f21457feb8c408fbc47670e1cb913825ff33fa1b/github-highlight-selected.safariextz -------------------------------------------------------------------------------- /icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuclides/github-highlight-selected/f21457feb8c408fbc47670e1cb913825ff33fa1b/icon-128.png -------------------------------------------------------------------------------- /icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuclides/github-highlight-selected/f21457feb8c408fbc47670e1cb913825ff33fa1b/icon-48.png -------------------------------------------------------------------------------- /icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuclides/github-highlight-selected/f21457feb8c408fbc47670e1cb913825ff33fa1b/icon-64.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GitHub Highlight Selected", 3 | "version": "2.3", 4 | "manifest_version": 2, 5 | 6 | "description": "Highlight selected word in GitHub source view like Sublime Text", 7 | "icons": { 8 | "48": "icon-48.png", 9 | "128": "icon-128.png" 10 | }, 11 | 12 | "content_scripts": [ { 13 | "js": [ "src/highlight-selected.js" ], 14 | "matches": [ "https://github.com/*", "https://gist.github.com/*" ] 15 | } ], 16 | "minimum_chrome_version": "20", 17 | "permissions": [ 18 | "" 19 | ], 20 | 21 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 22 | } 23 | -------------------------------------------------------------------------------- /src/highlight-selected.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function() { 2 | var container = '.blob-wrapper, .js-blob-wrapper'; 3 | var lastPage = location.href; 4 | var textNodes = []; 5 | var highlighted = []; 6 | var highlightedIndex; 7 | 8 | function wrapTextNodes(query) { 9 | // Why did all the gawd dam text selection stuff fail, urgggggghh....at least this worked! 10 | // http://cwestblog.com/2014/03/14/javascript-getting-all-text-nodes/ 11 | function getTextNodesIn(elem, opt_fnFilter) { 12 | if (elem) { 13 | for (var nodes = elem.childNodes, i = nodes.length; i--;) { 14 | var node = nodes[i], 15 | nodeType = node.nodeType; 16 | if (nodeType == 3) { 17 | if (!opt_fnFilter || opt_fnFilter(node, elem)) { 18 | if (node.nodeValue.trim() !== '') { 19 | var span = document.createElement('span'); 20 | span.textContent = node.nodeValue; 21 | node.parentNode.replaceChild(span, node); 22 | textNodes.push(span); 23 | } 24 | } 25 | } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) { 26 | getTextNodesIn(node, opt_fnFilter); 27 | } 28 | } 29 | } 30 | } 31 | var elements = document.querySelectorAll(query); 32 | if (elements.length == 0) return; 33 | textNodes = []; 34 | 35 | for (var el of elements) { 36 | getTextNodesIn(el); 37 | } 38 | 39 | textNodes.reverse(); 40 | } 41 | 42 | function observePullRequestDiffs(mutationObserverOptions) { 43 | var diffExpandMutationObserver = new MutationObserver(function(mutationRecords) { 44 | wrapTextNodes(container); 45 | }); 46 | var diffTables = document.querySelectorAll('.diff-table > tbody'); 47 | for (var diffTable of diffTables) { 48 | diffExpandMutationObserver.observe(diffTable, mutationObserverOptions); 49 | } 50 | } 51 | 52 | wrapTextNodes(container); 53 | 54 | // watch for page updating... 55 | var whatToObserve = { 56 | childList: true, 57 | attributes: false, 58 | subtree: false, 59 | attributeOldValue: false /*, attributeFilter: []*/ 60 | }; 61 | var mutationObserver = new MutationObserver(function(mutationRecords) { 62 | if (location.href != lastPage) { 63 | lastPage = location.href; 64 | wrapTextNodes(container); 65 | observePullRequestDiffs(whatToObserve); 66 | } 67 | }); 68 | var codeContainer = document.querySelector('#js-repo-pjax-container'); 69 | if (!codeContainer) codeContainer = document.querySelector('#gist-pjax-container'); // for gists 70 | if (!codeContainer) codeContainer = document.querySelector('main'); // It looks that GitHub has updated the DOM. Using `main` as a fallback. 71 | mutationObserver.observe(codeContainer, whatToObserve); 72 | observePullRequestDiffs(whatToObserve); 73 | 74 | function restore() { 75 | highlighted.forEach(function(el) { 76 | if (el.classList.contains('ghs-highlight')) el.classList.remove("ghs-highlight"); 77 | else { 78 | var parent = el.parentNode; 79 | parent.replaceChild(el.childNodes[0], el); 80 | parent.normalize(); 81 | } 82 | }); 83 | highlighted = []; 84 | } 85 | 86 | function selectElement(el) { 87 | var range = document.createRange(); 88 | range.selectNodeContents(el); 89 | var sel = window.getSelection(); 90 | sel.removeAllRanges(); 91 | sel.addRange(range); 92 | } 93 | 94 | function splitReplace(el, str, selectionIndex) { 95 | var strLength = str.length; 96 | var source = el.textContent; 97 | var pos = -1; 98 | var lastPos = 0; 99 | var result = document.createDocumentFragment(); 100 | var selected; 101 | while ((pos = source.indexOf(str, pos + 1)) != -1) { 102 | if (pos > lastPos) result.appendChild(document.createTextNode(source.substring(lastPos, pos))); 103 | var highlight = document.createElement('span'); 104 | highlight.textContent = source.substring(pos, pos + strLength); 105 | highlight.classList.add('ghs-partial-highlight'); 106 | if (pos === selectionIndex) { 107 | selected = highlight; 108 | highlightedIndex = highlighted.length; 109 | } 110 | result.appendChild(highlight); 111 | highlighted.push(highlight); 112 | lastPos = pos + strLength; 113 | } 114 | result.appendChild(document.createTextNode(source.substring(lastPos))); 115 | el.replaceChild(result, el.childNodes[0]); 116 | if (selected) selectElement(selected); 117 | } 118 | 119 | var canvasDraggin = false; 120 | 121 | function barScrollToY(y){ 122 | var iHieght = document.documentElement.clientHeight; 123 | var box = document.documentElement.getBoundingClientRect(); 124 | var heightRatio = box.height / iHieght; 125 | var half= iHieght/2 ; 126 | window.scrollTo(window.pageXOffset,(y * heightRatio)-half); 127 | } 128 | 129 | document.body.addEventListener('mousedown', function(e) { 130 | if (e.target == canvas && e.which===1) { 131 | canvasDraggin = true; 132 | barScrollToY(e.clientY); 133 | window.addEventListener('mousemove', canvasDragger); 134 | return; 135 | } 136 | if (e.which != 1 || highlighted.length === 0) return; 137 | restore(); 138 | canvas.style.display = 'none'; 139 | }); 140 | 141 | function canvasDragger(e) { 142 | if (!canvasDraggin) return; 143 | barScrollToY(e.clientY); 144 | e.preventDefault(); 145 | return false; 146 | } 147 | 148 | document.body.addEventListener('mouseup', function(e) { 149 | if (e.which != 1) return; 150 | if (canvasDraggin){ 151 | canvasDraggin = false; 152 | window.removeEventListener('mousemove', canvasDragger); 153 | return; 154 | } 155 | var selection = window.getSelection(); 156 | var selected = selection.toString().trim(); 157 | 158 | if (selected) { 159 | textNodes.forEach(function(el) { 160 | if (el.textContent == selected) { 161 | el.classList.add("ghs-highlight"); 162 | if (el == e.target) highlightedIndex = highlighted.length; 163 | highlighted.push(el); 164 | } else if (el.textContent.indexOf(selected) > -1) { 165 | if (el != e.target) splitReplace(el, selected); 166 | else splitReplace(el, selected, Math.min(selection.anchorOffset, selection.focusOffset)); 167 | } 168 | 169 | }); 170 | updateHighlighter(); 171 | } 172 | }); 173 | 174 | window.addEventListener('keydown', function(e) { 175 | 176 | if (highlighted.length === 0 || !e.ctrlKey) return; 177 | 178 | if (e.keyCode == 38) { // down key 179 | highlightedIndex--; 180 | if (highlightedIndex < 0) highlightedIndex = highlighted.length - 1; 181 | 182 | } else if (e.keyCode == 40) { // up key 183 | highlightedIndex++; 184 | if (highlightedIndex >= highlighted.length) highlightedIndex = 0; 185 | 186 | } else return; 187 | 188 | selectElement(highlighted[highlightedIndex]); 189 | showElement(highlighted[highlightedIndex]); 190 | updateHighlighter(); 191 | e.preventDefault(); 192 | return false; 193 | }); 194 | 195 | function showElement(el) { 196 | var rect = el.getBoundingClientRect(); 197 | if (rect.bottom >= document.documentElement.clientHeight) el.scrollIntoView(false); 198 | else if (rect.top <= 0) el.scrollIntoView(true); 199 | } 200 | 201 | // Do the Highlighter bar on the right 202 | canvas = document.createElement("canvas"); 203 | 204 | canvas.setAttribute('id', 'ghs-bar'); 205 | var canvasUpdating = false; 206 | document.body.appendChild(canvas); 207 | var ctx = canvas.getContext('2d'); 208 | 209 | function generateHighlighter() { 210 | var canvasHeight = window.document.documentElement.clientHeight; // Height to make the bar 211 | var heightRatio = canvasHeight / document.documentElement.getBoundingClientRect().height; 212 | 213 | canvas.style.display = 'block'; 214 | canvas.height = canvasHeight; 215 | canvas.width = 20; 216 | 217 | var lastY = -1; 218 | var y = 0; 219 | highlighted.forEach(function(el, index) { 220 | var box = el.getBoundingClientRect(); 221 | y = (((window.scrollY + box.top) * heightRatio) + 0.5) | 0; 222 | if (y == lastY && index != highlightedIndex) return; 223 | 224 | if (index == highlightedIndex) ctx.fillStyle = "rgba(54, 149, 230, 1)"; 225 | else ctx.fillStyle = "rgba(241, 209, 47, 1)"; 226 | ctx.fillRect(0, y, canvas.width, (((box.height * heightRatio) + 0.5) | 0) || 1); 227 | lastY = y; 228 | }); 229 | 230 | var y1 = ((window.scrollY * heightRatio) + 0.5) | 0; 231 | var y2 = ((canvasHeight * heightRatio) + 0.5) | 0; 232 | ctx.fillStyle = "rgba(255, 255, 255, 0.8)"; 233 | ctx.fillRect(0, y1, canvas.width, y2); 234 | ctx.lineWidth = 1; 235 | ctx.strokeStyle = "rgba(204, 204, 204, 1)"; 236 | ctx.strokeRect(0, y1, canvas.width, y2); 237 | canvasUpdating = false; 238 | } 239 | 240 | function updateHighlighter() { 241 | if (highlighted.length && canvasUpdating === false) { 242 | canvasUpdating = true; 243 | window.requestAnimationFrame(generateHighlighter); 244 | } 245 | } 246 | 247 | window.addEventListener('scroll', updateHighlighter); 248 | window.addEventListener('resize', updateHighlighter); 249 | 250 | // Add the css...with a content script this would be seperate but I put it here for dev purposes..this was made in the Snippets section of the dev tools Sources panel 251 | var css = document.createElement("style"); 252 | css.type = "text/css"; 253 | css.innerHTML = (function() { 254 | /* 255 | .ghs-highlight, 256 | .ghs-partial-highlight { 257 | outline: 1px solid rgba(255, 181, 21, .6); 258 | background-color: rgba(255, 181, 21, .3); 259 | } 260 | 261 | #ghs-bar { 262 | width: 16px; 263 | border-left: 1px solid #ccc; 264 | background-color: #f3f3f3; 265 | position: fixed; 266 | top: 0px; 267 | bottom: 0px; 268 | right: 0; 269 | height: 100%; 270 | display: none; 271 | } 272 | */ 273 | }).toString().split('\n').slice(2, -2).join('\n').trim(); 274 | document.body.appendChild(css); 275 | }); 276 | --------------------------------------------------------------------------------