Skip to content

Commit 4e80e1d

Browse files
committed
Merge branch 'master' into tags
2 parents fe323d5 + 03b8dbb commit 4e80e1d

38 files changed

+1880
-339
lines changed

browser/components/CodeEditor.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,10 @@ export default class CodeEditor extends React.Component {
604604
body,
605605
'text/html'
606606
)
607-
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
607+
const escapePipe = (str) => {
608+
return str.replace('|', '\\|')
609+
}
610+
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
608611
resolve(linkWithTitle)
609612
} catch (e) {
610613
reject(e)

browser/lib/findNoteTitle.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ export function findNoteTitle (value) {
33
let title = null
44
let isInsideCodeBlock = false
55

6+
if (splitted[0] === '---') {
7+
let line = 0
8+
while (++line < splitted.length) {
9+
if (splitted[line] === '---') {
10+
splitted.splice(0, line + 1)
11+
12+
break
13+
}
14+
}
15+
}
16+
617
splitted.some((line, index) => {
718
const trimmedLine = line.trim()
819
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict'
2+
3+
module.exports = function frontMatterPlugin (md) {
4+
function frontmatter (state, startLine, endLine, silent) {
5+
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
6+
return false
7+
}
8+
9+
let line = 0
10+
while (++line < state.lineMax) {
11+
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
12+
state.line = line + 1
13+
14+
return true
15+
}
16+
}
17+
18+
return false
19+
}
20+
21+
md.block.ruler.before('table', 'frontmatter', frontmatter, {
22+
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
23+
})
24+
}

browser/lib/markdown-toc-generator.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* @fileoverview Markdown table of contents generator
3+
*/
4+
5+
import toc from 'markdown-toc'
6+
import diacritics from 'diacritics-map'
7+
import stripColor from 'strip-color'
8+
9+
const EOL = require('os').EOL
10+
11+
/**
12+
* @caseSensitiveSlugify Custom slugify function
13+
* Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js),
14+
* but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067
15+
*/
16+
function caseSensitiveSlugify (str) {
17+
function replaceDiacritics (str) {
18+
return str.replace(/[À-ž]/g, function (ch) {
19+
return diacritics[ch] || ch
20+
})
21+
}
22+
23+
function getTitle (str) {
24+
if (/^\[[^\]]+\]\(/.test(str)) {
25+
var m = /^\[([^\]]+)\]/.exec(str)
26+
if (m) return m[1]
27+
}
28+
return str
29+
}
30+
31+
str = getTitle(str)
32+
str = stripColor(str)
33+
// str = str.toLowerCase() //let's be case sensitive
34+
35+
// `.split()` is often (but not always) faster than `.replace()`
36+
str = str.split(' ').join('-')
37+
str = str.split(/\t/).join('--')
38+
str = str.split(/<\/?[^>]+>/).join('')
39+
str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('')
40+
str = str.split(/[ ]/).join('')
41+
str = replaceDiacritics(str)
42+
return str
43+
}
44+
45+
const TOC_MARKER_START = '<!-- toc -->'
46+
const TOC_MARKER_END = '<!-- tocstop -->'
47+
48+
/**
49+
* Takes care of proper updating given editor with TOC.
50+
* If TOC doesn't exit in the editor, it's inserted at current caret position.
51+
* Otherwise,TOC is updated in place.
52+
* @param editor CodeMirror editor to be updated with TOC
53+
*/
54+
export function generateInEditor (editor) {
55+
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
56+
57+
function tocExistsInEditor () {
58+
return tocRegex.test(editor.getValue())
59+
}
60+
61+
function updateExistingToc () {
62+
const toc = generate(editor.getValue())
63+
const search = editor.getSearchCursor(tocRegex)
64+
while (search.findNext()) {
65+
search.replace(toc)
66+
}
67+
}
68+
69+
function addTocAtCursorPosition () {
70+
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
71+
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
72+
}
73+
74+
if (tocExistsInEditor()) {
75+
updateExistingToc()
76+
} else {
77+
addTocAtCursorPosition()
78+
}
79+
}
80+
81+
/**
82+
* Generates MD TOC based on MD document passed as string.
83+
* @param markdownText MD document
84+
* @returns generatedTOC String containing generated TOC
85+
*/
86+
export function generate (markdownText) {
87+
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify})
88+
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
89+
}
90+
91+
function wrapTocWithEol (toc, editor) {
92+
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
93+
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
94+
return leftWrap + toc + rightWrap
95+
}
96+
97+
export default {
98+
generate,
99+
generateInEditor
100+
}

browser/lib/markdown.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ class Markdown {
149149
})
150150
this.md.use(require('markdown-it-kbd'))
151151
this.md.use(require('markdown-it-admonition'))
152+
this.md.use(require('./markdown-it-frontmatter'))
152153

153154
const deflate = require('markdown-it-plantuml/lib/deflate')
154155
this.md.use(require('markdown-it-plantuml'), '', {

browser/lib/newNote.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { hashHistory } from 'react-router'
2+
import dataApi from 'browser/main/lib/dataApi'
3+
import ee from 'browser/main/lib/eventEmitter'
4+
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
5+
6+
export function createMarkdownNote (storage, folder, dispatch, location) {
7+
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
8+
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
9+
return dataApi
10+
.createNote(storage, {
11+
type: 'MARKDOWN_NOTE',
12+
folder: folder,
13+
title: '',
14+
content: ''
15+
})
16+
.then(note => {
17+
const noteHash = note.key
18+
dispatch({
19+
type: 'UPDATE_NOTE',
20+
note: note
21+
})
22+
23+
hashHistory.push({
24+
pathname: location.pathname,
25+
query: { key: noteHash }
26+
})
27+
ee.emit('list:jump', noteHash)
28+
ee.emit('detail:focus')
29+
})
30+
}
31+
32+
export function createSnippetNote (storage, folder, dispatch, location, config) {
33+
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
34+
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
35+
return dataApi
36+
.createNote(storage, {
37+
type: 'SNIPPET_NOTE',
38+
folder: folder,
39+
title: '',
40+
description: '',
41+
snippets: [
42+
{
43+
name: '',
44+
mode: config.editor.snippetDefaultLanguage || 'text',
45+
content: ''
46+
}
47+
]
48+
})
49+
.then(note => {
50+
const noteHash = note.key
51+
dispatch({
52+
type: 'UPDATE_NOTE',
53+
note: note
54+
})
55+
hashHistory.push({
56+
pathname: location.pathname,
57+
query: { key: noteHash }
58+
})
59+
ee.emit('list:jump', noteHash)
60+
ee.emit('detail:focus')
61+
})
62+
}

browser/main/Detail/MarkdownNoteDetail.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter'
2929
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
3030
import striptags from 'striptags'
3131
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
32+
import markdownToc from 'browser/lib/markdown-toc-generator'
3233

3334
class MarkdownNoteDetail extends React.Component {
3435
constructor (props) {
@@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component {
4748
this.dispatchTimer = null
4849

4950
this.toggleLockButton = this.handleToggleLockButton.bind(this)
51+
this.generateToc = () => this.handleGenerateToc()
5052
}
5153

5254
focus () {
@@ -59,6 +61,7 @@ class MarkdownNoteDetail extends React.Component {
5961
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
6062
this.handleSwitchMode(reversedType)
6163
})
64+
ee.on('code:generate-toc', this.generateToc)
6265
}
6366

6467
componentWillReceiveProps (nextProps) {
@@ -75,6 +78,7 @@ class MarkdownNoteDetail extends React.Component {
7578

7679
componentWillUnmount () {
7780
ee.off('topbar:togglelockbutton', this.toggleLockButton)
81+
ee.off('code:generate-toc', this.generateToc)
7882
if (this.saveQueue != null) this.saveNow()
7983
}
8084

@@ -262,6 +266,11 @@ class MarkdownNoteDetail extends React.Component {
262266
}
263267
}
264268

269+
handleGenerateToc () {
270+
const editor = this.refs.content.refs.code.editor
271+
markdownToc.generateInEditor(editor)
272+
}
273+
265274
handleFocus (e) {
266275
this.focus()
267276
}
@@ -365,6 +374,7 @@ class MarkdownNoteDetail extends React.Component {
365374
value={this.state.note.tags}
366375
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
367376
showTagsAlphabetically={config.ui.showTagsAlphabetically}
377+
data={data}
368378
onChange={this.handleUpdateTag.bind(this)}
369379
/>
370380
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />

browser/main/Detail/NoteDetailInfo.styl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ $info-margin-under-border = 30px
1313
display flex
1414
align-items center
1515
padding 0 20px
16+
z-index 99
1617

1718
.info-left
1819
padding 0 10px

browser/main/Detail/SnippetNoteDetail.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
2929
import { formatDate } from 'browser/lib/date-formatter'
3030
import i18n from 'browser/lib/i18n'
3131
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
32+
import markdownToc from 'browser/lib/markdown-toc-generator'
3233

3334
const electron = require('electron')
3435
const { remote } = electron
@@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component {
5253
}
5354

5455
this.scrollToNextTabThreshold = 0.7
56+
this.generateToc = () => this.handleGenerateToc()
5557
}
5658

5759
componentDidMount () {
@@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component {
6567
enableLeftArrow: allTabs.offsetLeft !== 0
6668
})
6769
}
70+
ee.on('code:generate-toc', this.generateToc)
6871
}
6972

7073
componentWillReceiveProps (nextProps) {
@@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component {
9194

9295
componentWillUnmount () {
9396
if (this.saveQueue != null) this.saveNow()
97+
ee.off('code:generate-toc', this.generateToc)
98+
}
99+
100+
handleGenerateToc () {
101+
const { note, snippetIndex } = this.state
102+
const currentMode = note.snippets[snippetIndex].mode
103+
if (currentMode.includes('Markdown')) {
104+
const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor
105+
markdownToc.generateInEditor(currentEditor)
106+
}
94107
}
95108

96109
handleChange (e) {
@@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component {
441454
const isSuper = global.process.platform === 'darwin'
442455
? e.metaKey
443456
: e.ctrlKey
444-
if (isSuper && !e.shiftKey) {
457+
if (isSuper && !e.shiftKey && !e.altKey) {
445458
e.preventDefault()
446459
this.addSnippet()
447460
}

0 commit comments

Comments
 (0)