mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46: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:
52
browser/lib/markdown-toc-generator.js
Normal file
52
browser/lib/markdown-toc-generator.js
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter'
|
|||||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||||
|
this.generateToc = () => this.handleGenerateToc()
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
@@ -59,6 +61,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
this.handleSwitchMode(reversedType)
|
this.handleSwitchMode(reversedType)
|
||||||
})
|
})
|
||||||
|
ee.on('code:generate-toc', this.generateToc)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
@@ -75,6 +78,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
ee.off('code:generate-toc', this.generateToc)
|
||||||
if (this.saveQueue != null) this.saveNow()
|
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) {
|
handleFocus (e) {
|
||||||
this.focus()
|
this.focus()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
|
|||||||
import { formatDate } from 'browser/lib/date-formatter'
|
import { formatDate } from 'browser/lib/date-formatter'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.scrollToNextTabThreshold = 0.7
|
this.scrollToNextTabThreshold = 0.7
|
||||||
|
this.generateToc = () => this.handleGenerateToc()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
enableLeftArrow: allTabs.offsetLeft !== 0
|
enableLeftArrow: allTabs.offsetLeft !== 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
ee.on('code:generate-toc', this.generateToc)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
@@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
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) {
|
handleChange (e) {
|
||||||
@@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const isSuper = global.process.platform === 'darwin'
|
const isSuper = global.process.platform === 'darwin'
|
||||||
? e.metaKey
|
? e.metaKey
|
||||||
: e.ctrlKey
|
: e.ctrlKey
|
||||||
if (isSuper && !e.shiftKey) {
|
if (isSuper && !e.shiftKey && !e.altKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.addSnippet()
|
this.addSnippet()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,16 @@ const edit = {
|
|||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('editor:add-tag')
|
mainWindow.webContents.send('editor:add-tag')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Generate/Update Markdown TOC',
|
||||||
|
accelerator: 'CommandOrControl+Alt+T',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('code:generate-toc')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
"markdown-it-named-headers": "^0.0.4",
|
"markdown-it-named-headers": "^0.0.4",
|
||||||
"markdown-it-plantuml": "^1.1.0",
|
"markdown-it-plantuml": "^1.1.0",
|
||||||
"markdown-it-smartarrows": "^1.0.1",
|
"markdown-it-smartarrows": "^1.0.1",
|
||||||
|
"markdown-toc": "^1.2.0",
|
||||||
"mdurl": "^1.0.1",
|
"mdurl": "^1.0.1",
|
||||||
"mermaid": "^8.0.0-rc.8",
|
"mermaid": "^8.0.0-rc.8",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ var config = {
|
|||||||
'markdown-it-kbd',
|
'markdown-it-kbd',
|
||||||
'markdown-it-plantuml',
|
'markdown-it-plantuml',
|
||||||
'markdown-it-admonition',
|
'markdown-it-admonition',
|
||||||
|
'markdown-toc',
|
||||||
'devtron',
|
'devtron',
|
||||||
'@rokt33r/season',
|
'@rokt33r/season',
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user