mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
110 Commits
v0.15.3
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4a7065496 | ||
|
|
8958e67fcf | ||
|
|
47b796909a | ||
|
|
67d76abdfa | ||
|
|
d75d68ba72 | ||
|
|
323be6b72d | ||
|
|
031a113338 | ||
|
|
b50c5386a6 | ||
|
|
65777b1d56 | ||
|
|
fe728874ac | ||
|
|
c5b4c327fa | ||
|
|
4c39922ead | ||
|
|
1cdc74a2f0 | ||
|
|
6213a820e6 | ||
|
|
ce81b26d1d | ||
|
|
fae91255f9 | ||
|
|
a82a3efb14 | ||
|
|
9556417447 | ||
|
|
60fbb7db5d | ||
|
|
e504f8e63e | ||
|
|
071ce12a7e | ||
|
|
0decaf187c | ||
|
|
961644747e | ||
|
|
d1a81984fb | ||
|
|
bd9b1306b1 | ||
|
|
0ca18d8ca5 | ||
|
|
540d72696c | ||
|
|
87a530612f | ||
|
|
7f3fdedb5d | ||
|
|
3a80706938 | ||
|
|
f4259bb4d0 | ||
|
|
aa8b589569 | ||
|
|
febc98c101 | ||
|
|
b678c3bd89 | ||
|
|
80b8948433 | ||
|
|
5414fe3384 | ||
|
|
db4016385d | ||
|
|
2cf5f8e966 | ||
|
|
8ede1a4989 | ||
|
|
76da76ae76 | ||
|
|
c02ab033f4 | ||
|
|
1aaba74e24 | ||
|
|
6fe6794796 | ||
|
|
fd3e243855 | ||
|
|
938b075bf6 | ||
|
|
81ac3d1748 | ||
|
|
40d10eae04 | ||
|
|
9b6a61a91c | ||
|
|
7116c305ca | ||
|
|
4fbbb4651d | ||
|
|
2ac38e9644 | ||
|
|
98d4fa0603 | ||
|
|
2ea0514bbe | ||
|
|
137aa692bc | ||
|
|
634fec39c0 | ||
|
|
d269f1e8fd | ||
|
|
4b67026bbf | ||
|
|
8b13ec4f0e | ||
|
|
7fef7660e4 | ||
|
|
01d021cc4c | ||
|
|
c355f81525 | ||
|
|
d138a54dfd | ||
|
|
a7ead67c2d | ||
|
|
2f16784a20 | ||
|
|
8ca3ba21ee | ||
|
|
58ae6419f0 | ||
|
|
3f320f4337 | ||
|
|
fc08d2f8c3 | ||
|
|
59e361cb37 | ||
|
|
1993a6588d | ||
|
|
218fba1aa1 | ||
|
|
4de6c69f5d | ||
|
|
2b3538d3b1 | ||
|
|
b84f1173b7 | ||
|
|
bdfe8c0445 | ||
|
|
f64d0b35e1 | ||
|
|
3921655157 | ||
|
|
e4e10d523f | ||
|
|
404dddcb86 | ||
|
|
ffb2603485 | ||
|
|
928e0edf4d | ||
|
|
80a63f7404 | ||
|
|
6e45ee6a38 | ||
|
|
ba34458feb | ||
|
|
a2fb50a71c | ||
|
|
b15a4007ee | ||
|
|
93f0d3c1cf | ||
|
|
8ec7d19f30 | ||
|
|
a0f5a06c73 | ||
|
|
39a98e795f | ||
|
|
57705cf41b | ||
|
|
052c70bb38 | ||
|
|
6dc88262c9 | ||
|
|
9d43e34cfa | ||
|
|
12f9b9342d | ||
|
|
e76bc72667 | ||
|
|
9310e5e86c | ||
|
|
fa157f6f76 | ||
|
|
d6a54b8a26 | ||
|
|
9813412c8e | ||
|
|
d76b7235db | ||
|
|
418a789568 | ||
|
|
2d941c3ea3 | ||
|
|
540c608cc6 | ||
|
|
e723d4cd59 | ||
|
|
9e770ef357 | ||
|
|
c796b3b30e | ||
|
|
168fe212f5 | ||
|
|
87515dbd3f | ||
|
|
696c2f29b5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ node_modules/*
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
config.json
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
|
|||||||
import { createTurndownService } from '../lib/turndown'
|
import { createTurndownService } from '../lib/turndown'
|
||||||
import { languageMaps } from '../lib/CMLanguageList'
|
import { languageMaps } from '../lib/CMLanguageList'
|
||||||
import snippetManager from '../lib/SnippetManager'
|
import snippetManager from '../lib/SnippetManager'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
import { sendWakatimeHeartBeat } from 'browser/lib/wakatime-plugin'
|
||||||
import {
|
import {
|
||||||
generateInEditor,
|
generateInEditor,
|
||||||
tocExistsInEditor
|
tocExistsInEditor
|
||||||
@@ -61,7 +63,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
ipcRenderer.send('editor:focused', true)
|
ipcRenderer.send('editor:focused', true)
|
||||||
}
|
}
|
||||||
const debouncedDeletionOfAttachments = _.debounce(
|
this.debouncedDeletionOfAttachments = _.debounce(
|
||||||
attachmentManagement.deleteAttachmentsNotPresentInNote,
|
attachmentManagement.deleteAttachmentsNotPresentInNote,
|
||||||
30000
|
30000
|
||||||
)
|
)
|
||||||
@@ -78,7 +80,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
const { storageKey, noteKey } = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
if (this.props.deleteUnusedAttachments === true) {
|
if (this.props.deleteUnusedAttachments === true) {
|
||||||
debouncedDeletionOfAttachments(
|
this.debouncedDeletionOfAttachments(
|
||||||
this.editor.getValue(),
|
this.editor.getValue(),
|
||||||
storageKey,
|
storageKey,
|
||||||
noteKey
|
noteKey
|
||||||
@@ -113,6 +115,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editorActivityHandler = () => this.handleEditorActivity()
|
this.editorActivityHandler = () => this.handleEditorActivity()
|
||||||
|
|
||||||
this.turndownService = createTurndownService()
|
this.turndownService = createTurndownService()
|
||||||
|
|
||||||
|
// wakatime
|
||||||
|
const { storageKey, noteKey } = this.props
|
||||||
|
const storage = findStorage(storageKey)
|
||||||
|
if (storage)
|
||||||
|
sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
|
||||||
|
isWrite: false,
|
||||||
|
hasFileChanges: false,
|
||||||
|
isFileChange: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch(msg) {
|
handleSearch(msg) {
|
||||||
@@ -158,6 +170,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleEditorActivity() {
|
handleEditorActivity() {
|
||||||
|
if (this.props.onCursorActivity) {
|
||||||
|
this.props.onCursorActivity(this.editor)
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.textEditorInterface.transaction) {
|
if (!this.textEditorInterface.transaction) {
|
||||||
this.updateTableEditorState()
|
this.updateTableEditorState()
|
||||||
}
|
}
|
||||||
@@ -219,11 +235,19 @@ export default class CodeEditor extends React.Component {
|
|||||||
},
|
},
|
||||||
[translateHotkey(hotkey.insertDate)]: function(cm) {
|
[translateHotkey(hotkey.insertDate)]: function(cm) {
|
||||||
const dateNow = new Date()
|
const dateNow = new Date()
|
||||||
|
if (self.props.dateFormatISO8601) {
|
||||||
|
cm.replaceSelection(dateNow.toISOString().split('T')[0])
|
||||||
|
} else {
|
||||||
cm.replaceSelection(dateNow.toLocaleDateString())
|
cm.replaceSelection(dateNow.toLocaleDateString())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[translateHotkey(hotkey.insertDateTime)]: function(cm) {
|
[translateHotkey(hotkey.insertDateTime)]: function(cm) {
|
||||||
const dateNow = new Date()
|
const dateNow = new Date()
|
||||||
|
if (self.props.dateFormatISO8601) {
|
||||||
|
cm.replaceSelection(dateNow.toISOString())
|
||||||
|
} else {
|
||||||
cm.replaceSelection(dateNow.toLocaleString())
|
cm.replaceSelection(dateNow.toLocaleString())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
||||||
'Ctrl-C': cm => {
|
'Ctrl-C': cm => {
|
||||||
@@ -321,10 +345,18 @@ export default class CodeEditor extends React.Component {
|
|||||||
'CodeMirror-lint-markers'
|
'CodeMirror-lint-markers'
|
||||||
],
|
],
|
||||||
autoCloseBrackets: {
|
autoCloseBrackets: {
|
||||||
|
codeBlock: {
|
||||||
|
pairs: this.props.codeBlockMatchingPairs,
|
||||||
|
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||||
|
triples: this.props.codeBlockMatchingTriples,
|
||||||
|
explode: this.props.codeBlockExplodingPairs
|
||||||
|
},
|
||||||
|
markdown: {
|
||||||
pairs: this.props.matchingPairs,
|
pairs: this.props.matchingPairs,
|
||||||
|
closeBefore: this.props.matchingCloseBefore,
|
||||||
triples: this.props.matchingTriples,
|
triples: this.props.matchingTriples,
|
||||||
explode: this.props.explodingPairs,
|
explode: this.props.explodingPairs
|
||||||
override: true
|
}
|
||||||
},
|
},
|
||||||
extraKeys: this.defaultKeyMap,
|
extraKeys: this.defaultKeyMap,
|
||||||
prettierConfig: this.props.prettierConfig
|
prettierConfig: this.props.prettierConfig
|
||||||
@@ -352,6 +384,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
eventEmitter.emit('code:init')
|
eventEmitter.emit('code:init')
|
||||||
this.editor.on('scroll', this.scrollHandler)
|
this.editor.on('scroll', this.scrollHandler)
|
||||||
|
this.editor.on('cursorActivity', this.editorActivityHandler)
|
||||||
|
|
||||||
const editorTheme = document.getElementById('editorTheme')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||||
@@ -489,7 +522,6 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (this.props.enableTableEditor) {
|
if (this.props.enableTableEditor) {
|
||||||
this.editor.on('cursorActivity', this.editorActivityHandler)
|
|
||||||
this.editor.on('changes', this.editorActivityHandler)
|
this.editor.on('changes', this.editorActivityHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,12 +580,18 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.off('paste', this.pasteHandler)
|
this.editor.off('paste', this.pasteHandler)
|
||||||
eventEmitter.off('top:search', this.searchHandler)
|
eventEmitter.off('top:search', this.searchHandler)
|
||||||
this.editor.off('scroll', this.scrollHandler)
|
this.editor.off('scroll', this.scrollHandler)
|
||||||
|
this.editor.off('cursorActivity', this.editorActivityHandler)
|
||||||
this.editor.off('contextmenu', this.contextMenuHandler)
|
this.editor.off('contextmenu', this.contextMenuHandler)
|
||||||
|
|
||||||
const editorTheme = document.getElementById('editorTheme')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
|
spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
|
||||||
eventEmitter.off('code:format-table', this.formatTable)
|
eventEmitter.off('code:format-table', this.formatTable)
|
||||||
|
|
||||||
|
if (this.props.enableTableEditor) {
|
||||||
|
this.editor.off('changes', this.editorActivityHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
@@ -629,16 +667,32 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
prevProps.matchingPairs !== this.props.matchingPairs ||
|
prevProps.matchingPairs !== this.props.matchingPairs ||
|
||||||
|
prevProps.matchingCloseBefore !== this.props.matchingCloseBefore ||
|
||||||
prevProps.matchingTriples !== this.props.matchingTriples ||
|
prevProps.matchingTriples !== this.props.matchingTriples ||
|
||||||
prevProps.explodingPairs !== this.props.explodingPairs
|
prevProps.explodingPairs !== this.props.explodingPairs ||
|
||||||
|
prevProps.codeBlockMatchingPairs !== this.props.codeBlockMatchingPairs ||
|
||||||
|
prevProps.codeBlockMatchingCloseBefore !==
|
||||||
|
this.props.codeBlockMatchingCloseBefore ||
|
||||||
|
prevProps.codeBlockMatchingTriples !==
|
||||||
|
this.props.codeBlockMatchingTriples ||
|
||||||
|
prevProps.codeBlockExplodingPairs !== this.props.codeBlockExplodingPairs
|
||||||
) {
|
) {
|
||||||
const bracketObject = {
|
const autoCloseBrackets = {
|
||||||
|
codeBlock: {
|
||||||
|
pairs: this.props.codeBlockMatchingPairs,
|
||||||
|
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||||
|
triples: this.props.codeBlockMatchingTriples,
|
||||||
|
explode: this.props.codeBlockExplodingPairs
|
||||||
|
},
|
||||||
|
markdown: {
|
||||||
pairs: this.props.matchingPairs,
|
pairs: this.props.matchingPairs,
|
||||||
|
closeBefore: this.props.matchingCloseBefore,
|
||||||
triples: this.props.matchingTriples,
|
triples: this.props.matchingTriples,
|
||||||
explode: this.props.explodingPairs,
|
explode: this.props.explodingPairs
|
||||||
override: true
|
|
||||||
}
|
}
|
||||||
this.editor.setOption('autoCloseBrackets', bracketObject)
|
}
|
||||||
|
|
||||||
|
this.editor.setOption('autoCloseBrackets', autoCloseBrackets)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
|
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
|
||||||
@@ -756,6 +810,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleChange(editor, changeObject) {
|
handleChange(editor, changeObject) {
|
||||||
|
this.debouncedDeletionOfAttachments.cancel()
|
||||||
|
|
||||||
spellcheck.handleChange(editor, changeObject)
|
spellcheck.handleChange(editor, changeObject)
|
||||||
|
|
||||||
// The current note contains an toc. We'll check for changes on headlines.
|
// The current note contains an toc. We'll check for changes on headlines.
|
||||||
@@ -793,9 +849,23 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.updateHighlight(editor, changeObject)
|
this.updateHighlight(editor, changeObject)
|
||||||
|
|
||||||
this.value = editor.getValue()
|
this.value = editor.getValue()
|
||||||
|
|
||||||
|
const { storageKey, noteKey } = this.props
|
||||||
|
const storage = findStorage(storageKey)
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
this.props.onChange(editor)
|
this.props.onChange(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isWrite = !!this.props.onChange
|
||||||
|
const hasFileChanges = isWrite
|
||||||
|
|
||||||
|
if (storage) {
|
||||||
|
sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
|
||||||
|
isWrite,
|
||||||
|
hasFileChanges,
|
||||||
|
isFileChange: false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
linePossibleContainsHeadline(currentLine) {
|
linePossibleContainsHeadline(currentLine) {
|
||||||
@@ -923,6 +993,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.restartHighlighting()
|
this.restartHighlighting()
|
||||||
this.editor.on('change', this.changeHandler)
|
this.editor.on('change', this.changeHandler)
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
|
|
||||||
|
// wakatime
|
||||||
|
const { storageKey, noteKey } = this.props
|
||||||
|
const storage = findStorage(storageKey)
|
||||||
|
if (storage)
|
||||||
|
sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
|
||||||
|
isWrite: false,
|
||||||
|
hasFileChanges: false,
|
||||||
|
isFileChange: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(value) {
|
setValue(value) {
|
||||||
@@ -1240,18 +1320,19 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, fontSize } = this.props
|
const { className, fontSize, fontFamily, width, height } = this.props
|
||||||
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
const normalisedFontFamily = normalizeEditorFontFamily(fontFamily)
|
||||||
const width = this.props.width
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
|
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
|
||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={{
|
style={{
|
||||||
fontFamily,
|
fontFamily: normalisedFontFamily,
|
||||||
fontSize: fontSize,
|
fontSize,
|
||||||
width: width
|
width,
|
||||||
|
height
|
||||||
}}
|
}}
|
||||||
onDrop={e => this.handleDropImage(e)}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
this.previewRef.current.focus()
|
this.previewRef.current.focus()
|
||||||
this.previewRef.current.scrollToRow(cursorPosition.line)
|
this.previewRef.current.scrollToLine(cursorPosition.line)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
@@ -323,6 +323,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
storageKey,
|
storageKey,
|
||||||
noteKey,
|
noteKey,
|
||||||
linesHighlighted,
|
linesHighlighted,
|
||||||
|
getNote,
|
||||||
RTL
|
RTL
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
@@ -365,8 +366,15 @@ class MarkdownEditor extends React.Component {
|
|||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
lineWrapping
|
lineWrapping
|
||||||
matchingPairs={config.editor.matchingPairs}
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||||
matchingTriples={config.editor.matchingTriples}
|
matchingTriples={config.editor.matchingTriples}
|
||||||
explodingPairs={config.editor.explodingPairs}
|
explodingPairs={config.editor.explodingPairs}
|
||||||
|
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||||
|
codeBlockMatchingCloseBefore={
|
||||||
|
config.editor.codeBlockMatchingCloseBefore
|
||||||
|
}
|
||||||
|
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||||
|
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
@@ -381,6 +389,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
switchPreview={config.editor.switchPreview}
|
switchPreview={config.editor.switchPreview}
|
||||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||||
|
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||||
prettierConfig={config.editor.prettierConfig}
|
prettierConfig={config.editor.prettierConfig}
|
||||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
@@ -418,6 +427,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
|
getNote={getNote}
|
||||||
|
export={config.export}
|
||||||
onDrop={e => this.handleDropImage(e)}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,258 +18,30 @@ import convertModeName from 'browser/lib/convertModeName'
|
|||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import mdurl from 'mdurl'
|
import mdurl from 'mdurl'
|
||||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||||
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
import formatMarkdown from 'browser/main/lib/dataApi/formatMarkdown'
|
||||||
|
import formatHTML, {
|
||||||
|
CSS_FILES,
|
||||||
|
buildStyle,
|
||||||
|
getCodeThemeLink,
|
||||||
|
getStyleParams,
|
||||||
|
escapeHtmlCharactersInCodeTag
|
||||||
|
} from 'browser/main/lib/dataApi/formatHTML'
|
||||||
|
import formatPDF from 'browser/main/lib/dataApi/formatPDF'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import path from 'path'
|
||||||
|
import { remote, shell } from 'electron'
|
||||||
|
import attachmentManagement from '../main/lib/dataApi/attachmentManagement'
|
||||||
|
import filenamify from 'filenamify'
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
import Carousel from 'react-image-carousel'
|
import Carousel from 'react-image-carousel'
|
||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
import ConfigManager from '../main/lib/ConfigManager'
|
import ConfigManager from '../main/lib/ConfigManager'
|
||||||
import uiThemes from 'browser/lib/ui-themes'
|
import uiThemes from 'browser/lib/ui-themes'
|
||||||
import i18n from 'browser/lib/i18n'
|
import { buildMarkdownPreviewContextMenu } from 'browser/lib/contextMenuBuilder'
|
||||||
|
|
||||||
const { remote, shell } = require('electron')
|
|
||||||
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
|
||||||
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder')
|
|
||||||
.buildMarkdownPreviewContextMenu
|
|
||||||
|
|
||||||
const { app } = remote
|
|
||||||
const path = require('path')
|
|
||||||
const fileUrl = require('file-url')
|
|
||||||
|
|
||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
|
||||||
const appPath = fileUrl(
|
|
||||||
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
|
||||||
)
|
|
||||||
const CSS_FILES = [
|
|
||||||
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
|
||||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`,
|
|
||||||
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {String} opts.fontFamily
|
|
||||||
* @param {Numberl} opts.fontSize
|
|
||||||
* @param {String} opts.codeBlockFontFamily
|
|
||||||
* @param {String} opts.theme
|
|
||||||
* @param {Boolean} [opts.lineNumber] Should show line number
|
|
||||||
* @param {Boolean} [opts.scrollPastEnd]
|
|
||||||
* @param {Boolean} [opts.allowCustomCSS] Should add custom css
|
|
||||||
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
function buildStyle(opts) {
|
|
||||||
const {
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
codeBlockFontFamily,
|
|
||||||
lineNumber,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
} = opts
|
|
||||||
return `
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
|
||||||
src: url('${appPath}/resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
|
||||||
url('${appPath}/resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('${appPath}/resources/fonts/Lato-Regular.ttf') format('truetype');
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
|
||||||
src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */
|
|
||||||
url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype');
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Material Icons';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local('Material Icons'),
|
|
||||||
local('MaterialIcons-Regular'),
|
|
||||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
|
|
||||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
|
||||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
${markdownStyle}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: '${fontFamily.join("','")}';
|
|
||||||
font-size: ${fontSize}px;
|
|
||||||
|
|
||||||
${
|
|
||||||
scrollPastEnd
|
|
||||||
? `
|
|
||||||
padding-bottom: 90vh;
|
|
||||||
box-sizing: border-box;
|
|
||||||
`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
${RTL ? 'direction: rtl;' : ''}
|
|
||||||
${RTL ? 'text-align: right;' : ''}
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
body {
|
|
||||||
padding-bottom: initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
font-family: '${codeBlockFontFamily.join("','")}';
|
|
||||||
background-color: rgba(0,0,0,0.04);
|
|
||||||
text-align: left;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
p code,
|
|
||||||
li code,
|
|
||||||
td code
|
|
||||||
{
|
|
||||||
padding: 2px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
[data-theme="default"] p code,
|
|
||||||
[data-theme="default"] li code,
|
|
||||||
[data-theme="default"] td code
|
|
||||||
{
|
|
||||||
background-color: #F4F4F4;
|
|
||||||
border-color: #d9d9d9;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
[data-theme="white"] p code,
|
|
||||||
[data-theme="white"] li code,
|
|
||||||
[data-theme="white"] td code
|
|
||||||
{
|
|
||||||
background-color: #F4F4F4;
|
|
||||||
border-color: #d9d9d9;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] p code,
|
|
||||||
[data-theme="dark"] li code,
|
|
||||||
[data-theme="dark"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="dracula"] p code,
|
|
||||||
[data-theme="dracula"] li code,
|
|
||||||
[data-theme="dracula"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="monokai"] p code,
|
|
||||||
[data-theme="monokai"] li code,
|
|
||||||
[data-theme="monokai"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="nord"] p code,
|
|
||||||
[data-theme="nord"] li code,
|
|
||||||
[data-theme="nord"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="solarized-dark"] p code,
|
|
||||||
[data-theme="solarized-dark"] li code,
|
|
||||||
[data-theme="solarized-dark"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="vulcan"] p code,
|
|
||||||
[data-theme="vulcan"] li code,
|
|
||||||
[data-theme="vulcan"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lineNumber {
|
|
||||||
${lineNumber && 'display: block !important;'}
|
|
||||||
font-family: '${codeBlockFontFamily.join("','")}';
|
|
||||||
}
|
|
||||||
|
|
||||||
.clipboardButton {
|
|
||||||
color: rgba(147,147,149,0.8);;
|
|
||||||
fill: rgba(147,147,149,1);;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin: 0px 10px;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
outline: none;
|
|
||||||
height: 15px;
|
|
||||||
width: 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clipboardButton:hover {
|
|
||||||
transition: 0.2s;
|
|
||||||
color: #939395;
|
|
||||||
fill: #939395;
|
|
||||||
background-color: rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 1em 0 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4, h5, h6 {
|
|
||||||
margin: 1.1em 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
padding: 0.2em 0 0.2em;
|
|
||||||
margin: 1em 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
padding: 0.2em 0 0.2em;
|
|
||||||
margin: 1em 0 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body p {
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
body[data-theme="${theme}"] {
|
|
||||||
color: #000;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
.clipboardButton {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
${allowCustomCSS ? customCSS : ''}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollBarStyle = `
|
const scrollBarStyle = `
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||||
@@ -301,22 +73,6 @@ const scrollBarDarkStyle = `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
|
||||||
|
|
||||||
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
|
||||||
if (!OSX) {
|
|
||||||
defaultFontFamily.unshift('Microsoft YaHei')
|
|
||||||
defaultFontFamily.unshift('meiryo')
|
|
||||||
}
|
|
||||||
const defaultCodeBlockFontFamily = [
|
|
||||||
'Monaco',
|
|
||||||
'Menlo',
|
|
||||||
'Ubuntu Mono',
|
|
||||||
'Consolas',
|
|
||||||
'source-code-pro',
|
|
||||||
'monospace'
|
|
||||||
]
|
|
||||||
|
|
||||||
// return the line number of the line that used to generate the specified element
|
// return the line number of the line that used to generate the specified element
|
||||||
// return -1 if the line is not found
|
// return -1 if the line is not found
|
||||||
function getSourceLineNumberByElement(element) {
|
function getSourceLineNumberByElement(element) {
|
||||||
@@ -430,94 +186,15 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsMd() {
|
handleSaveAsMd() {
|
||||||
this.exportAsDocument('md')
|
this.exportAsDocument('md', formatMarkdown(this.props))
|
||||||
}
|
|
||||||
|
|
||||||
htmlContentFormatter(noteContent, exportTasks, targetDir) {
|
|
||||||
const {
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
codeBlockFontFamily,
|
|
||||||
lineNumber,
|
|
||||||
codeBlockTheme,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
} = this.getStyleParams()
|
|
||||||
|
|
||||||
const inlineStyles = buildStyle({
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
codeBlockFontFamily,
|
|
||||||
lineNumber,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
})
|
|
||||||
let body = this.refs.root.contentWindow.document.body.innerHTML
|
|
||||||
body = attachmentManagement.fixLocalURLS(body, this.props.storagePath)
|
|
||||||
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
|
||||||
files.forEach(file => {
|
|
||||||
if (global.process.platform === 'win32') {
|
|
||||||
file = file.replace('file:///', '')
|
|
||||||
} else {
|
|
||||||
file = file.replace('file://', '')
|
|
||||||
}
|
|
||||||
exportTasks.push({
|
|
||||||
src: file,
|
|
||||||
dst: 'css'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let styles = ''
|
|
||||||
files.forEach(file => {
|
|
||||||
styles += `<link rel="stylesheet" href="../css/${path.basename(file)}">`
|
|
||||||
})
|
|
||||||
|
|
||||||
return `<html>
|
|
||||||
<head>
|
|
||||||
<base href="file://${targetDir}/">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
|
||||||
<style id="style">${inlineStyles}</style>
|
|
||||||
${styles}
|
|
||||||
</head>
|
|
||||||
<body>${body}</body>
|
|
||||||
</html>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsHtml() {
|
handleSaveAsHtml() {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) =>
|
this.exportAsDocument('html', formatHTML(this.props))
|
||||||
Promise.resolve(
|
|
||||||
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsPdf() {
|
handleSaveAsPdf() {
|
||||||
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
|
this.exportAsDocument('pdf', formatPDF(this.props))
|
||||||
const printout = new remote.BrowserWindow({
|
|
||||||
show: false,
|
|
||||||
webPreferences: { webSecurity: false, javascript: false }
|
|
||||||
})
|
|
||||||
printout.loadURL(
|
|
||||||
'data:text/html;charset=UTF-8,' +
|
|
||||||
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
|
|
||||||
)
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
printout.webContents.on('did-finish-load', () => {
|
|
||||||
printout.webContents.printToPDF({}, (err, data) => {
|
|
||||||
if (err) reject(err)
|
|
||||||
else resolve(data)
|
|
||||||
printout.destroy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePrint() {
|
handlePrint() {
|
||||||
@@ -525,18 +202,21 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exportAsDocument(fileType, contentFormatter) {
|
exportAsDocument(fileType, contentFormatter) {
|
||||||
|
const note = this.props.getNote()
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
defaultPath: filenamify(note.title, {
|
||||||
|
replacement: '_'
|
||||||
|
}),
|
||||||
filters: [{ name: 'Documents', extensions: [fileType] }],
|
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const content = this.props.value
|
const storagePath = this.props.storagePath
|
||||||
const storage = this.props.storagePath
|
|
||||||
const nodeKey = this.props.noteKey
|
|
||||||
|
|
||||||
exportNote(nodeKey, storage, content, filename, contentFormatter)
|
exportNote(storagePath, note, filename, contentFormatter)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
@@ -567,32 +247,6 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Convert special characters between three ```
|
|
||||||
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
|
|
||||||
* @returns {string} HTML in which special characters between three ``` have been converted
|
|
||||||
*/
|
|
||||||
escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
|
|
||||||
for (let index = 0; index < splitWithCodeTag.length; index++) {
|
|
||||||
const codeTagRequired =
|
|
||||||
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
|
|
||||||
if (codeTagRequired) {
|
|
||||||
splitWithCodeTag.splice(index + 1, 0, '```')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let inCodeTag = false
|
|
||||||
let result = ''
|
|
||||||
for (let content of splitWithCodeTag) {
|
|
||||||
if (content === '```') {
|
|
||||||
inCodeTag = !inCodeTag
|
|
||||||
} else if (inCodeTag) {
|
|
||||||
content = escapeHtmlCharacters(content)
|
|
||||||
}
|
|
||||||
result += content
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
getScrollBarStyle() {
|
getScrollBarStyle() {
|
||||||
const { theme } = this.props
|
const { theme } = this.props
|
||||||
|
|
||||||
@@ -743,47 +397,6 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams() {
|
|
||||||
const {
|
|
||||||
fontSize,
|
|
||||||
lineNumber,
|
|
||||||
codeBlockTheme,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
} = this.props
|
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
|
||||||
fontFamily =
|
|
||||||
_.isString(fontFamily) && fontFamily.trim().length > 0
|
|
||||||
? fontFamily
|
|
||||||
.split(',')
|
|
||||||
.map(fontName => fontName.trim())
|
|
||||||
.concat(defaultFontFamily)
|
|
||||||
: defaultFontFamily
|
|
||||||
codeBlockFontFamily =
|
|
||||||
_.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
|
||||||
? codeBlockFontFamily
|
|
||||||
.split(',')
|
|
||||||
.map(fontName => fontName.trim())
|
|
||||||
.concat(defaultCodeBlockFontFamily)
|
|
||||||
: defaultCodeBlockFontFamily
|
|
||||||
|
|
||||||
return {
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
codeBlockFontFamily,
|
|
||||||
lineNumber,
|
|
||||||
codeBlockTheme,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyStyle() {
|
applyStyle() {
|
||||||
const {
|
const {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
@@ -796,12 +409,13 @@ class MarkdownPreview extends React.Component {
|
|||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS,
|
customCSS,
|
||||||
RTL
|
RTL
|
||||||
} = this.getStyleParams()
|
} = getStyleParams(this.props)
|
||||||
|
|
||||||
this.getWindow().document.getElementById(
|
this.getWindow().document.getElementById(
|
||||||
'codeTheme'
|
'codeTheme'
|
||||||
).href = this.getCodeThemeLink(codeBlockTheme)
|
).href = getCodeThemeLink(codeBlockTheme)
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle({
|
|
||||||
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
codeBlockFontFamily,
|
codeBlockFontFamily,
|
||||||
@@ -811,15 +425,7 @@ class MarkdownPreview extends React.Component {
|
|||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS,
|
customCSS,
|
||||||
RTL
|
RTL
|
||||||
})
|
)
|
||||||
}
|
|
||||||
|
|
||||||
getCodeThemeLink(name) {
|
|
||||||
const theme = consts.THEMES.find(theme => theme.name === name)
|
|
||||||
|
|
||||||
return theme != null
|
|
||||||
? theme.path
|
|
||||||
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe() {
|
rewriteIframe() {
|
||||||
@@ -853,7 +459,7 @@ class MarkdownPreview extends React.Component {
|
|||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
if (sanitize === 'NONE') {
|
if (sanitize === 'NONE') {
|
||||||
const splitWithCodeTag = value.split('```')
|
const splitWithCodeTag = value.split('```')
|
||||||
value = this.escapeHtmlCharactersInCodeTag(splitWithCodeTag)
|
value = escapeHtmlCharactersInCodeTag(splitWithCodeTag)
|
||||||
}
|
}
|
||||||
const renderedHTML = this.markdown.render(value)
|
const renderedHTML = this.markdown.render(value)
|
||||||
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
||||||
@@ -916,13 +522,9 @@ class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const opts = {}
|
const opts = {}
|
||||||
// if (this.props.theme === 'dark') {
|
|
||||||
// opts['font-color'] = '#DDD'
|
|
||||||
// opts['line-color'] = '#DDD'
|
|
||||||
// opts['element-color'] = '#DDD'
|
|
||||||
// opts['fill'] = '#3A404C'
|
|
||||||
// }
|
|
||||||
_.forEach(
|
_.forEach(
|
||||||
this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
|
this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
|
||||||
el => {
|
el => {
|
||||||
@@ -1145,17 +747,18 @@ class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {Number} targetRow
|
* @param {Number} targetLine
|
||||||
*/
|
*/
|
||||||
scrollToRow(targetRow) {
|
scrollToLine(targetLine) {
|
||||||
const blocks = this.getWindow().document.querySelectorAll(
|
const blocks = this.getWindow().document.querySelectorAll(
|
||||||
'body>[data-line]'
|
'body [data-line]'
|
||||||
)
|
)
|
||||||
|
|
||||||
for (let index = 0; index < blocks.length; index++) {
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
let block = blocks[index]
|
let block = blocks[index]
|
||||||
const row = parseInt(block.getAttribute('data-line'))
|
const line = parseInt(block.getAttribute('data-line'))
|
||||||
if (row > targetRow || index === blocks.length - 1) {
|
|
||||||
|
if (line > targetLine || index === blocks.length - 1) {
|
||||||
block = blocks[index - 1]
|
block = blocks[index - 1]
|
||||||
block != null && this.scrollTo(0, block.offsetTop)
|
block != null && this.scrollTo(0, block.offsetTop)
|
||||||
break
|
break
|
||||||
@@ -1192,7 +795,10 @@ class MarkdownPreview extends React.Component {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
const rawHref = e.target.getAttribute('href')
|
const el = e.target.closest('a[href]')
|
||||||
|
if (!el) return
|
||||||
|
|
||||||
|
const rawHref = el.getAttribute('href')
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
|
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,77 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
this.value = props.value
|
this.value = props.value
|
||||||
this.focus = () => this.refs.code.focus()
|
this.focus = () => this.refs.code.focus()
|
||||||
this.reload = () => this.refs.code.reload()
|
this.reload = () => this.refs.code.reload()
|
||||||
this.userScroll = true
|
this.userScroll = props.config.preview.scrollSync
|
||||||
this.state = {
|
this.state = {
|
||||||
isSliderFocused: false,
|
isSliderFocused: false,
|
||||||
codeEditorWidthInPercent: 50
|
codeEditorWidthInPercent: 50,
|
||||||
|
codeEditorHeightInPercent: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (
|
||||||
|
this.props.config.preview.scrollSync !==
|
||||||
|
prevProps.config.preview.scrollSync
|
||||||
|
) {
|
||||||
|
this.userScroll = this.props.config.preview.scrollSync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCursorActivity(editor) {
|
||||||
|
if (this.userScroll) {
|
||||||
|
const previewDoc = _.get(
|
||||||
|
this,
|
||||||
|
'refs.preview.refs.root.contentWindow.document'
|
||||||
|
)
|
||||||
|
const previewTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
|
||||||
|
const line = editor.doc.getCursor().line
|
||||||
|
let top
|
||||||
|
if (line === 0) {
|
||||||
|
top = 0
|
||||||
|
} else {
|
||||||
|
const blockElements = previewDoc.querySelectorAll('body [data-line]')
|
||||||
|
const blocks = []
|
||||||
|
for (const block of blockElements) {
|
||||||
|
const l = parseInt(block.getAttribute('data-line'))
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: l,
|
||||||
|
top: block.offsetTop
|
||||||
|
})
|
||||||
|
|
||||||
|
if (l > line) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocks.length === 1) {
|
||||||
|
const block = blockElements[blockElements.length - 1]
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: editor.doc.size,
|
||||||
|
top: block.offsetTop + block.offsetHeight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = blocks.length - 1
|
||||||
|
|
||||||
|
const ratio =
|
||||||
|
(blocks[i].top - blocks[i - 1].top) /
|
||||||
|
(blocks[i].line - blocks[i - 1].line)
|
||||||
|
|
||||||
|
const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
|
||||||
|
|
||||||
|
top =
|
||||||
|
blocks[i - 1].top +
|
||||||
|
Math.floor((line - blocks[i - 1].line) * ratio) -
|
||||||
|
delta
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTo(previewTop, top, y =>
|
||||||
|
_.set(previewDoc, 'body.scrollTop', y)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,59 +96,125 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
this.props.onChange(e)
|
this.props.onChange(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll(e) {
|
handleEditorScroll(e) {
|
||||||
if (!this.props.config.preview.scrollSync) return
|
if (this.userScroll) {
|
||||||
|
|
||||||
const previewDoc = _.get(
|
const previewDoc = _.get(
|
||||||
this,
|
this,
|
||||||
'refs.preview.refs.root.contentWindow.document'
|
'refs.preview.refs.root.contentWindow.document'
|
||||||
)
|
)
|
||||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||||
let srcTop, srcHeight, targetTop, targetHeight
|
|
||||||
|
|
||||||
if (this.userScroll) {
|
const from = codeDoc.cm.coordsChar({ left: 0, top: 0 }).line
|
||||||
if (e.doc) {
|
const to = codeDoc.cm.coordsChar({
|
||||||
srcTop = _.get(e, 'doc.scrollTop')
|
left: 0,
|
||||||
srcHeight = _.get(e, 'doc.height')
|
top: codeDoc.cm.display.lastWrapHeight * 1.125
|
||||||
targetTop = _.get(previewDoc, 'body.scrollTop')
|
}).line
|
||||||
targetHeight = _.get(previewDoc, 'body.scrollHeight')
|
const previewTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
|
||||||
|
let top
|
||||||
|
if (from === 0) {
|
||||||
|
top = 0
|
||||||
|
} else if (to === codeDoc.lastLine()) {
|
||||||
|
top =
|
||||||
|
_.get(previewDoc, 'body.scrollHeight') -
|
||||||
|
_.get(previewDoc, 'body.clientHeight')
|
||||||
} else {
|
} else {
|
||||||
srcTop = _.get(previewDoc, 'body.scrollTop')
|
const line = from + Math.floor((to - from) / 3)
|
||||||
srcHeight = _.get(previewDoc, 'body.scrollHeight')
|
|
||||||
targetTop = _.get(codeDoc, 'scrollTop')
|
const blockElements = previewDoc.querySelectorAll('body [data-line]')
|
||||||
targetHeight = _.get(codeDoc, 'height')
|
const blocks = []
|
||||||
|
for (const block of blockElements) {
|
||||||
|
const l = parseInt(block.getAttribute('data-line'))
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: l,
|
||||||
|
top: block.offsetTop
|
||||||
|
})
|
||||||
|
|
||||||
|
if (l > line) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const distance = (targetHeight * srcTop) / srcHeight - targetTop
|
if (blocks.length === 1) {
|
||||||
const framerate = 1000 / 60
|
const block = blockElements[blockElements.length - 1]
|
||||||
const frames = 20
|
|
||||||
const refractory = frames * framerate
|
|
||||||
|
|
||||||
this.userScroll = false
|
blocks.push({
|
||||||
|
line: codeDoc.size,
|
||||||
|
top: block.offsetTop + block.offsetHeight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let frame = 0
|
const i = blocks.length - 1
|
||||||
let scrollPos, time
|
|
||||||
const timer = setInterval(() => {
|
const ratio =
|
||||||
time = frame / frames
|
(blocks[i].top - blocks[i - 1].top) /
|
||||||
scrollPos =
|
(blocks[i].line - blocks[i - 1].line)
|
||||||
time < 0.5
|
|
||||||
? 2 * time * time // ease in
|
top =
|
||||||
: -1 + (4 - 2 * time) * time // ease out
|
blocks[i - 1].top + Math.floor((line - blocks[i - 1].line) * ratio)
|
||||||
if (e.doc)
|
}
|
||||||
_.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
|
||||||
else
|
this.scrollTo(previewTop, top, y =>
|
||||||
_.get(this, 'refs.code.editor').scrollTo(
|
_.set(previewDoc, 'body.scrollTop', y)
|
||||||
0,
|
|
||||||
targetTop + scrollPos * distance
|
|
||||||
)
|
)
|
||||||
if (frame >= frames) {
|
|
||||||
clearInterval(timer)
|
|
||||||
setTimeout(() => {
|
|
||||||
this.userScroll = true
|
|
||||||
}, refractory)
|
|
||||||
}
|
}
|
||||||
frame++
|
}
|
||||||
}, framerate)
|
|
||||||
|
handlePreviewScroll(e) {
|
||||||
|
if (this.userScroll) {
|
||||||
|
const previewDoc = _.get(
|
||||||
|
this,
|
||||||
|
'refs.preview.refs.root.contentWindow.document'
|
||||||
|
)
|
||||||
|
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||||
|
|
||||||
|
const srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
const editorTop = _.get(codeDoc, 'scrollTop')
|
||||||
|
|
||||||
|
let top
|
||||||
|
if (srcTop === 0) {
|
||||||
|
top = 0
|
||||||
|
} else {
|
||||||
|
const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
|
||||||
|
const previewTop = srcTop + delta
|
||||||
|
|
||||||
|
const blockElements = previewDoc.querySelectorAll('body [data-line]')
|
||||||
|
const blocks = []
|
||||||
|
for (const block of blockElements) {
|
||||||
|
const top = block.offsetTop
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: parseInt(block.getAttribute('data-line')),
|
||||||
|
top
|
||||||
|
})
|
||||||
|
|
||||||
|
if (top > previewTop) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocks.length === 1) {
|
||||||
|
const block = blockElements[blockElements.length - 1]
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: codeDoc.size,
|
||||||
|
top: block.offsetTop + block.offsetHeight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = blocks.length - 1
|
||||||
|
|
||||||
|
const from = codeDoc.cm.heightAtLine(blocks[i - 1].line, 'local')
|
||||||
|
const to = codeDoc.cm.heightAtLine(blocks[i].line, 'local')
|
||||||
|
|
||||||
|
const ratio =
|
||||||
|
(previewTop - blocks[i - 1].top) / (blocks[i].top - blocks[i - 1].top)
|
||||||
|
|
||||||
|
top = from + Math.floor((to - from) * ratio) - delta
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTo(editorTop, top, y => codeDoc.cm.scrollTo(0, y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +247,25 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
if (this.state.isSliderFocused) {
|
if (this.state.isSliderFocused) {
|
||||||
const rootRect = this.refs.root.getBoundingClientRect()
|
const rootRect = this.refs.root.getBoundingClientRect()
|
||||||
|
if (this.props.isStacking) {
|
||||||
|
const rootHeight = rootRect.height
|
||||||
|
const offset = rootRect.top
|
||||||
|
let newCodeEditorHeightInPercent =
|
||||||
|
((e.pageY - offset) / rootHeight) * 100
|
||||||
|
|
||||||
|
// limit minSize to 10%, maxSize to 90%
|
||||||
|
if (newCodeEditorHeightInPercent <= 10) {
|
||||||
|
newCodeEditorHeightInPercent = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCodeEditorHeightInPercent >= 90) {
|
||||||
|
newCodeEditorHeightInPercent = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
codeEditorHeightInPercent: newCodeEditorHeightInPercent
|
||||||
|
})
|
||||||
|
} else {
|
||||||
const rootWidth = rootRect.width
|
const rootWidth = rootRect.width
|
||||||
const offset = rootRect.left
|
const offset = rootRect.left
|
||||||
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
|
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
|
||||||
@@ -132,6 +284,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -147,6 +300,35 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollTo(from, to, scroller) {
|
||||||
|
const distance = to - from
|
||||||
|
const framerate = 1000 / 60
|
||||||
|
const frames = 20
|
||||||
|
const refractory = frames * framerate
|
||||||
|
|
||||||
|
this.userScroll = false
|
||||||
|
|
||||||
|
let frame = 0
|
||||||
|
let scrollPos, time
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
time = frame / frames
|
||||||
|
scrollPos =
|
||||||
|
time < 0.5
|
||||||
|
? 2 * time * time // ease in
|
||||||
|
: -1 + (4 - 2 * time) * time // ease out
|
||||||
|
|
||||||
|
scroller(from + scrollPos * distance)
|
||||||
|
|
||||||
|
if (frame >= frames) {
|
||||||
|
clearInterval(timer)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.userScroll = true
|
||||||
|
}, refractory)
|
||||||
|
}
|
||||||
|
frame++
|
||||||
|
}, framerate)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
config,
|
config,
|
||||||
@@ -154,6 +336,8 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
storageKey,
|
storageKey,
|
||||||
noteKey,
|
noteKey,
|
||||||
linesHighlighted,
|
linesHighlighted,
|
||||||
|
getNote,
|
||||||
|
isStacking,
|
||||||
RTL
|
RTL
|
||||||
} = this.props
|
} = this.props
|
||||||
let storage
|
let storage
|
||||||
@@ -162,14 +346,62 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return <div />
|
return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let editorStyle = {}
|
||||||
|
let previewStyle = {}
|
||||||
|
let sliderStyle = {}
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
editorStyle.fontSize = editorFontSize
|
||||||
|
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorStyle.fontSize > 0 && editorStyle.fontSize < 132))
|
||||||
const previewStyle = {}
|
editorIndentSize = 4
|
||||||
previewStyle.width = 100 - this.state.codeEditorWidthInPercent + '%'
|
editorStyle.indentSize = editorIndentSize
|
||||||
|
|
||||||
|
editorStyle = Object.assign(
|
||||||
|
editorStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
width: '100%',
|
||||||
|
height: `${this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: `${this.state.codeEditorWidthInPercent}%`,
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
previewStyle = Object.assign(
|
||||||
|
previewStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
width: '100%',
|
||||||
|
height: `${100 - this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: `${100 - this.state.codeEditorWidthInPercent}%`,
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sliderStyle = Object.assign(
|
||||||
|
sliderStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
left: 0,
|
||||||
|
top: `${this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
left: `${this.state.codeEditorWidthInPercent}%`,
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
|
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
|
||||||
previewStyle.pointerEvents = 'none'
|
previewStyle.pointerEvents = 'none'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
styleName='root'
|
styleName='root'
|
||||||
@@ -179,20 +411,28 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
>
|
>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
ref='code'
|
ref='code'
|
||||||
width={this.state.codeEditorWidthInPercent + '%'}
|
width={editorStyle.width}
|
||||||
|
height={editorStyle.height}
|
||||||
mode='Boost Flavored Markdown'
|
mode='Boost Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
fontFamily={config.editor.fontFamily}
|
fontFamily={config.editor.fontFamily}
|
||||||
fontSize={editorFontSize}
|
fontSize={editorStyle.fontSize}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
lineWrapping
|
lineWrapping
|
||||||
matchingPairs={config.editor.matchingPairs}
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||||
matchingTriples={config.editor.matchingTriples}
|
matchingTriples={config.editor.matchingTriples}
|
||||||
explodingPairs={config.editor.explodingPairs}
|
explodingPairs={config.editor.explodingPairs}
|
||||||
|
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||||
|
codeBlockMatchingCloseBefore={
|
||||||
|
config.editor.codeBlockMatchingCloseBefore
|
||||||
|
}
|
||||||
|
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||||
|
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorStyle.indentSize}
|
||||||
enableRulers={config.editor.enableRulers}
|
enableRulers={config.editor.enableRulers}
|
||||||
rulers={config.editor.rulers}
|
rulers={config.editor.rulers}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
@@ -202,19 +442,21 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
linesHighlighted={linesHighlighted}
|
linesHighlighted={linesHighlighted}
|
||||||
onChange={e => this.handleOnChange(e)}
|
onChange={e => this.handleOnChange(e)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={e => this.handleEditorScroll(e)}
|
||||||
|
onCursorActivity={e => this.handleCursorActivity(e)}
|
||||||
spellCheck={config.editor.spellcheck}
|
spellCheck={config.editor.spellcheck}
|
||||||
enableSmartPaste={config.editor.enableSmartPaste}
|
enableSmartPaste={config.editor.enableSmartPaste}
|
||||||
hotkey={config.hotkey}
|
hotkey={config.hotkey}
|
||||||
switchPreview={config.editor.switchPreview}
|
switchPreview={config.editor.switchPreview}
|
||||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||||
|
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
styleName='slider'
|
styleName={isStacking ? 'slider-hoz' : 'slider'}
|
||||||
style={{ left: this.state.codeEditorWidthInPercent + '%' }}
|
style={{ left: sliderStyle.left, top: sliderStyle.top }}
|
||||||
onMouseDown={e => this.handleMouseDown(e)}
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
>
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
@@ -229,6 +471,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
codeBlockTheme={config.preview.codeBlockTheme}
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
codeBlockFontFamily={config.editor.fontFamily}
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
smartArrows={config.preview.smartArrows}
|
smartArrows={config.preview.smartArrows}
|
||||||
@@ -238,13 +481,15 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
tabInde='0'
|
tabInde='0'
|
||||||
value={value}
|
value={value}
|
||||||
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={e => this.handlePreviewScroll(e)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
|
getNote={getNote}
|
||||||
|
export={config.export}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
height 100%
|
height 100%
|
||||||
font-size 30px
|
font-size 30px
|
||||||
display flex
|
display flex
|
||||||
|
flex-wrap wrap
|
||||||
.slider
|
.slider
|
||||||
absolute top bottom
|
absolute top bottom
|
||||||
top -2px
|
top -2px
|
||||||
@@ -15,6 +16,14 @@
|
|||||||
left -3px
|
left -3px
|
||||||
z-index 10
|
z-index 10
|
||||||
cursor col-resize
|
cursor col-resize
|
||||||
|
.slider-hoz
|
||||||
|
absolute left right
|
||||||
|
.slider-hitbox
|
||||||
|
absolute left right
|
||||||
|
width: 100%
|
||||||
|
height 7px
|
||||||
|
cursor row-resize
|
||||||
|
|
||||||
|
|
||||||
apply-theme(theme)
|
apply-theme(theme)
|
||||||
body[data-theme={theme}]
|
body[data-theme={theme}]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import mermaidAPI from 'mermaid'
|
import mermaidAPI from 'mermaid/dist/mermaid.min.js'
|
||||||
import uiThemes from 'browser/lib/ui-themes'
|
import uiThemes from 'browser/lib/ui-themes'
|
||||||
|
|
||||||
// fixes bad styling in the mermaid dark theme
|
// fixes bad styling in the mermaid dark theme
|
||||||
@@ -61,7 +61,6 @@ function render(element, content, theme, enableHTMLLabel) {
|
|||||||
|
|
||||||
el.setAttribute('ratio', ratio)
|
el.setAttribute('ratio', ratio)
|
||||||
el.setAttribute('height', el.parentNode.clientWidth / ratio)
|
el.setAttribute('height', el.parentNode.clientWidth / ratio)
|
||||||
console.log(el)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ class Markdown {
|
|||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
breaks: config.preview.breaks,
|
breaks: config.preview.breaks,
|
||||||
sanitize: 'STRICT'
|
sanitize: 'STRICT',
|
||||||
|
onFence: () => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedOptions = Object.assign(defaultOptions, options)
|
const updatedOptions = Object.assign(defaultOptions, options)
|
||||||
@@ -266,6 +267,8 @@ class Markdown {
|
|||||||
token.parameters.format = 'yaml'
|
token.parameters.format = 'yaml'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedOptions.onFence('chart', token.parameters.format)
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="chart" data-height="${
|
<div class="chart" data-height="${
|
||||||
@@ -276,6 +279,8 @@ class Markdown {
|
|||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
flowchart: token => {
|
flowchart: token => {
|
||||||
|
updatedOptions.onFence('flowchart')
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="flowchart" data-height="${token.parameters.height}">${
|
<div class="flowchart" data-height="${token.parameters.height}">${
|
||||||
@@ -305,6 +310,8 @@ class Markdown {
|
|||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
mermaid: token => {
|
mermaid: token => {
|
||||||
|
updatedOptions.onFence('mermaid')
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="mermaid" data-height="${token.parameters.height}">${
|
<div class="mermaid" data-height="${token.parameters.height}">${
|
||||||
@@ -313,6 +320,8 @@ class Markdown {
|
|||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
sequence: token => {
|
sequence: token => {
|
||||||
|
updatedOptions.onFence('sequence')
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="sequence" data-height="${token.parameters.height}">${
|
<div class="sequence" data-height="${token.parameters.height}">${
|
||||||
@@ -322,6 +331,8 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
token => {
|
token => {
|
||||||
|
updatedOptions.onFence('code', token.langType)
|
||||||
|
|
||||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
${createGutter(token.content, token.firstLineNumber)}
|
${createGutter(token.content, token.firstLineNumber)}
|
||||||
|
|||||||
49
browser/lib/wakatime-plugin.js
Normal file
49
browser/lib/wakatime-plugin.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import config from 'browser/main/lib/ConfigManager'
|
||||||
|
const exec = require('child_process').exec
|
||||||
|
const path = require('path')
|
||||||
|
let lastHeartbeat = 0
|
||||||
|
|
||||||
|
function sendWakatimeHeartBeat(
|
||||||
|
storagePath,
|
||||||
|
noteKey,
|
||||||
|
storageName,
|
||||||
|
{ isWrite, hasFileChanges, isFileChange }
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
config.get().wakatime.isActive &&
|
||||||
|
!!config.get().wakatime.key &&
|
||||||
|
(new Date().getTime() - lastHeartbeat > 120000 || isFileChange)
|
||||||
|
) {
|
||||||
|
const notePath = path.join(storagePath, 'notes', noteKey + '.cson')
|
||||||
|
|
||||||
|
if (!isWrite && !hasFileChanges && !isFileChange) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHeartbeat = new Date()
|
||||||
|
const wakatimeKey = config.get().wakatime.key
|
||||||
|
if (wakatimeKey) {
|
||||||
|
exec(
|
||||||
|
`wakatime --file ${notePath} --project '${storageName}' --key ${wakatimeKey} --plugin Boostnote-wakatime`,
|
||||||
|
(error, stdOut, stdErr) => {
|
||||||
|
if (error) {
|
||||||
|
console.log(error)
|
||||||
|
lastHeartbeat = 0
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
'wakatime',
|
||||||
|
'isWrite',
|
||||||
|
isWrite,
|
||||||
|
'hasChanges',
|
||||||
|
hasFileChanges,
|
||||||
|
'isFileChange',
|
||||||
|
isFileChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { sendWakatimeHeartBeat }
|
||||||
@@ -57,9 +57,11 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
|
||||||
this.generateToc = this.handleGenerateToc.bind(this)
|
this.generateToc = this.handleGenerateToc.bind(this)
|
||||||
|
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||||
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
||||||
|
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
|
||||||
|
this.getNote = this.getNote.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
@@ -67,6 +69,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
ee.on('editor:orientation', this.handleSwitchStackDirection)
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
||||||
ee.on('topbar:togglemodebutton', () => {
|
ee.on('topbar:togglemodebutton', () => {
|
||||||
@@ -383,7 +386,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
handleSwitchMode(type) {
|
handleSwitchMode(type) {
|
||||||
// If in split mode, hide the lock button
|
// If in split mode, hide the lock button
|
||||||
this.setState(
|
this.setState(
|
||||||
{ editorType: type, isLockButtonShown: !(type === 'SPLIT') },
|
{ editorType: type, isLockButtonShown: type !== 'SPLIT' },
|
||||||
() => {
|
() => {
|
||||||
this.focus()
|
this.focus()
|
||||||
const newConfig = Object.assign({}, this.props.config)
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
@@ -393,6 +396,18 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSwitchStackDirection() {
|
||||||
|
this.setState(
|
||||||
|
prevState => ({ isStacking: !prevState.isStacking }),
|
||||||
|
() => {
|
||||||
|
this.focus()
|
||||||
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
|
newConfig.ui.isStacking = this.state.isStacking
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
handleSwitchDirection() {
|
handleSwitchDirection() {
|
||||||
if (!this.props.config.editor.rtlEnabled) {
|
if (!this.props.config.editor.rtlEnabled) {
|
||||||
return
|
return
|
||||||
@@ -427,9 +442,13 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNote() {
|
||||||
|
return this.state.note
|
||||||
|
}
|
||||||
|
|
||||||
renderEditor() {
|
renderEditor() {
|
||||||
const { config, ignorePreviewPointerEvents } = this.props
|
const { config, ignorePreviewPointerEvents } = this.props
|
||||||
const { note } = this.state
|
const { note, isStacking } = this.state
|
||||||
|
|
||||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||||
return (
|
return (
|
||||||
@@ -442,8 +461,8 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
noteKey={note.key}
|
noteKey={note.key}
|
||||||
linesHighlighted={note.linesHighlighted}
|
linesHighlighted={note.linesHighlighted}
|
||||||
onChange={this.handleUpdateContent}
|
onChange={this.handleUpdateContent}
|
||||||
isLocked={this.state.isLocked}
|
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
getNote={this.getNote}
|
||||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -455,9 +474,11 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
value={note.content}
|
value={note.content}
|
||||||
storageKey={note.storage}
|
storageKey={note.storage}
|
||||||
noteKey={note.key}
|
noteKey={note.key}
|
||||||
|
isStacking={isStacking}
|
||||||
linesHighlighted={note.linesHighlighted}
|
linesHighlighted={note.linesHighlighted}
|
||||||
onChange={this.handleUpdateContent}
|
onChange={this.handleUpdateContent}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
getNote={this.getNote}
|
||||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -859,8 +859,15 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
matchingPairs={config.editor.matchingPairs}
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||||
matchingTriples={config.editor.matchingTriples}
|
matchingTriples={config.editor.matchingTriples}
|
||||||
explodingPairs={config.editor.explodingPairs}
|
explodingPairs={config.editor.explodingPairs}
|
||||||
|
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||||
|
codeBlockMatchingCloseBefore={
|
||||||
|
config.editor.codeBlockMatchingCloseBefore
|
||||||
|
}
|
||||||
|
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||||
|
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
@@ -870,6 +877,9 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
enableSmartPaste={config.editor.enableSmartPaste}
|
enableSmartPaste={config.editor.enableSmartPaste}
|
||||||
hotkey={config.hotkey}
|
hotkey={config.hotkey}
|
||||||
autoDetect={autoDetect}
|
autoDetect={autoDetect}
|
||||||
|
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||||
|
storageKey={storageKey}
|
||||||
|
noteKey={note.key}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.handleAddTag = this.handleAddTag.bind(this)
|
this.handleAddTag = this.handleAddTag.bind(this)
|
||||||
|
this.handleRenameTag = this.handleRenameTag.bind(this)
|
||||||
this.onInputBlur = this.onInputBlur.bind(this)
|
this.onInputBlur = this.onInputBlur.bind(this)
|
||||||
this.onInputChange = this.onInputChange.bind(this)
|
this.onInputChange = this.onInputChange.bind(this)
|
||||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||||
@@ -88,6 +89,7 @@ class TagSelect extends React.Component {
|
|||||||
this.buildSuggestions()
|
this.buildSuggestions()
|
||||||
|
|
||||||
ee.on('editor:add-tag', this.handleAddTag)
|
ee.on('editor:add-tag', this.handleAddTag)
|
||||||
|
ee.on('sidebar:rename-tag', this.handleRenameTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@@ -96,12 +98,23 @@ class TagSelect extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ee.off('editor:add-tag', this.handleAddTag)
|
ee.off('editor:add-tag', this.handleAddTag)
|
||||||
|
ee.off('sidebar:rename-tag', this.handleRenameTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddTag() {
|
handleAddTag() {
|
||||||
this.refs.newTag.input.focus()
|
this.refs.newTag.input.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRenameTag(event, tagChange) {
|
||||||
|
const { value } = this.props
|
||||||
|
const { tag, updatedTag } = tagChange
|
||||||
|
const newTags = value.slice()
|
||||||
|
|
||||||
|
newTags[value.indexOf(tag)] = updatedTag
|
||||||
|
this.value = newTags
|
||||||
|
this.props.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
handleTagLabelClick(tag) {
|
handleTagLabelClick(tag) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import Markdown from '../../lib/markdown'
|
|||||||
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 context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
|
import filenamify from 'filenamify'
|
||||||
import queryString from 'query-string'
|
import queryString from 'query-string'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
@@ -634,6 +635,38 @@ class NoteList extends React.Component {
|
|||||||
this.selectNextNote()
|
this.selectNextNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleExportClick(e, note, fileType) {
|
||||||
|
const options = {
|
||||||
|
defaultPath: filenamify(note.title, {
|
||||||
|
replacement: '_'
|
||||||
|
}),
|
||||||
|
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||||
|
properties: ['openFile', 'createDirectory']
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
||||||
|
if (filename) {
|
||||||
|
const { config } = this.props
|
||||||
|
|
||||||
|
dataApi
|
||||||
|
.exportNoteAs(note, filename, fileType, config)
|
||||||
|
.then(res => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: `Exported to ${filename}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
err ? err.message || err : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleNoteContextMenu(e, uniqueKey) {
|
handleNoteContextMenu(e, uniqueKey) {
|
||||||
const { location } = this.props
|
const { location } = this.props
|
||||||
const { selectedNoteKeys } = this.state
|
const { selectedNoteKeys } = this.state
|
||||||
@@ -689,9 +722,40 @@ class NoteList extends React.Component {
|
|||||||
click: this.copyNoteLink.bind(this, note)
|
click: this.copyNoteLink.bind(this, note)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
|
templates.push(
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export Note'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'txt')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'pdf')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
||||||
templates.push(
|
templates.push(
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: updateLabel,
|
label: updateLabel,
|
||||||
click: this.publishMarkdown.bind(this)
|
click: this.publishMarkdown.bind(this)
|
||||||
@@ -702,10 +766,15 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
templates.push({
|
templates.push(
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
label: publishLabel,
|
label: publishLabel,
|
||||||
click: this.publishMarkdown.bind(this)
|
click: this.publishMarkdown.bind(this)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,12 +43,20 @@ class StorageItem extends React.Component {
|
|||||||
label: i18n.__('Export Storage'),
|
label: i18n.__('Export Storage'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as txt'),
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
click: e => this.handleExportStorageClick(e, 'txt')
|
click: e => this.handleExportStorageClick(e, 'txt')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as md'),
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
click: e => this.handleExportStorageClick(e, 'md')
|
click: e => this.handleExportStorageClick(e, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportStorageClick(e, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportStorageClick(e, 'pdf')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -97,14 +105,28 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
if (paths && paths.length === 1) {
|
if (paths && paths.length === 1) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch, config } = this.props
|
||||||
dataApi.exportStorage(storage.key, fileType, paths[0]).then(data => {
|
dataApi
|
||||||
|
.exportStorage(storage.key, fileType, paths[0], config)
|
||||||
|
.then(data => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: `Exported to ${paths[0]}`
|
||||||
|
})
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EXPORT_STORAGE',
|
type: 'EXPORT_STORAGE',
|
||||||
storage: data.storage,
|
storage: data.storage,
|
||||||
fileType: data.fileType
|
fileType: data.fileType
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
error ? error.message || error : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -166,12 +188,20 @@ class StorageItem extends React.Component {
|
|||||||
label: i18n.__('Export Folder'),
|
label: i18n.__('Export Folder'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as txt'),
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
click: e => this.handleExportFolderClick(e, folder, 'txt')
|
click: e => this.handleExportFolderClick(e, folder, 'txt')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as md'),
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
click: e => this.handleExportFolderClick(e, folder, 'md')
|
click: e => this.handleExportFolderClick(e, folder, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportFolderClick(e, folder, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportFolderClick(e, folder, 'pdf')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -202,30 +232,28 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
if (paths && paths.length === 1) {
|
if (paths && paths.length === 1) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch, config } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.exportFolder(storage.key, folder.key, fileType, paths[0])
|
.exportFolder(storage.key, folder.key, fileType, paths[0], config)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: `Exported to ${paths[0]}`
|
||||||
|
})
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EXPORT_FOLDER',
|
type: 'EXPORT_FOLDER',
|
||||||
storage: data.storage,
|
storage: data.storage,
|
||||||
folderKey: data.folderKey,
|
folderKey: data.folderKey,
|
||||||
fileType: data.fileType
|
fileType: data.fileType
|
||||||
})
|
})
|
||||||
return data
|
|
||||||
})
|
})
|
||||||
.then(data => {
|
.catch(error => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
|
||||||
type: 'info',
|
|
||||||
message: 'Exported to "' + data.exportDir + '"'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
dialog.showErrorBox(
|
dialog.showErrorBox(
|
||||||
'Export error',
|
'Export error',
|
||||||
err ? err.message || err : 'Unexpected error during export'
|
error ? error.message || error : 'Unexpected error during export'
|
||||||
)
|
)
|
||||||
throw err
|
throw error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import styles from './SideNav.styl'
|
import styles from './SideNav.styl'
|
||||||
import { openModal } from 'browser/main/lib/modal'
|
import { openModal } from 'browser/main/lib/modal'
|
||||||
import PreferencesModal from '../modals/PreferencesModal'
|
import PreferencesModal from '../modals/PreferencesModal'
|
||||||
|
import RenameTagModal from 'browser/main/modals/RenameTagModal'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import StorageItem from './StorageItem'
|
import StorageItem from './StorageItem'
|
||||||
import TagListItem from 'browser/components/TagListItem'
|
import TagListItem from 'browser/components/TagListItem'
|
||||||
@@ -25,6 +26,8 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
|||||||
import ColorPicker from 'browser/components/ColorPicker'
|
import ColorPicker from 'browser/components/ColorPicker'
|
||||||
import { every, sortBy } from 'lodash'
|
import { every, sortBy } from 'lodash'
|
||||||
|
|
||||||
|
const { dialog } = remote
|
||||||
|
|
||||||
function matchActiveTags(tags, activeTags) {
|
function matchActiveTags(tags, activeTags) {
|
||||||
return every(activeTags, v => tags.indexOf(v) >= 0)
|
return every(activeTags, v => tags.indexOf(v) >= 0)
|
||||||
}
|
}
|
||||||
@@ -62,15 +65,12 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteTag(tag) {
|
deleteTag(tag) {
|
||||||
const selectedButton = remote.dialog.showMessageBox(
|
const selectedButton = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
remote.getCurrentWindow(),
|
|
||||||
{
|
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Confirm tag deletion'),
|
message: i18n.__('Confirm tag deletion'),
|
||||||
detail: i18n.__('This will permanently remove this tag.'),
|
detail: i18n.__('This will permanently remove this tag.'),
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
if (selectedButton === 0) {
|
if (selectedButton === 0) {
|
||||||
const {
|
const {
|
||||||
@@ -154,23 +154,80 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTagContextMenu(e, tag) {
|
handleTagContextMenu(e, tag) {
|
||||||
const menu = []
|
context.popup([
|
||||||
|
{
|
||||||
menu.push({
|
label: i18n.__('Rename Tag'),
|
||||||
label: i18n.__('Delete Tag'),
|
click: this.handleRenameTagClick.bind(this, tag)
|
||||||
click: this.deleteTag.bind(this, tag)
|
},
|
||||||
})
|
{
|
||||||
|
|
||||||
menu.push({
|
|
||||||
label: i18n.__('Customize Color'),
|
label: i18n.__('Customize Color'),
|
||||||
click: this.displayColorPicker.bind(
|
click: this.displayColorPicker.bind(
|
||||||
this,
|
this,
|
||||||
tag,
|
tag,
|
||||||
e.target.getBoundingClientRect()
|
e.target.getBoundingClientRect()
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export Tag'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'txt')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'pdf')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Delete Tag'),
|
||||||
|
click: this.deleteTag.bind(this, tag)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
context.popup(menu)
|
handleExportTagClick(e, tag, fileType) {
|
||||||
|
const options = {
|
||||||
|
properties: ['openDirectory', 'createDirectory'],
|
||||||
|
buttonLabel: i18n.__('Select directory'),
|
||||||
|
title: i18n.__('Select a folder to export the files to'),
|
||||||
|
multiSelections: false
|
||||||
|
}
|
||||||
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
|
if (paths && paths.length === 1) {
|
||||||
|
const { data, config } = this.props
|
||||||
|
dataApi
|
||||||
|
.exportTag(data, tag, fileType, paths[0], config)
|
||||||
|
.then(data => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: `Exported to ${paths[0]}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
error ? error.message || error : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissColorPicker() {
|
dismissColorPicker() {
|
||||||
@@ -193,6 +250,16 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRenameTagClick(tagName) {
|
||||||
|
const { data, dispatch } = this.props
|
||||||
|
|
||||||
|
openModal(RenameTagModal, {
|
||||||
|
tagName,
|
||||||
|
data,
|
||||||
|
dispatch
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleColorPickerConfirm(color) {
|
handleColorPickerConfirm(color) {
|
||||||
const {
|
const {
|
||||||
dispatch,
|
dispatch,
|
||||||
@@ -314,6 +381,7 @@ class SideNav extends React.Component {
|
|||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||||
useDragHandle
|
useDragHandle
|
||||||
|
config={config}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
disableDirectWrite: false,
|
disableDirectWrite: false,
|
||||||
showScrollBar: true,
|
showScrollBar: true,
|
||||||
defaultNote: 'ALWAYS_ASK', // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
defaultNote: 'ALWAYS_ASK', // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
||||||
showMenuBar: false
|
showMenuBar: false,
|
||||||
|
isStacking: false
|
||||||
},
|
},
|
||||||
editor: {
|
editor: {
|
||||||
theme: 'base16-light',
|
theme: 'base16-light',
|
||||||
@@ -85,8 +86,13 @@ export const DEFAULT_CONFIG = {
|
|||||||
rulers: [80, 120],
|
rulers: [80, 120],
|
||||||
displayLineNumbers: true,
|
displayLineNumbers: true,
|
||||||
matchingPairs: '()[]{}\'\'""$$**``~~__',
|
matchingPairs: '()[]{}\'\'""$$**``~~__',
|
||||||
|
matchingCloseBefore: ')]}\'":;>',
|
||||||
matchingTriples: '```"""\'\'\'',
|
matchingTriples: '```"""\'\'\'',
|
||||||
explodingPairs: '[]{}``$$',
|
explodingPairs: '[]{}``$$',
|
||||||
|
codeBlockMatchingPairs: '()[]{}\'\'""``',
|
||||||
|
codeBlockMatchingCloseBefore: ')]}\'":;>',
|
||||||
|
codeBlockMatchingTriples: '',
|
||||||
|
codeBlockExplodingPairs: '[]{}``',
|
||||||
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
|
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
|
||||||
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
|
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
@@ -99,6 +105,7 @@ export const DEFAULT_CONFIG = {
|
|||||||
enableSmartPaste: false,
|
enableSmartPaste: false,
|
||||||
enableMarkdownLint: false,
|
enableMarkdownLint: false,
|
||||||
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG,
|
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG,
|
||||||
|
dateFormatISO8601: false,
|
||||||
prettierConfig: `{
|
prettierConfig: `{
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
@@ -137,7 +144,15 @@ export const DEFAULT_CONFIG = {
|
|||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: ''
|
||||||
},
|
},
|
||||||
coloredTags: {}
|
export: {
|
||||||
|
metadata: 'DONT_EXPORT', // 'DONT_EXPORT', 'MERGE_HEADER', 'MERGE_VARIABLE'
|
||||||
|
variable: 'boostnote',
|
||||||
|
prefixAttachmentFolder: false
|
||||||
|
},
|
||||||
|
coloredTags: {},
|
||||||
|
wakatime: {
|
||||||
|
key: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate(config) {
|
function validate(config) {
|
||||||
@@ -253,6 +268,12 @@ function assignConfigValues(originalConfig, rcConfig) {
|
|||||||
originalConfig.hotkey,
|
originalConfig.hotkey,
|
||||||
rcConfig.hotkey
|
rcConfig.hotkey
|
||||||
)
|
)
|
||||||
|
config.wakatime = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_CONFIG.wakatime,
|
||||||
|
originalConfig.wakatime,
|
||||||
|
rcConfig.wakatime
|
||||||
|
)
|
||||||
config.blog = Object.assign(
|
config.blog = Object.assign(
|
||||||
{},
|
{},
|
||||||
DEFAULT_CONFIG.blog,
|
DEFAULT_CONFIG.blog,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import uiThemes from 'browser/lib/ui-themes'
|
||||||
|
|
||||||
const saveChanges = newConfig => {
|
const saveChanges = newConfig => {
|
||||||
ConfigManager.set(newConfig)
|
ConfigManager.set(newConfig)
|
||||||
@@ -40,14 +41,7 @@ const chooseTheme = config => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const applyTheme = theme => {
|
const applyTheme = theme => {
|
||||||
const supportedThemes = [
|
if (uiThemes.some(item => item.name === theme)) {
|
||||||
'dark',
|
|
||||||
'white',
|
|
||||||
'solarized-dark',
|
|
||||||
'monokai',
|
|
||||||
'dracula'
|
|
||||||
]
|
|
||||||
if (supportedThemes.indexOf(theme) !== -1) {
|
|
||||||
document.body.setAttribute('data-theme', theme)
|
document.body.setAttribute('data-theme', theme)
|
||||||
if (document.body.querySelector('.MarkdownPreview')) {
|
if (document.body.querySelector('.MarkdownPreview')) {
|
||||||
document.body
|
document.body
|
||||||
|
|||||||
@@ -706,14 +706,15 @@ function replaceNoteKeyWithNewNoteKey(noteContent, oldNoteKey, newNoteKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Deletes all :storage and noteKey references from the given input.
|
* @description replace all :storage references with given destination folder.
|
||||||
* @param input Input in which the references should be deleted
|
* @param input Input in which the references should be replaced
|
||||||
* @param noteKey Key of the current note
|
* @param noteKey Key of the current note
|
||||||
|
* @param destinationFolder Destination folder of the attachements
|
||||||
* @returns {String} Input without the references
|
* @returns {String} Input without the references
|
||||||
*/
|
*/
|
||||||
function removeStorageAndNoteReferences(input, noteKey) {
|
function replaceStorageReferences(input, noteKey, destinationFolder) {
|
||||||
return input.replace(
|
return input.replace(
|
||||||
new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?("|\\))', 'g'),
|
new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '[^"\\)<\\s]+', 'g'),
|
||||||
function(match) {
|
function(match) {
|
||||||
return match
|
return match
|
||||||
.replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), path.posix.sep)
|
.replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), path.posix.sep)
|
||||||
@@ -735,7 +736,7 @@ function removeStorageAndNoteReferences(input, noteKey) {
|
|||||||
')?',
|
')?',
|
||||||
'g'
|
'g'
|
||||||
),
|
),
|
||||||
DESTINATION_FOLDER
|
destinationFolder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1101,8 +1102,8 @@ module.exports = {
|
|||||||
getAttachmentsInMarkdownContent,
|
getAttachmentsInMarkdownContent,
|
||||||
getAbsolutePathsOfAttachmentsInContent,
|
getAbsolutePathsOfAttachmentsInContent,
|
||||||
importAttachments,
|
importAttachments,
|
||||||
removeStorageAndNoteReferences,
|
|
||||||
removeAttachmentsByPaths,
|
removeAttachmentsByPaths,
|
||||||
|
replaceStorageReferences,
|
||||||
deleteAttachmentFolder,
|
deleteAttachmentFolder,
|
||||||
deleteAttachmentsNotPresentInNote,
|
deleteAttachmentsNotPresentInNote,
|
||||||
getAttachmentsPathAndStatus,
|
getAttachmentsPathAndStatus,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const fs = require('fs')
|
import fs from 'fs'
|
||||||
const path = require('path')
|
import fx from 'fs-extra'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Copy a file from source to destination
|
* @description Copy a file from source to destination
|
||||||
@@ -14,7 +15,8 @@ function copyFile(srcPath, dstPath) {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const dstFolder = path.dirname(dstPath)
|
const dstFolder = path.dirname(dstPath)
|
||||||
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
|
|
||||||
|
fx.ensureDirSync(dstFolder)
|
||||||
|
|
||||||
const input = fs.createReadStream(decodeURI(srcPath))
|
const input = fs.createReadStream(decodeURI(srcPath))
|
||||||
const output = fs.createWriteStream(dstPath)
|
const output = fs.createWriteStream(dstPath)
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { findStorage } from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
import resolveStorageData from './resolveStorageData'
|
import resolveStorageData from './resolveStorageData'
|
||||||
import resolveStorageNotes from './resolveStorageNotes'
|
import resolveStorageNotes from './resolveStorageNotes'
|
||||||
|
import getFilename from './getFilename'
|
||||||
import exportNote from './exportNote'
|
import exportNote from './exportNote'
|
||||||
import filenamify from 'filenamify'
|
import getContentFormatter from './getContentFormatter'
|
||||||
import * as path from 'path'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
* @param {String} folderKey
|
* @param {String} folderKey
|
||||||
* @param {String} fileType
|
* @param {String} fileType
|
||||||
* @param {String} exportDir
|
* @param {String} exportDir
|
||||||
|
* @param {Object} config
|
||||||
*
|
*
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
* ```
|
* ```
|
||||||
@@ -22,7 +23,7 @@ import * as path from 'path'
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function exportFolder(storageKey, folderKey, fileType, exportDir) {
|
function exportFolder(storageKey, folderKey, fileType, exportDir, config) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -30,38 +31,33 @@ function exportFolder(storageKey, folderKey, fileType, exportDir) {
|
|||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
const deduplicator = {}
|
||||||
.then(function assignNotes(storage) {
|
|
||||||
return resolveStorageNotes(storage).then(notes => {
|
|
||||||
return {
|
|
||||||
storage,
|
|
||||||
notes
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(function exportNotes(data) {
|
|
||||||
const { storage, notes } = data
|
|
||||||
|
|
||||||
return Promise.all(
|
return resolveStorageData(targetStorage)
|
||||||
notes
|
.then(storage => {
|
||||||
.filter(
|
return resolveStorageNotes(storage).then(notes => ({
|
||||||
|
storage,
|
||||||
|
notes: notes.filter(
|
||||||
note =>
|
note =>
|
||||||
note.folder === folderKey &&
|
note.folder === folderKey &&
|
||||||
note.isTrashed === false &&
|
!note.isTrashed &&
|
||||||
note.type === 'MARKDOWN_NOTE'
|
note.type === 'MARKDOWN_NOTE'
|
||||||
)
|
)
|
||||||
.map(note => {
|
}))
|
||||||
const notePath = path.join(
|
})
|
||||||
|
.then(({ storage, notes }) => {
|
||||||
|
const contentFormatter = getContentFormatter(storage, fileType, config)
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
notes.map(note => {
|
||||||
|
const targetPath = getFilename(
|
||||||
|
note,
|
||||||
|
fileType,
|
||||||
exportDir,
|
exportDir,
|
||||||
`${filenamify(note.title, { replacement: '_' })}.${fileType}`
|
deduplicator
|
||||||
)
|
|
||||||
return exportNote(
|
|
||||||
note.key,
|
|
||||||
storage.path,
|
|
||||||
note.content,
|
|
||||||
notePath,
|
|
||||||
null
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return exportNote(storage.key, note, targetPath, contentFormatter)
|
||||||
})
|
})
|
||||||
).then(() => ({
|
).then(() => ({
|
||||||
storage,
|
storage,
|
||||||
|
|||||||
@@ -4,58 +4,35 @@ import { findStorage } from 'browser/lib/findStorage'
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const attachmentManagement = require('./attachmentManagement')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export note together with attachments
|
* Export note together with attachments
|
||||||
*
|
*
|
||||||
* If attachments are stored in the storage, creates 'attachments' subfolder in target directory
|
* If attachments are stored in the storage, creates 'attachments' subfolder in target directory
|
||||||
* and copies attachments to it. Changes links to images in the content of the note
|
* and copies attachments to it. Changes links to images in the content of the note
|
||||||
*
|
*
|
||||||
* @param {String} nodeKey key of the node that should be exported
|
|
||||||
* @param {String} storageKey or storage path
|
* @param {String} storageKey or storage path
|
||||||
* @param {String} noteContent Content to export
|
* @param {Object} note Note to export
|
||||||
* @param {String} targetPath Path to exported file
|
* @param {String} targetPath Path to exported file
|
||||||
* @param {function} outputFormatter
|
* @param {function} outputFormatter
|
||||||
* @return {Promise.<*[]>}
|
* @return {Promise.<*[]>}
|
||||||
*/
|
*/
|
||||||
function exportNote(
|
function exportNote(storageKey, note, targetPath, outputFormatter) {
|
||||||
nodeKey,
|
|
||||||
storageKey,
|
|
||||||
noteContent,
|
|
||||||
targetPath,
|
|
||||||
outputFormatter
|
|
||||||
) {
|
|
||||||
const storagePath = path.isAbsolute(storageKey)
|
const storagePath = path.isAbsolute(storageKey)
|
||||||
? storageKey
|
? storageKey
|
||||||
: findStorage(storageKey).path
|
: findStorage(storageKey).path
|
||||||
|
|
||||||
const exportTasks = []
|
const exportTasks = []
|
||||||
|
|
||||||
if (!storagePath) {
|
if (!storagePath) {
|
||||||
throw new Error('Storage path is not found')
|
throw new Error('Storage path is not found')
|
||||||
}
|
}
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
|
||||||
noteContent,
|
|
||||||
storagePath
|
|
||||||
)
|
|
||||||
attachmentsAbsolutePaths.forEach(attachment => {
|
|
||||||
exportTasks.push({
|
|
||||||
src: attachment,
|
|
||||||
dst: attachmentManagement.DESTINATION_FOLDER
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let exportedData = attachmentManagement.removeStorageAndNoteReferences(
|
const exportedData = Promise.resolve(
|
||||||
noteContent,
|
outputFormatter
|
||||||
nodeKey
|
? outputFormatter(note, targetPath, exportTasks)
|
||||||
|
: note.content
|
||||||
)
|
)
|
||||||
|
|
||||||
if (outputFormatter) {
|
|
||||||
exportedData = outputFormatter(exportedData, exportTasks, targetPath)
|
|
||||||
} else {
|
|
||||||
exportedData = Promise.resolve(exportedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
||||||
|
|
||||||
return Promise.all(tasks.map(task => copyFile(task.src, task.dst)))
|
return Promise.all(tasks.map(task => copyFile(task.src, task.dst)))
|
||||||
@@ -63,9 +40,9 @@ function exportNote(
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
return saveToFile(data, targetPath)
|
return saveToFile(data, targetPath)
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(error => {
|
||||||
rollbackExport(tasks)
|
rollbackExport(tasks)
|
||||||
throw err
|
throw error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,14 +84,14 @@ function rollbackExport(tasks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(fullpath)) {
|
if (fs.existsSync(fullpath)) {
|
||||||
fs.unlink(fullpath)
|
fs.unlinkSync(fullpath)
|
||||||
folders.add(path.dirname(fullpath))
|
folders.add(path.dirname(fullpath))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
folders.forEach(folder => {
|
folders.forEach(folder => {
|
||||||
if (fs.readdirSync(folder).length === 0) {
|
if (fs.readdirSync(folder).length === 0) {
|
||||||
fs.rmdir(folder)
|
fs.rmdirSync(folder)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
19
browser/main/lib/dataApi/exportNoteAs.js
Normal file
19
browser/main/lib/dataApi/exportNoteAs.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
import exportNote from './exportNote'
|
||||||
|
import getContentFormatter from './getContentFormatter'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} note
|
||||||
|
* @param {String} filename
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
|
||||||
|
function exportNoteAs(note, filename, fileType, config) {
|
||||||
|
const storage = findStorage(note.storage)
|
||||||
|
const contentFormatter = getContentFormatter(storage, fileType, config)
|
||||||
|
|
||||||
|
return exportNote(storage.key, note, filename, contentFormatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exportNoteAs
|
||||||
@@ -2,13 +2,17 @@ import { findStorage } from 'browser/lib/findStorage'
|
|||||||
import resolveStorageData from './resolveStorageData'
|
import resolveStorageData from './resolveStorageData'
|
||||||
import resolveStorageNotes from './resolveStorageNotes'
|
import resolveStorageNotes from './resolveStorageNotes'
|
||||||
import filenamify from 'filenamify'
|
import filenamify from 'filenamify'
|
||||||
import * as path from 'path'
|
import path from 'path'
|
||||||
import * as fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import exportNote from './exportNote'
|
||||||
|
import getContentFormatter from './getContentFormatter'
|
||||||
|
import getFilename from './getFilename'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
* @param {String} fileType
|
* @param {String} fileType
|
||||||
* @param {String} exportDir
|
* @param {String} exportDir
|
||||||
|
* @param {Object} config
|
||||||
*
|
*
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
* ```
|
* ```
|
||||||
@@ -20,7 +24,7 @@ import * as fs from 'fs'
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function exportStorage(storageKey, fileType, exportDir) {
|
function exportStorage(storageKey, fileType, exportDir, config) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -29,39 +33,52 @@ function exportStorage(storageKey, fileType, exportDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(storage =>
|
.then(storage => {
|
||||||
resolveStorageNotes(storage).then(notes => ({ storage, notes }))
|
return resolveStorageNotes(storage).then(notes => ({
|
||||||
|
storage,
|
||||||
|
notes: notes.filter(
|
||||||
|
note => !note.isTrashed && note.type === 'MARKDOWN_NOTE'
|
||||||
)
|
)
|
||||||
.then(function exportNotes(data) {
|
}))
|
||||||
const { storage, notes } = data
|
})
|
||||||
|
.then(({ storage, notes }) => {
|
||||||
|
const contentFormatter = getContentFormatter(storage, fileType, config)
|
||||||
|
|
||||||
const folderNamesMapping = {}
|
const folderNamesMapping = {}
|
||||||
|
const deduplicators = {}
|
||||||
|
|
||||||
storage.folders.forEach(folder => {
|
storage.folders.forEach(folder => {
|
||||||
const folderExportedDir = path.join(
|
const folderExportedDir = path.join(
|
||||||
exportDir,
|
exportDir,
|
||||||
filenamify(folder.name, { replacement: '_' })
|
filenamify(folder.name, { replacement: '_' })
|
||||||
)
|
)
|
||||||
|
|
||||||
folderNamesMapping[folder.key] = folderExportedDir
|
folderNamesMapping[folder.key] = folderExportedDir
|
||||||
|
|
||||||
// make sure directory exists
|
// make sure directory exists
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(folderExportedDir)
|
fs.mkdirSync(folderExportedDir)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
})
|
|
||||||
notes
|
deduplicators[folder.key] = {}
|
||||||
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
|
|
||||||
.forEach(markdownNote => {
|
|
||||||
const folderExportedDir = folderNamesMapping[markdownNote.folder]
|
|
||||||
const snippetName = `${filenamify(markdownNote.title, {
|
|
||||||
replacement: '_'
|
|
||||||
})}.${fileType}`
|
|
||||||
const notePath = path.join(folderExportedDir, snippetName)
|
|
||||||
fs.writeFileSync(notePath, markdownNote.content)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return Promise.all(
|
||||||
|
notes.map(note => {
|
||||||
|
const targetPath = getFilename(
|
||||||
|
note,
|
||||||
|
fileType,
|
||||||
|
folderNamesMapping[note.folder],
|
||||||
|
deduplicators[note.folder]
|
||||||
|
)
|
||||||
|
|
||||||
|
return exportNote(storage.key, note, targetPath, contentFormatter)
|
||||||
|
})
|
||||||
|
).then(() => ({
|
||||||
storage,
|
storage,
|
||||||
fileType,
|
fileType,
|
||||||
exportDir
|
exportDir
|
||||||
}
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
28
browser/main/lib/dataApi/exportTag.js
Normal file
28
browser/main/lib/dataApi/exportTag.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import exportNoteAs from './exportNoteAs'
|
||||||
|
import getFilename from './getFilename'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {String} tag
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {String} exportDir
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
|
||||||
|
function exportTag(data, tag, fileType, exportDir, config) {
|
||||||
|
const notes = data.noteMap
|
||||||
|
.map(note => note)
|
||||||
|
.filter(note => note.tags.indexOf(tag) !== -1)
|
||||||
|
|
||||||
|
const deduplicator = {}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
notes.map(note => {
|
||||||
|
const filename = getFilename(note, fileType, exportDir, deduplicator)
|
||||||
|
|
||||||
|
return exportNoteAs(note, filename, fileType, config)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exportTag
|
||||||
796
browser/main/lib/dataApi/formatHTML.js
Normal file
796
browser/main/lib/dataApi/formatHTML.js
Normal file
@@ -0,0 +1,796 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import fileUrl from 'file-url'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { remote } from 'electron'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import Markdown from 'browser/lib/markdown'
|
||||||
|
import attachmentManagement from './attachmentManagement'
|
||||||
|
import { version as codemirrorVersion } from 'codemirror/package.json'
|
||||||
|
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||||
|
|
||||||
|
const { app } = remote
|
||||||
|
const appPath = fileUrl(
|
||||||
|
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
||||||
|
)
|
||||||
|
|
||||||
|
let markdownStyle = ''
|
||||||
|
try {
|
||||||
|
markdownStyle = require('!!css!stylus?sourceMap!../../../components/markdown.styl')[0][1]
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
export const CSS_FILES = [
|
||||||
|
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
||||||
|
`${appPath}/node_modules/codemirror/lib/codemirror.css`,
|
||||||
|
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
||||||
|
]
|
||||||
|
|
||||||
|
const macos = global.process.platform === 'darwin'
|
||||||
|
|
||||||
|
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||||
|
if (!macos) {
|
||||||
|
defaultFontFamily.unshift('Microsoft YaHei')
|
||||||
|
defaultFontFamily.unshift('meiryo')
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCodeBlockFontFamily = [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
|
|
||||||
|
function unprefix(file) {
|
||||||
|
if (global.process.platform === 'win32') {
|
||||||
|
return file.replace('file:///', '')
|
||||||
|
} else {
|
||||||
|
return file.replace('file://', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* fontFamily,
|
||||||
|
* fontSize,
|
||||||
|
* lineNumber,
|
||||||
|
* codeBlockFontFamily,
|
||||||
|
* codeBlockTheme,
|
||||||
|
* scrollPastEnd,
|
||||||
|
* theme,
|
||||||
|
* allowCustomCSS,
|
||||||
|
* customCSS
|
||||||
|
* smartQuotes,
|
||||||
|
* sanitize,
|
||||||
|
* breaks,
|
||||||
|
* storagePath,
|
||||||
|
* export,
|
||||||
|
* indentSize
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default function formatHTML(props) {
|
||||||
|
const {
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
} = getStyleParams(props)
|
||||||
|
|
||||||
|
const inlineStyles = buildStyle(
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
)
|
||||||
|
|
||||||
|
const { smartQuotes, sanitize, breaks } = props
|
||||||
|
|
||||||
|
let indentSize = parseInt(props.indentSize, 10)
|
||||||
|
if (!(indentSize > 0 && indentSize < 132)) {
|
||||||
|
indentSize = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = [getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
|
|
||||||
|
return function(note, targetPath, exportTasks) {
|
||||||
|
const styles = files
|
||||||
|
.map(file => `<link rel="stylesheet" href="css/${path.basename(file)}">`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
let inlineScripts = ''
|
||||||
|
let scripts = ''
|
||||||
|
|
||||||
|
let decodeEntities = false
|
||||||
|
function addDecodeEntities() {
|
||||||
|
if (decodeEntities) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeEntities = true
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function decodeEntities (text) {
|
||||||
|
var entities = [
|
||||||
|
['apos', '\\''],
|
||||||
|
['amp', '&'],
|
||||||
|
['lt', '<'],
|
||||||
|
['gt', '>'],
|
||||||
|
['#63', '\\?'],
|
||||||
|
['#36', '\\$']
|
||||||
|
]
|
||||||
|
|
||||||
|
for (var i = 0, max = entities.length; i < max; ++i) {
|
||||||
|
text = text.replace(new RegExp(\`&\${entities[i][0]};\`, 'g'), entities[i][1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let lodash = false
|
||||||
|
function addLodash() {
|
||||||
|
if (lodash) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lodash = true
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/lodash/lodash.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/lodash.min.js"></script>`
|
||||||
|
}
|
||||||
|
|
||||||
|
let raphael = false
|
||||||
|
function addRaphael() {
|
||||||
|
if (raphael) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raphael = true
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/raphael/raphael.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/raphael.min.js"></script>`
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = false
|
||||||
|
function addYAML() {
|
||||||
|
if (yaml) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
yaml = true
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/js-yaml/dist/js-yaml.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/js-yaml.min.js"></script>`
|
||||||
|
}
|
||||||
|
|
||||||
|
let chart = false
|
||||||
|
function addChart() {
|
||||||
|
if (chart) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chart = true
|
||||||
|
|
||||||
|
addLodash()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/chart.js/dist/Chart.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/Chart.min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displayCharts() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.chart'),
|
||||||
|
el => {
|
||||||
|
try {
|
||||||
|
const format = el.attributes.getNamedItem('data-format').value
|
||||||
|
const chartConfig = format === 'yaml' ? jsyaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
|
||||||
|
el.innerHTML = ''
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
el.appendChild(canvas)
|
||||||
|
|
||||||
|
const height = el.attributes.getNamedItem('data-height')
|
||||||
|
if (height && height.value !== 'undefined') {
|
||||||
|
el.style.height = height.value + 'vh'
|
||||||
|
canvas.height = height.value + 'vh'
|
||||||
|
}
|
||||||
|
|
||||||
|
const chart = new Chart(canvas, chartConfig)
|
||||||
|
} catch (e) {
|
||||||
|
el.className = 'chart-error'
|
||||||
|
el.innerHTML = 'chartjs diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayCharts);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let codemirror = false
|
||||||
|
function addCodeMirror() {
|
||||||
|
if (codemirror) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
codemirror = true
|
||||||
|
|
||||||
|
addDecodeEntities()
|
||||||
|
addLodash()
|
||||||
|
|
||||||
|
exportTasks.push(
|
||||||
|
{
|
||||||
|
src: unprefix(`${appPath}/node_modules/codemirror/lib/codemirror.js`),
|
||||||
|
dst: 'js/codemirror'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: unprefix(`${appPath}/node_modules/codemirror/mode/meta.js`),
|
||||||
|
dst: 'js/codemirror/mode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: unprefix(
|
||||||
|
`${appPath}/node_modules/codemirror/addon/mode/loadmode.js`
|
||||||
|
),
|
||||||
|
dst: 'js/codemirror/addon/mode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: unprefix(
|
||||||
|
`${appPath}/node_modules/codemirror/addon/runmode/runmode.js`
|
||||||
|
),
|
||||||
|
dst: 'js/codemirror/addon/runmode'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
scripts += `
|
||||||
|
<script src="js/codemirror/codemirror.js"></script>
|
||||||
|
<script src="js/codemirror/mode/meta.js"></script>
|
||||||
|
<script src="js/codemirror/addon/mode/loadmode.js"></script>
|
||||||
|
<script src="js/codemirror/addon/runmode/runmode.js"></script>
|
||||||
|
`
|
||||||
|
|
||||||
|
let className = `cm-s-${codeBlockTheme}`
|
||||||
|
if (codeBlockTheme.indexOf('solarized') === 0) {
|
||||||
|
const [refThema, color] = codeBlockTheme.split(' ')
|
||||||
|
className = `cm-s-${refThema} cm-s-${color}`
|
||||||
|
}
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
CodeMirror.modeURL = 'https://cdn.jsdelivr.net/npm/codemirror@${codemirrorVersion}/mode/%N/%N.js';
|
||||||
|
|
||||||
|
function displayCodeBlocks() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.code code'),
|
||||||
|
el => {
|
||||||
|
el.parentNode.className += ' ${className}'
|
||||||
|
let syntax = CodeMirror.findModeByName(el.className)
|
||||||
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
|
const content = decodeEntities(el.innerHTML)
|
||||||
|
el.innerHTML = ''
|
||||||
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
|
tabSize: ${indentSize}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayCodeBlocks);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let flowchart = false
|
||||||
|
function addFlowchart() {
|
||||||
|
if (flowchart) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flowchart = true
|
||||||
|
|
||||||
|
addDecodeEntities()
|
||||||
|
addLodash()
|
||||||
|
addRaphael()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(
|
||||||
|
`${appPath}/node_modules/flowchart.js/release/flowchart.min.js`
|
||||||
|
),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/flowchart.min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displayFlowcharts() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.flowchart'),
|
||||||
|
el => {
|
||||||
|
try {
|
||||||
|
const diagram = flowchart.parse(
|
||||||
|
decodeEntities(el.innerHTML)
|
||||||
|
)
|
||||||
|
el.innerHTML = ''
|
||||||
|
diagram.drawSVG(el)
|
||||||
|
} catch (e) {
|
||||||
|
el.className = 'flowchart-error'
|
||||||
|
el.innerHTML = 'Flowchart parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayFlowcharts);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let mermaid = false
|
||||||
|
function addMermaid() {
|
||||||
|
if (mermaid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mermaid = true
|
||||||
|
|
||||||
|
addLodash()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/mermaid/dist/mermaid.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/mermaid.min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displayMermaids() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.mermaid'),
|
||||||
|
el => {
|
||||||
|
const height = el.attributes.getNamedItem('data-height')
|
||||||
|
if (height && height.value !== 'undefined') {
|
||||||
|
el.style.height = height.value + 'vh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayMermaids);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let sequence = false
|
||||||
|
function addSequence() {
|
||||||
|
if (sequence) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence = true
|
||||||
|
|
||||||
|
addDecodeEntities()
|
||||||
|
addLodash()
|
||||||
|
addRaphael()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(
|
||||||
|
`${appPath}/node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js`
|
||||||
|
),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/sequence-diagram-min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displaySequences() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.sequence'),
|
||||||
|
el => {
|
||||||
|
try {
|
||||||
|
const diagram = Diagram.parse(
|
||||||
|
decodeEntities(el.innerHTML)
|
||||||
|
)
|
||||||
|
el.innerHTML = ''
|
||||||
|
diagram.drawSVG(el, { theme: 'simple' })
|
||||||
|
} catch (e) {
|
||||||
|
el.className = 'sequence-error'
|
||||||
|
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displaySequences);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const modes = {}
|
||||||
|
const markdown = new Markdown({
|
||||||
|
typographer: smartQuotes,
|
||||||
|
sanitize,
|
||||||
|
breaks,
|
||||||
|
onFence(type, mode) {
|
||||||
|
if (type === 'chart') {
|
||||||
|
addChart()
|
||||||
|
|
||||||
|
if (mode === 'yaml') {
|
||||||
|
addYAML()
|
||||||
|
}
|
||||||
|
} else if (type === 'code') {
|
||||||
|
addCodeMirror()
|
||||||
|
|
||||||
|
if (mode && modes[mode] !== true) {
|
||||||
|
const file = unprefix(
|
||||||
|
`${appPath}/node_modules/codemirror/mode/${mode}/${mode}.js`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
exportTasks.push({
|
||||||
|
src: file,
|
||||||
|
dst: `js/codemirror/mode/${mode}`
|
||||||
|
})
|
||||||
|
|
||||||
|
modes[mode] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === 'flowchart') {
|
||||||
|
addFlowchart()
|
||||||
|
} else if (type === 'mermaid') {
|
||||||
|
addMermaid()
|
||||||
|
} else if (type === 'sequence') {
|
||||||
|
addSequence()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let body = note.content
|
||||||
|
|
||||||
|
if (sanitize === 'NONE') {
|
||||||
|
body = escapeHtmlCharactersInCodeTag(body.split('```'))
|
||||||
|
}
|
||||||
|
|
||||||
|
body = markdown.render(note.content)
|
||||||
|
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
|
note.content,
|
||||||
|
props.storagePath
|
||||||
|
)
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(file),
|
||||||
|
dst: 'css'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const destinationFolder = props.export.prefixAttachmentFolder
|
||||||
|
? `${path.parse(targetPath).name} - ${
|
||||||
|
attachmentManagement.DESTINATION_FOLDER
|
||||||
|
}`
|
||||||
|
: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
|
||||||
|
attachmentsAbsolutePaths.forEach(attachment => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: destinationFolder
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
body = attachmentManagement.replaceStorageReferences(
|
||||||
|
body,
|
||||||
|
note.key,
|
||||||
|
destinationFolder
|
||||||
|
)
|
||||||
|
|
||||||
|
return `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||||
|
<style id="style">${inlineStyles}</style>
|
||||||
|
${styles}
|
||||||
|
${scripts}
|
||||||
|
<script>${inlineScripts}</script>
|
||||||
|
</head>
|
||||||
|
<body data-theme="${theme}">
|
||||||
|
${body}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStyleParams(props) {
|
||||||
|
const {
|
||||||
|
fontSize,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS,
|
||||||
|
RTL
|
||||||
|
} = props
|
||||||
|
|
||||||
|
let { fontFamily, codeBlockFontFamily } = props
|
||||||
|
|
||||||
|
fontFamily =
|
||||||
|
_.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
|
? fontFamily
|
||||||
|
.split(',')
|
||||||
|
.map(fontName => fontName.trim())
|
||||||
|
.concat(defaultFontFamily)
|
||||||
|
: defaultFontFamily
|
||||||
|
|
||||||
|
codeBlockFontFamily =
|
||||||
|
_.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
||||||
|
? codeBlockFontFamily
|
||||||
|
.split(',')
|
||||||
|
.map(fontName => fontName.trim())
|
||||||
|
.concat(defaultCodeBlockFontFamily)
|
||||||
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
|
return {
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS,
|
||||||
|
RTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodeThemeLink(name) {
|
||||||
|
const theme = consts.THEMES.find(theme => theme.name === name)
|
||||||
|
|
||||||
|
return theme != null
|
||||||
|
? theme.path
|
||||||
|
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildStyle(
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS,
|
||||||
|
RTL
|
||||||
|
) {
|
||||||
|
return `
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: url('${appPath}/resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Material Icons'),
|
||||||
|
local('MaterialIcons-Regular'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
${markdownStyle}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: '${fontFamily.join("','")}';
|
||||||
|
font-size: ${fontSize}px;
|
||||||
|
${scrollPastEnd && 'padding-bottom: 90vh;box-sizing: border-box;'}
|
||||||
|
${RTL && 'direction: rtl;text-align: right;'}
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
padding-bottom: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
|
background-color: rgba(0,0,0,0.04);
|
||||||
|
text-align: left;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
p code,
|
||||||
|
li code,
|
||||||
|
td code
|
||||||
|
{
|
||||||
|
padding: 2px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
[data-theme="default"] p code,
|
||||||
|
[data-theme="default"] li code,
|
||||||
|
[data-theme="default"] td code
|
||||||
|
{
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
[data-theme="white"] p code,
|
||||||
|
[data-theme="white"] li code,
|
||||||
|
[data-theme="white"] td code
|
||||||
|
{
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] p code,
|
||||||
|
[data-theme="dark"] li code,
|
||||||
|
[data-theme="dark"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="dracula"] p code,
|
||||||
|
[data-theme="dracula"] li code,
|
||||||
|
[data-theme="dracula"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="monokai"] p code,
|
||||||
|
[data-theme="monokai"] li code,
|
||||||
|
[data-theme="monokai"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="nord"] p code,
|
||||||
|
[data-theme="nord"] li code,
|
||||||
|
[data-theme="nord"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="solarized-dark"] p code,
|
||||||
|
[data-theme="solarized-dark"] li code,
|
||||||
|
[data-theme="solarized-dark"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="vulcan"] p code,
|
||||||
|
[data-theme="vulcan"] li code,
|
||||||
|
[data-theme="vulcan"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineNumber {
|
||||||
|
${lineNumber && 'display: block !important;'}
|
||||||
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
|
}
|
||||||
|
|
||||||
|
.clipboardButton {
|
||||||
|
color: rgba(147,147,149,0.8);;
|
||||||
|
fill: rgba(147,147,149,1);;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0px 10px;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clipboardButton:hover {
|
||||||
|
transition: 0.2s;
|
||||||
|
color: #939395;
|
||||||
|
fill: #939395;
|
||||||
|
background-color: rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
margin: 1em 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding-bottom: 0.2em;
|
||||||
|
margin: 1em 0 0.37em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body p {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body[data-theme="${theme}"] {
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.clipboardButton {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${allowCustomCSS ? customCSS : ''}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Convert special characters between three ```
|
||||||
|
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
|
||||||
|
* @returns {string} HTML in which special characters between three ``` have been converted
|
||||||
|
*/
|
||||||
|
export function escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
|
||||||
|
for (let index = 0; index < splitWithCodeTag.length; index++) {
|
||||||
|
const codeTagRequired =
|
||||||
|
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
|
||||||
|
if (codeTagRequired) {
|
||||||
|
splitWithCodeTag.splice(index + 1, 0, '```')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let inCodeTag = false
|
||||||
|
let result = ''
|
||||||
|
for (let content of splitWithCodeTag) {
|
||||||
|
if (content === '```') {
|
||||||
|
inCodeTag = !inCodeTag
|
||||||
|
} else if (inCodeTag) {
|
||||||
|
content = escapeHtmlCharacters(content)
|
||||||
|
}
|
||||||
|
result += content
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
103
browser/main/lib/dataApi/formatMarkdown.js
Normal file
103
browser/main/lib/dataApi/formatMarkdown.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import attachmentManagement from './attachmentManagement'
|
||||||
|
import yaml from 'js-yaml'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const delimiterRegExp = /^\-{3}/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* storagePath,
|
||||||
|
* export
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default function formatMarkdown(props) {
|
||||||
|
return function(note, targetPath, exportTasks) {
|
||||||
|
let result = note.content
|
||||||
|
|
||||||
|
if (props.storagePath && note.key) {
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
|
result,
|
||||||
|
props.storagePath
|
||||||
|
)
|
||||||
|
|
||||||
|
const destinationFolder = props.export.prefixAttachmentFolder
|
||||||
|
? `${path.parse(targetPath).name} - ${
|
||||||
|
attachmentManagement.DESTINATION_FOLDER
|
||||||
|
}`
|
||||||
|
: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
|
||||||
|
attachmentsAbsolutePaths.forEach(attachment => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: destinationFolder
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
result = attachmentManagement.replaceStorageReferences(
|
||||||
|
result,
|
||||||
|
note.key,
|
||||||
|
destinationFolder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.export.metadata === 'MERGE_HEADER') {
|
||||||
|
const metadata = getFrontMatter(result)
|
||||||
|
|
||||||
|
const values = Object.assign({}, note)
|
||||||
|
delete values.content
|
||||||
|
delete values.isTrashed
|
||||||
|
|
||||||
|
for (const key in values) {
|
||||||
|
metadata[key] = values[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
result = replaceFrontMatter(result, metadata)
|
||||||
|
} else if (props.export.metadata === 'MERGE_VARIABLE') {
|
||||||
|
const metadata = getFrontMatter(result)
|
||||||
|
|
||||||
|
const values = Object.assign({}, note)
|
||||||
|
delete values.content
|
||||||
|
delete values.isTrashed
|
||||||
|
|
||||||
|
if (props.export.variable) {
|
||||||
|
metadata[props.export.variable] = values
|
||||||
|
} else {
|
||||||
|
for (const key in values) {
|
||||||
|
metadata[key] = values[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = replaceFrontMatter(result, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFrontMatter(markdown) {
|
||||||
|
const lines = markdown.split('\n')
|
||||||
|
|
||||||
|
if (delimiterRegExp.test(lines[0])) {
|
||||||
|
let line = 0
|
||||||
|
while (++line < lines.length && !delimiterRegExp.test(lines[line])) {}
|
||||||
|
|
||||||
|
return yaml.load(lines.slice(1, line).join('\n')) || {}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceFrontMatter(markdown, metadata) {
|
||||||
|
const lines = markdown.split('\n')
|
||||||
|
|
||||||
|
if (delimiterRegExp.test(lines[0])) {
|
||||||
|
let line = 0
|
||||||
|
while (++line < lines.length && !delimiterRegExp.test(lines[line])) {}
|
||||||
|
|
||||||
|
return `---\n${yaml.dump(metadata)}---\n${lines.slice(line + 1).join('\n')}`
|
||||||
|
} else {
|
||||||
|
return `---\n${yaml.dump(metadata)}---\n\n${markdown}`
|
||||||
|
}
|
||||||
|
}
|
||||||
26
browser/main/lib/dataApi/formatPDF.js
Normal file
26
browser/main/lib/dataApi/formatPDF.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import formatHTML from './formatHTML'
|
||||||
|
import { remote } from 'electron'
|
||||||
|
|
||||||
|
export default function formatPDF(props) {
|
||||||
|
return function(note, targetPath, exportTasks) {
|
||||||
|
const printout = new remote.BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
webPreferences: { webSecurity: false, javascript: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
printout.loadURL(
|
||||||
|
'data:text/html;charset=UTF-8,' +
|
||||||
|
formatHTML(props)(note, targetPath, exportTasks)
|
||||||
|
)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
printout.webContents.on('did-finish-load', () => {
|
||||||
|
printout.webContents.printToPDF({}, (err, data) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(data)
|
||||||
|
printout.destroy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
58
browser/main/lib/dataApi/getContentFormatter.js
Normal file
58
browser/main/lib/dataApi/getContentFormatter.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import formatMarkdown from './formatMarkdown'
|
||||||
|
import formatHTML from './formatHTML'
|
||||||
|
import formatPDF from './formatPDF'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} storage
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function getContentFormatter(storage, fileType, config) {
|
||||||
|
if (fileType === 'md') {
|
||||||
|
return formatMarkdown({
|
||||||
|
storagePath: storage.path,
|
||||||
|
export: config.export
|
||||||
|
})
|
||||||
|
} else if (fileType === 'html') {
|
||||||
|
return formatHTML({
|
||||||
|
theme: config.ui.theme,
|
||||||
|
fontSize: config.preview.fontSize,
|
||||||
|
fontFamily: config.preview.fontFamily,
|
||||||
|
codeBlockTheme: config.preview.codeBlockTheme,
|
||||||
|
codeBlockFontFamily: config.editor.fontFamily,
|
||||||
|
lineNumber: config.preview.lineNumber,
|
||||||
|
indentSize: config.editor.indentSize,
|
||||||
|
scrollPastEnd: config.preview.scrollPastEnd,
|
||||||
|
smartQuotes: config.preview.smartQuotes,
|
||||||
|
breaks: config.preview.breaks,
|
||||||
|
sanitize: config.preview.sanitize,
|
||||||
|
customCSS: config.preview.customCSS,
|
||||||
|
allowCustomCSS: config.preview.allowCustomCSS,
|
||||||
|
storagePath: storage.path,
|
||||||
|
export: config.export,
|
||||||
|
RTL: config.editor.rtlEnabled /* && this.state.RTL */
|
||||||
|
})
|
||||||
|
} else if (fileType === 'pdf') {
|
||||||
|
return formatPDF({
|
||||||
|
theme: config.ui.theme,
|
||||||
|
fontSize: config.preview.fontSize,
|
||||||
|
fontFamily: config.preview.fontFamily,
|
||||||
|
codeBlockTheme: config.preview.codeBlockTheme,
|
||||||
|
codeBlockFontFamily: config.editor.fontFamily,
|
||||||
|
lineNumber: config.preview.lineNumber,
|
||||||
|
indentSize: config.editor.indentSize,
|
||||||
|
scrollPastEnd: config.preview.scrollPastEnd,
|
||||||
|
smartQuotes: config.preview.smartQuotes,
|
||||||
|
breaks: config.preview.breaks,
|
||||||
|
sanitize: config.preview.sanitize,
|
||||||
|
customCSS: config.preview.customCSS,
|
||||||
|
allowCustomCSS: config.preview.allowCustomCSS,
|
||||||
|
storagePath: storage.path,
|
||||||
|
export: config.export,
|
||||||
|
RTL: config.editor.rtlEnabled /* && this.state.RTL */
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
37
browser/main/lib/dataApi/getFilename.js
Normal file
37
browser/main/lib/dataApi/getFilename.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import filenamify from 'filenamify'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} note
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {String} directory
|
||||||
|
* @param {Object} deduplicator
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getFilename(note, fileType, directory, deduplicator) {
|
||||||
|
const basename = note.title
|
||||||
|
? filenamify(note.title, { replacement: '_' })
|
||||||
|
: i18n.__('Untitled')
|
||||||
|
|
||||||
|
if (deduplicator) {
|
||||||
|
if (deduplicator[basename]) {
|
||||||
|
const filename = path.join(
|
||||||
|
directory,
|
||||||
|
`${basename} (${deduplicator[basename]}).${fileType}`
|
||||||
|
)
|
||||||
|
|
||||||
|
++deduplicator[basename]
|
||||||
|
|
||||||
|
return filename
|
||||||
|
} else {
|
||||||
|
deduplicator[basename] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(directory, `${basename}.${fileType}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getFilename
|
||||||
@@ -15,11 +15,14 @@ const dataApi = {
|
|||||||
updateNote: require('./updateNote'),
|
updateNote: require('./updateNote'),
|
||||||
deleteNote: require('./deleteNote'),
|
deleteNote: require('./deleteNote'),
|
||||||
moveNote: require('./moveNote'),
|
moveNote: require('./moveNote'),
|
||||||
|
exportNoteAs: require('./exportNoteAs'),
|
||||||
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
||||||
createSnippet: require('./createSnippet'),
|
createSnippet: require('./createSnippet'),
|
||||||
deleteSnippet: require('./deleteSnippet'),
|
deleteSnippet: require('./deleteSnippet'),
|
||||||
updateSnippet: require('./updateSnippet'),
|
updateSnippet: require('./updateSnippet'),
|
||||||
fetchSnippet: require('./fetchSnippet'),
|
fetchSnippet: require('./fetchSnippet'),
|
||||||
|
exportTag: require('./exportTag'),
|
||||||
|
getFilename: require('./getFilename'),
|
||||||
|
|
||||||
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
||||||
_resolveStorageData: require('./resolveStorageData'),
|
_resolveStorageData: require('./resolveStorageData'),
|
||||||
|
|||||||
@@ -139,6 +139,13 @@ div[id^="firstRow"]
|
|||||||
margin-right 10px
|
margin-right 10px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
|
|
||||||
|
.group-section-label-right
|
||||||
|
width 200px
|
||||||
|
text-align right
|
||||||
|
margin-right 10px
|
||||||
|
font-size 14px
|
||||||
|
padding-right 1.5rem
|
||||||
|
|
||||||
.group-section-control
|
.group-section-control
|
||||||
flex 1
|
flex 1
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
|
|||||||
184
browser/main/modals/PreferencesModal/ExportTab.js
Normal file
184
browser/main/modals/PreferencesModal/ExportTab.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ConfigTab.styl'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import store from 'browser/main/store'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const ipc = electron.ipcRenderer
|
||||||
|
|
||||||
|
class ExportTab extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
config: props.config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessage() {
|
||||||
|
_.debounce(() => {
|
||||||
|
this.setState({
|
||||||
|
ExportAlert: null
|
||||||
|
})
|
||||||
|
}, 2000)()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.handleSettingDone = () => {
|
||||||
|
this.setState({
|
||||||
|
ExportAlert: {
|
||||||
|
type: 'success',
|
||||||
|
message: i18n.__('Successfully applied!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.handleSettingError = err => {
|
||||||
|
this.setState({
|
||||||
|
ExportAlert: {
|
||||||
|
type: 'error',
|
||||||
|
message:
|
||||||
|
err.message != null ? err.message : i18n.__('An error occurred!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldExport = this.state.config.export
|
||||||
|
|
||||||
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
|
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
|
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveButtonClick(e) {
|
||||||
|
const newConfig = {
|
||||||
|
export: this.state.config.export
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: 'SET_UI',
|
||||||
|
config: newConfig
|
||||||
|
})
|
||||||
|
|
||||||
|
this.clearMessage()
|
||||||
|
this.props.haveToSave()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleExportChange(e) {
|
||||||
|
const { config } = this.state
|
||||||
|
|
||||||
|
config.export = {
|
||||||
|
metadata: this.refs.metadata.value,
|
||||||
|
variable: !_.isNil(this.refs.variable)
|
||||||
|
? this.refs.variable.value
|
||||||
|
: config.export.variable,
|
||||||
|
prefixAttachmentFolder: this.refs.prefixAttachmentFolder.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
config
|
||||||
|
})
|
||||||
|
|
||||||
|
if (_.isEqual(this.oldExport, config.export)) {
|
||||||
|
this.props.haveToSave()
|
||||||
|
} else {
|
||||||
|
this.props.haveToSave({
|
||||||
|
tab: 'Export',
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Unsaved Changes!')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { config, ExportAlert } = this.state
|
||||||
|
|
||||||
|
const ExportAlertElement =
|
||||||
|
ExportAlert != null ? (
|
||||||
|
<p className={`alert ${ExportAlert.type}`}>{ExportAlert.message}</p>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='group'>
|
||||||
|
<div styleName='group-header'>{i18n.__('Export')}</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Metadata')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<select
|
||||||
|
value={config.export.metadata}
|
||||||
|
onChange={e => this.handleExportChange(e)}
|
||||||
|
ref='metadata'
|
||||||
|
>
|
||||||
|
<option value='DONT_EXPORT'>{i18n.__(`Don't export`)}</option>
|
||||||
|
<option value='MERGE_HEADER'>
|
||||||
|
{i18n.__('Merge with the header')}
|
||||||
|
</option>
|
||||||
|
<option value='MERGE_VARIABLE'>
|
||||||
|
{i18n.__('Merge with a variable')}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{config.export.metadata === 'MERGE_VARIABLE' && (
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Variable Name')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
onChange={e => this.handleExportChange(e)}
|
||||||
|
ref='variable'
|
||||||
|
value={config.export.variable}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
onChange={e => this.handleExportChange(e)}
|
||||||
|
checked={config.export.prefixAttachmentFolder}
|
||||||
|
ref='prefixAttachmentFolder'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
|
||||||
|
{i18n.__('Prefix attachment folder')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-control'>
|
||||||
|
<button
|
||||||
|
styleName='group-control-rightButton'
|
||||||
|
onClick={e => this.handleSaveButtonClick(e)}
|
||||||
|
>
|
||||||
|
{i18n.__('Save')}
|
||||||
|
</button>
|
||||||
|
{ExportAlertElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExportTab.propTypes = {
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
haveToSave: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ExportTab, styles)
|
||||||
@@ -16,7 +16,10 @@ class InfoTab extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
config: this.props.config
|
config: this.props.config,
|
||||||
|
subscriptionFormStatus: 'idle',
|
||||||
|
subscriptionFormErrorMessage: null,
|
||||||
|
subscriptionFormEmail: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +34,48 @@ class InfoTab extends React.Component {
|
|||||||
this.setState({ config: newConfig })
|
this.setState({ config: newConfig })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSubscriptionFormSubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormStatus: 'sending',
|
||||||
|
subscriptionFormErrorMessage: null
|
||||||
|
})
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
'https://boostmails.boostio.co/api/public/lists/5f434dccd05f3160b41c0d49/subscriptions',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ email: this.state.subscriptionFormEmail })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
if (response.status >= 400) {
|
||||||
|
return response.text().then(text => {
|
||||||
|
throw new Error(text)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormStatus: 'done'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormStatus: 'idle',
|
||||||
|
subscriptionFormErrorMessage: error.message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubscriptionFormEmailChange(e) {
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormEmail: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleSaveButtonClick(e) {
|
handleSaveButtonClick(e) {
|
||||||
const newConfig = {
|
const newConfig = {
|
||||||
amaEnabled: this.state.config.amaEnabled
|
amaEnabled: this.state.config.amaEnabled
|
||||||
@@ -134,6 +179,40 @@ class InfoTab extends React.Component {
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
<div styleName='group-header--sub'>Subscribe Update Notes</div>
|
||||||
|
{this.state.subscriptionFormStatus === 'done' ? (
|
||||||
|
<div>
|
||||||
|
<blockquote color={{ color: 'green' }}>
|
||||||
|
Thanks for the subscription!
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{this.state.subscriptionFormErrorMessage != null && (
|
||||||
|
<blockquote style={{ color: 'red' }}>
|
||||||
|
{this.state.subscriptionFormErrorMessage}
|
||||||
|
</blockquote>
|
||||||
|
)}
|
||||||
|
<form onSubmit={e => this.handleSubscriptionFormSubmit(e)}>
|
||||||
|
<input
|
||||||
|
styleName='subscription-email-input'
|
||||||
|
placeholder='E-mail'
|
||||||
|
type='email'
|
||||||
|
onChange={e => this.handleSubscriptionFormEmailChange(e)}
|
||||||
|
disabled={this.state.subscriptionFormStatus === 'sending'}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
styleName='subscription-submit-button'
|
||||||
|
type='submit'
|
||||||
|
disabled={this.state.subscriptionFormStatus === 'sending'}
|
||||||
|
>
|
||||||
|
Subscribe
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<hr />
|
||||||
|
|
||||||
<div styleName='group-header--sub'>{i18n.__('About')}</div>
|
<div styleName='group-header--sub'>{i18n.__('About')}</div>
|
||||||
|
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
@@ -145,9 +224,7 @@ class InfoTab extends React.Component {
|
|||||||
height='92'
|
height='92'
|
||||||
/>
|
/>
|
||||||
<div styleName='icon-right'>
|
<div styleName='icon-right'>
|
||||||
<div styleName='appId'>
|
<div styleName='appId'>Boostnote Legacy {appVersion}</div>
|
||||||
{i18n.__('Boostnote')} {appVersion}
|
|
||||||
</div>
|
|
||||||
<div styleName='description'>
|
<div styleName='description'>
|
||||||
{i18n.__(
|
{i18n.__(
|
||||||
'An open source note-taking app made for programmers just like you.'
|
'An open source note-taking app made for programmers just like you.'
|
||||||
|
|||||||
@@ -33,6 +33,35 @@
|
|||||||
.separate-line
|
.separate-line
|
||||||
margin 40px 0
|
margin 40px 0
|
||||||
|
|
||||||
|
.subscription-email-input
|
||||||
|
height 35px
|
||||||
|
vertical-align middle
|
||||||
|
width 200px
|
||||||
|
font-size $tab--button-font-size
|
||||||
|
border solid 1px $border-color
|
||||||
|
border-radius 2px
|
||||||
|
padding 0 5px
|
||||||
|
margin-right 5px
|
||||||
|
outline none
|
||||||
|
&:disabled
|
||||||
|
background-color $ui-input--disabled-backgroundColor
|
||||||
|
|
||||||
|
.subscription-submit-button
|
||||||
|
margin-top 10px
|
||||||
|
height 35px
|
||||||
|
border-radius 2px
|
||||||
|
border none
|
||||||
|
background-color alpha(#1EC38B, 90%)
|
||||||
|
padding-left 20px
|
||||||
|
padding-right 20px
|
||||||
|
text-decoration none
|
||||||
|
color white
|
||||||
|
font-weight 600
|
||||||
|
font-size 16px
|
||||||
|
&:hover
|
||||||
|
background-color #1EC38B
|
||||||
|
transition 0.2s
|
||||||
|
|
||||||
.policy-submit
|
.policy-submit
|
||||||
margin-top 10px
|
margin-top 10px
|
||||||
height 35px
|
height 35px
|
||||||
|
|||||||
207
browser/main/modals/PreferencesModal/PluginsTab.js
Normal file
207
browser/main/modals/PreferencesModal/PluginsTab.js
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ConfigTab.styl'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import { store } from 'browser/main/store'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import { sync as commandExists } from 'command-exists'
|
||||||
|
const electron = require('electron')
|
||||||
|
const ipc = electron.ipcRenderer
|
||||||
|
const { remote } = electron
|
||||||
|
const { dialog } = remote
|
||||||
|
class PluginsTab extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
config: props.config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.handleSettingDone = () => {
|
||||||
|
this.setState({
|
||||||
|
pluginsAlert: {
|
||||||
|
type: 'success',
|
||||||
|
message: i18n.__('Successfully applied!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.handleSettingError = err => {
|
||||||
|
this.setState({
|
||||||
|
pluginsAlert: {
|
||||||
|
type: 'error',
|
||||||
|
message:
|
||||||
|
err.message != null ? err.message : i18n.__('An error occurred!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.oldWakatimeConfig = this.state.config.wakatime
|
||||||
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
|
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
|
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkWakatimePluginRequirement() {
|
||||||
|
const { wakatime } = this.state.config
|
||||||
|
if (wakatime.isActive && !commandExists('wakatime')) {
|
||||||
|
this.setState({
|
||||||
|
wakatimePluginAlert: {
|
||||||
|
type: i18n.__('Warning'),
|
||||||
|
message: i18n.__('Missing wakatime cli')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const alertConfig = {
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Missing Wakatime CLI'),
|
||||||
|
detail: i18n.__(
|
||||||
|
`Please install Wakatime CLI to use Wakatime tracker feature.`
|
||||||
|
),
|
||||||
|
buttons: [i18n.__('OK')]
|
||||||
|
}
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
wakatimePluginAlert: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveButtonClick(e) {
|
||||||
|
const newConfig = {
|
||||||
|
wakatime: {
|
||||||
|
isActive: this.state.config.wakatime.isActive,
|
||||||
|
key: this.state.config.wakatime.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: 'SET_CONFIG',
|
||||||
|
config: newConfig
|
||||||
|
})
|
||||||
|
this.clearMessage()
|
||||||
|
this.props.haveToSave()
|
||||||
|
this.checkWakatimePluginRequirement()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIsWakatimePluginActiveChange(e) {
|
||||||
|
const { config } = this.state
|
||||||
|
config.wakatime.isActive = !config.wakatime.isActive
|
||||||
|
this.setState({
|
||||||
|
config
|
||||||
|
})
|
||||||
|
if (_.isEqual(this.oldWakatimeConfig.isActive, config.wakatime.isActive)) {
|
||||||
|
this.props.haveToSave()
|
||||||
|
} else {
|
||||||
|
this.props.haveToSave({
|
||||||
|
tab: 'Plugins',
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Unsaved Changes!')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWakatimeKeyChange(e) {
|
||||||
|
const { config } = this.state
|
||||||
|
config.wakatime = {
|
||||||
|
isActive: true,
|
||||||
|
key: this.refs.wakatimeKey.value
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
config
|
||||||
|
})
|
||||||
|
if (_.isEqual(this.oldWakatimeConfig.key, config.wakatime.key)) {
|
||||||
|
this.props.haveToSave()
|
||||||
|
} else {
|
||||||
|
this.props.haveToSave({
|
||||||
|
tab: 'Plugins',
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Unsaved Changes!')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessage() {
|
||||||
|
_.debounce(() => {
|
||||||
|
this.setState({
|
||||||
|
pluginsAlert: null
|
||||||
|
})
|
||||||
|
}, 2000)()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const pluginsAlert = this.state.pluginsAlert
|
||||||
|
const pluginsAlertElement =
|
||||||
|
pluginsAlert != null ? (
|
||||||
|
<p className={`alert ${pluginsAlert.type}`}>{pluginsAlert.message}</p>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
const wakatimeAlert = this.state.wakatimePluginAlert
|
||||||
|
const wakatimePluginAlertElement =
|
||||||
|
wakatimeAlert != null ? (
|
||||||
|
<p className={`alert ${wakatimeAlert.type}`}>{wakatimeAlert.message}</p>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
const { config } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='group'>
|
||||||
|
<div styleName='group-header'>{i18n.__('Plugins')}</div>
|
||||||
|
<div styleName='group-header2'>{i18n.__('Wakatime')}</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
onChange={e => this.handleIsWakatimePluginActiveChange(e)}
|
||||||
|
checked={config.wakatime.isActive}
|
||||||
|
ref='wakatimeIsActive'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
|
||||||
|
{i18n.__('Enable Wakatime')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Wakatime key')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
onChange={e => this.handleWakatimeKeyChange(e)}
|
||||||
|
disabled={!config.wakatime.isActive}
|
||||||
|
ref='wakatimeKey'
|
||||||
|
value={config.wakatime.key}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
{wakatimePluginAlertElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-control'>
|
||||||
|
<button
|
||||||
|
styleName='group-control-rightButton'
|
||||||
|
onClick={e => this.handleSaveButtonClick(e)}
|
||||||
|
>
|
||||||
|
{i18n.__('Save')}
|
||||||
|
</button>
|
||||||
|
{pluginsAlertElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginsTab.propTypes = {
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
haveToSave: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(PluginsTab, styles)
|
||||||
@@ -35,10 +35,18 @@ class SnippetEditor extends React.Component {
|
|||||||
foldGutter: true,
|
foldGutter: true,
|
||||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
autoCloseBrackets: {
|
autoCloseBrackets: {
|
||||||
|
codeBlock: {
|
||||||
|
pairs: this.props.codeBlockMatchingPairs,
|
||||||
|
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||||
|
triples: this.props.codeBlockMatchingTriples,
|
||||||
|
explode: this.props.codeBlockExplodingPairs
|
||||||
|
},
|
||||||
|
markdown: {
|
||||||
pairs: this.props.matchingPairs,
|
pairs: this.props.matchingPairs,
|
||||||
|
closeBefore: this.props.matchingCloseBefore,
|
||||||
triples: this.props.matchingTriples,
|
triples: this.props.matchingTriples,
|
||||||
explode: this.props.explodingPairs,
|
explode: this.props.explodingPairs
|
||||||
override: true
|
}
|
||||||
},
|
},
|
||||||
mode: 'null'
|
mode: 'null'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -152,8 +152,15 @@ class SnippetTab extends React.Component {
|
|||||||
rulers={config.editor.rulers}
|
rulers={config.editor.rulers}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
matchingPairs={config.editor.matchingPairs}
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||||
matchingTriples={config.editor.matchingTriples}
|
matchingTriples={config.editor.matchingTriples}
|
||||||
explodingPairs={config.editor.explodingPairs}
|
explodingPairs={config.editor.explodingPairs}
|
||||||
|
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||||
|
codeBlockMatchingCloseBefore={
|
||||||
|
config.editor.codeBlockMatchingCloseBefore
|
||||||
|
}
|
||||||
|
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||||
|
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
onRef={ref => {
|
onRef={ref => {
|
||||||
this.snippetEditor = ref
|
this.snippetEditor = ref
|
||||||
|
|||||||
@@ -124,14 +124,21 @@ class UiTab extends React.Component {
|
|||||||
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
|
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
|
||||||
frontMatterTitleField: this.refs.frontMatterTitleField.value,
|
frontMatterTitleField: this.refs.frontMatterTitleField.value,
|
||||||
matchingPairs: this.refs.matchingPairs.value,
|
matchingPairs: this.refs.matchingPairs.value,
|
||||||
|
matchingCloseBefore: this.refs.matchingCloseBefore.value,
|
||||||
matchingTriples: this.refs.matchingTriples.value,
|
matchingTriples: this.refs.matchingTriples.value,
|
||||||
explodingPairs: this.refs.explodingPairs.value,
|
explodingPairs: this.refs.explodingPairs.value,
|
||||||
|
codeBlockMatchingPairs: this.refs.codeBlockMatchingPairs.value,
|
||||||
|
codeBlockMatchingCloseBefore: this.refs.codeBlockMatchingCloseBefore
|
||||||
|
.value,
|
||||||
|
codeBlockMatchingTriples: this.refs.codeBlockMatchingTriples.value,
|
||||||
|
codeBlockExplodingPairs: this.refs.codeBlockExplodingPairs.value,
|
||||||
spellcheck: this.refs.spellcheck.checked,
|
spellcheck: this.refs.spellcheck.checked,
|
||||||
enableSmartPaste: this.refs.enableSmartPaste.checked,
|
enableSmartPaste: this.refs.enableSmartPaste.checked,
|
||||||
enableMarkdownLint: this.refs.enableMarkdownLint.checked,
|
enableMarkdownLint: this.refs.enableMarkdownLint.checked,
|
||||||
customMarkdownLintConfig: this.customMarkdownLintConfigCM
|
customMarkdownLintConfig: this.customMarkdownLintConfigCM
|
||||||
.getCodeMirror()
|
.getCodeMirror()
|
||||||
.getValue(),
|
.getValue(),
|
||||||
|
dateFormatISO8601: this.refs.dateFormatISO8601.checked,
|
||||||
prettierConfig: this.prettierConfigCM.getCodeMirror().getValue(),
|
prettierConfig: this.prettierConfigCM.getCodeMirror().getValue(),
|
||||||
deleteUnusedAttachments: this.refs.deleteUnusedAttachments.checked,
|
deleteUnusedAttachments: this.refs.deleteUnusedAttachments.checked,
|
||||||
rtlEnabled: this.refs.rtlEnabled.checked
|
rtlEnabled: this.refs.rtlEnabled.checked
|
||||||
@@ -745,6 +752,126 @@ class UiTab extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Matching character pairs')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.matchingPairs}
|
||||||
|
ref='matchingPairs'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label-right'>
|
||||||
|
{i18n.__('in code blocks')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.codeBlockMatchingPairs}
|
||||||
|
ref='codeBlockMatchingPairs'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Close pairs before')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.matchingCloseBefore}
|
||||||
|
ref='matchingCloseBefore'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label-right'>
|
||||||
|
{i18n.__('in code blocks')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.codeBlockMatchingCloseBefore}
|
||||||
|
ref='codeBlockMatchingCloseBefore'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Matching character triples')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.matchingTriples}
|
||||||
|
ref='matchingTriples'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label-right'>
|
||||||
|
{i18n.__('in code blocks')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.codeBlockMatchingTriples}
|
||||||
|
ref='codeBlockMatchingTriples'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Exploding character pairs')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.explodingPairs}
|
||||||
|
ref='explodingPairs'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label-right'>
|
||||||
|
{i18n.__('in code blocks')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={this.state.config.editor.codeBlockExplodingPairs}
|
||||||
|
ref='codeBlockExplodingPairs'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-checkBoxSection'>
|
<div styleName='group-checkBoxSection'>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
@@ -875,50 +1002,19 @@ class UiTab extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div styleName='group-section'>
|
<div styleName='group-checkBoxSection'>
|
||||||
<div styleName='group-section-label'>
|
<label>
|
||||||
{i18n.__('Matching character pairs')}
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
<input
|
<input
|
||||||
styleName='group-section-control-input'
|
|
||||||
value={this.state.config.editor.matchingPairs}
|
|
||||||
ref='matchingPairs'
|
|
||||||
onChange={e => this.handleUIChange(e)}
|
onChange={e => this.handleUIChange(e)}
|
||||||
type='text'
|
checked={this.state.config.editor.dateFormatISO8601}
|
||||||
|
ref='dateFormatISO8601'
|
||||||
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
{i18n.__('Date shortcut use iso 8601 format')}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div styleName='group-section'>
|
|
||||||
<div styleName='group-section-label'>
|
|
||||||
{i18n.__('Matching character triples')}
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
<input
|
|
||||||
styleName='group-section-control-input'
|
|
||||||
value={this.state.config.editor.matchingTriples}
|
|
||||||
ref='matchingTriples'
|
|
||||||
onChange={e => this.handleUIChange(e)}
|
|
||||||
type='text'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div styleName='group-section'>
|
|
||||||
<div styleName='group-section-label'>
|
|
||||||
{i18n.__('Exploding character pairs')}
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
<input
|
|
||||||
styleName='group-section-control-input'
|
|
||||||
value={this.state.config.editor.explodingPairs}
|
|
||||||
ref='explodingPairs'
|
|
||||||
onChange={e => this.handleUIChange(e)}
|
|
||||||
type='text'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>
|
<div styleName='group-section-label'>
|
||||||
{i18n.__('Custom MarkdownLint Rules')}
|
{i18n.__('Custom MarkdownLint Rules')}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import UiTab from './UiTab'
|
|||||||
import InfoTab from './InfoTab'
|
import InfoTab from './InfoTab'
|
||||||
import Crowdfunding from './Crowdfunding'
|
import Crowdfunding from './Crowdfunding'
|
||||||
import StoragesTab from './StoragesTab'
|
import StoragesTab from './StoragesTab'
|
||||||
|
import ExportTab from './ExportTab'
|
||||||
import SnippetTab from './SnippetTab'
|
import SnippetTab from './SnippetTab'
|
||||||
|
import PluginsTab from './PluginsTab'
|
||||||
import Blog from './Blog'
|
import Blog from './Blog'
|
||||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -23,7 +25,8 @@ class Preferences extends React.Component {
|
|||||||
currentTab: 'STORAGES',
|
currentTab: 'STORAGES',
|
||||||
UIAlert: '',
|
UIAlert: '',
|
||||||
HotkeyAlert: '',
|
HotkeyAlert: '',
|
||||||
BlogAlert: ''
|
BlogAlert: '',
|
||||||
|
ExportAlert: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +83,25 @@ class Preferences extends React.Component {
|
|||||||
haveToSave={alert => this.setState({ BlogAlert: alert })}
|
haveToSave={alert => this.setState({ BlogAlert: alert })}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
case 'EXPORT':
|
||||||
|
return (
|
||||||
|
<ExportTab
|
||||||
|
dispatch={dispatch}
|
||||||
|
config={config}
|
||||||
|
data={data}
|
||||||
|
haveToSave={alert => this.setState({ ExportAlert: alert })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'SNIPPET':
|
case 'SNIPPET':
|
||||||
return <SnippetTab dispatch={dispatch} config={config} data={data} />
|
return <SnippetTab dispatch={dispatch} config={config} data={data} />
|
||||||
|
case 'PLUGINS':
|
||||||
|
return (
|
||||||
|
<PluginsTab
|
||||||
|
dispatch={dispatch}
|
||||||
|
config={config}
|
||||||
|
haveToSave={alert => this.setState({ PluginsAlert: alert })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'STORAGES':
|
case 'STORAGES':
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
@@ -122,7 +142,13 @@ class Preferences extends React.Component {
|
|||||||
{ target: 'INFO', label: i18n.__('About') },
|
{ target: 'INFO', label: i18n.__('About') },
|
||||||
{ target: 'CROWDFUNDING', label: i18n.__('Crowdfunding') },
|
{ target: 'CROWDFUNDING', label: i18n.__('Crowdfunding') },
|
||||||
{ target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert },
|
{ target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert },
|
||||||
{ target: 'SNIPPET', label: i18n.__('Snippets') }
|
{
|
||||||
|
target: 'EXPORT',
|
||||||
|
label: i18n.__('Export'),
|
||||||
|
Export: this.state.ExportAlert
|
||||||
|
},
|
||||||
|
{ target: 'SNIPPET', label: i18n.__('Snippets') },
|
||||||
|
{ target: 'PLUGINS', label: i18n.__('Plugins') }
|
||||||
]
|
]
|
||||||
|
|
||||||
const navButtons = tabs.map(tab => {
|
const navButtons = tabs.map(tab => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './RenameFolderModal.styl'
|
import styles from './RenameModal.styl'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { store } from 'browser/main/store'
|
import { store } from 'browser/main/store'
|
||||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
|
|||||||
@@ -46,13 +46,18 @@
|
|||||||
font-size 14px
|
font-size 14px
|
||||||
colorPrimaryButton()
|
colorPrimaryButton()
|
||||||
|
|
||||||
|
.error
|
||||||
|
text-align center
|
||||||
|
color #F44336
|
||||||
|
height 20px
|
||||||
|
|
||||||
apply-theme(theme)
|
apply-theme(theme)
|
||||||
body[data-theme={theme}]
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
background-color transparent
|
background-color transparent
|
||||||
|
|
||||||
.header
|
.header
|
||||||
background-color get-theme-var(theme, 'button--hover-backgroundColor')
|
background-color transparent
|
||||||
border-color get-theme-var(theme, 'borderColor')
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
color get-theme-var(theme, 'text-color')
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
196
browser/main/modals/RenameTagModal.js
Normal file
196
browser/main/modals/RenameTagModal.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './RenameModal.styl'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import { replace } from 'connected-react-router'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import electron from 'electron'
|
||||||
|
|
||||||
|
const { remote } = electron
|
||||||
|
const { dialog } = remote
|
||||||
|
|
||||||
|
class RenameTagModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.nameInput = null
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this)
|
||||||
|
|
||||||
|
this.setTextInputRef = el => {
|
||||||
|
this.nameInput = el
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
name: props.tagName,
|
||||||
|
oldName: props.tagName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.nameInput.focus()
|
||||||
|
this.nameInput.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
|
this.setState({
|
||||||
|
name: this.nameInput.value,
|
||||||
|
showerror: false,
|
||||||
|
errormessage: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(e) {
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
this.props.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputKeyDown(e) {
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 13:
|
||||||
|
this.handleConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirm() {
|
||||||
|
if (this.state.name.trim().length > 0) {
|
||||||
|
const { name, oldName } = this.state
|
||||||
|
this.renameTag(oldName, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(message) {
|
||||||
|
this.setState({
|
||||||
|
showerror: true,
|
||||||
|
errormessage: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renameTag(tag, updatedTag) {
|
||||||
|
const { data, dispatch } = this.props
|
||||||
|
|
||||||
|
if (tag === updatedTag) {
|
||||||
|
// confirm with-out any change - just dismiss the modal
|
||||||
|
this.props.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
data.noteMap
|
||||||
|
.map(note => note)
|
||||||
|
.some(note => note.tags.indexOf(updatedTag) !== -1)
|
||||||
|
) {
|
||||||
|
const alertConfig = {
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Confirm tag merge'),
|
||||||
|
detail: i18n.__(
|
||||||
|
`Tag ${tag} will be merged with existing tag ${updatedTag}`
|
||||||
|
),
|
||||||
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogButtonIndex = dialog.showMessageBox(
|
||||||
|
remote.getCurrentWindow(),
|
||||||
|
alertConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
if (dialogButtonIndex === 1) {
|
||||||
|
return // bail early on cancel click
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes = data.noteMap
|
||||||
|
.map(note => note)
|
||||||
|
.filter(
|
||||||
|
note => note.tags.indexOf(tag) !== -1 && note.tags.indexOf(updatedTag)
|
||||||
|
)
|
||||||
|
.map(note => {
|
||||||
|
note = Object.assign({}, note)
|
||||||
|
note.tags = note.tags.slice()
|
||||||
|
|
||||||
|
note.tags[note.tags.indexOf(tag)] = updatedTag
|
||||||
|
|
||||||
|
return note
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isEmpty(notes)) {
|
||||||
|
this.showError(i18n.__('Tag exists'))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
notes.map(note => dataApi.updateNote(note.storage, note.key, note))
|
||||||
|
)
|
||||||
|
.then(updatedNotes => {
|
||||||
|
updatedNotes.forEach(note => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (window.location.hash.includes(tag)) {
|
||||||
|
dispatch(replace(`/tags/${updatedTag}`))
|
||||||
|
}
|
||||||
|
ee.emit('sidebar:rename-tag', { tag, updatedTag })
|
||||||
|
this.props.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { close } = this.props
|
||||||
|
const { errormessage } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
styleName='root'
|
||||||
|
tabIndex='-1'
|
||||||
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
|
>
|
||||||
|
<div styleName='header'>
|
||||||
|
<div styleName='title'>{i18n.__('Rename Tag')}</div>
|
||||||
|
</div>
|
||||||
|
<ModalEscButton handleEscButtonClick={close} />
|
||||||
|
|
||||||
|
<div styleName='control'>
|
||||||
|
<input
|
||||||
|
styleName='control-input'
|
||||||
|
placeholder={i18n.__('Tag Name')}
|
||||||
|
ref={this.setTextInputRef}
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onKeyDown={e => this.handleInputKeyDown(e)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
styleName='control-confirmButton'
|
||||||
|
onClick={() => this.handleConfirm()}
|
||||||
|
>
|
||||||
|
{i18n.__('Confirm')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='error' styleName='error'>
|
||||||
|
{errormessage}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenameTagModal.propTypes = {
|
||||||
|
storage: PropTypes.shape({
|
||||||
|
key: PropTypes.string
|
||||||
|
}),
|
||||||
|
folder: PropTypes.shape({
|
||||||
|
key: PropTypes.string,
|
||||||
|
name: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(RenameTagModal, styles)
|
||||||
196
extra_scripts/codemirror/addon/edit/closebrackets.js
vendored
Normal file
196
extra_scripts/codemirror/addon/edit/closebrackets.js
vendored
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
var defaults = {
|
||||||
|
pairs: "()[]{}''\"\"",
|
||||||
|
closeBefore: ")]}'\":;>",
|
||||||
|
triples: "",
|
||||||
|
explode: "[]{}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
|
||||||
|
if (old && old != CodeMirror.Init) {
|
||||||
|
cm.removeKeyMap(keyMap);
|
||||||
|
cm.state.closeBrackets = null;
|
||||||
|
}
|
||||||
|
if (val) {
|
||||||
|
ensureBound(getOption(val.markdown, "pairs"))
|
||||||
|
cm.state.closeBrackets = val;
|
||||||
|
cm.addKeyMap(keyMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getOption(conf, name) {
|
||||||
|
if (name == "pairs" && typeof conf == "string") return conf;
|
||||||
|
if (typeof conf == "object" && conf[name] != null) return conf[name];
|
||||||
|
return defaults[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
|
||||||
|
function ensureBound(chars) {
|
||||||
|
for (var i = 0; i < chars.length; i++) {
|
||||||
|
var ch = chars.charAt(i), key = "'" + ch + "'"
|
||||||
|
if (!keyMap[key]) keyMap[key] = handler(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ensureBound(defaults.pairs + "`")
|
||||||
|
|
||||||
|
function handler(ch) {
|
||||||
|
return function(cm) { return handleChar(cm, ch); };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfig(cm) {
|
||||||
|
var cursor = cm.getCursor();
|
||||||
|
var token = cm.getTokenAt(cursor);
|
||||||
|
var inCodeBlock = !!token.state.fencedEndRE;
|
||||||
|
|
||||||
|
if (inCodeBlock) {
|
||||||
|
return cm.state.closeBrackets.codeBlock
|
||||||
|
} else {
|
||||||
|
return cm.state.closeBrackets.markdown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBackspace(cm) {
|
||||||
|
var conf = getConfig(cm);
|
||||||
|
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||||
|
|
||||||
|
var pairs = getOption(conf, "pairs");
|
||||||
|
var ranges = cm.listSelections();
|
||||||
|
for (var i = 0; i < ranges.length; i++) {
|
||||||
|
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||||
|
var around = charsAround(cm, ranges[i].head);
|
||||||
|
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
||||||
|
}
|
||||||
|
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||||
|
var cur = ranges[i].head;
|
||||||
|
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEnter(cm) {
|
||||||
|
var conf = getConfig(cm);
|
||||||
|
var explode = conf && getOption(conf, "explode");
|
||||||
|
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||||
|
|
||||||
|
var ranges = cm.listSelections();
|
||||||
|
for (var i = 0; i < ranges.length; i++) {
|
||||||
|
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||||
|
var around = charsAround(cm, ranges[i].head);
|
||||||
|
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
||||||
|
}
|
||||||
|
cm.operation(function() {
|
||||||
|
var linesep = cm.lineSeparator() || "\n";
|
||||||
|
cm.replaceSelection(linesep + linesep, null);
|
||||||
|
cm.execCommand("goCharLeft");
|
||||||
|
ranges = cm.listSelections();
|
||||||
|
for (var i = 0; i < ranges.length; i++) {
|
||||||
|
var line = ranges[i].head.line;
|
||||||
|
cm.indentLine(line, null, true);
|
||||||
|
cm.indentLine(line + 1, null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function contractSelection(sel) {
|
||||||
|
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
|
||||||
|
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
|
||||||
|
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChar(cm, ch) {
|
||||||
|
var conf = getConfig(cm);
|
||||||
|
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||||
|
|
||||||
|
var pairs = getOption(conf, "pairs");
|
||||||
|
var pos = pairs.indexOf(ch);
|
||||||
|
if (pos == -1) return CodeMirror.Pass;
|
||||||
|
|
||||||
|
var closeBefore = getOption(conf,"closeBefore");
|
||||||
|
|
||||||
|
var triples = getOption(conf, "triples");
|
||||||
|
|
||||||
|
var identical = pairs.charAt(pos + 1) == ch;
|
||||||
|
var ranges = cm.listSelections();
|
||||||
|
var opening = pos % 2 == 0;
|
||||||
|
|
||||||
|
var type;
|
||||||
|
for (var i = 0; i < ranges.length; i++) {
|
||||||
|
var range = ranges[i], cur = range.head, curType;
|
||||||
|
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
|
||||||
|
if (opening && !range.empty()) {
|
||||||
|
curType = "surround";
|
||||||
|
} else if ((identical || !opening) && next == ch) {
|
||||||
|
if (identical && stringStartsAfter(cm, cur))
|
||||||
|
curType = "both";
|
||||||
|
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
|
||||||
|
curType = "skipThree";
|
||||||
|
else
|
||||||
|
curType = "skip";
|
||||||
|
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
|
||||||
|
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
|
||||||
|
if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
|
||||||
|
curType = "addFour";
|
||||||
|
} else if (identical) {
|
||||||
|
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
|
||||||
|
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
|
||||||
|
else return CodeMirror.Pass;
|
||||||
|
} else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
|
||||||
|
curType = "both";
|
||||||
|
} else {
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
}
|
||||||
|
if (!type) type = curType;
|
||||||
|
else if (type != curType) return CodeMirror.Pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
|
||||||
|
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
|
||||||
|
cm.operation(function() {
|
||||||
|
if (type == "skip") {
|
||||||
|
cm.execCommand("goCharRight");
|
||||||
|
} else if (type == "skipThree") {
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
cm.execCommand("goCharRight");
|
||||||
|
} else if (type == "surround") {
|
||||||
|
var sels = cm.getSelections();
|
||||||
|
for (var i = 0; i < sels.length; i++)
|
||||||
|
sels[i] = left + sels[i] + right;
|
||||||
|
cm.replaceSelections(sels, "around");
|
||||||
|
sels = cm.listSelections().slice();
|
||||||
|
for (var i = 0; i < sels.length; i++)
|
||||||
|
sels[i] = contractSelection(sels[i]);
|
||||||
|
cm.setSelections(sels);
|
||||||
|
} else if (type == "both") {
|
||||||
|
cm.replaceSelection(left + right, null);
|
||||||
|
cm.triggerElectric(left + right);
|
||||||
|
cm.execCommand("goCharLeft");
|
||||||
|
} else if (type == "addFour") {
|
||||||
|
cm.replaceSelection(left + left + left + left, "before");
|
||||||
|
cm.execCommand("goCharRight");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function charsAround(cm, pos) {
|
||||||
|
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
|
||||||
|
Pos(pos.line, pos.ch + 1));
|
||||||
|
return str.length == 2 ? str : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringStartsAfter(cm, pos) {
|
||||||
|
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
|
||||||
|
return /\bstring/.test(token.type) && token.start == pos.ch &&
|
||||||
|
(pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
|
||||||
|
}
|
||||||
|
});
|
||||||
74
extra_scripts/codemirror/mode/bfm/bfm.js
vendored
74
extra_scripts/codemirror/mode/bfm/bfm.js
vendored
@@ -55,9 +55,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.defineMode(
|
CodeMirror.defineMode('bfm', function (config, baseConfig) {
|
||||||
'bfm',
|
|
||||||
function(config, baseConfig) {
|
|
||||||
baseConfig.name = 'yaml-frontmatter'
|
baseConfig.name = 'yaml-frontmatter'
|
||||||
const baseMode = CodeMirror.getMode(config, baseConfig)
|
const baseMode = CodeMirror.getMode(config, baseConfig)
|
||||||
|
|
||||||
@@ -88,9 +86,7 @@
|
|||||||
overlayCur: null,
|
overlayCur: null,
|
||||||
|
|
||||||
fencedMode: s.fencedMode,
|
fencedMode: s.fencedMode,
|
||||||
fencedState: s.fencedMode
|
fencedState: s.fencedMode ? CodeMirror.copyState(s.fencedMode, s.fencedState) : null,
|
||||||
? CodeMirror.copyState(s.fencedMode, s.fencedState)
|
|
||||||
: null,
|
|
||||||
|
|
||||||
fencedEndRE: s.fencedEndRE,
|
fencedEndRE: s.fencedEndRE,
|
||||||
|
|
||||||
@@ -101,27 +97,28 @@
|
|||||||
token: function(stream, state) {
|
token: function(stream, state) {
|
||||||
const initialPos = stream.pos
|
const initialPos = stream.pos
|
||||||
|
|
||||||
if (state.fencedEndRE && stream.match(state.fencedEndRE)) {
|
if (state.fencedEndRE) {
|
||||||
|
if (stream.match(state.fencedEndRE)) {
|
||||||
state.fencedEndRE = null
|
state.fencedEndRE = null
|
||||||
state.fencedMode = null
|
state.fencedMode = null
|
||||||
state.fencedState = null
|
state.fencedState = null
|
||||||
|
|
||||||
stream.pos = initialPos
|
stream.pos = initialPos
|
||||||
} else {
|
} else if (state.fencedMode) {
|
||||||
if (state.fencedMode) {
|
|
||||||
return state.fencedMode.token(stream, state.fencedState)
|
return state.fencedMode.token(stream, state.fencedState)
|
||||||
}
|
} else {
|
||||||
|
state.overlayCur = this.overlayToken(stream, state)
|
||||||
|
state.overlayPos = stream.pos
|
||||||
|
|
||||||
|
return state.overlayCur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
const match = stream.match(fencedCodeRE, true)
|
const match = stream.match(fencedCodeRE, true)
|
||||||
if (match) {
|
if (match) {
|
||||||
state.fencedEndRE = new RegExp(match[1] + '+ *$')
|
state.fencedEndRE = new RegExp(match[1] + '+ *$')
|
||||||
|
|
||||||
state.fencedMode = getMode(
|
state.fencedMode = getMode(match[2], match[3], config, stream.lineOracle.doc.cm)
|
||||||
match[2],
|
|
||||||
match[3],
|
|
||||||
config,
|
|
||||||
stream.lineOracle.doc.cm
|
|
||||||
)
|
|
||||||
if (state.fencedMode) {
|
if (state.fencedMode) {
|
||||||
state.fencedState = CodeMirror.startState(state.fencedMode)
|
state.fencedState = CodeMirror.startState(state.fencedMode)
|
||||||
}
|
}
|
||||||
@@ -130,10 +127,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (stream != state.streamSeen || Math.min(state.basePos, state.overlayPos) < stream.start) {
|
||||||
stream != state.streamSeen ||
|
|
||||||
Math.min(state.basePos, state.overlayPos) < stream.start
|
|
||||||
) {
|
|
||||||
state.streamSeen = stream
|
state.streamSeen = stream
|
||||||
state.basePos = state.overlayPos = stream.start
|
state.basePos = state.overlayPos = stream.start
|
||||||
}
|
}
|
||||||
@@ -151,44 +145,21 @@
|
|||||||
|
|
||||||
if (state.overlayCur == null) {
|
if (state.overlayCur == null) {
|
||||||
return state.baseCur
|
return state.baseCur
|
||||||
} else if (state.baseCur != null && state.combineTokens) {
|
}
|
||||||
|
else if (state.baseCur != null && state.combineTokens) {
|
||||||
return state.baseCur + ' ' + state.overlayCur
|
return state.baseCur + ' ' + state.overlayCur
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return state.overlayCur
|
return state.overlayCur
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
overlayToken: function(stream, state) {
|
overlayToken: function(stream, state) {
|
||||||
state.combineTokens = false
|
state.combineTokens = false
|
||||||
|
|
||||||
if (state.fencedEndRE && stream.match(state.fencedEndRE)) {
|
|
||||||
state.fencedEndRE = null
|
|
||||||
state.localMode = null
|
|
||||||
state.localState = null
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.localMode) {
|
if (state.localMode) {
|
||||||
return state.localMode.token(stream, state.localState) || ''
|
return state.localMode.token(stream, state.localState) || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = stream.match(fencedCodeRE, true)
|
|
||||||
if (match) {
|
|
||||||
state.fencedEndRE = new RegExp(match[1] + '+ *$')
|
|
||||||
|
|
||||||
state.localMode = getMode(
|
|
||||||
match[2],
|
|
||||||
match[3],
|
|
||||||
config,
|
|
||||||
stream.lineOracle.doc.cm
|
|
||||||
)
|
|
||||||
if (state.localMode) {
|
|
||||||
state.localState = CodeMirror.startState(state.localMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
state.combineTokens = true
|
state.combineTokens = true
|
||||||
|
|
||||||
if (state.inTable) {
|
if (state.inTable) {
|
||||||
@@ -239,18 +210,13 @@
|
|||||||
state.inTable = false
|
state.inTable = false
|
||||||
|
|
||||||
if (state.fencedMode) {
|
if (state.fencedMode) {
|
||||||
return (
|
return state.fencedMode.blankLine && state.fencedMode.blankLine(state.fencedState)
|
||||||
state.fencedMode.blankLine &&
|
|
||||||
state.fencedMode.blankLine(state.fencedState)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
return baseMode.blankLine(state.baseState)
|
return baseMode.blankLine(state.baseState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, 'yaml-frontmatter')
|
||||||
'yaml-frontmatter'
|
|
||||||
)
|
|
||||||
|
|
||||||
CodeMirror.defineMIME('text/x-bfm', 'bfm')
|
CodeMirror.defineMIME('text/x-bfm', 'bfm')
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ module.exports = function(grunt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChildProcess.exec(
|
ChildProcess.exec(
|
||||||
`codesign --verbose --deep --force --sign \"${OSX_COMMON_NAME}\" dist/Boostnote-darwin-x64/Boostnote.app`,
|
`codesign --verbose --deep --force --timestamp=none --sign \"${OSX_COMMON_NAME}\" dist/Boostnote-darwin-x64/Boostnote.app`,
|
||||||
function(err, stdout, stderr) {
|
function(err, stdout, stderr) {
|
||||||
grunt.log.writeln(stdout)
|
grunt.log.writeln(stdout)
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -314,6 +314,12 @@ const view = {
|
|||||||
mainWindow.webContents.send('editor:fullscreen')
|
mainWindow.webContents.send('editor:fullscreen')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Toggle Editor Orientation',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.send('editor:orientation')
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
@@ -466,9 +472,21 @@ const help = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const team = {
|
||||||
|
label: 'For Team',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'BoostHub',
|
||||||
|
click: async () => {
|
||||||
|
shell.openExternal('https://boosthub.io/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
process.platform === 'darwin'
|
process.platform === 'darwin'
|
||||||
? [boost, file, edit, view, window, help]
|
? [boost, file, edit, view, window, team, help]
|
||||||
: process.platform === 'win32'
|
: process.platform === 'win32'
|
||||||
? [boost, file, view, help]
|
? [boost, file, view, team, help]
|
||||||
: [file, view, help]
|
: [file, view, team, help]
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
<script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script>
|
<script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script>
|
||||||
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
|
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
|
||||||
|
|
||||||
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
|
<script src="../extra_scripts/codemirror/addon/edit/closebrackets.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
|
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
|
||||||
|
|
||||||
<script src="../node_modules/codemirror/addon/search/search.js"></script>
|
<script src="../node_modules/codemirror/addon/search/search.js"></script>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
<script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script>
|
<script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script>
|
||||||
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
|
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
|
||||||
|
|
||||||
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
|
<script src="../extra_scripts/codemirror/addon/edit/closebrackets.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
|
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
|
||||||
|
|
||||||
<script src="../node_modules/codemirror/addon/search/search.js"></script>
|
<script src="../node_modules/codemirror/addon/search/search.js"></script>
|
||||||
|
|||||||
@@ -202,7 +202,6 @@
|
|||||||
"Create new folder": "Ordner erstellen",
|
"Create new folder": "Ordner erstellen",
|
||||||
"Folder name": "Ordnername",
|
"Folder name": "Ordnername",
|
||||||
"Create": "Erstellen",
|
"Create": "Erstellen",
|
||||||
"Untitled": "Neuer Ordner",
|
|
||||||
"Unlink Storage": "Speicherverknüpfung aufheben",
|
"Unlink Storage": "Speicherverknüpfung aufheben",
|
||||||
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "Die Verknüpfung des Speichers mit Boostnote wird entfernt. Es werden keine Daten gelöscht. Um die Daten dauerhaft zu löschen musst du den Ordner auf der Festplatte manuell entfernen.",
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "Die Verknüpfung des Speichers mit Boostnote wird entfernt. Es werden keine Daten gelöscht. Um die Daten dauerhaft zu löschen musst du den Ordner auf der Festplatte manuell entfernen.",
|
||||||
"Empty note": "Leere Notiz",
|
"Empty note": "Leere Notiz",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.15.3",
|
"version": "0.16.1",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -61,6 +61,7 @@
|
|||||||
"chart.js": "^2.7.2",
|
"chart.js": "^2.7.2",
|
||||||
"codemirror": "^5.40.2",
|
"codemirror": "^5.40.2",
|
||||||
"codemirror-mode-elixir": "^1.1.1",
|
"codemirror-mode-elixir": "^1.1.1",
|
||||||
|
"command-exists": "^1.2.9",
|
||||||
"connected-react-router": "^6.4.0",
|
"connected-react-router": "^6.4.0",
|
||||||
"electron-config": "^1.0.0",
|
"electron-config": "^1.0.0",
|
||||||
"electron-gh-releases": "^2.0.4",
|
"electron-gh-releases": "^2.0.4",
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
"flowchart.js": "^1.6.5",
|
"flowchart.js": "^1.6.5",
|
||||||
"font-awesome": "^4.3.0",
|
"font-awesome": "^4.3.0",
|
||||||
"fs-extra": "^5.0.0",
|
"fs-extra": "^5.0.0",
|
||||||
"highlight.js": "^9.13.1",
|
"highlight.js": "^10.4.1",
|
||||||
"i18n-2": "^0.7.2",
|
"i18n-2": "^0.7.2",
|
||||||
"iconv-lite": "^0.4.19",
|
"iconv-lite": "^0.4.19",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"jsonlint-mod": "^1.7.4",
|
"jsonlint-mod": "^1.7.4",
|
||||||
"katex": "^0.10.1",
|
"katex": "^0.10.1",
|
||||||
"lodash": "^4.17.13",
|
"lodash": "^4.17.19",
|
||||||
"lodash-move": "^1.1.1",
|
"lodash-move": "^1.1.1",
|
||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
"markdown-it-abbr": "^1.0.4",
|
"markdown-it-abbr": "^1.0.4",
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
"markdown-it-sup": "^1.0.0",
|
"markdown-it-sup": "^1.0.0",
|
||||||
"markdown-toc": "^1.2.0",
|
"markdown-toc": "^1.2.0",
|
||||||
"mdurl": "^1.0.1",
|
"mdurl": "^1.0.1",
|
||||||
"mermaid": "^8.4.2",
|
"mermaid": "^8.5.2",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
"mousetrap": "^1.6.2",
|
"mousetrap": "^1.6.2",
|
||||||
"mousetrap-global-bind": "^1.1.0",
|
"mousetrap-global-bind": "^1.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"trailingComma": "es5",
|
"singleQuote": true,
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": true
|
"jsxSingleQuote": true
|
||||||
}
|
}
|
||||||
24
readme.md
24
readme.md
@@ -1,5 +1,11 @@
|
|||||||
> [We've launched desktop and mobile app of the new Boost Note now.](https://github.com/BoostIO/BoostNote.next)
|
> [We've launched desktop and mobile app of the new Boost Note now.](https://github.com/BoostIO/BoostNote.next)
|
||||||
|
|
||||||
|
> ### [Boost Note for Teams](https://boosthub.io/)
|
||||||
|
>
|
||||||
|
> We've developed a collaborative workspace app called "Boost Hub" for developer teams.
|
||||||
|
>
|
||||||
|
> It's customizable and easy to optimize for your team like rego blocks and even lets you edit documents together in real-time!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<h4 align="center">Note-taking app for programmers. </h4>
|
<h4 align="center">Note-taking app for programmers. </h4>
|
||||||
@@ -15,6 +21,10 @@
|
|||||||
|
|
||||||
[Find the latest release of Boostnote here!](https://github.com/BoostIO/boost-releases/releases/)
|
[Find the latest release of Boostnote here!](https://github.com/BoostIO/boost-releases/releases/)
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
[Boost Note Roadmap 2020](https://medium.com/boostnote/boost-note-roadmap-2020-9f06a642f5f1)
|
||||||
|
|
||||||
## Authors & Maintainers
|
## Authors & Maintainers
|
||||||
|
|
||||||
- [Rokt33r](https://github.com/rokt33r)
|
- [Rokt33r](https://github.com/rokt33r)
|
||||||
@@ -22,11 +32,13 @@
|
|||||||
- [ZeroX-DG](https://github.com/ZeroX-DG)
|
- [ZeroX-DG](https://github.com/ZeroX-DG)
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
Thank you to all the people who have contributed to Boostnote!
|
Thank you to all the people who have contributed to Boostnote!
|
||||||
|
|
||||||
<a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a>
|
<a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a>
|
||||||
|
|
||||||
## Supporting Boostnote
|
## Supporting Boostnote
|
||||||
|
|
||||||
Boostnote is an open source project. It's an independent project with its ongoing development made possible thanks to the support by our amazing backers.
|
Boostnote is an open source project. It's an independent project with its ongoing development made possible thanks to the support by our amazing backers.
|
||||||
|
|
||||||
Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer:
|
Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer:
|
||||||
@@ -34,18 +46,22 @@ Issues on Boostnote can be funded by anyone and the money will be distributed to
|
|||||||
[](https://issuehunt.io/repos/53266139)
|
[](https://issuehunt.io/repos/53266139)
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||||
- [Twitter](https://twitter.com/boostnoteapp)
|
- [Twitter](https://twitter.com/boostnoteapp)
|
||||||
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LWQxZTQwNjBlMDI4YjkyYjg2MTRiZGJhNzA1YjQ5ODA5M2M0M2NlMjI5YjhiYWQzNzgzYmU0MDMwOTlmZmZmMGE)
|
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/zt-cun7pas3-WwkaezxHBB1lCbUHrwQLXw)
|
||||||
- [Blog](https://medium.com/boostnote)
|
- [Blog](https://medium.com/boostnote)
|
||||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||||
|
|
||||||
|
### Boostnote mobile
|
||||||
|
A community project developing a mobile cross-platform version of boostnote for iOS and Android can be found here: [NoteApp](https://github.com/T0M0F/NoteApp)
|
||||||
|
|
||||||
|
|
||||||
#### More Information
|
#### More Information
|
||||||
* Website: https://boostnote.io
|
|
||||||
* [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
|
|
||||||
* Copyright (C) 2016 - 2020 BoostIO, Inc.
|
|
||||||
|
|
||||||
|
- Website: https://boostnote.io
|
||||||
|
- [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
|
||||||
|
- Copyright (C) 2016 - 2020 BoostIO, Inc.
|
||||||
|
|
||||||
#### License
|
#### License
|
||||||
|
|
||||||
|
|||||||
@@ -702,14 +702,15 @@ it('should remove the all ":storage" and noteKey references', function() {
|
|||||||
' </p>\n' +
|
' </p>\n' +
|
||||||
' </body>\n' +
|
' </body>\n' +
|
||||||
'</html>'
|
'</html>'
|
||||||
const actual = systemUnderTest.removeStorageAndNoteReferences(
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
testInput,
|
testInput,
|
||||||
noteKey
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
)
|
)
|
||||||
expect(actual).toEqual(expectedOutput)
|
expect(actual).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should make sure that "removeStorageAndNoteReferences" works with markdown content as well', function() {
|
it('should make sure that "replaceStorageReferences" works with markdown content as well', function() {
|
||||||
const noteKey = 'noteKey'
|
const noteKey = 'noteKey'
|
||||||
const testInput =
|
const testInput =
|
||||||
'Test input' +
|
'Test input' +
|
||||||
@@ -736,9 +737,113 @@ it('should make sure that "removeStorageAndNoteReferences" works with markdown c
|
|||||||
systemUnderTest.DESTINATION_FOLDER +
|
systemUnderTest.DESTINATION_FOLDER +
|
||||||
path.posix.sep +
|
path.posix.sep +
|
||||||
'pdf.pdf)'
|
'pdf.pdf)'
|
||||||
const actual = systemUnderTest.removeStorageAndNoteReferences(
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
testInput,
|
testInput,
|
||||||
noteKey
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
|
)
|
||||||
|
expect(actual).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should replace the all ":storage" references', function() {
|
||||||
|
const storageFolder = systemUnderTest.DESTINATION_FOLDER
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const testInput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src=":storage' +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
noteKey +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
'0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href=":storage' +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
noteKey +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
'0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src=":storage' +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
noteKey +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const expectedOutput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src="' +
|
||||||
|
storageFolder +
|
||||||
|
path.sep +
|
||||||
|
'0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href="' +
|
||||||
|
storageFolder +
|
||||||
|
path.sep +
|
||||||
|
'0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src="' +
|
||||||
|
storageFolder +
|
||||||
|
path.sep +
|
||||||
|
'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
|
testInput,
|
||||||
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
|
)
|
||||||
|
expect(actual).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make sure that "replaceStorageReferences" works with markdown content as well', function() {
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[pdf](' +
|
||||||
|
systemUnderTest.STORAGE_FOLDER_PLACEHOLDER +
|
||||||
|
path.posix.sep +
|
||||||
|
noteKey +
|
||||||
|
path.posix.sep +
|
||||||
|
'pdf.pdf)'
|
||||||
|
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[pdf](' +
|
||||||
|
systemUnderTest.DESTINATION_FOLDER +
|
||||||
|
path.posix.sep +
|
||||||
|
'pdf.pdf)'
|
||||||
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
|
testInput,
|
||||||
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
)
|
)
|
||||||
expect(actual).toEqual(expectedOutput)
|
expect(actual).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const test = require('ava')
|
|
||||||
const copyFile = require('browser/main/lib/dataApi/copyFile')
|
const copyFile = require('browser/main/lib/dataApi/copyFile')
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
@@ -13,23 +12,25 @@ const srcPath = path.join(srcFolder, testFile)
|
|||||||
const dstFolder = path.join(__dirname, '😇')
|
const dstFolder = path.join(__dirname, '😇')
|
||||||
const dstPath = path.join(dstFolder, testFile)
|
const dstPath = path.join(dstFolder, testFile)
|
||||||
|
|
||||||
test.before(t => {
|
beforeAll(() => {
|
||||||
if (!fs.existsSync(srcFolder)) fs.mkdirSync(srcFolder)
|
if (!fs.existsSync(srcFolder)) fs.mkdirSync(srcFolder)
|
||||||
|
|
||||||
fs.writeFileSync(srcPath, 'test')
|
fs.writeFileSync(srcPath, 'test')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('`copyFile` should handle encoded URI on src path', t => {
|
it('`copyFile` should handle encoded URI on src path', done => {
|
||||||
return copyFile(encodeURI(srcPath), dstPath)
|
return copyFile(encodeURI(srcPath), dstPath)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
t.true(true)
|
expect(true).toBe(true)
|
||||||
|
done()
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
t.true(false)
|
expect(false).toBe(true)
|
||||||
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after(t => {
|
afterAll(() => {
|
||||||
fs.unlinkSync(srcPath)
|
fs.unlinkSync(srcPath)
|
||||||
fs.unlinkSync(dstPath)
|
fs.unlinkSync(dstPath)
|
||||||
execSync(removeDirCommand + '"' + srcFolder + '"')
|
execSync(removeDirCommand + '"' + srcFolder + '"')
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const test = require('ava')
|
|
||||||
const createFolder = require('browser/main/lib/dataApi/createFolder')
|
const createFolder = require('browser/main/lib/dataApi/createFolder')
|
||||||
|
|
||||||
global.document = require('jsdom').jsdom('<body></body>')
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
@@ -19,32 +18,34 @@ const CSON = require('@rokt33r/season')
|
|||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/create-folder')
|
const storagePath = path.join(os.tmpdir(), 'test/create-folder')
|
||||||
|
|
||||||
test.beforeEach(t => {
|
let storageContext
|
||||||
t.context.storage = TestDummy.dummyStorage(storagePath)
|
|
||||||
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
beforeAll(() => {
|
||||||
|
storageContext = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
|
||||||
})
|
})
|
||||||
|
|
||||||
test.serial('Create a folder', t => {
|
it('Create a folder', done => {
|
||||||
const storageKey = t.context.storage.cache.key
|
const storageKey = storageContext.cache.key
|
||||||
const input = {
|
const input = {
|
||||||
name: 'created',
|
name: 'created',
|
||||||
color: '#ff5555'
|
color: '#ff5555'
|
||||||
}
|
}
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(function doTest() {
|
.then(() => {
|
||||||
return createFolder(storageKey, input)
|
return createFolder(storageKey, input)
|
||||||
})
|
})
|
||||||
.then(function assert(data) {
|
.then(data => {
|
||||||
t.true(_.find(data.storage.folders, input) != null)
|
expect(_.find(data.storage.folders, input)).not.toBeNull()
|
||||||
const jsonData = CSON.readFileSync(
|
const jsonData = CSON.readFileSync(
|
||||||
path.join(data.storage.path, 'boostnote.json')
|
path.join(data.storage.path, 'boostnote.json')
|
||||||
)
|
)
|
||||||
console.log(path.join(data.storage.path, 'boostnote.json'))
|
expect(_.find(jsonData.folders, input)).not.toBeNull()
|
||||||
t.true(_.find(jsonData.folders, input) != null)
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after(function after() {
|
afterAll(() => {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
sander.rimrafSync(storagePath)
|
sander.rimrafSync(storagePath)
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const test = require('ava')
|
|
||||||
const createNote = require('browser/main/lib/dataApi/createNote')
|
const createNote = require('browser/main/lib/dataApi/createNote')
|
||||||
|
|
||||||
global.document = require('jsdom').jsdom('<body></body>')
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
@@ -19,14 +18,16 @@ const faker = require('faker')
|
|||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/create-note')
|
const storagePath = path.join(os.tmpdir(), 'test/create-note')
|
||||||
|
|
||||||
test.beforeEach(t => {
|
let storageContext
|
||||||
t.context.storage = TestDummy.dummyStorage(storagePath)
|
|
||||||
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
beforeEach(() => {
|
||||||
|
storageContext = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
|
||||||
})
|
})
|
||||||
|
|
||||||
test.serial('Create a note', t => {
|
it('Create a note', done => {
|
||||||
const storageKey = t.context.storage.cache.key
|
const storageKey = storageContext.cache.key
|
||||||
const folderKey = t.context.storage.json.folders[0].key
|
const folderKey = storageContext.json.folders[0].key
|
||||||
|
|
||||||
const randLinesHighlightedArray = new Array(10)
|
const randLinesHighlightedArray = new Array(10)
|
||||||
.fill()
|
.fill()
|
||||||
@@ -58,58 +59,58 @@ test.serial('Create a note', t => {
|
|||||||
input2.title = input2.content.split('\n').shift()
|
input2.title = input2.content.split('\n').shift()
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(function doTest() {
|
.then(() => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
createNote(storageKey, input1),
|
createNote(storageKey, input1),
|
||||||
createNote(storageKey, input2)
|
createNote(storageKey, input2)
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
.then(function assert(data) {
|
.then(data => {
|
||||||
const data1 = data[0]
|
const data1 = data[0]
|
||||||
const data2 = data[1]
|
const data2 = data[1]
|
||||||
|
|
||||||
t.is(storageKey, data1.storage)
|
expect(storageKey).toEqual(data1.storage)
|
||||||
const jsonData1 = CSON.readFileSync(
|
const jsonData1 = CSON.readFileSync(
|
||||||
path.join(storagePath, 'notes', data1.key + '.cson')
|
path.join(storagePath, 'notes', data1.key + '.cson')
|
||||||
)
|
)
|
||||||
|
|
||||||
t.is(input1.title, data1.title)
|
expect(input1.title).toEqual(data1.title)
|
||||||
t.is(input1.title, jsonData1.title)
|
expect(input1.title).toEqual(jsonData1.title)
|
||||||
t.is(input1.description, data1.description)
|
expect(input1.description).toEqual(data1.description)
|
||||||
t.is(input1.description, jsonData1.description)
|
expect(input1.description).toEqual(jsonData1.description)
|
||||||
t.is(input1.tags.length, data1.tags.length)
|
expect(input1.tags.length).toEqual(data1.tags.length)
|
||||||
t.is(input1.tags.length, jsonData1.tags.length)
|
expect(input1.tags.length).toEqual(jsonData1.tags.length)
|
||||||
t.is(input1.snippets.length, data1.snippets.length)
|
expect(input1.snippets.length).toEqual(data1.snippets.length)
|
||||||
t.is(input1.snippets.length, jsonData1.snippets.length)
|
expect(input1.snippets.length).toEqual(jsonData1.snippets.length)
|
||||||
t.is(input1.snippets[0].content, data1.snippets[0].content)
|
expect(input1.snippets[0].content).toEqual(data1.snippets[0].content)
|
||||||
t.is(input1.snippets[0].content, jsonData1.snippets[0].content)
|
expect(input1.snippets[0].content).toEqual(jsonData1.snippets[0].content)
|
||||||
t.is(input1.snippets[0].name, data1.snippets[0].name)
|
expect(input1.snippets[0].name).toEqual(data1.snippets[0].name)
|
||||||
t.is(input1.snippets[0].name, jsonData1.snippets[0].name)
|
expect(input1.snippets[0].name).toEqual(jsonData1.snippets[0].name)
|
||||||
t.deepEqual(
|
expect(input1.snippets[0].linesHighlighted).toEqual(
|
||||||
input1.snippets[0].linesHighlighted,
|
|
||||||
data1.snippets[0].linesHighlighted
|
data1.snippets[0].linesHighlighted
|
||||||
)
|
)
|
||||||
t.deepEqual(
|
expect(input1.snippets[0].linesHighlighted).toEqual(
|
||||||
input1.snippets[0].linesHighlighted,
|
|
||||||
jsonData1.snippets[0].linesHighlighted
|
jsonData1.snippets[0].linesHighlighted
|
||||||
)
|
)
|
||||||
|
|
||||||
t.is(storageKey, data2.storage)
|
expect(storageKey).toEqual(data2.storage)
|
||||||
const jsonData2 = CSON.readFileSync(
|
const jsonData2 = CSON.readFileSync(
|
||||||
path.join(storagePath, 'notes', data2.key + '.cson')
|
path.join(storagePath, 'notes', data2.key + '.cson')
|
||||||
)
|
)
|
||||||
t.is(input2.title, data2.title)
|
expect(input2.title).toEqual(data2.title)
|
||||||
t.is(input2.title, jsonData2.title)
|
expect(input2.title).toEqual(jsonData2.title)
|
||||||
t.is(input2.content, data2.content)
|
expect(input2.content).toEqual(data2.content)
|
||||||
t.is(input2.content, jsonData2.content)
|
expect(input2.content).toEqual(jsonData2.content)
|
||||||
t.is(input2.tags.length, data2.tags.length)
|
expect(input2.tags.length).toEqual(data2.tags.length)
|
||||||
t.is(input2.tags.length, jsonData2.tags.length)
|
expect(input2.tags.length).toEqual(jsonData2.tags.length)
|
||||||
t.deepEqual(input2.linesHighlighted, data2.linesHighlighted)
|
expect(input2.linesHighlighted).toEqual(data2.linesHighlighted)
|
||||||
t.deepEqual(input2.linesHighlighted, jsonData2.linesHighlighted)
|
expect(input2.linesHighlighted).toEqual(jsonData2.linesHighlighted)
|
||||||
|
|
||||||
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after(function after() {
|
afterAll(function after() {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
sander.rimrafSync(storagePath)
|
sander.rimrafSync(storagePath)
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const test = require('ava')
|
|
||||||
const createNoteFromUrl = require('browser/main/lib/dataApi/createNoteFromUrl')
|
const createNoteFromUrl = require('browser/main/lib/dataApi/createNoteFromUrl')
|
||||||
|
|
||||||
global.document = require('jsdom').jsdom('<body></body>')
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
@@ -18,32 +17,34 @@ const CSON = require('@rokt33r/season')
|
|||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/create-note-from-url')
|
const storagePath = path.join(os.tmpdir(), 'test/create-note-from-url')
|
||||||
|
|
||||||
test.beforeEach(t => {
|
let storageContext
|
||||||
t.context.storage = TestDummy.dummyStorage(storagePath)
|
|
||||||
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
beforeEach(() => {
|
||||||
|
storageContext = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
|
||||||
})
|
})
|
||||||
|
|
||||||
test.serial('Create a note from URL', t => {
|
it('Create a note from URL', () => {
|
||||||
const storageKey = t.context.storage.cache.key
|
const storageKey = storageContext.cache.key
|
||||||
const folderKey = t.context.storage.json.folders[0].key
|
const folderKey = storageContext.json.folders[0].key
|
||||||
|
|
||||||
const url = 'https://shapeshed.com/writing-cross-platform-node/'
|
const url = 'https://shapeshed.com/writing-cross-platform-node/'
|
||||||
|
|
||||||
return createNoteFromUrl(url, storageKey, folderKey).then(function assert({
|
return createNoteFromUrl(url, storageKey, folderKey).then(function assert({
|
||||||
note
|
note
|
||||||
}) {
|
}) {
|
||||||
t.is(storageKey, note.storage)
|
expect(storageKey).toEqual(note.storage)
|
||||||
const jsonData = CSON.readFileSync(
|
const jsonData = CSON.readFileSync(
|
||||||
path.join(storagePath, 'notes', note.key + '.cson')
|
path.join(storagePath, 'notes', note.key + '.cson')
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test if saved content is matching the created in memory note
|
// Test if saved content is matching the created in memory note
|
||||||
t.is(note.content, jsonData.content)
|
expect(note.content).toEqual(jsonData.content)
|
||||||
t.is(note.tags.length, jsonData.tags.length)
|
expect(note.tags.length).toEqual(jsonData.tags.length)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after(function after() {
|
afterAll(function after() {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
sander.rimrafSync(storagePath)
|
sander.rimrafSync(storagePath)
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const test = require('ava')
|
|
||||||
const createSnippet = require('browser/main/lib/dataApi/createSnippet')
|
const createSnippet = require('browser/main/lib/dataApi/createSnippet')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
@@ -7,29 +6,27 @@ const path = require('path')
|
|||||||
const snippetFilePath = path.join(os.tmpdir(), 'test', 'create-snippet')
|
const snippetFilePath = path.join(os.tmpdir(), 'test', 'create-snippet')
|
||||||
const snippetFile = path.join(snippetFilePath, 'snippets.json')
|
const snippetFile = path.join(snippetFilePath, 'snippets.json')
|
||||||
|
|
||||||
test.beforeEach(t => {
|
beforeEach(() => {
|
||||||
sander.writeFileSync(snippetFile, '[]')
|
sander.writeFileSync(snippetFile, '[]')
|
||||||
})
|
})
|
||||||
|
|
||||||
test.serial('Create a snippet', t => {
|
it('Create a snippet', () => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(function doTest() {
|
.then(() => Promise.all([createSnippet(snippetFile)]))
|
||||||
return Promise.all([createSnippet(snippetFile)])
|
|
||||||
})
|
|
||||||
.then(function assert(data) {
|
.then(function assert(data) {
|
||||||
data = data[0]
|
data = data[0]
|
||||||
const snippets = JSON.parse(sander.readFileSync(snippetFile))
|
const snippets = JSON.parse(sander.readFileSync(snippetFile))
|
||||||
const snippet = snippets.find(
|
const snippet = snippets.find(
|
||||||
currentSnippet => currentSnippet.id === data.id
|
currentSnippet => currentSnippet.id === data.id
|
||||||
)
|
)
|
||||||
t.not(snippet, undefined)
|
expect(snippet).not.toBeUndefined()
|
||||||
t.is(snippet.name, data.name)
|
expect(snippet.name).toEqual(data.name)
|
||||||
t.deepEqual(snippet.prefix, data.prefix)
|
expect(snippet.prefix).toEqual(data.prefix)
|
||||||
t.is(snippet.content, data.content)
|
expect(snippet.content).toEqual(data.content)
|
||||||
t.deepEqual(snippet.linesHighlighted, data.linesHighlighted)
|
expect(snippet.linesHighlighted).toEqual(data.linesHighlighted)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after.always(() => {
|
afterAll(() => {
|
||||||
sander.rimrafSync(snippetFilePath)
|
sander.rimrafSync(snippetFilePath)
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const test = require('ava')
|
|
||||||
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
|
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
|
||||||
const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement')
|
const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement')
|
||||||
const createNote = require('browser/main/lib/dataApi/createNote')
|
const createNote = require('browser/main/lib/dataApi/createNote')
|
||||||
@@ -23,14 +22,16 @@ const CSON = require('@rokt33r/season')
|
|||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/delete-folder')
|
const storagePath = path.join(os.tmpdir(), 'test/delete-folder')
|
||||||
|
|
||||||
test.beforeEach(t => {
|
let storageContext
|
||||||
t.context.storage = TestDummy.dummyStorage(storagePath)
|
|
||||||
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
beforeEach(() => {
|
||||||
|
storageContext = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
|
||||||
})
|
})
|
||||||
|
|
||||||
test.serial('Delete a folder', t => {
|
it('Delete a folder', () => {
|
||||||
const storageKey = t.context.storage.cache.key
|
const storageKey = storageContext.cache.key
|
||||||
const folderKey = t.context.storage.json.folders[0].key
|
const folderKey = storageContext.json.folders[0].key
|
||||||
let noteKey
|
let noteKey
|
||||||
|
|
||||||
const input1 = {
|
const input1 = {
|
||||||
@@ -72,16 +73,15 @@ test.serial('Delete a folder', t => {
|
|||||||
return deleteFolder(storageKey, folderKey)
|
return deleteFolder(storageKey, folderKey)
|
||||||
})
|
})
|
||||||
.then(function assert(data) {
|
.then(function assert(data) {
|
||||||
t.true(_.find(data.storage.folders, { key: folderKey }) == null)
|
expect(_.find(data.storage.folders, { key: folderKey })).toBeUndefined()
|
||||||
const jsonData = CSON.readFileSync(
|
const jsonData = CSON.readFileSync(
|
||||||
path.join(data.storage.path, 'boostnote.json')
|
path.join(data.storage.path, 'boostnote.json')
|
||||||
)
|
)
|
||||||
|
|
||||||
t.true(_.find(jsonData.folders, { key: folderKey }) == null)
|
expect(_.find(jsonData.folders, { key: folderKey })).toBeUndefined()
|
||||||
const notePaths = sander.readdirSync(data.storage.path, 'notes')
|
const notePaths = sander.readdirSync(data.storage.path, 'notes')
|
||||||
t.is(
|
expect(notePaths.length).toBe(
|
||||||
notePaths.length,
|
storageContext.notes.filter(note => note.folder !== folderKey).length
|
||||||
t.context.storage.notes.filter(note => note.folder !== folderKey).length
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const attachmentFolderPath = path.join(
|
const attachmentFolderPath = path.join(
|
||||||
@@ -89,11 +89,11 @@ test.serial('Delete a folder', t => {
|
|||||||
attachmentManagement.DESTINATION_FOLDER,
|
attachmentManagement.DESTINATION_FOLDER,
|
||||||
noteKey
|
noteKey
|
||||||
)
|
)
|
||||||
t.false(fs.existsSync(attachmentFolderPath))
|
expect(fs.existsSync(attachmentFolderPath)).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after.always(function after() {
|
afterAll(() => {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
sander.rimrafSync(storagePath)
|
sander.rimrafSync(storagePath)
|
||||||
})
|
})
|
||||||
@@ -52,12 +52,20 @@ test.serial('Export a folder', t => {
|
|||||||
}
|
}
|
||||||
input2.title = 'input2'
|
input2.title = 'input2'
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
export: {
|
||||||
|
metadata: 'DONT_EXPORT',
|
||||||
|
variable: 'boostnote',
|
||||||
|
prefixAttachmentFolder: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return createNote(storageKey, input1)
|
return createNote(storageKey, input1)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return createNote(storageKey, input2)
|
return createNote(storageKey, input2)
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return exportFolder(storageKey, folderKey, 'md', storagePath)
|
return exportFolder(storageKey, folderKey, 'md', storagePath, config)
|
||||||
})
|
})
|
||||||
.then(function assert() {
|
.then(function assert() {
|
||||||
let filePath = path.join(storagePath, 'input1.md')
|
let filePath = path.join(storagePath, 'input1.md')
|
||||||
|
|||||||
@@ -35,7 +35,16 @@ test.serial('Export a storage', t => {
|
|||||||
acc[folder.key] = folder.name
|
acc[folder.key] = folder.name
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
return exportStorage(storageKey, 'md', exportDir).then(() => {
|
|
||||||
|
const config = {
|
||||||
|
export: {
|
||||||
|
metadata: 'DONT_EXPORT',
|
||||||
|
variable: 'boostnote',
|
||||||
|
prefixAttachmentFolder: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportStorage(storageKey, 'md', exportDir, config).then(() => {
|
||||||
notes.forEach(note => {
|
notes.forEach(note => {
|
||||||
const noteDir = path.join(
|
const noteDir = path.join(
|
||||||
exportDir,
|
exportDir,
|
||||||
|
|||||||
189
tests/lib/__snapshots__/markdown.test.js.snap
Normal file
189
tests/lib/__snapshots__/markdown.test.js.snap
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render PlantUML Ditaa correctly 1`] = `
|
||||||
|
"<img src=\\"http://www.plantuml.com/plantuml/png/SoWkIImgISaiIKpaqjQ50cq51GLj93Q2mrMZ00NQO3cmHX3RJW4cKmDI4v9QKQ805a8nfyObCp6zA34NgCObFxiqDpMl1AIcHj4tCJqpLH5i18evG52TKbk3B8og1kmC0cvMKB1Im0NYkA2ckMRcANWabgQbvYau5YMbPfP0p4UOWmcqkHnIyrB0GG00\\" alt=\\"uml diagram\\" />
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render PlantUML Gantt correctly 1`] = `
|
||||||
|
"<img src=\\"http://www.plantuml.com/plantuml/svg/SoWkIImgIK_CAodXYWueoY_9BwaiI5L8IItEJC-BLSX9B2ufLZ0qLKX9h2pcYWv9BIvHA82fWaiRu906crsia5YYW6cqUh52QbuAbmEG0DiE0000\\" alt=\\"uml diagram\\" />
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render PlantUML MindMaps correctly 1`] = `
|
||||||
|
"<img src=\\"http://www.plantuml.com/plantuml/svg/JOzD3e8m44Rtd6BMtNW192IM5I29HEDsAbKdeLD2MvNRIsjCMCsRlFd9LpgFipV4Wy4f4o2r8kHC23Yhm3wi9A0X3XzeYNrgwx1H6wvb1KTjqtRJoYhMtexBSAqJUescwoEUq4tn3xp9Fm7XfUS5HiiFO3Gw7SjT4QUCkkKxLy2-WAvl3rkrtEclBdOCXcnMwZN7ByiN\\" alt=\\"uml diagram\\" />
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render PlantUML Umls correctly 1`] = `
|
||||||
|
"<img src=\\"http://www.plantuml.com/plantuml/svg/LOzD2eCm44RtESMtj0jx01V5E_G4Gvngo2_912gbTsz4LBfylCV7p5Y4ibJlbEENG2AocHV1P39hCJ6eOar8bCaZaROqyrDMnzWqXTcn8YqnGzSYqNC-q76sweoW5zOsLi57uMpHz-WESslY0jmVw1AjdaE30IPeLoVUceLTslrL3-2tS9ZA_qZRtm_vgh7PzkOF\\" alt=\\"uml diagram\\" />
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render PlantUML WBS correctly 1`] = `
|
||||||
|
"<img src=\\"http://www.plantuml.com/plantuml/svg/ZP2_JiD03CRtFeNdRF04fR140gdGeREv-z8plVYYimFYxSabKbaxsR9-ylTdRyxLVpvjrz5XDb6OqR6MqEPRYSXPz4BdmsdNTVJAiuP4da1JBLy8lbmxUYxZbE6Wa_CLgUI8IXymS0rf9NeL5yxKDt24EhiKfMDcRNzVO79HcX8RLdvLfZBGa_KtFx2RKcpK7TZ3dTpZfWgskMAZ9jIXr94rW4PubM1RbBZOb-6NtcS9LpgBjlj_1w9QldbPjZHxQ5pg_GC0\\" alt=\\"uml diagram\\" />
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render footnote correctly 1`] = `
|
||||||
|
"<p data-line=\\"1\\"><sup class=\\"footnote-ref\\"><a href=\\"#fn1\\" id=\\"fnref1\\">[1]</a></sup><br />
|
||||||
|
hello-world: <a href=\\"https://github.com/BoostIO/Boostnote/\\">https://github.com/BoostIO/Boostnote/</a></p>
|
||||||
|
<hr class=\\"footnotes-sep\\" />
|
||||||
|
<section class=\\"footnotes\\">
|
||||||
|
<ol class=\\"footnotes-list\\">
|
||||||
|
<li id=\\"fn1\\" class=\\"footnote-item\\"><p>hello-world <a href=\\"#fnref1\\" class=\\"footnote-backref\\">↩︎</a></p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render line breaks correctly 1`] = `
|
||||||
|
"<p data-line=\\"0\\">This is the first line.<br />
|
||||||
|
This is the second line.</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render line breaks correctly 2`] = `
|
||||||
|
"<p data-line=\\"0\\">This is the first line.
|
||||||
|
This is the second line.</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should render shortcuts correctly 1`] = `
|
||||||
|
"<p data-line=\\"0\\"><kbd>Ctrl</kbd></p>
|
||||||
|
<p data-line=\\"2\\"><kbd>Ctrl</kbd></p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders [TOC] placholder correctly 1`] = `
|
||||||
|
"<p data-line=\\"1\\"><div class=\\"markdownIt-TOC-wrapper\\"><ul class=\\"markdownIt-TOC\\">
|
||||||
|
<li><a href=\\"#H1\\">H1</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href=\\"#H2\\">H2</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href=\\"#H3\\">H3</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div></p>
|
||||||
|
<h1 id=\\"H1\\" data-line=\\"2\\">H1</h1>
|
||||||
|
<h2 id=\\"H2\\" data-line=\\"3\\">H2</h2>
|
||||||
|
<h3 id=\\"H3\\" data-line=\\"4\\">H3</h3>
|
||||||
|
<p data-line=\\"5\\">###$ H4</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders KaTeX correctly 1`] = `
|
||||||
|
"<span class=\\"katex-display\\"><span class=\\"katex\\"><span class=\\"katex-mathml\\"><math><semantics><mrow><mi>c</mi><mo>=</mo><mi>p</mi><mi>m</mi><mi>s</mi><mi>q</mi><mi>r</mi><mi>t</mi><mrow><msup><mi>a</mi><mn>2</mn></msup><mo>+</mo><msup><mi>b</mi><mn>2</mn></msup></mrow></mrow><annotation encoding=\\"application/x-tex\\">c = pmsqrt{a^2 + b^2}</annotation></semantics></math></span><span class=\\"katex-html\\" aria-hidden=\\"true\\"><span class=\\"base\\"><span class=\\"strut\\" style=\\"height:0.43056em;vertical-align:0em;\\"></span><span class=\\"mord mathdefault\\">c</span><span class=\\"mspace\\" style=\\"margin-right:0.2777777777777778em;\\"></span><span class=\\"mrel\\">=</span><span class=\\"mspace\\" style=\\"margin-right:0.2777777777777778em;\\"></span></span><span class=\\"base\\"><span class=\\"strut\\" style=\\"height:1.0585479999999998em;vertical-align:-0.19444em;\\"></span><span class=\\"mord mathdefault\\">p</span><span class=\\"mord mathdefault\\">m</span><span class=\\"mord mathdefault\\">s</span><span class=\\"mord mathdefault\\" style=\\"margin-right:0.03588em;\\">q</span><span class=\\"mord mathdefault\\" style=\\"margin-right:0.02778em;\\">r</span><span class=\\"mord mathdefault\\">t</span><span class=\\"mord\\"><span class=\\"mord\\"><span class=\\"mord mathdefault\\">a</span><span class=\\"msupsub\\"><span class=\\"vlist-t\\"><span class=\\"vlist-r\\"><span class=\\"vlist\\" style=\\"height:0.8641079999999999em;\\"><span style=\\"top:-3.113em;margin-right:0.05em;\\"><span class=\\"pstrut\\" style=\\"height:2.7em;\\"></span><span class=\\"sizing reset-size6 size3 mtight\\"><span class=\\"mord mtight\\">2</span></span></span></span></span></span></span></span><span class=\\"mspace\\" style=\\"margin-right:0.2222222222222222em;\\"></span><span class=\\"mbin\\">+</span><span class=\\"mspace\\" style=\\"margin-right:0.2222222222222222em;\\"></span><span class=\\"mord\\"><span class=\\"mord mathdefault\\">b</span><span class=\\"msupsub\\"><span class=\\"vlist-t\\"><span class=\\"vlist-r\\"><span class=\\"vlist\\" style=\\"height:0.8641079999999999em;\\"><span style=\\"top:-3.113em;margin-right:0.05em;\\"><span class=\\"pstrut\\" style=\\"height:2.7em;\\"></span><span class=\\"sizing reset-size6 size3 mtight\\"><span class=\\"mord mtight\\">2</span></span></span></span></span></span></span></span></span></span></span></span></span>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders abbrevations correctly 1`] = `
|
||||||
|
"<h2 id=\\"abbr\\" data-line=\\"1\\">abbr</h2>
|
||||||
|
<p data-line=\\"3\\">The <abbr title=\\"Hyper Text Markup Language\\">HTML</abbr> specification<br />
|
||||||
|
is maintained by the <abbr title=\\"World Wide Web Consortium\\">W3C</abbr>.</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders checkboxes 1`] = `
|
||||||
|
"<ul>
|
||||||
|
<li class=\\"taskListItem\\" data-line=\\"1\\"><input type=\\"checkbox\\" id=\\"checkbox-2\\" /> Unchecked</li>
|
||||||
|
<li class=\\"taskListItem checked\\" data-line=\\"2\\"><input type=\\"checkbox\\" checked id=\\"checkbox-3\\" /> Checked</li>
|
||||||
|
</ul>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders codeblock correctly 1`] = `
|
||||||
|
"<pre class=\\"code CodeMirror\\" data-line=\\"1\\">
|
||||||
|
<span class=\\"filename\\">filename.js</span>
|
||||||
|
<span class=\\"lineNumber CodeMirror-gutters\\"><span class=\\"CodeMirror-linenumber\\">2</span></span>
|
||||||
|
<code class=\\"js\\">var project = 'boostnote';
|
||||||
|
</code>
|
||||||
|
</pre>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders definition lists correctly 1`] = `
|
||||||
|
"<h2 id=\\"definition-list\\" data-line=\\"1\\">definition list</h2>
|
||||||
|
<h3 id=\\"list-1\\" data-line=\\"3\\">list 1</h3>
|
||||||
|
<dl>
|
||||||
|
<dt data-line=\\"5\\">Term 1</dt>
|
||||||
|
<dd data-line=\\"6\\">Definition 1</dd>
|
||||||
|
<dt data-line=\\"8\\">Term 2</dt>
|
||||||
|
<dd data-line=\\"9\\">Definition 2a</dd>
|
||||||
|
<dd data-line=\\"10\\">Definition 2b</dd>
|
||||||
|
</dl>
|
||||||
|
<p data-line=\\"12\\">Term 3<br />
|
||||||
|
~</p>
|
||||||
|
<h3 id=\\"list-2\\" data-line=\\"16\\">list 2</h3>
|
||||||
|
<dl>
|
||||||
|
<dt data-line=\\"18\\">Term 1</dt>
|
||||||
|
<dd data-line=\\"20\\">
|
||||||
|
<p data-line=\\"20\\">Definition 1</p>
|
||||||
|
</dd>
|
||||||
|
<dt data-line=\\"22\\">Term 2 with <em>inline markup</em></dt>
|
||||||
|
<dd data-line=\\"24\\">
|
||||||
|
<p data-line=\\"24\\">Definition 2</p>
|
||||||
|
<pre><code> { some code, part of Definition 2 }
|
||||||
|
</code></pre>
|
||||||
|
<p data-line=\\"28\\">Third paragraph of definition 2.</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders markdown correctly 1`] = `
|
||||||
|
"<h1 id=\\"Welcome-to-Boostnote\\" data-line=\\"1\\">Welcome to Boostnote!</h1>
|
||||||
|
<h2 id=\\"Click-here-to-edit-markdown\\" data-line=\\"2\\">Click here to edit markdown 👋</h2>
|
||||||
|
<iframe width=\\"560\\" height=\\"315\\" src=\\"https://www.youtube.com/embed/L0qNPLsvmyM\\" frameborder=\\"0\\" allowfullscreen></iframe>
|
||||||
|
<h2 id=\\"Docs\\" data-line=\\"6\\">Docs 📝</h2>
|
||||||
|
<ul>
|
||||||
|
<li data-line=\\"7\\"><a href=\\"https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe\\">Boostnote | Boost your happiness, productivity and creativity.</a></li>
|
||||||
|
<li data-line=\\"8\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup\\">Cloud Syncing & Backups</a></li>
|
||||||
|
<li data-line=\\"9\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps\\">How to sync your data across Desktop and Mobile apps</a></li>
|
||||||
|
<li data-line=\\"10\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Evernote\\">Convert data from <strong>Evernote</strong> to Boostnote.</a></li>
|
||||||
|
<li data-line=\\"11\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts\\">Keyboard Shortcuts</a></li>
|
||||||
|
<li data-line=\\"12\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode\\">Keymaps in Editor mode</a></li>
|
||||||
|
<li data-line=\\"13\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting\\">How to set syntax highlight in Snippet note</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<h2 id=\\"Article-Archive\\" data-line=\\"17\\">Article Archive 📚</h2>
|
||||||
|
<ul>
|
||||||
|
<li data-line=\\"18\\"><a href=\\"http://bit.ly/2mOJPu7\\">Reddit English</a></li>
|
||||||
|
<li data-line=\\"19\\"><a href=\\"https://www.reddit.com/r/boostnote_es/\\">Reddit Spanish</a></li>
|
||||||
|
<li data-line=\\"20\\"><a href=\\"https://www.reddit.com/r/boostnote_cn/\\">Reddit Chinese</a></li>
|
||||||
|
<li data-line=\\"21\\"><a href=\\"https://www.reddit.com/r/boostnote_jp/\\">Reddit Japanese</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<h2 id=\\"Community\\" data-line=\\"25\\">Community 🍻</h2>
|
||||||
|
<ul>
|
||||||
|
<li data-line=\\"26\\"><a href=\\"http://bit.ly/2AWWzkD\\">GitHub</a></li>
|
||||||
|
<li data-line=\\"27\\"><a href=\\"http://bit.ly/2z8BUJZ\\">Twitter</a></li>
|
||||||
|
<li data-line=\\"28\\"><a href=\\"http://bit.ly/2jcca8t\\">Facebook Group</a></li>
|
||||||
|
</ul>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders sub correctly 1`] = `
|
||||||
|
"<h2 id=\\"sub\\" data-line=\\"1\\">sub</h2>
|
||||||
|
<p data-line=\\"3\\">H<sub>2</sub>0</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should renders sup correctly 1`] = `
|
||||||
|
"<h2 id=\\"sup\\" data-line=\\"1\\">sup</h2>
|
||||||
|
<p data-line=\\"3\\">29<sup>th</sup></p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should text with quotes correctly 1`] = `
|
||||||
|
"<p data-line=\\"0\\">This is a “QUOTE”.</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Markdown.render() should text with quotes correctly 2`] = `
|
||||||
|
"<p data-line=\\"0\\">This is a "QUOTE".</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
@@ -1,46 +1,45 @@
|
|||||||
const { escapeHtmlCharacters } = require('browser/lib/utils')
|
const { escapeHtmlCharacters } = require('browser/lib/utils')
|
||||||
const test = require('ava')
|
|
||||||
|
|
||||||
test('escapeHtmlCharacters should return the original string if nothing needed to escape', t => {
|
test('escapeHtmlCharacters should return the original string if nothing needed to escape', () => {
|
||||||
const input = 'Nothing to be escaped'
|
const input = 'Nothing to be escaped'
|
||||||
const expected = 'Nothing to be escaped'
|
const expected = 'Nothing to be escaped'
|
||||||
const actual = escapeHtmlCharacters(input)
|
const actual = escapeHtmlCharacters(input)
|
||||||
t.is(actual, expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('escapeHtmlCharacters should skip code block if that option is enabled', t => {
|
test('escapeHtmlCharacters should skip code block if that option is enabled', () => {
|
||||||
const input = ` <no escape>
|
const input = ` <no escape>
|
||||||
<escapeMe>`
|
<escapeMe>`
|
||||||
const expected = ` <no escape>
|
const expected = ` <no escape>
|
||||||
<escapeMe>`
|
<escapeMe>`
|
||||||
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
||||||
t.is(actual, expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', t => {
|
test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', () => {
|
||||||
const input = '4 spaces &'
|
const input = '4 spaces &'
|
||||||
const expected = '4 spaces &'
|
const expected = '4 spaces &'
|
||||||
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
||||||
t.is(actual, expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', t => {
|
test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', () => {
|
||||||
const input = ` <no escape>
|
const input = ` <no escape>
|
||||||
<escapeMe>`
|
<escapeMe>`
|
||||||
const expected = ` <no escape>
|
const expected = ` <no escape>
|
||||||
<escapeMe>`
|
<escapeMe>`
|
||||||
const actual = escapeHtmlCharacters(input)
|
const actual = escapeHtmlCharacters(input)
|
||||||
t.is(actual, expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("escapeHtmlCharacters should NOT escape & character if it's a part of an escaped character", t => {
|
test("escapeHtmlCharacters should NOT escape & character if it's a part of an escaped character", () => {
|
||||||
const input = 'Do not escape & or " but do escape &'
|
const input = 'Do not escape & or " but do escape &'
|
||||||
const expected = 'Do not escape & or " but do escape &'
|
const expected = 'Do not escape & or " but do escape &'
|
||||||
const actual = escapeHtmlCharacters(input)
|
const actual = escapeHtmlCharacters(input)
|
||||||
t.is(actual, expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('escapeHtmlCharacters should skip char if in code block', t => {
|
test('escapeHtmlCharacters should skip char if in code block', () => {
|
||||||
const input = `
|
const input = `
|
||||||
\`\`\`
|
\`\`\`
|
||||||
<dontescapeme>
|
<dontescapeme>
|
||||||
@@ -62,12 +61,12 @@ dasdasdasd
|
|||||||
\`\`\`
|
\`\`\`
|
||||||
`
|
`
|
||||||
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
||||||
t.is(actual, expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('escapeHtmlCharacters should return the correct result', t => {
|
test('escapeHtmlCharacters should return the correct result', () => {
|
||||||
const input = '& < > " \''
|
const input = '& < > " \''
|
||||||
const expected = '& < > " ''
|
const expected = '& < > " ''
|
||||||
const actual = escapeHtmlCharacters(input)
|
const actual = escapeHtmlCharacters(input)
|
||||||
t.is(actual, expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const test = require('ava')
|
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
global.document = require('jsdom').jsdom('<body></body>')
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
@@ -16,20 +15,22 @@ const sander = require('sander')
|
|||||||
const os = require('os')
|
const os = require('os')
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/find-storage')
|
const storagePath = path.join(os.tmpdir(), 'test/find-storage')
|
||||||
|
|
||||||
test.beforeEach(t => {
|
let storageContext
|
||||||
t.context.storage = TestDummy.dummyStorage(storagePath)
|
|
||||||
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
beforeEach(() => {
|
||||||
|
storageContext = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Unit test
|
// Unit test
|
||||||
test('findStorage() should return a correct storage path(string)', t => {
|
test('findStorage() should return a correct storage path(string)', () => {
|
||||||
const storageKey = t.context.storage.cache.key
|
const storageKey = storageContext.cache.key
|
||||||
|
|
||||||
t.is(findStorage(storageKey).key, storageKey)
|
expect(findStorage(storageKey).key).toBe(storageKey)
|
||||||
t.is(findStorage(storageKey).path, storagePath)
|
expect(findStorage(storageKey).path).toBe(storagePath)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after(function after() {
|
afterAll(function after() {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
sander.rimrafSync(storagePath)
|
sander.rimrafSync(storagePath)
|
||||||
})
|
})
|
||||||
@@ -2,11 +2,10 @@
|
|||||||
* @fileoverview Unit test for browser/lib/findTitle
|
* @fileoverview Unit test for browser/lib/findTitle
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const test = require('ava')
|
|
||||||
const { findNoteTitle } = require('browser/lib/findNoteTitle')
|
const { findNoteTitle } = require('browser/lib/findNoteTitle')
|
||||||
|
|
||||||
// Unit test
|
// Unit test
|
||||||
test('findNoteTitle#find should return a correct title (string)', t => {
|
test('findNoteTitle#find should return a correct title (string)', () => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
['# hoge\nfuga', '# hoge'],
|
['# hoge\nfuga', '# hoge'],
|
||||||
@@ -20,15 +19,11 @@ test('findNoteTitle#find should return a correct title (string)', t => {
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(findNoteTitle(input, false)).toBe(expected)
|
||||||
findNoteTitle(input, false),
|
|
||||||
expected,
|
|
||||||
`Test for find() input: ${input} expected: ${expected}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('findNoteTitle#find should ignore front matter when enableFrontMatterTitle=false', t => {
|
test('findNoteTitle#find should ignore front matter when enableFrontMatterTitle=false', () => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
['---\nlayout: test\ntitle: hoge hoge hoge \n---\n# fuga', '# fuga'],
|
['---\nlayout: test\ntitle: hoge hoge hoge \n---\n# fuga', '# fuga'],
|
||||||
@@ -38,15 +33,11 @@ test('findNoteTitle#find should ignore front matter when enableFrontMatterTitle
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(findNoteTitle(input, false)).toBe(expected)
|
||||||
findNoteTitle(input, false),
|
|
||||||
expected,
|
|
||||||
`Test for find() input: ${input} expected: ${expected}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('findNoteTitle#find should respect front matter when enableFrontMatterTitle=true', t => {
|
test('findNoteTitle#find should respect front matter when enableFrontMatterTitle=true', () => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
[
|
[
|
||||||
@@ -59,15 +50,11 @@ test('findNoteTitle#find should respect front matter when enableFrontMatterTitl
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(findNoteTitle(input, true)).toBe(expected)
|
||||||
findNoteTitle(input, true),
|
|
||||||
expected,
|
|
||||||
`Test for find() input: ${input} expected: ${expected}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('findNoteTitle#find should respect frontMatterTitleField when provided', t => {
|
test('findNoteTitle#find should respect frontMatterTitleField when provided', () => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
['---\ntitle: hoge\n---\n# fuga', '# fuga'],
|
['---\ntitle: hoge\n---\n# fuga', '# fuga'],
|
||||||
@@ -76,10 +63,6 @@ test('findNoteTitle#find should respect frontMatterTitleField when provided', t
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(findNoteTitle(input, true, 'custom')).toBe(expected)
|
||||||
findNoteTitle(input, true, 'custom'),
|
|
||||||
expected,
|
|
||||||
`Test for find() input: ${input} expected: ${expected}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
const test = require('ava')
|
|
||||||
const { getTodoStatus } = require('browser/lib/getTodoStatus')
|
const { getTodoStatus } = require('browser/lib/getTodoStatus')
|
||||||
|
|
||||||
// Unit test
|
// Unit test
|
||||||
test('getTodoStatus should return a correct hash object', t => {
|
test('getTodoStatus should return a correct hash object', () => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
['', { total: 0, completed: 0 }],
|
['', { total: 0, completed: 0 }],
|
||||||
@@ -40,15 +39,7 @@ test('getTodoStatus should return a correct hash object', t => {
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(getTodoStatus(input).total).toBe(expected.total)
|
||||||
getTodoStatus(input).total,
|
expect(getTodoStatus(input).completed).toBe(expected.completed)
|
||||||
expected.total,
|
|
||||||
`Test for getTodoStatus() input: ${input} expected: ${expected.total}`
|
|
||||||
)
|
|
||||||
t.is(
|
|
||||||
getTodoStatus(input).completed,
|
|
||||||
expected.completed,
|
|
||||||
`Test for getTodoStatus() input: ${input} expected: ${expected.completed}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Unit test for browser/lib/htmlTextHelper
|
* @fileoverview Unit test for browser/lib/htmlTextHelper
|
||||||
*/
|
*/
|
||||||
const test = require('ava')
|
|
||||||
const htmlTextHelper = require('browser/lib/htmlTextHelper')
|
const htmlTextHelper = require('browser/lib/htmlTextHelper')
|
||||||
|
|
||||||
// Unit test
|
// Unit test
|
||||||
test('htmlTextHelper#decodeEntities should return encoded text (string)', t => {
|
test('htmlTextHelper#decodeEntities should return encoded text (string)', () => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
['<a href=', '<a href='],
|
['<a href=', '<a href='],
|
||||||
@@ -21,15 +20,11 @@ test('htmlTextHelper#decodeEntities should return encoded text (string)', t => {
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(htmlTextHelper.decodeEntities(input)).toBe(expected)
|
||||||
htmlTextHelper.decodeEntities(input),
|
|
||||||
expected,
|
|
||||||
`Test for decodeEntities() input: ${input} expected: ${expected}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('htmlTextHelper#decodeEntities() should return decoded text (string)', t => {
|
test('htmlTextHelper#decodeEntities() should return decoded text (string)', () => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
['<a href=', '<a href='],
|
['<a href=', '<a href='],
|
||||||
@@ -44,16 +39,12 @@ test('htmlTextHelper#decodeEntities() should return decoded text (string)', t =>
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(htmlTextHelper.encodeEntities(input)).toBe(expected)
|
||||||
htmlTextHelper.encodeEntities(input),
|
|
||||||
expected,
|
|
||||||
`Test for encodeEntities() input: ${input} expected: ${expected}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Integration test
|
// Integration test
|
||||||
test(t => {
|
test(() => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
"var test = 'test'",
|
"var test = 'test'",
|
||||||
"<a href='https://boostnote.io'>Boostnote",
|
"<a href='https://boostnote.io'>Boostnote",
|
||||||
@@ -63,10 +54,6 @@ test(t => {
|
|||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const encodedText = htmlTextHelper.encodeEntities(testCase)
|
const encodedText = htmlTextHelper.encodeEntities(testCase)
|
||||||
const decodedText = htmlTextHelper.decodeEntities(encodedText)
|
const decodedText = htmlTextHelper.decodeEntities(encodedText)
|
||||||
t.is(
|
expect(decodedText).toBe(testCase)
|
||||||
decodedText,
|
|
||||||
testCase,
|
|
||||||
'Integration test through encodedText() and decodedText()'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Unit test for browser/lib/markdown
|
* @fileoverview Unit test for browser/lib/markdown
|
||||||
*/
|
*/
|
||||||
const test = require('ava')
|
|
||||||
const markdown = require('browser/lib/markdownTextHelper')
|
const markdown = require('browser/lib/markdownTextHelper')
|
||||||
|
|
||||||
test(t => {
|
test(() => {
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
// List
|
// List
|
||||||
@@ -42,10 +41,6 @@ test(t => {
|
|||||||
|
|
||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expected] = testCase
|
const [input, expected] = testCase
|
||||||
t.is(
|
expect(markdown.strip(input)).toBe(expected)
|
||||||
markdown.strip(input),
|
|
||||||
expected,
|
|
||||||
`Test for strip() input: ${input} expected: ${expected}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -4,11 +4,10 @@
|
|||||||
|
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
require('codemirror/addon/search/searchcursor.js')
|
require('codemirror/addon/search/searchcursor.js')
|
||||||
const test = require('ava')
|
|
||||||
const markdownToc = require('browser/lib/markdown-toc-generator')
|
const markdownToc = require('browser/lib/markdown-toc-generator')
|
||||||
const EOL = require('os').EOL
|
const EOL = require('os').EOL
|
||||||
|
|
||||||
test(t => {
|
test(() => {
|
||||||
/**
|
/**
|
||||||
* Contains array of test cases in format :
|
* Contains array of test cases in format :
|
||||||
* [
|
* [
|
||||||
@@ -261,15 +260,11 @@ this is a text
|
|||||||
const expectedToc = testCase[2].trim()
|
const expectedToc = testCase[2].trim()
|
||||||
const generatedToc = markdownToc.generate(inputMd)
|
const generatedToc = markdownToc.generate(inputMd)
|
||||||
|
|
||||||
t.is(
|
expect(generatedToc).toBe(expectedToc)
|
||||||
generatedToc,
|
|
||||||
expectedToc,
|
|
||||||
`generate test : ${title} , generated : ${EOL}${generatedToc}, expected : ${EOL}${expectedToc}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(t => {
|
test(() => {
|
||||||
/**
|
/**
|
||||||
* Contains array of test cases in format :
|
* Contains array of test cases in format :
|
||||||
* [
|
* [
|
||||||
@@ -667,10 +662,6 @@ this is a level one text
|
|||||||
editor.setCursor(cursor)
|
editor.setCursor(cursor)
|
||||||
markdownToc.generateInEditor(editor)
|
markdownToc.generateInEditor(editor)
|
||||||
|
|
||||||
t.is(
|
expect(expectedMd).toBe(editor.getValue())
|
||||||
expectedMd,
|
|
||||||
editor.getValue(),
|
|
||||||
`generateInEditor test : ${title} , generated : ${EOL}${editor.getValue()}, expected : ${EOL}${expectedMd}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,17 @@
|
|||||||
import test from 'ava'
|
jest.mock(
|
||||||
|
'electron',
|
||||||
|
() => {
|
||||||
|
return {
|
||||||
|
remote: {
|
||||||
|
app: {
|
||||||
|
getPath: jest.fn().mockReturnValue('.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ virtual: true }
|
||||||
|
)
|
||||||
|
|
||||||
import Markdown from 'browser/lib/markdown'
|
import Markdown from 'browser/lib/markdown'
|
||||||
import markdownFixtures from '../fixtures/markdowns'
|
import markdownFixtures from '../fixtures/markdowns'
|
||||||
|
|
||||||
@@ -6,100 +19,100 @@ import markdownFixtures from '../fixtures/markdowns'
|
|||||||
// To test markdown options, initialize a new instance in your test case
|
// To test markdown options, initialize a new instance in your test case
|
||||||
const md = new Markdown()
|
const md = new Markdown()
|
||||||
|
|
||||||
test('Markdown.render() should renders markdown correctly', t => {
|
test('Markdown.render() should renders markdown correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.basic)
|
const rendered = md.render(markdownFixtures.basic)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders codeblock correctly', t => {
|
test('Markdown.render() should renders codeblock correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.codeblock)
|
const rendered = md.render(markdownFixtures.codeblock)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders KaTeX correctly', t => {
|
test('Markdown.render() should renders KaTeX correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.katex)
|
const rendered = md.render(markdownFixtures.katex)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders checkboxes', t => {
|
test('Markdown.render() should renders checkboxes', () => {
|
||||||
const rendered = md.render(markdownFixtures.checkboxes)
|
const rendered = md.render(markdownFixtures.checkboxes)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should text with quotes correctly', t => {
|
test('Markdown.render() should text with quotes correctly', () => {
|
||||||
const renderedSmartQuotes = md.render(markdownFixtures.smartQuotes)
|
const renderedSmartQuotes = md.render(markdownFixtures.smartQuotes)
|
||||||
t.snapshot(renderedSmartQuotes)
|
expect(renderedSmartQuotes).toMatchSnapshot()
|
||||||
|
|
||||||
const newmd = new Markdown({ typographer: false })
|
const newmd = new Markdown({ typographer: false })
|
||||||
const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes)
|
const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes)
|
||||||
t.snapshot(renderedNonSmartQuotes)
|
expect(renderedNonSmartQuotes).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render line breaks correctly', t => {
|
test('Markdown.render() should render line breaks correctly', () => {
|
||||||
const renderedBreaks = md.render(markdownFixtures.breaks)
|
const renderedBreaks = md.render(markdownFixtures.breaks)
|
||||||
t.snapshot(renderedBreaks)
|
expect(renderedBreaks).toMatchSnapshot()
|
||||||
|
|
||||||
const newmd = new Markdown({ breaks: false })
|
const newmd = new Markdown({ breaks: false })
|
||||||
const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
|
const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
|
||||||
t.snapshot(renderedNonBreaks)
|
expect(renderedNonBreaks).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders abbrevations correctly', t => {
|
test('Markdown.render() should renders abbrevations correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.abbrevations)
|
const rendered = md.render(markdownFixtures.abbrevations)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders sub correctly', t => {
|
test('Markdown.render() should renders sub correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.subTexts)
|
const rendered = md.render(markdownFixtures.subTexts)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders sup correctly', t => {
|
test('Markdown.render() should renders sup correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.supTexts)
|
const rendered = md.render(markdownFixtures.supTexts)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders definition lists correctly', t => {
|
test('Markdown.render() should renders definition lists correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.deflists)
|
const rendered = md.render(markdownFixtures.deflists)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render shortcuts correctly', t => {
|
test('Markdown.render() should render shortcuts correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.shortcuts)
|
const rendered = md.render(markdownFixtures.shortcuts)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render footnote correctly', t => {
|
test('Markdown.render() should render footnote correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.footnote)
|
const rendered = md.render(markdownFixtures.footnote)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should renders [TOC] placholder correctly', t => {
|
test('Markdown.render() should renders [TOC] placholder correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.tocPlaceholder)
|
const rendered = md.render(markdownFixtures.tocPlaceholder)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render PlantUML MindMaps correctly', t => {
|
test('Markdown.render() should render PlantUML MindMaps correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.plantUmlMindMap)
|
const rendered = md.render(markdownFixtures.plantUmlMindMap)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render PlantUML Gantt correctly', t => {
|
test('Markdown.render() should render PlantUML Gantt correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.plantUmlGantt)
|
const rendered = md.render(markdownFixtures.plantUmlGantt)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render PlantUML WBS correctly', t => {
|
test('Markdown.render() should render PlantUML WBS correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.plantUmlWbs)
|
const rendered = md.render(markdownFixtures.plantUmlWbs)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render PlantUML Umls correctly', t => {
|
test('Markdown.render() should render PlantUML Umls correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.plantUmlUml)
|
const rendered = md.render(markdownFixtures.plantUmlUml)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Markdown.render() should render PlantUML Ditaa correctly', t => {
|
test('Markdown.render() should render PlantUML Ditaa correctly', () => {
|
||||||
const rendered = md.render(markdownFixtures.plantUmlDitaa)
|
const rendered = md.render(markdownFixtures.plantUmlDitaa)
|
||||||
t.snapshot(rendered)
|
expect(rendered).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Unit test for browser/lib/normalizeEditorFontFamily
|
* @fileoverview Unit test for browser/lib/normalizeEditorFontFamily
|
||||||
*/
|
*/
|
||||||
import test from 'ava'
|
|
||||||
import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily'
|
import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily'
|
||||||
import consts from '../../browser/lib/consts'
|
import consts from '../../browser/lib/consts'
|
||||||
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||||
|
|
||||||
test('normalizeEditorFontFamily() should return default font family (string[])', t => {
|
test('normalizeEditorFontFamily() should return default font family (string[])', () => {
|
||||||
t.is(normalizeEditorFontFamily(), defaultEditorFontFamily.join(', '))
|
expect(normalizeEditorFontFamily()).toBe(defaultEditorFontFamily.join(', '))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', t => {
|
test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', () => {
|
||||||
const arg = 'font1, font2'
|
const arg = 'font1, font2'
|
||||||
t.is(
|
expect(normalizeEditorFontFamily(arg)).toBe(
|
||||||
normalizeEditorFontFamily(arg),
|
|
||||||
`${arg}, ${defaultEditorFontFamily.join(', ')}`
|
`${arg}, ${defaultEditorFontFamily.join(', ')}`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
const test = require('ava')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { parse } = require('browser/lib/RcParser')
|
const { parse } = require('browser/lib/RcParser')
|
||||||
|
|
||||||
// Unit test
|
// Unit test
|
||||||
test('RcParser should return a json object', t => {
|
test('RcParser should return a json object', () => {
|
||||||
const validJson = {
|
const validJson = {
|
||||||
editor: { keyMap: 'vim', switchPreview: 'BLUR', theme: 'monokai' },
|
editor: { keyMap: 'vim', switchPreview: 'BLUR', theme: 'monokai' },
|
||||||
hotkey: { toggleMain: 'Control + L' },
|
hotkey: { toggleMain: 'Control + L' },
|
||||||
@@ -51,20 +50,12 @@ test('RcParser should return a json object', t => {
|
|||||||
|
|
||||||
validTestCases.forEach(validTestCase => {
|
validTestCases.forEach(validTestCase => {
|
||||||
const [input, expected] = validTestCase
|
const [input, expected] = validTestCase
|
||||||
t.is(
|
expect(parse(filePath(input)).editor.keyMap).toBe(expected.editor.keyMap)
|
||||||
parse(filePath(input)).editor.keyMap,
|
|
||||||
expected.editor.keyMap,
|
|
||||||
`Test for getTodoStatus() input: ${input} expected: ${expected.keyMap}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
invalidTestCases.forEach(invalidTestCase => {
|
invalidTestCases.forEach(invalidTestCase => {
|
||||||
const [input, expected] = invalidTestCase
|
const [input, expected] = invalidTestCase
|
||||||
t.is(
|
expect(parse(filePath(input)).editor).toBe(expected.editor)
|
||||||
parse(filePath(input)).editor,
|
|
||||||
expected.editor,
|
|
||||||
`Test for getTodoStatus() input: ${input} expected: ${expected.editor}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import test from 'ava'
|
|
||||||
import searchFromNotes from 'browser/lib/search'
|
import searchFromNotes from 'browser/lib/search'
|
||||||
import { dummyNote } from '../fixtures/TestDummy'
|
import { dummyNote } from '../fixtures/TestDummy'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
@@ -11,7 +10,7 @@ const pickContents = notes =>
|
|||||||
let notes = []
|
let notes = []
|
||||||
let note1, note2, note3
|
let note1, note2, note3
|
||||||
|
|
||||||
test.before(t => {
|
beforeAll(() => {
|
||||||
const data1 = { type: 'MARKDOWN_NOTE', content: 'content1', tags: ['tag1'] }
|
const data1 = { type: 'MARKDOWN_NOTE', content: 'content1', tags: ['tag1'] }
|
||||||
const data2 = {
|
const data2 = {
|
||||||
type: 'MARKDOWN_NOTE',
|
type: 'MARKDOWN_NOTE',
|
||||||
@@ -27,7 +26,7 @@ test.before(t => {
|
|||||||
notes = [note1, note2, note3]
|
notes = [note1, note2, note3]
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it can find notes by tags and words', t => {
|
test('it can find notes by tags and words', () => {
|
||||||
// [input, expected content (Array)]
|
// [input, expected content (Array)]
|
||||||
const testWithTags = [
|
const testWithTags = [
|
||||||
['#tag1', [note1.content, note2.content, note3.content]],
|
['#tag1', [note1.content, note2.content, note3.content]],
|
||||||
@@ -49,6 +48,8 @@ test('it can find notes by tags and words', t => {
|
|||||||
testCases.forEach(testCase => {
|
testCases.forEach(testCase => {
|
||||||
const [input, expectedContents] = testCase
|
const [input, expectedContents] = testCase
|
||||||
const results = searchFromNotes(notes, input)
|
const results = searchFromNotes(notes, input)
|
||||||
t.true(_.isEqual(pickContents(results).sort(), expectedContents.sort()))
|
expect(
|
||||||
|
_.isEqual(pickContents(results).sort(), expectedContents.sort())
|
||||||
|
).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,58 +1,57 @@
|
|||||||
import test from 'ava'
|
|
||||||
import slugify from 'browser/lib/slugify'
|
import slugify from 'browser/lib/slugify'
|
||||||
|
|
||||||
test('alphabet and digit', t => {
|
test('alphabet and digit', () => {
|
||||||
const upperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
const upperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
const lowerAlphabet = 'abcdefghijklmnopqrstuvwxyz'
|
const lowerAlphabet = 'abcdefghijklmnopqrstuvwxyz'
|
||||||
const digit = '0123456789'
|
const digit = '0123456789'
|
||||||
const testCase = upperAlphabet + lowerAlphabet + digit
|
const testCase = upperAlphabet + lowerAlphabet + digit
|
||||||
const decodeSlug = decodeURI(slugify(testCase))
|
const decodeSlug = decodeURI(slugify(testCase))
|
||||||
|
|
||||||
t.true(decodeSlug === testCase)
|
expect(decodeSlug === testCase).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should delete unavailable symbols', t => {
|
test('should delete unavailable symbols', () => {
|
||||||
const availableSymbols = '_-'
|
const availableSymbols = '_-'
|
||||||
const testCase = availableSymbols + "][!'#$%&()*+,./:;<=>?@\\^{|}~`"
|
const testCase = availableSymbols + "][!'#$%&()*+,./:;<=>?@\\^{|}~`"
|
||||||
const decodeSlug = decodeURI(slugify(testCase))
|
const decodeSlug = decodeURI(slugify(testCase))
|
||||||
|
|
||||||
t.true(decodeSlug === availableSymbols)
|
expect(decodeSlug === availableSymbols).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should convert from white spaces between words to hyphens', t => {
|
test('should convert from white spaces between words to hyphens', () => {
|
||||||
const testCase = 'This is one'
|
const testCase = 'This is one'
|
||||||
const expectedString = 'This-is-one'
|
const expectedString = 'This-is-one'
|
||||||
const decodeSlug = decodeURI(slugify(testCase))
|
const decodeSlug = decodeURI(slugify(testCase))
|
||||||
|
|
||||||
t.true(decodeSlug === expectedString)
|
expect(decodeSlug === expectedString).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should remove leading white spaces', t => {
|
test('should remove leading white spaces', () => {
|
||||||
const testCase = ' This is one'
|
const testCase = ' This is one'
|
||||||
const expectedString = 'This-is-one'
|
const expectedString = 'This-is-one'
|
||||||
const decodeSlug = decodeURI(slugify(testCase))
|
const decodeSlug = decodeURI(slugify(testCase))
|
||||||
|
|
||||||
t.true(decodeSlug === expectedString)
|
expect(decodeSlug === expectedString).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should remove trailing white spaces', t => {
|
test('should remove trailing white spaces', () => {
|
||||||
const testCase = 'This is one '
|
const testCase = 'This is one '
|
||||||
const expectedString = 'This-is-one'
|
const expectedString = 'This-is-one'
|
||||||
const decodeSlug = decodeURI(slugify(testCase))
|
const decodeSlug = decodeURI(slugify(testCase))
|
||||||
|
|
||||||
t.true(decodeSlug === expectedString)
|
expect(decodeSlug === expectedString).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('2-byte charactor support', t => {
|
test('2-byte charactor support', () => {
|
||||||
const testCase = '菠萝芒果テストÀžƁƵ'
|
const testCase = '菠萝芒果テストÀžƁƵ'
|
||||||
const decodeSlug = decodeURI(slugify(testCase))
|
const decodeSlug = decodeURI(slugify(testCase))
|
||||||
|
|
||||||
t.true(decodeSlug === testCase)
|
expect(decodeSlug === testCase).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('emoji', t => {
|
test('emoji', () => {
|
||||||
const testCase = '🌸'
|
const testCase = '🌸'
|
||||||
const decodeSlug = decodeURI(slugify(testCase))
|
const decodeSlug = decodeURI(slugify(testCase))
|
||||||
|
|
||||||
t.true(decodeSlug === testCase)
|
expect(decodeSlug === testCase).toBe(true)
|
||||||
})
|
})
|
||||||
Binary file not shown.
120
yarn.lock
120
yarn.lock
@@ -1966,6 +1966,11 @@ combined-stream@1.0.6, combined-stream@~1.0.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream "~1.0.0"
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
|
command-exists@^1.2.9:
|
||||||
|
version "1.2.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
|
||||||
|
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
|
||||||
|
|
||||||
commander@2:
|
commander@2:
|
||||||
version "2.16.0"
|
version "2.16.0"
|
||||||
resolved "http://registry.npm.taobao.org/commander/download/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
|
resolved "http://registry.npm.taobao.org/commander/download/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
|
||||||
@@ -2583,7 +2588,44 @@ d3-zoom@1:
|
|||||||
d3-selection "1"
|
d3-selection "1"
|
||||||
d3-transition "1"
|
d3-transition "1"
|
||||||
|
|
||||||
d3@^5.12, d3@^5.7.0:
|
d3@^5.14:
|
||||||
|
version "5.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877"
|
||||||
|
integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==
|
||||||
|
dependencies:
|
||||||
|
d3-array "1"
|
||||||
|
d3-axis "1"
|
||||||
|
d3-brush "1"
|
||||||
|
d3-chord "1"
|
||||||
|
d3-collection "1"
|
||||||
|
d3-color "1"
|
||||||
|
d3-contour "1"
|
||||||
|
d3-dispatch "1"
|
||||||
|
d3-drag "1"
|
||||||
|
d3-dsv "1"
|
||||||
|
d3-ease "1"
|
||||||
|
d3-fetch "1"
|
||||||
|
d3-force "1"
|
||||||
|
d3-format "1"
|
||||||
|
d3-geo "1"
|
||||||
|
d3-hierarchy "1"
|
||||||
|
d3-interpolate "1"
|
||||||
|
d3-path "1"
|
||||||
|
d3-polygon "1"
|
||||||
|
d3-quadtree "1"
|
||||||
|
d3-random "1"
|
||||||
|
d3-scale "2"
|
||||||
|
d3-scale-chromatic "1"
|
||||||
|
d3-selection "1"
|
||||||
|
d3-shape "1"
|
||||||
|
d3-time "1"
|
||||||
|
d3-time-format "2"
|
||||||
|
d3-timer "1"
|
||||||
|
d3-transition "1"
|
||||||
|
d3-voronoi "1"
|
||||||
|
d3-zoom "1"
|
||||||
|
|
||||||
|
d3@^5.7.0:
|
||||||
version "5.12.0"
|
version "5.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3/-/d3-5.12.0.tgz#0ddeac879c28c882317cd439b495290acd59ab61"
|
resolved "https://registry.yarnpkg.com/d3/-/d3-5.12.0.tgz#0ddeac879c28c882317cd439b495290acd59ab61"
|
||||||
integrity sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==
|
integrity sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==
|
||||||
@@ -2626,13 +2668,14 @@ d@1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es5-ext "^0.10.9"
|
es5-ext "^0.10.9"
|
||||||
|
|
||||||
dagre-d3@dagrejs/dagre-d3:
|
dagre-d3@^0.6.4:
|
||||||
version "0.6.4-pre"
|
version "0.6.4"
|
||||||
resolved "https://codeload.github.com/dagrejs/dagre-d3/tar.gz/e1a00e5cb518f5d2304a35647e024f31d178e55b"
|
resolved "https://registry.yarnpkg.com/dagre-d3/-/dagre-d3-0.6.4.tgz#0728d5ce7f177ca2337df141ceb60fbe6eeb7b29"
|
||||||
|
integrity sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
d3 "^5.12"
|
d3 "^5.14"
|
||||||
dagre "^0.8.4"
|
dagre "^0.8.5"
|
||||||
graphlib "^2.1.7"
|
graphlib "^2.1.8"
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
dagre@^0.8.4:
|
dagre@^0.8.4:
|
||||||
@@ -2643,6 +2686,14 @@ dagre@^0.8.4:
|
|||||||
graphlib "^2.1.7"
|
graphlib "^2.1.7"
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.4"
|
||||||
|
|
||||||
|
dagre@^0.8.5:
|
||||||
|
version "0.8.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee"
|
||||||
|
integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==
|
||||||
|
dependencies:
|
||||||
|
graphlib "^2.1.8"
|
||||||
|
lodash "^4.17.15"
|
||||||
|
|
||||||
dashdash@^1.12.0:
|
dashdash@^1.12.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||||
@@ -3130,6 +3181,13 @@ entities@^1.1.1, entities@~1.1.1:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
|
||||||
|
|
||||||
|
entity-decode@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/entity-decode/-/entity-decode-2.0.2.tgz#e4f807e52c3294246e9347d1f2b02b07fd5f92e7"
|
||||||
|
integrity sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==
|
||||||
|
dependencies:
|
||||||
|
he "^1.1.1"
|
||||||
|
|
||||||
env-paths@^1.0.0:
|
env-paths@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
|
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
|
||||||
@@ -4302,6 +4360,13 @@ graphlib@^2.1.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.5"
|
lodash "^4.17.5"
|
||||||
|
|
||||||
|
graphlib@^2.1.8:
|
||||||
|
version "2.1.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
|
||||||
|
integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
|
||||||
|
dependencies:
|
||||||
|
lodash "^4.17.15"
|
||||||
|
|
||||||
gray-matter@^2.1.0:
|
gray-matter@^2.1.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-2.1.1.tgz#3042d9adec2a1ded6a7707a9ed2380f8a17a430e"
|
resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-2.1.1.tgz#3042d9adec2a1ded6a7707a9ed2380f8a17a430e"
|
||||||
@@ -4490,15 +4555,15 @@ has@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.0.2"
|
function-bind "^1.0.2"
|
||||||
|
|
||||||
he@^1.2.0:
|
he@^1.1.1, he@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
highlight.js@^9.13.1:
|
highlight.js@^10.4.1:
|
||||||
version "9.13.1"
|
version "10.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0"
|
||||||
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
|
integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==
|
||||||
|
|
||||||
highlight.js@^9.3.0:
|
highlight.js@^9.3.0:
|
||||||
version "9.12.0"
|
version "9.12.0"
|
||||||
@@ -6108,15 +6173,10 @@ lodash.uniq@^4.5.0:
|
|||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||||
|
|
||||||
lodash@^4.0.0, lodash@^4.0.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
|
lodash@^4.0.0, lodash@^4.0.1, lodash@^4.12.0, lodash@^4.13.0, lodash@^4.13.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
|
||||||
version "4.17.13"
|
version "4.17.19"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||||
integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==
|
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||||
|
|
||||||
lodash@^4.13.0, lodash@^4.17.11, lodash@^4.17.15:
|
|
||||||
version "4.17.15"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
|
||||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
|
||||||
|
|
||||||
lodash@~0.9.2:
|
lodash@~0.9.2:
|
||||||
version "0.9.2"
|
version "0.9.2"
|
||||||
@@ -6400,22 +6460,21 @@ merge@^1.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
|
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
|
||||||
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
|
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
|
||||||
|
|
||||||
mermaid@^8.4.2:
|
mermaid@^8.5.2:
|
||||||
version "8.4.2"
|
version "8.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.4.2.tgz#91d3d8e9541e72eed7a78d0e882db11564fab3bb"
|
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.5.2.tgz#0f1914cda53d4ea5377380e5ce07a38bef2ea7e8"
|
||||||
integrity sha512-vYSCP2u4XkOnjliWz/QIYwvzF/znQAq22vWJJ3YV40SnwV2JQyHblnwwNYXCprkXw7XfwBKDpSNaJ3HP4WfnZw==
|
integrity sha512-I+s+8/RzlazF3dGOhDUfU/ERkUV4zfIlTWb3703jNx+2lfACs+4AdY9ULQaw6BPWzW3gB+XlXFOOX/m/vqujIA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@braintree/sanitize-url" "^3.1.0"
|
"@braintree/sanitize-url" "^3.1.0"
|
||||||
crypto-random-string "^3.0.1"
|
crypto-random-string "^3.0.1"
|
||||||
d3 "^5.7.0"
|
d3 "^5.7.0"
|
||||||
dagre "^0.8.4"
|
dagre "^0.8.4"
|
||||||
dagre-d3 dagrejs/dagre-d3
|
dagre-d3 "^0.6.4"
|
||||||
|
entity-decode "^2.0.2"
|
||||||
graphlib "^2.1.7"
|
graphlib "^2.1.7"
|
||||||
he "^1.2.0"
|
he "^1.2.0"
|
||||||
lodash "^4.17.11"
|
|
||||||
minify "^4.1.1"
|
minify "^4.1.1"
|
||||||
moment-mini "^2.22.1"
|
moment-mini "^2.22.1"
|
||||||
prettier "^1.18.2"
|
|
||||||
scope-css "^1.2.1"
|
scope-css "^1.2.1"
|
||||||
|
|
||||||
methods@~1.1.2:
|
methods@~1.1.2:
|
||||||
@@ -10027,8 +10086,9 @@ websocket-driver@>=0.5.1:
|
|||||||
websocket-extensions ">=0.1.1"
|
websocket-extensions ">=0.1.1"
|
||||||
|
|
||||||
websocket-extensions@>=0.1.1:
|
websocket-extensions@>=0.1.1:
|
||||||
version "0.1.3"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
|
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
|
||||||
|
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
|
||||||
|
|
||||||
well-known-symbols@^1.0.0:
|
well-known-symbols@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user