├── .gitignore ├── RECORD.gif ├── CHANGELOG.md ├── keymaps └── markdown-toc.cson ├── package.json ├── lib ├── markdown-toc.coffee └── Toc.coffee ├── menus └── markdown-toc.cson ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /RECORD.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nok/markdown-toc/HEAD/RECORD.gif -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.3 2 | * Adapt Atom 1.0.0 API 3 | * Add ordered list feature by https://github.com/spjoe 4 | -------------------------------------------------------------------------------- /keymaps/markdown-toc.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/advanced/keymaps 10 | 'atom-workspace': 11 | 'ctrl-alt-c': 'markdown-toc:create' 12 | 'ctrl-alt-u': 'markdown-toc:update' 13 | 'ctrl-alt-r': 'markdown-toc:delete' 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-toc", 3 | "main": "./lib/markdown-toc", 4 | "version": "0.4.2", 5 | "description": "Generate TOC (table of contents) of headlines from parsed markdown file", 6 | "activationCommands": { 7 | "atom-workspace": [ 8 | "markdown-toc:toggle", 9 | "markdown-toc:create", 10 | "markdown-toc:update", 11 | "markdown-toc:delete" 12 | ] 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/nok/markdown-toc" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/nok/markdown-toc/issues" 20 | }, 21 | "homepage": "https://github.com/nok/markdown-toc", 22 | "license": "MIT", 23 | "engines": { 24 | "atom": ">=0.174.0 <2.0.0" 25 | }, 26 | "dependencies": {} 27 | } 28 | -------------------------------------------------------------------------------- /lib/markdown-toc.coffee: -------------------------------------------------------------------------------- 1 | Toc = require './Toc' 2 | 3 | module.exports = 4 | 5 | activate: (state) -> 6 | atom.commands.add 'atom-workspace', 'markdown-toc:create': => 7 | @toc = new Toc(atom.workspace.getActivePaneItem()) 8 | @toc.create() 9 | atom.commands.add 'atom-workspace', 'markdown-toc:update': => 10 | @toc = new Toc(atom.workspace.getActivePaneItem()) 11 | @toc.update() 12 | atom.commands.add 'atom-workspace', 'markdown-toc:delete': => 13 | @toc = new Toc(atom.workspace.getActivePaneItem()); 14 | @toc.delete() 15 | atom.commands.add 'atom-workspace', 'markdown-toc:toggle': => 16 | @toc = new Toc(atom.workspace.getActivePaneItem()) 17 | @toc.toggle() 18 | 19 | # deactivate: -> 20 | # @toc.destroy() 21 | -------------------------------------------------------------------------------- /menus/markdown-toc.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 'context-menu': 3 | 'atom-text-editor': [ 4 | { 5 | 'label': 'Toggle Markdown TOC' 6 | 'command': 'markdown-toc:toggle' 7 | } 8 | ] 9 | 'menu': [ 10 | { 11 | 'label': 'Packages' 12 | 'submenu': [ 13 | 'label': 'Markdown TOC' 14 | 'submenu': [ 15 | { 16 | 'label': 'Toggle TOC' 17 | 'command': 'markdown-toc:toggle' 18 | }, 19 | { 20 | 'label': 'Add TOC' 21 | 'command': 'markdown-toc:create' 22 | }, 23 | { 24 | 'label': 'Update TOC' 25 | 'command': 'markdown-toc:update' 26 | }, 27 | { 28 | 'label': 'Remove TOC' 29 | 'command': 'markdown-toc:delete' 30 | } 31 | ] 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016, Darius Morawiec 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Markdown TOC 2 | ============ 3 | 4 | Generate and update magically a table of contents based on the headlines of a parsed [markdown](http://en.wikipedia.org/wiki/Markdown) file. 5 | 6 | 7 | ## Table of Contents 8 | 9 | - [Usage](#usage) 10 | - [Installation](#installation) 11 | - [Features](#features) 12 | - [Contributors](#contributors) 13 | - [Questions?](#questions) 14 | - [License](#license) 15 | 16 | 17 | ## Usage 18 | 19 | ![Magic](https://raw.githubusercontent.com/nok/markdown-toc/master/RECORD.gif) 20 | 21 | 30 | 31 | ## Installation 32 | 33 | ```bash 34 | apm install markdown-toc 35 | ``` 36 | 37 | 38 | ## Features 39 | 40 | - Auto linking via anchor tags, e.g. `# A 1` → `#a-1` 41 | - Depth control [1-6] with `depthFrom:1` and `depthTo:6` 42 | - Enable or disable links with `withLinks:1` 43 | - Refresh list on save with `updateOnSave:1` 44 | - Use ordered list (1. ..., 2. ...) with `orderedList:0` 45 | 46 | 47 | ## Contributors 48 | 49 | Thanks to all contributors for any fix or improvement, whether small or large. 50 | 51 | - [Giacomo Bresciani](https://github.com/brescia123) 52 | - [Kévin Lanceplaine](https://github.com/lanceplaine) 53 | - [Ilya Zelenin](https://github.com/wyster) 54 | - [spjoe](https://github.com/spjoe) 55 | - [Tom Byrer](https://github.com/tomByrer) 56 | - [betrue12](https://github.com/betrue12) 57 | 58 | 59 | ## Questions? 60 | 61 | Don't be shy and feel free to contact me on [Twitter](https://twitter.com/darius_morawiec). 62 | 63 | 64 | ## License 65 | 66 | The package is Open Source Software released under the [MIT](LICENSE.md) license. 67 | -------------------------------------------------------------------------------- /lib/Toc.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | class Toc 3 | 4 | 5 | constructor: (@editor) -> 6 | @lines = [] 7 | @list = [] 8 | @options = 9 | depthFrom: 1 # depthFrom 10 | depthTo: 6 # depthTo 11 | withLinks: 1 # withLinks 12 | updateOnSave: 1 # updateOnSave 13 | orderedList: 0 # orderedList 14 | 15 | at = @ 16 | @editor.getBuffer().onWillSave () -> 17 | if at.options.updateOnSave is 1 18 | if at._hasToc() 19 | at._deleteToc() 20 | at.editor.setTextInBufferRange [[at.open,0], [at.open,0]], at._createToc() 21 | 22 | 23 | # ---------------------------------------------------------------------------- 24 | # main methods (highest logic level) 25 | 26 | 27 | create: -> 28 | if @_hasToc() 29 | @_deleteToc() 30 | @editor.setTextInBufferRange [[@open,0], [@open,0]], @_createToc() 31 | @editor.insertText @_createToc() 32 | 33 | 34 | update: -> 35 | if @_hasToc() 36 | @_deleteToc() 37 | @editor.setTextInBufferRange [[@open,0], [@open,0]], @_createToc() 38 | else 39 | @editor.insertText @_createToc() 40 | 41 | 42 | delete: -> 43 | if @_hasToc() 44 | @_deleteToc() 45 | 46 | 47 | autosave: -> 48 | if @_hasToc() 49 | @_deleteToc() 50 | @editor.setTextInBufferRange [[@open,0], [@open,0]], @_createToc() 51 | 52 | toggle: -> 53 | if @_hasToc() 54 | @_deleteToc() 55 | else 56 | @editor.insertText @_createToc() 57 | 58 | 59 | 60 | # ---------------------------------------------------------------------------- 61 | 62 | 63 | _hasToc: () -> 64 | @___updateLines() 65 | 66 | if @lines.length > 0 67 | @open = false 68 | @close = false 69 | options = undefined 70 | 71 | for i of @lines 72 | line = @lines[i] 73 | if @open is false 74 | if line.match /^$/g 75 | @open = parseInt i 76 | options = line 77 | else 78 | if line.match /^$/g 79 | @close = parseInt i 80 | break 81 | 82 | if @open isnt false and @close isnt false 83 | if options isnt undefined 84 | @__updateOptions options 85 | return true 86 | return false 87 | 88 | 89 | # embed list with the open and close comment: 90 | # [list] 91 | _createToc: () -> 92 | @__updateList() 93 | if Object.keys(@list).length > 0 94 | text = [] 95 | text.push "\n" 96 | list = @__createList() 97 | if list isnt false 98 | Array.prototype.push.apply text, list 99 | text.push "\n" 100 | return text.join "\n" 101 | return "" 102 | 103 | 104 | _deleteToc: () -> 105 | @editor.setTextInBufferRange [[@open,0], [@close,14]], "" 106 | 107 | 108 | # ---------------------------------------------------------------------------- 109 | 110 | 111 | # parse all lines and find markdown headlines 112 | __updateList: () -> 113 | @___updateLines() 114 | @list = [] 115 | isInCodeBlock = false 116 | for i of @lines 117 | line = @lines[i] 118 | isInCodeBlock = !isInCodeBlock if line.match /^```/ 119 | continue if isInCodeBlock 120 | result = line.match /^\#{1,6}/ 121 | if result 122 | depthFrom = if @options.depthFrom isnt undefined then @options.depthFrom else 1 123 | depthTo = if @options.depthTo isnt undefined then @options.depthTo else 6 124 | if (result[0].length <= parseInt depthTo) and (result[0].length >= parseInt depthFrom) 125 | @list.push 126 | depth: result[0].length 127 | line: new String line 128 | 129 | 130 | # create hierarchical markdown list 131 | __createList: () -> 132 | list = [] 133 | depthFrom = if @options.depthFrom isnt undefined then @options.depthFrom else 1 134 | depthTo = if @options.depthTo isnt undefined then @options.depthTo else 6 135 | indicesOfDepth = Array.apply(null, new Array(depthTo - depthFrom + 1)).map(Number.prototype.valueOf, 0); 136 | for own i, item of @list 137 | row = [] 138 | for tab in [depthFrom..item.depth] when tab > depthFrom 139 | row.push "\t" 140 | if @options.orderedList is 1 141 | row.push ++indicesOfDepth[item.depth-1] + ". " 142 | indicesOfDepth = indicesOfDepth.map((value, index) -> if index < item.depth then value else 0) 143 | else 144 | row.push "- " 145 | 146 | line = item.line.substr item.depth 147 | line = line.trim() 148 | if @options.withLinks is 1 149 | row.push @___createLink line 150 | else 151 | row.push line 152 | 153 | list.push row.join "" 154 | if list.length > 0 155 | return list 156 | return false 157 | 158 | 159 | __updateOptions: (line) -> 160 | options = line.match /(\w+(=|:)(\d|yes|no))+/g 161 | if options 162 | @options = {} 163 | for i of options 164 | option = options[i] 165 | 166 | key = option.match /^(\w+)/g 167 | key = new String key[0] 168 | 169 | value = option.match /(\d|yes|no)$/g 170 | value = new String value[0] 171 | if value.length > 1 172 | if value.toLowerCase().valueOf() is new String("yes").valueOf() 173 | value = 1 174 | else 175 | value = 0 176 | 177 | if key.toLowerCase().valueOf() is new String("depthfrom").valueOf() 178 | @options.depthFrom = parseInt value 179 | else if key.toLowerCase().valueOf() is new String("depthto").valueOf() 180 | @options.depthTo = parseInt value 181 | else if key.toLowerCase().valueOf() is new String("withlinks").valueOf() 182 | @options.withLinks = parseInt value 183 | else if key.toLowerCase().valueOf() is new String("updateonsave").valueOf() 184 | @options.updateOnSave = parseInt value 185 | else if key.toLowerCase().valueOf() is new String("orderedlist").valueOf() 186 | @options.orderedList = parseInt value 187 | 188 | 189 | # ---------------------------------------------------------------------------- 190 | # lightweight methods 191 | 192 | 193 | # update raw lines after initialization or changes 194 | ___updateLines: -> 195 | if @editor isnt undefined 196 | @lines = @editor.getBuffer().getLines() 197 | else 198 | @lines = [] 199 | 200 | 201 | # create hash and surround link withit 202 | ___createLink: (name) -> 203 | hash = new String name 204 | hash = hash.toLowerCase().replace /\s/g, "-" 205 | hash = hash.replace /[^a-z0-9\u4e00-\u9fa5äüö\-]/g, "" 206 | if hash.indexOf("--") > -1 207 | hash = hash.replace /(-)+/g, "-" 208 | if name.indexOf(":-") > -1 209 | hash = hash.replace /:-/g, "-" 210 | link = [] 211 | link.push "[" 212 | link.push name 213 | link.push "](#" 214 | link.push hash 215 | link.push ")" 216 | return link.join "" 217 | --------------------------------------------------------------------------------