mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-12 17:26:17 +00:00
Merge branch 'master' into export-yfm
This commit is contained in:
@@ -21,6 +21,8 @@ const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
|
||||
import { createTurndownService } from '../lib/turndown'
|
||||
import { languageMaps } from '../lib/CMLanguageList'
|
||||
import snippetManager from '../lib/SnippetManager'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import { sendWakatimeHeartBeat } from 'browser/lib/wakatime-plugin'
|
||||
import {
|
||||
generateInEditor,
|
||||
tocExistsInEditor
|
||||
@@ -113,6 +115,16 @@ export default class CodeEditor extends React.Component {
|
||||
this.editorActivityHandler = () => this.handleEditorActivity()
|
||||
|
||||
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) {
|
||||
@@ -158,6 +170,10 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
handleEditorActivity() {
|
||||
if (this.props.onCursorActivity) {
|
||||
this.props.onCursorActivity(this.editor)
|
||||
}
|
||||
|
||||
if (!this.textEditorInterface.transaction) {
|
||||
this.updateTableEditorState()
|
||||
}
|
||||
@@ -219,11 +235,19 @@ export default class CodeEditor extends React.Component {
|
||||
},
|
||||
[translateHotkey(hotkey.insertDate)]: function(cm) {
|
||||
const dateNow = new Date()
|
||||
cm.replaceSelection(dateNow.toLocaleDateString())
|
||||
if (self.props.dateFormatISO8601) {
|
||||
cm.replaceSelection(dateNow.toISOString().split('T')[0])
|
||||
} else {
|
||||
cm.replaceSelection(dateNow.toLocaleDateString())
|
||||
}
|
||||
},
|
||||
[translateHotkey(hotkey.insertDateTime)]: function(cm) {
|
||||
const dateNow = new Date()
|
||||
cm.replaceSelection(dateNow.toLocaleString())
|
||||
if (self.props.dateFormatISO8601) {
|
||||
cm.replaceSelection(dateNow.toISOString())
|
||||
} else {
|
||||
cm.replaceSelection(dateNow.toLocaleString())
|
||||
}
|
||||
},
|
||||
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
||||
'Ctrl-C': cm => {
|
||||
@@ -321,10 +345,18 @@ export default class CodeEditor extends React.Component {
|
||||
'CodeMirror-lint-markers'
|
||||
],
|
||||
autoCloseBrackets: {
|
||||
pairs: this.props.matchingPairs,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs,
|
||||
override: true
|
||||
codeBlock: {
|
||||
pairs: this.props.codeBlockMatchingPairs,
|
||||
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||
triples: this.props.codeBlockMatchingTriples,
|
||||
explode: this.props.codeBlockExplodingPairs
|
||||
},
|
||||
markdown: {
|
||||
pairs: this.props.matchingPairs,
|
||||
closeBefore: this.props.matchingCloseBefore,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs
|
||||
}
|
||||
},
|
||||
extraKeys: this.defaultKeyMap,
|
||||
prettierConfig: this.props.prettierConfig
|
||||
@@ -352,6 +384,7 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
eventEmitter.emit('code:init')
|
||||
this.editor.on('scroll', this.scrollHandler)
|
||||
this.editor.on('cursorActivity', this.editorActivityHandler)
|
||||
|
||||
const editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||
@@ -489,7 +522,6 @@ export default class CodeEditor extends React.Component {
|
||||
})
|
||||
|
||||
if (this.props.enableTableEditor) {
|
||||
this.editor.on('cursorActivity', 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)
|
||||
eventEmitter.off('top:search', this.searchHandler)
|
||||
this.editor.off('scroll', this.scrollHandler)
|
||||
this.editor.off('cursorActivity', this.editorActivityHandler)
|
||||
this.editor.off('contextmenu', this.contextMenuHandler)
|
||||
|
||||
const editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||
|
||||
spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
|
||||
eventEmitter.off('code:format-table', this.formatTable)
|
||||
|
||||
if (this.props.enableTableEditor) {
|
||||
this.editor.off('changes', this.editorActivityHandler)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@@ -629,16 +667,32 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
if (
|
||||
prevProps.matchingPairs !== this.props.matchingPairs ||
|
||||
prevProps.matchingCloseBefore !== this.props.matchingCloseBefore ||
|
||||
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 = {
|
||||
pairs: this.props.matchingPairs,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs,
|
||||
override: true
|
||||
const autoCloseBrackets = {
|
||||
codeBlock: {
|
||||
pairs: this.props.codeBlockMatchingPairs,
|
||||
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||
triples: this.props.codeBlockMatchingTriples,
|
||||
explode: this.props.codeBlockExplodingPairs
|
||||
},
|
||||
markdown: {
|
||||
pairs: this.props.matchingPairs,
|
||||
closeBefore: this.props.matchingCloseBefore,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs
|
||||
}
|
||||
}
|
||||
this.editor.setOption('autoCloseBrackets', bracketObject)
|
||||
|
||||
this.editor.setOption('autoCloseBrackets', autoCloseBrackets)
|
||||
}
|
||||
|
||||
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
|
||||
@@ -793,9 +847,23 @@ export default class CodeEditor extends React.Component {
|
||||
this.updateHighlight(editor, changeObject)
|
||||
|
||||
this.value = editor.getValue()
|
||||
|
||||
const { storageKey, noteKey } = this.props
|
||||
const storage = findStorage(storageKey)
|
||||
if (this.props.onChange) {
|
||||
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) {
|
||||
@@ -923,6 +991,16 @@ export default class CodeEditor extends React.Component {
|
||||
this.restartHighlighting()
|
||||
this.editor.on('change', this.changeHandler)
|
||||
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) {
|
||||
|
||||
@@ -139,7 +139,7 @@ class MarkdownEditor extends React.Component {
|
||||
},
|
||||
() => {
|
||||
this.previewRef.current.focus()
|
||||
this.previewRef.current.scrollToRow(cursorPosition.line)
|
||||
this.previewRef.current.scrollToLine(cursorPosition.line)
|
||||
}
|
||||
)
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
@@ -366,8 +366,15 @@ class MarkdownEditor extends React.Component {
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
lineWrapping
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||
codeBlockMatchingCloseBefore={
|
||||
config.editor.codeBlockMatchingCloseBefore
|
||||
}
|
||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
storageKey={storageKey}
|
||||
noteKey={noteKey}
|
||||
@@ -382,6 +389,7 @@ class MarkdownEditor extends React.Component {
|
||||
switchPreview={config.editor.switchPreview}
|
||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||
prettierConfig={config.editor.prettierConfig}
|
||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||
RTL={RTL}
|
||||
|
||||
@@ -747,17 +747,18 @@ class MarkdownPreview extends React.Component {
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {Number} targetRow
|
||||
* @param {Number} targetLine
|
||||
*/
|
||||
scrollToRow(targetRow) {
|
||||
scrollToLine(targetLine) {
|
||||
const blocks = this.getWindow().document.querySelectorAll(
|
||||
'body>[data-line]'
|
||||
'body [data-line]'
|
||||
)
|
||||
|
||||
for (let index = 0; index < blocks.length; index++) {
|
||||
let block = blocks[index]
|
||||
const row = parseInt(block.getAttribute('data-line'))
|
||||
if (row > targetRow || index === blocks.length - 1) {
|
||||
const line = parseInt(block.getAttribute('data-line'))
|
||||
|
||||
if (line > targetLine || index === blocks.length - 1) {
|
||||
block = blocks[index - 1]
|
||||
block != null && this.scrollTo(0, block.offsetTop)
|
||||
break
|
||||
@@ -794,7 +795,10 @@ class MarkdownPreview extends React.Component {
|
||||
e.preventDefault()
|
||||
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
|
||||
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
this.value = props.value
|
||||
this.focus = () => this.refs.code.focus()
|
||||
this.reload = () => this.refs.code.reload()
|
||||
this.userScroll = true
|
||||
this.userScroll = props.config.preview.scrollSync
|
||||
this.state = {
|
||||
isSliderFocused: false,
|
||||
codeEditorWidthInPercent: 50,
|
||||
@@ -21,6 +21,72 @@ class MarkdownSplitEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.refs.code.setValue(value)
|
||||
}
|
||||
@@ -30,59 +96,125 @@ class MarkdownSplitEditor extends React.Component {
|
||||
this.props.onChange(e)
|
||||
}
|
||||
|
||||
handleScroll(e) {
|
||||
if (!this.props.config.preview.scrollSync) return
|
||||
|
||||
const previewDoc = _.get(
|
||||
this,
|
||||
'refs.preview.refs.root.contentWindow.document'
|
||||
)
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
let srcTop, srcHeight, targetTop, targetHeight
|
||||
|
||||
handleEditorScroll(e) {
|
||||
if (this.userScroll) {
|
||||
if (e.doc) {
|
||||
srcTop = _.get(e, 'doc.scrollTop')
|
||||
srcHeight = _.get(e, 'doc.height')
|
||||
targetTop = _.get(previewDoc, 'body.scrollTop')
|
||||
targetHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||
const previewDoc = _.get(
|
||||
this,
|
||||
'refs.preview.refs.root.contentWindow.document'
|
||||
)
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
|
||||
const from = codeDoc.cm.coordsChar({ left: 0, top: 0 }).line
|
||||
const to = codeDoc.cm.coordsChar({
|
||||
left: 0,
|
||||
top: codeDoc.cm.display.lastWrapHeight * 1.125
|
||||
}).line
|
||||
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 {
|
||||
srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||
srcHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||
targetTop = _.get(codeDoc, 'scrollTop')
|
||||
targetHeight = _.get(codeDoc, 'height')
|
||||
const line = from + Math.floor((to - from) / 3)
|
||||
|
||||
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: codeDoc.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)
|
||||
|
||||
top =
|
||||
blocks[i - 1].top + Math.floor((line - blocks[i - 1].line) * ratio)
|
||||
}
|
||||
|
||||
const distance = (targetHeight * srcTop) / srcHeight - targetTop
|
||||
const framerate = 1000 / 60
|
||||
const frames = 20
|
||||
const refractory = frames * framerate
|
||||
this.scrollTo(previewTop, top, y =>
|
||||
_.set(previewDoc, 'body.scrollTop', y)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.userScroll = false
|
||||
handlePreviewScroll(e) {
|
||||
if (this.userScroll) {
|
||||
const previewDoc = _.get(
|
||||
this,
|
||||
'refs.preview.refs.root.contentWindow.document'
|
||||
)
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
|
||||
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
|
||||
if (e.doc)
|
||||
_.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
||||
else
|
||||
_.get(this, 'refs.code.editor').scrollTo(
|
||||
0,
|
||||
targetTop + scrollPos * distance
|
||||
)
|
||||
if (frame >= frames) {
|
||||
clearInterval(timer)
|
||||
setTimeout(() => {
|
||||
this.userScroll = true
|
||||
}, refractory)
|
||||
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
|
||||
}
|
||||
}
|
||||
frame++
|
||||
}, framerate)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,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() {
|
||||
const {
|
||||
config,
|
||||
@@ -261,8 +422,15 @@ class MarkdownSplitEditor extends React.Component {
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
lineWrapping
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||
codeBlockMatchingCloseBefore={
|
||||
config.editor.codeBlockMatchingCloseBefore
|
||||
}
|
||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||
indentType={config.editor.indentType}
|
||||
indentSize={editorStyle.indentSize}
|
||||
enableRulers={config.editor.enableRulers}
|
||||
@@ -274,13 +442,15 @@ class MarkdownSplitEditor extends React.Component {
|
||||
noteKey={noteKey}
|
||||
linesHighlighted={linesHighlighted}
|
||||
onChange={e => this.handleOnChange(e)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
onScroll={e => this.handleEditorScroll(e)}
|
||||
onCursorActivity={e => this.handleCursorActivity(e)}
|
||||
spellCheck={config.editor.spellcheck}
|
||||
enableSmartPaste={config.editor.enableSmartPaste}
|
||||
hotkey={config.hotkey}
|
||||
switchPreview={config.editor.switchPreview}
|
||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||
RTL={RTL}
|
||||
/>
|
||||
@@ -311,7 +481,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
tabInde='0'
|
||||
value={value}
|
||||
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
onScroll={e => this.handlePreviewScroll(e)}
|
||||
showCopyNotification={config.ui.showCopyNotification}
|
||||
storagePath={storage.path}
|
||||
noteKey={noteKey}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import mermaidAPI from 'mermaid'
|
||||
import mermaidAPI from 'mermaid/dist/mermaid.min.js'
|
||||
import uiThemes from 'browser/lib/ui-themes'
|
||||
|
||||
// fixes bad styling in the mermaid dark theme
|
||||
|
||||
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 }
|
||||
@@ -859,8 +859,15 @@ class SnippetNoteDetail extends React.Component {
|
||||
indentSize={editorIndentSize}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||
codeBlockMatchingCloseBefore={
|
||||
config.editor.codeBlockMatchingCloseBefore
|
||||
}
|
||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||
keyMap={config.editor.keyMap}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
@@ -870,6 +877,9 @@ class SnippetNoteDetail extends React.Component {
|
||||
enableSmartPaste={config.editor.enableSmartPaste}
|
||||
hotkey={config.hotkey}
|
||||
autoDetect={autoDetect}
|
||||
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||
storageKey={storageKey}
|
||||
noteKey={note.key}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -86,8 +86,13 @@ export const DEFAULT_CONFIG = {
|
||||
rulers: [80, 120],
|
||||
displayLineNumbers: true,
|
||||
matchingPairs: '()[]{}\'\'""$$**``~~__',
|
||||
matchingCloseBefore: ')]}\'":;>',
|
||||
matchingTriples: '```"""\'\'\'',
|
||||
explodingPairs: '[]{}``$$',
|
||||
codeBlockMatchingPairs: '()[]{}\'\'""``',
|
||||
codeBlockMatchingCloseBefore: ')]}\'":;>',
|
||||
codeBlockMatchingTriples: '',
|
||||
codeBlockExplodingPairs: '[]{}``',
|
||||
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
|
||||
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
|
||||
scrollPastEnd: false,
|
||||
@@ -100,6 +105,7 @@ export const DEFAULT_CONFIG = {
|
||||
enableSmartPaste: false,
|
||||
enableMarkdownLint: false,
|
||||
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG,
|
||||
dateFormatISO8601: false,
|
||||
prettierConfig: `{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
@@ -143,7 +149,10 @@ export const DEFAULT_CONFIG = {
|
||||
variable: 'boostnote',
|
||||
prefixAttachmentFolder: false
|
||||
},
|
||||
coloredTags: {}
|
||||
coloredTags: {},
|
||||
wakatime: {
|
||||
key: null
|
||||
}
|
||||
}
|
||||
|
||||
function validate(config) {
|
||||
@@ -259,6 +268,12 @@ function assignConfigValues(originalConfig, rcConfig) {
|
||||
originalConfig.hotkey,
|
||||
rcConfig.hotkey
|
||||
)
|
||||
config.wakatime = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.wakatime,
|
||||
originalConfig.wakatime,
|
||||
rcConfig.wakatime
|
||||
)
|
||||
config.blog = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.blog,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import uiThemes from 'browser/lib/ui-themes'
|
||||
|
||||
const saveChanges = newConfig => {
|
||||
ConfigManager.set(newConfig)
|
||||
@@ -40,14 +41,7 @@ const chooseTheme = config => {
|
||||
}
|
||||
|
||||
const applyTheme = theme => {
|
||||
const supportedThemes = [
|
||||
'dark',
|
||||
'white',
|
||||
'solarized-dark',
|
||||
'monokai',
|
||||
'dracula'
|
||||
]
|
||||
if (supportedThemes.indexOf(theme) !== -1) {
|
||||
if (uiThemes.some(item => item.name === theme)) {
|
||||
document.body.setAttribute('data-theme', theme)
|
||||
if (document.body.querySelector('.MarkdownPreview')) {
|
||||
document.body
|
||||
|
||||
@@ -139,6 +139,13 @@ div[id^="firstRow"]
|
||||
margin-right 10px
|
||||
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
|
||||
flex 1
|
||||
margin-left 5px
|
||||
|
||||
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,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
autoCloseBrackets: {
|
||||
pairs: this.props.matchingPairs,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs,
|
||||
override: true
|
||||
codeBlock: {
|
||||
pairs: this.props.codeBlockMatchingPairs,
|
||||
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||
triples: this.props.codeBlockMatchingTriples,
|
||||
explode: this.props.codeBlockExplodingPairs
|
||||
},
|
||||
markdown: {
|
||||
pairs: this.props.matchingPairs,
|
||||
closeBefore: this.props.matchingCloseBefore,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs
|
||||
}
|
||||
},
|
||||
mode: 'null'
|
||||
})
|
||||
|
||||
@@ -152,8 +152,15 @@ class SnippetTab extends React.Component {
|
||||
rulers={config.editor.rulers}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||
codeBlockMatchingCloseBefore={
|
||||
config.editor.codeBlockMatchingCloseBefore
|
||||
}
|
||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
onRef={ref => {
|
||||
this.snippetEditor = ref
|
||||
|
||||
@@ -124,14 +124,21 @@ class UiTab extends React.Component {
|
||||
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
|
||||
frontMatterTitleField: this.refs.frontMatterTitleField.value,
|
||||
matchingPairs: this.refs.matchingPairs.value,
|
||||
matchingCloseBefore: this.refs.matchingCloseBefore.value,
|
||||
matchingTriples: this.refs.matchingTriples.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,
|
||||
enableSmartPaste: this.refs.enableSmartPaste.checked,
|
||||
enableMarkdownLint: this.refs.enableMarkdownLint.checked,
|
||||
customMarkdownLintConfig: this.customMarkdownLintConfigCM
|
||||
.getCodeMirror()
|
||||
.getValue(),
|
||||
dateFormatISO8601: this.refs.dateFormatISO8601.checked,
|
||||
prettierConfig: this.prettierConfigCM.getCodeMirror().getValue(),
|
||||
deleteUnusedAttachments: this.refs.deleteUnusedAttachments.checked,
|
||||
rtlEnabled: this.refs.rtlEnabled.checked
|
||||
@@ -745,6 +752,126 @@ class UiTab extends React.Component {
|
||||
</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'>
|
||||
<label>
|
||||
<input
|
||||
@@ -875,50 +1002,19 @@ class UiTab extends React.Component {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Matching character pairs')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
value={this.state.config.editor.matchingPairs}
|
||||
ref='matchingPairs'
|
||||
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 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-label'>
|
||||
{i18n.__('Custom MarkdownLint Rules')}
|
||||
|
||||
@@ -8,6 +8,7 @@ import Crowdfunding from './Crowdfunding'
|
||||
import StoragesTab from './StoragesTab'
|
||||
import ExportTab from './ExportTab'
|
||||
import SnippetTab from './SnippetTab'
|
||||
import PluginsTab from './PluginsTab'
|
||||
import Blog from './Blog'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
@@ -93,6 +94,14 @@ class Preferences extends React.Component {
|
||||
)
|
||||
case 'SNIPPET':
|
||||
return <SnippetTab dispatch={dispatch} config={config} data={data} />
|
||||
case 'PLUGINS':
|
||||
return (
|
||||
<PluginsTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
haveToSave={alert => this.setState({ PluginsAlert: alert })}
|
||||
/>
|
||||
)
|
||||
case 'STORAGES':
|
||||
default:
|
||||
return (
|
||||
@@ -138,7 +147,8 @@ class Preferences extends React.Component {
|
||||
label: i18n.__('Export'),
|
||||
Export: this.state.ExportAlert
|
||||
},
|
||||
{ target: 'SNIPPET', label: i18n.__('Snippets') }
|
||||
{ target: 'SNIPPET', label: i18n.__('Snippets') },
|
||||
{ target: 'PLUGINS', label: i18n.__('Plugins') }
|
||||
]
|
||||
|
||||
const navButtons = tabs.map(tab => {
|
||||
|
||||
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)))
|
||||
}
|
||||
});
|
||||
302
extra_scripts/codemirror/mode/bfm/bfm.js
vendored
302
extra_scripts/codemirror/mode/bfm/bfm.js
vendored
@@ -55,202 +55,168 @@
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.defineMode(
|
||||
'bfm',
|
||||
function(config, baseConfig) {
|
||||
baseConfig.name = 'yaml-frontmatter'
|
||||
const baseMode = CodeMirror.getMode(config, baseConfig)
|
||||
CodeMirror.defineMode('bfm', function (config, baseConfig) {
|
||||
baseConfig.name = 'yaml-frontmatter'
|
||||
const baseMode = CodeMirror.getMode(config, baseConfig)
|
||||
|
||||
return {
|
||||
startState: function() {
|
||||
return {
|
||||
baseState: CodeMirror.startState(baseMode),
|
||||
return {
|
||||
startState: function() {
|
||||
return {
|
||||
baseState: CodeMirror.startState(baseMode),
|
||||
|
||||
basePos: 0,
|
||||
baseCur: null,
|
||||
overlayPos: 0,
|
||||
overlayCur: null,
|
||||
streamSeen: null,
|
||||
basePos: 0,
|
||||
baseCur: null,
|
||||
overlayPos: 0,
|
||||
overlayCur: null,
|
||||
streamSeen: null,
|
||||
|
||||
fencedEndRE: null,
|
||||
fencedEndRE: null,
|
||||
|
||||
inTable: false,
|
||||
rowIndex: 0
|
||||
}
|
||||
},
|
||||
copyState: function(s) {
|
||||
return {
|
||||
baseState: CodeMirror.copyState(baseMode, s.baseState),
|
||||
inTable: false,
|
||||
rowIndex: 0
|
||||
}
|
||||
},
|
||||
copyState: function(s) {
|
||||
return {
|
||||
baseState: CodeMirror.copyState(baseMode, s.baseState),
|
||||
|
||||
basePos: s.basePos,
|
||||
baseCur: null,
|
||||
overlayPos: s.overlayPos,
|
||||
overlayCur: null,
|
||||
basePos: s.basePos,
|
||||
baseCur: null,
|
||||
overlayPos: s.overlayPos,
|
||||
overlayCur: null,
|
||||
|
||||
fencedMode: s.fencedMode,
|
||||
fencedState: s.fencedMode
|
||||
? CodeMirror.copyState(s.fencedMode, s.fencedState)
|
||||
: null,
|
||||
fencedMode: s.fencedMode,
|
||||
fencedState: s.fencedMode ? CodeMirror.copyState(s.fencedMode, s.fencedState) : null,
|
||||
|
||||
fencedEndRE: s.fencedEndRE,
|
||||
fencedEndRE: s.fencedEndRE,
|
||||
|
||||
inTable: s.inTable,
|
||||
rowIndex: s.rowIndex
|
||||
}
|
||||
},
|
||||
token: function(stream, state) {
|
||||
const initialPos = stream.pos
|
||||
inTable: s.inTable,
|
||||
rowIndex: s.rowIndex
|
||||
}
|
||||
},
|
||||
token: function(stream, state) {
|
||||
const initialPos = stream.pos
|
||||
|
||||
if (state.fencedEndRE && stream.match(state.fencedEndRE)) {
|
||||
if (state.fencedEndRE) {
|
||||
if (stream.match(state.fencedEndRE)) {
|
||||
state.fencedEndRE = null
|
||||
state.fencedMode = null
|
||||
state.fencedState = null
|
||||
|
||||
stream.pos = initialPos
|
||||
} else if (state.fencedMode) {
|
||||
return state.fencedMode.token(stream, state.fencedState)
|
||||
} else {
|
||||
if (state.fencedMode) {
|
||||
return state.fencedMode.token(stream, state.fencedState)
|
||||
}
|
||||
|
||||
const match = stream.match(fencedCodeRE, true)
|
||||
if (match) {
|
||||
state.fencedEndRE = new RegExp(match[1] + '+ *$')
|
||||
|
||||
state.fencedMode = getMode(
|
||||
match[2],
|
||||
match[3],
|
||||
config,
|
||||
stream.lineOracle.doc.cm
|
||||
)
|
||||
if (state.fencedMode) {
|
||||
state.fencedState = CodeMirror.startState(state.fencedMode)
|
||||
}
|
||||
|
||||
stream.pos = initialPos
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
stream != state.streamSeen ||
|
||||
Math.min(state.basePos, state.overlayPos) < stream.start
|
||||
) {
|
||||
state.streamSeen = stream
|
||||
state.basePos = state.overlayPos = stream.start
|
||||
}
|
||||
|
||||
if (stream.start == state.basePos) {
|
||||
state.baseCur = baseMode.token(stream, state.baseState)
|
||||
state.basePos = stream.pos
|
||||
}
|
||||
if (stream.start == state.overlayPos) {
|
||||
stream.pos = stream.start
|
||||
state.overlayCur = this.overlayToken(stream, state)
|
||||
state.overlayPos = stream.pos
|
||||
}
|
||||
stream.pos = Math.min(state.basePos, state.overlayPos)
|
||||
|
||||
if (state.overlayCur == null) {
|
||||
return state.baseCur
|
||||
} else if (state.baseCur != null && state.combineTokens) {
|
||||
return state.baseCur + ' ' + state.overlayCur
|
||||
} else {
|
||||
return state.overlayCur
|
||||
}
|
||||
},
|
||||
overlayToken: function(stream, state) {
|
||||
state.combineTokens = false
|
||||
|
||||
if (state.fencedEndRE && stream.match(state.fencedEndRE)) {
|
||||
state.fencedEndRE = null
|
||||
state.localMode = null
|
||||
state.localState = null
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (state.localMode) {
|
||||
return state.localMode.token(stream, state.localState) || ''
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
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)
|
||||
state.fencedMode = getMode(match[2], match[3], config, stream.lineOracle.doc.cm)
|
||||
if (state.fencedMode) {
|
||||
state.fencedState = CodeMirror.startState(state.fencedMode)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
state.combineTokens = true
|
||||
|
||||
if (state.inTable) {
|
||||
if (stream.match(/^\|/)) {
|
||||
++state.rowIndex
|
||||
|
||||
stream.skipToEnd()
|
||||
|
||||
if (state.rowIndex === 1) {
|
||||
return 'table table-separator'
|
||||
} else if (state.rowIndex % 2 === 0) {
|
||||
return 'table table-row table-row-even'
|
||||
} else {
|
||||
return 'table table-row table-row-odd'
|
||||
}
|
||||
} else {
|
||||
state.inTable = false
|
||||
|
||||
stream.skipToEnd()
|
||||
return null
|
||||
}
|
||||
} else if (stream.match(/^\|/)) {
|
||||
state.inTable = true
|
||||
state.rowIndex = 0
|
||||
|
||||
stream.skipToEnd()
|
||||
return 'table table-header'
|
||||
}
|
||||
|
||||
stream.skipToEnd()
|
||||
return null
|
||||
},
|
||||
electricChars: baseMode.electricChars,
|
||||
innerMode: function(state) {
|
||||
if (state.fencedMode) {
|
||||
return {
|
||||
mode: state.fencedMode,
|
||||
state: state.fencedState
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
mode: baseMode,
|
||||
state: state.baseState
|
||||
}
|
||||
}
|
||||
},
|
||||
blankLine: function(state) {
|
||||
state.inTable = false
|
||||
|
||||
if (state.fencedMode) {
|
||||
return (
|
||||
state.fencedMode.blankLine &&
|
||||
state.fencedMode.blankLine(state.fencedState)
|
||||
)
|
||||
} else {
|
||||
return baseMode.blankLine(state.baseState)
|
||||
stream.pos = initialPos
|
||||
}
|
||||
}
|
||||
|
||||
if (stream != state.streamSeen || Math.min(state.basePos, state.overlayPos) < stream.start) {
|
||||
state.streamSeen = stream
|
||||
state.basePos = state.overlayPos = stream.start
|
||||
}
|
||||
|
||||
if (stream.start == state.basePos) {
|
||||
state.baseCur = baseMode.token(stream, state.baseState)
|
||||
state.basePos = stream.pos
|
||||
}
|
||||
if (stream.start == state.overlayPos) {
|
||||
stream.pos = stream.start
|
||||
state.overlayCur = this.overlayToken(stream, state)
|
||||
state.overlayPos = stream.pos
|
||||
}
|
||||
stream.pos = Math.min(state.basePos, state.overlayPos)
|
||||
|
||||
if (state.overlayCur == null) {
|
||||
return state.baseCur
|
||||
}
|
||||
else if (state.baseCur != null && state.combineTokens) {
|
||||
return state.baseCur + ' ' + state.overlayCur
|
||||
}
|
||||
else {
|
||||
return state.overlayCur
|
||||
}
|
||||
},
|
||||
overlayToken: function(stream, state) {
|
||||
state.combineTokens = false
|
||||
|
||||
if (state.localMode) {
|
||||
return state.localMode.token(stream, state.localState) || ''
|
||||
}
|
||||
|
||||
state.combineTokens = true
|
||||
|
||||
if (state.inTable) {
|
||||
if (stream.match(/^\|/)) {
|
||||
++state.rowIndex
|
||||
|
||||
stream.skipToEnd()
|
||||
|
||||
if (state.rowIndex === 1) {
|
||||
return 'table table-separator'
|
||||
} else if (state.rowIndex % 2 === 0) {
|
||||
return 'table table-row table-row-even'
|
||||
} else {
|
||||
return 'table table-row table-row-odd'
|
||||
}
|
||||
} else {
|
||||
state.inTable = false
|
||||
|
||||
stream.skipToEnd()
|
||||
return null
|
||||
}
|
||||
} else if (stream.match(/^\|/)) {
|
||||
state.inTable = true
|
||||
state.rowIndex = 0
|
||||
|
||||
stream.skipToEnd()
|
||||
return 'table table-header'
|
||||
}
|
||||
|
||||
stream.skipToEnd()
|
||||
return null
|
||||
},
|
||||
electricChars: baseMode.electricChars,
|
||||
innerMode: function(state) {
|
||||
if (state.fencedMode) {
|
||||
return {
|
||||
mode: state.fencedMode,
|
||||
state: state.fencedState
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
mode: baseMode,
|
||||
state: state.baseState
|
||||
}
|
||||
}
|
||||
},
|
||||
blankLine: function(state) {
|
||||
state.inTable = false
|
||||
|
||||
if (state.fencedMode) {
|
||||
return state.fencedMode.blankLine && state.fencedMode.blankLine(state.fencedState)
|
||||
} else {
|
||||
return baseMode.blankLine(state.baseState)
|
||||
}
|
||||
}
|
||||
},
|
||||
'yaml-frontmatter'
|
||||
)
|
||||
}
|
||||
}, 'yaml-frontmatter')
|
||||
|
||||
CodeMirror.defineMIME('text/x-bfm', 'bfm')
|
||||
|
||||
@@ -259,4 +225,4 @@
|
||||
mime: 'text/x-bfm',
|
||||
mode: 'bfm'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -72,7 +72,7 @@
|
||||
border-left-color: rgba(142, 142, 142, 0.5);
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
|
||||
.CodeMirror-scroll {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
@@ -116,7 +116,7 @@
|
||||
<script src="../extra_scripts/codemirror/mode/gfm/gfm.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/search/search.js"></script>
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
<script src="../extra_scripts/codemirror/mode/gfm/gfm.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/search/search.js"></script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "boost",
|
||||
"productName": "Boostnote",
|
||||
"version": "0.15.3",
|
||||
"version": "0.16.0",
|
||||
"main": "index.js",
|
||||
"description": "Boostnote",
|
||||
"license": "GPL-3.0",
|
||||
@@ -61,6 +61,7 @@
|
||||
"chart.js": "^2.7.2",
|
||||
"codemirror": "^5.40.2",
|
||||
"codemirror-mode-elixir": "^1.1.1",
|
||||
"command-exists": "^1.2.9",
|
||||
"connected-react-router": "^6.4.0",
|
||||
"electron-config": "^1.0.0",
|
||||
"electron-gh-releases": "^2.0.4",
|
||||
@@ -79,7 +80,7 @@
|
||||
"js-yaml": "^3.13.1",
|
||||
"jsonlint-mod": "^1.7.4",
|
||||
"katex": "^0.10.1",
|
||||
"lodash": "^4.17.13",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash-move": "^1.1.1",
|
||||
"markdown-it": "^6.0.1",
|
||||
"markdown-it-abbr": "^1.0.4",
|
||||
@@ -95,7 +96,7 @@
|
||||
"markdown-it-sup": "^1.0.0",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"mdurl": "^1.0.1",
|
||||
"mermaid": "^8.4.2",
|
||||
"mermaid": "^8.5.2",
|
||||
"moment": "^2.10.3",
|
||||
"mousetrap": "^1.6.2",
|
||||
"mousetrap-global-bind": "^1.1.0",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
"jsxSingleQuote": true
|
||||
}
|
||||
10
readme.md
10
readme.md
@@ -1,10 +1,10 @@
|
||||
> [We've launched desktop and mobile app of the new Boost Note now.](https://github.com/BoostIO/BoostNote.next)
|
||||
|
||||
> ### [Boost Note for Teams](https://hub.boostio.co/)
|
||||
> ### [Boost Note for Teams](https://boosthub.io/)
|
||||
>
|
||||
> We'll launch the clean and simple wiki specially optimized for developers called "Boost Hub" at June 2020!
|
||||
> We've developed a collaborative workspace app called "Boost Hub" for developer teams.
|
||||
>
|
||||
> Boost Hub will aim to be a collaborative wiki tool for teams to centralize and amplify the availability and search ability of both first-party and third-party information.
|
||||
> It's customizable and easy to optimize for your team like rego blocks and even lets you edit documents together in real-time!
|
||||
|
||||

|
||||
|
||||
@@ -53,6 +53,10 @@ Issues on Boostnote can be funded by anyone and the money will be distributed to
|
||||
- [Blog](https://medium.com/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
|
||||
|
||||
- Website: https://boostnote.io
|
||||
|
||||
112
yarn.lock
112
yarn.lock
@@ -1966,6 +1966,11 @@ combined-stream@1.0.6, combined-stream@~1.0.5:
|
||||
dependencies:
|
||||
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:
|
||||
version "2.16.0"
|
||||
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-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"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-5.12.0.tgz#0ddeac879c28c882317cd439b495290acd59ab61"
|
||||
integrity sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==
|
||||
@@ -2626,13 +2668,14 @@ d@1:
|
||||
dependencies:
|
||||
es5-ext "^0.10.9"
|
||||
|
||||
dagre-d3@dagrejs/dagre-d3:
|
||||
version "0.6.4-pre"
|
||||
resolved "https://codeload.github.com/dagrejs/dagre-d3/tar.gz/e1a00e5cb518f5d2304a35647e024f31d178e55b"
|
||||
dagre-d3@^0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/dagre-d3/-/dagre-d3-0.6.4.tgz#0728d5ce7f177ca2337df141ceb60fbe6eeb7b29"
|
||||
integrity sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==
|
||||
dependencies:
|
||||
d3 "^5.12"
|
||||
dagre "^0.8.4"
|
||||
graphlib "^2.1.7"
|
||||
d3 "^5.14"
|
||||
dagre "^0.8.5"
|
||||
graphlib "^2.1.8"
|
||||
lodash "^4.17.15"
|
||||
|
||||
dagre@^0.8.4:
|
||||
@@ -2643,6 +2686,14 @@ dagre@^0.8.4:
|
||||
graphlib "^2.1.7"
|
||||
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:
|
||||
version "1.14.1"
|
||||
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"
|
||||
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:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
|
||||
@@ -4302,6 +4360,13 @@ graphlib@^2.1.7:
|
||||
dependencies:
|
||||
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:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-2.1.1.tgz#3042d9adec2a1ded6a7707a9ed2380f8a17a430e"
|
||||
@@ -4490,7 +4555,7 @@ has@^1.0.1:
|
||||
dependencies:
|
||||
function-bind "^1.0.2"
|
||||
|
||||
he@^1.2.0:
|
||||
he@^1.1.1, he@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
@@ -6108,15 +6173,10 @@ lodash.uniq@^4.5.0:
|
||||
version "4.5.0"
|
||||
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:
|
||||
version "4.17.13"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
|
||||
integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==
|
||||
|
||||
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@^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.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
lodash@~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"
|
||||
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
|
||||
|
||||
mermaid@^8.4.2:
|
||||
version "8.4.2"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.4.2.tgz#91d3d8e9541e72eed7a78d0e882db11564fab3bb"
|
||||
integrity sha512-vYSCP2u4XkOnjliWz/QIYwvzF/znQAq22vWJJ3YV40SnwV2JQyHblnwwNYXCprkXw7XfwBKDpSNaJ3HP4WfnZw==
|
||||
mermaid@^8.5.2:
|
||||
version "8.5.2"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.5.2.tgz#0f1914cda53d4ea5377380e5ce07a38bef2ea7e8"
|
||||
integrity sha512-I+s+8/RzlazF3dGOhDUfU/ERkUV4zfIlTWb3703jNx+2lfACs+4AdY9ULQaw6BPWzW3gB+XlXFOOX/m/vqujIA==
|
||||
dependencies:
|
||||
"@braintree/sanitize-url" "^3.1.0"
|
||||
crypto-random-string "^3.0.1"
|
||||
d3 "^5.7.0"
|
||||
dagre "^0.8.4"
|
||||
dagre-d3 dagrejs/dagre-d3
|
||||
dagre-d3 "^0.6.4"
|
||||
entity-decode "^2.0.2"
|
||||
graphlib "^2.1.7"
|
||||
he "^1.2.0"
|
||||
lodash "^4.17.11"
|
||||
minify "^4.1.1"
|
||||
moment-mini "^2.22.1"
|
||||
prettier "^1.18.2"
|
||||
scope-css "^1.2.1"
|
||||
|
||||
methods@~1.1.2:
|
||||
@@ -10027,8 +10086,9 @@ websocket-driver@>=0.5.1:
|
||||
websocket-extensions ">=0.1.1"
|
||||
|
||||
websocket-extensions@>=0.1.1:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
|
||||
version "0.1.4"
|
||||
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:
|
||||
version "1.0.0"
|
||||
|
||||
Reference in New Issue
Block a user