mirror of
https://github.com/BoostIo/Boostnote
synced 2026-02-08 21:41:37 +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 => {
|
||||
|
||||
Reference in New Issue
Block a user