1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 01:36:22 +00:00

Automatic table of contents generation for Markdown

Adds table of contents for any Markdown note or
Markdown snippet.
Consequent generations update existing TOC.
Generated TOC is case sensitive to handle #2067

Shortcut : CommandOrControl+Alt+T
Menu : Edit/Generate/Update Markdown TOC
This commit is contained in:
Maciek
2018-08-10 21:39:59 +02:00
parent 79fb04126c
commit 7804a22984
6 changed files with 87 additions and 1 deletions

View File

@@ -0,0 +1,52 @@
/**
* @fileoverview Markdown table of contents generator
*/
import toc from 'markdown-toc'
import diacritics from 'diacritics-map'
import stripColor from 'strip-color'
/**
* @caseSensitiveSlugify Custom slugify function
* Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js),
* but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067
*/
function caseSensitiveSlugify (str) {
function replaceDiacritics (str) {
return str.replace(/[À-ž]/g, function (ch) {
return diacritics[ch] || ch
})
}
function getTitle (str) {
if (/^\[[^\]]+\]\(/.test(str)) {
var m = /^\[([^\]]+)\]/.exec(str)
if (m) return m[1]
}
return str
}
str = getTitle(str)
str = stripColor(str)
// str = str.toLowerCase() //let's be case sensitive
// `.split()` is often (but not always) faster than `.replace()`
str = str.split(' ').join('-')
str = str.split(/\t/).join('--')
str = str.split(/<\/?[^>]+>/).join('')
str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('')
str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('')
str = replaceDiacritics(str)
return str
}
export function generate (currentValue, updateCallback) {
const TOC_MARKER = '<!-- toc -->'
if (!currentValue.includes(TOC_MARKER)) {
currentValue = TOC_MARKER + currentValue
}
updateCallback(toc.insert(currentValue, {slugify: caseSensitiveSlugify}))
}
export default {
generate
}

View File

@@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
class MarkdownNoteDetail extends React.Component {
constructor (props) {
@@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component {
this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.generateToc = () => this.handleGenerateToc()
}
focus () {
@@ -59,6 +61,7 @@ class MarkdownNoteDetail extends React.Component {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType)
})
ee.on('code:generate-toc', this.generateToc)
}
componentWillReceiveProps (nextProps) {
@@ -75,6 +78,7 @@ class MarkdownNoteDetail extends React.Component {
componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.off('code:generate-toc', this.generateToc)
if (this.saveQueue != null) this.saveNow()
}
@@ -262,6 +266,11 @@ class MarkdownNoteDetail extends React.Component {
}
}
handleGenerateToc () {
markdownToc.generate(this.refs.content.value,
(modifiedValue) => { this.refs.content.refs.code.setValue(modifiedValue) })
}
handleFocus (e) {
this.focus()
}

View File

@@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
const electron = require('electron')
const { remote } = electron
@@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component {
}
this.scrollToNextTabThreshold = 0.7
this.generateToc = () => this.handleGenerateToc()
}
componentDidMount () {
@@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component {
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
ee.on('code:generate-toc', this.generateToc)
}
componentWillReceiveProps (nextProps) {
@@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component {
componentWillUnmount () {
if (this.saveQueue != null) this.saveNow()
ee.off('code:generate-toc', this.generateToc)
}
handleGenerateToc () {
let currentMode = this.state.note.snippets[this.state.snippetIndex].mode
if (currentMode.includes('Markdown')) {
let currentValue = this.refs['code-' + this.state.snippetIndex].value
let currentEditor = this.refs['code-' + this.state.snippetIndex].refs.code.editor
markdownToc.generate(currentValue, (modifiedValue) => { currentEditor.setValue(modifiedValue) })
}
}
handleChange (e) {
@@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component {
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
if (isSuper && !e.shiftKey) {
if (isSuper && !e.shiftKey && !e.altKey) {
e.preventDefault()
this.addSnippet()
}

View File

@@ -228,6 +228,16 @@ const edit = {
click () {
mainWindow.webContents.send('editor:add-tag')
}
},
{
type: 'separator'
},
{
label: 'Generate/Update Markdown TOC',
accelerator: 'CommandOrControl+Alt+T',
click () {
mainWindow.webContents.send('code:generate-toc')
}
}
]
}

View File

@@ -82,6 +82,7 @@
"markdown-it-named-headers": "^0.0.4",
"markdown-it-plantuml": "^1.1.0",
"markdown-it-smartarrows": "^1.0.1",
"markdown-toc": "^1.2.0",
"mdurl": "^1.0.1",
"mermaid": "^8.0.0-rc.8",
"moment": "^2.10.3",

View File

@@ -37,6 +37,7 @@ var config = {
'markdown-it-kbd',
'markdown-it-plantuml',
'markdown-it-admonition',
'markdown-toc',
'devtron',
'@rokt33r/season',
{