├── .gitignore ├── LICENSE ├── README.md ├── index.css ├── index.js ├── manifest.json ├── options.html ├── options.js ├── screenshot.png └── test └── manual-tests.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dan Kaplun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Github Markdown Outline Extension 2 | ====== 3 | Displays a clickable outline of all topic headers for markdown documents on Github — *Install via [Chrome Web Store](https://chrome.google.com/webstore/detail/github-markdown-outline-e/gccinjjdbfdkkkebfbeipopijjfohfgj).* 4 | 5 | ![Screenshot](https://raw.githubusercontent.com/dbkaplun/github-markdown-outline-extension/master/screenshot.png) 6 | 7 | ## Installation 8 | 9 | Install via [Chrome Web Store](https://chrome.google.com/webstore/detail/github-markdown-outline-e/gccinjjdbfdkkkebfbeipopijjfohfgj). 10 | 11 | ## Usage 12 | 13 | When you visit any page with markdown on Github, this extension will automatically display a clickable outline near the top of the markdown document. 14 | 15 | To see all available options, copy and paste the following URL into Chrome: `chrome://extensions/?options=gccinjjdbfdkkkebfbeipopijjfohfgj` 16 | 17 | ## Development 18 | 19 | * `git clone git@github.com:dbkaplun/github-markdown-outline-extension.git` 20 | * Go to `chrome://extensions` in Chrome 21 | * Ensure the *Developer mode* checkbox is clicked 22 | * Click the *Load unpacked extension...* button 23 | * Select the `github-markdown-outline-extension` directory you just cloned 24 | * Make sure the extension was added by refreshing this page 25 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | .__github-markdown-outline-container.__github-markdown-outline-container--float { 2 | float: right; 3 | background: #fff; 4 | width: 15em; 5 | padding-left: 3em; 6 | border-left: 3px solid #eee; /* Github light gray */ 7 | margin-left: 16px; 8 | overflow-x: auto; 9 | } 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (root => { 2 | var document = root.document 3 | 4 | getHeaderLevel.REGEXP = /h(\d)/i 5 | function getHeaderLevel ($h) { 6 | var level = Number(((($h || {}).tagName || '').match(getHeaderLevel.REGEXP) || [])[1]) 7 | return isNaN(level) ? undefined : level 8 | } 9 | 10 | var headerSels = [] 11 | for (var l = 1; l <= 6; l++) headerSels.push('h'+l) 12 | var headerSel = headerSels.join(', ') 13 | var anchorSel = 'a[id]' 14 | var linkSel = 'a[href^="#"]:not(.anchor)' 15 | 16 | root.chrome.storage.sync.get({ 17 | // default values 18 | hideIfOutlineDetected: true, 19 | float: false, 20 | useInnerHTML: true 21 | }, options => { 22 | Array.from(document.querySelectorAll('.markdown-body')).forEach($md => { 23 | var $headers = Array.from($md.querySelectorAll(headerSel)) 24 | 25 | if (options.hideIfOutlineDetected && $md.querySelectorAll(linkSel).length >= Math.max($headers.length - 10, 0)) return // there's already an outline in the document 26 | 27 | var $container = document.createElement('div') 28 | $container.classList.add('__github-markdown-outline-container') 29 | if (options.float) $container.classList.add('__github-markdown-outline-container--float') 30 | 31 | var $outline = document.createElement('ul') 32 | $outline.classList.add('__github-markdown-outline') 33 | $container.appendChild($outline) 34 | 35 | // generate outline from headers 36 | $headers.forEach($h => { 37 | var level = getHeaderLevel($h) 38 | if (!level) return 39 | var $ul = $outline, $li, $child 40 | for (var l = 1; l < level; l++) { 41 | $li = $ul.lastChild || $ul.appendChild(document.createElement('li')) 42 | $child = $li.lastChild || {} 43 | $ul = $child.tagName === 'UL' 44 | ? $child 45 | : $li.appendChild(document.createElement('ul')) 46 | } 47 | var $topic = $ul 48 | .appendChild(document.createElement('li')) 49 | .appendChild(document.createElement('a')) 50 | if (options.useInnerHTML) { 51 | $topic.innerHTML = $h.innerHTML 52 | Array.from($topic.querySelectorAll(anchorSel)).forEach($child => { 53 | $child.parentNode.removeChild($child) 54 | }) 55 | } else { 56 | $topic.innerText = $h.innerText 57 | } 58 | $topic.href = `#${$h.querySelector(anchorSel).id}` 59 | }) 60 | 61 | // find all sublists with one item and replace with contents 62 | Array.from($container.querySelectorAll('ul')).forEach($ul => { 63 | var $parent = $ul.parentNode 64 | var $li = $ul.firstChild 65 | var $child = $li.firstChild 66 | if ($li !== $ul.lastChild || $child.tagName !== 'UL') return 67 | while ($child) { 68 | $parent.insertBefore($child, $ul.nextSibling) // inserts to end of list if $ul.nextSibling is null 69 | $child = $child.nextSibling 70 | } 71 | $parent.removeChild($ul) 72 | }) 73 | 74 | $md.insertBefore($container, $md.firstChild) 75 | }) 76 | }) 77 | })(this) 78 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Github Markdown Outline Extension", 4 | "description": "Displays a clickable outline of all topic headers for markdown documents on Github", 5 | "version": "1.4.2", 6 | "homepage_url": "https://github.com/dbkaplun/github-markdown-outline-extension", 7 | "author": "Dan Kaplun ", 8 | "icons": {}, 9 | "content_scripts": [{ 10 | "matches": ["https://github.com/*"], 11 | "js": ["index.js"], 12 | "css": ["index.css"] 13 | }], 14 | "options_ui": { 15 | "page": "options.html", 16 | "chrome_style": true 17 | }, 18 | "permissions": [ 19 | "storage" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | github-markdown-outline-extension options 5 | 8 | 9 | 10 |

 

11 |

12 |

13 |

14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | (root => { 2 | var STATUS_SHOW_MS = 2000 3 | var document = root.document 4 | 5 | document.addEventListener('DOMContentLoaded', () => { 6 | var $useInnerHTML = document.querySelector('[name="useInnerHTML"]') 7 | var $float = document.querySelector('[name="float"]') 8 | var $hideIfOutlineDetected = document.querySelector('[name="hideIfOutlineDetected"]') 9 | var $status = document.querySelector('#status') 10 | var initStatusTextContent = $status.textContent 11 | var statusShowTimeout 12 | 13 | function saveOptions () { 14 | chrome.storage.sync.set({ 15 | useInnerHTML: $useInnerHTML.checked, 16 | float: $float.checked, 17 | hideIfOutlineDetected: $hideIfOutlineDetected.checked 18 | }, () => { 19 | $status.textContent = "Options saved." 20 | if (statusShowTimeout) clearTimeout(statusShowTimeout) 21 | statusShowTimeout = setTimeout(() => { $status.textContent = initStatusTextContent }, STATUS_SHOW_MS) 22 | }) 23 | } 24 | 25 | function restoreOptions () { 26 | chrome.storage.sync.get({ 27 | // default values 28 | useInnerHTML: true, 29 | float: false, 30 | hideIfOutlineDetected: true 31 | }, options => { 32 | $useInnerHTML.checked = options.useInnerHTML 33 | $float.checked = options.float 34 | $hideIfOutlineDetected.checked = options.hideIfOutlineDetected 35 | }) 36 | } 37 | 38 | [$useInnerHTML, $float, $hideIfOutlineDetected].forEach($input => { 39 | $input.addEventListener('change', evt => { saveOptions() }) 40 | }) 41 | 42 | restoreOptions() 43 | }) 44 | })(this) 45 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbkaplun/github-markdown-outline-extension/7b89e3e832a79dcf869db9acec9ff60d07efcb32/screenshot.png -------------------------------------------------------------------------------- /test/manual-tests.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | ## Normalization logic 3 | * https://github.com/monolithed/__doc__/blob/0f7829045ad6eabdbd1ce5fb8d94b579202e15af/README.md 4 | 5 | ## `hideIfOutlineDetected` 6 | * https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet/d66f28ff3efd3ae8f925828dbf713d1b40636ff4 7 | * https://github.com/mhinz/vim-galore/blob/39a755188a37f67d5d117c8f1ff3247a495d78cc/README.md 8 | --------------------------------------------------------------------------------