mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
Merge branch 'master' into export-yfm
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -17,7 +17,7 @@
|
||||
"${workspaceFolder}/index.js"
|
||||
],
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modeules/.bin/electron.cmd"
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
72
Backers.md
72
Backers.md
@@ -1,72 +0,0 @@
|
||||
<h1 align="center">Sponsors & Backers</h1>
|
||||
|
||||
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. If you'd like to join them, please consider:
|
||||
|
||||
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
|
||||
|
||||
---
|
||||
|
||||
## Backers via OpenCollective
|
||||
|
||||
### [Gold Sponsors / $1,000 per month](https://opencollective.com/boostnoteio/order/2259)
|
||||
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
|
||||
|
||||
### [Silver Sponsors / $250 per month](https://opencollective.com/boostnoteio/order/2257)
|
||||
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
|
||||
|
||||
### [Bronze Sponsors / $50 per month](https://opencollective.com/boostnoteio/order/2258)
|
||||
- Get your name and Url (or E-mail) on Readme.md on GitHub.
|
||||
|
||||
### [Backers3 / $10 per month](https://opencollective.com/boostnoteio/order/2176)
|
||||
- [Ralph03](https://opencollective.com/ralph03)
|
||||
|
||||
- [Nikolas Dan](https://opencollective.com/nikolas-dan)
|
||||
|
||||
### [Backers2 / $5 per month](https://opencollective.com/boostnoteio/order/2175)
|
||||
- [Yeojong Kim](https://twitter.com/yeojoy)
|
||||
|
||||
- [Scotia Draven](https://opencollective.com/scotia-draven)
|
||||
|
||||
- [A. J. Vargas](https://opencollective.com/aj-vargas)
|
||||
|
||||
### [Backers1](https://opencollective.com/boostnoteio/order/2563) and One-time sponsors
|
||||
- Ryosuke Tamura - $30
|
||||
|
||||
- tatoosh11 - $10
|
||||
|
||||
- Alexander Borovkov - $10
|
||||
|
||||
- spoonhoop - $5
|
||||
|
||||
- Drew Williams - $2
|
||||
|
||||
- Andy Shaw - $2
|
||||
|
||||
- mysafesky -$2
|
||||
|
||||
---
|
||||
|
||||
## Backers via Bountysource
|
||||
https://salt.bountysource.com/teams/boostnote
|
||||
|
||||
- Kuzz - $65
|
||||
|
||||
- Intense Raiden - $45
|
||||
|
||||
- ravy22 - $25
|
||||
|
||||
- trentpolack - $20
|
||||
|
||||
- hikariru - $10
|
||||
|
||||
- kolchan11 - $10
|
||||
|
||||
- RonWalker22 - $10
|
||||
|
||||
- hocchuc - $5
|
||||
|
||||
- Adam - $5
|
||||
|
||||
- Steve - $5
|
||||
|
||||
- evmin - $5
|
||||
29
FAQ.md
Normal file
29
FAQ.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
|
||||
<details><summary>Allowing dangerous HTML tags</summary>
|
||||
|
||||
Sometimes it is useful to allow dangerous HTML tags to add interactivity to your notebook. One of the example is to use details/summary as a way to expand/collaps your todo-list.
|
||||
|
||||
* How to enable:
|
||||
* Go to **Preferences** → **Interface** → **Sanitization** → **Allow dangerous html tags**
|
||||
* Example note: Multiple todo-list
|
||||
* Create new notes
|
||||
* Paste the below code, and you'll see that you can expand/collaps the todo-list, and you can have multiple todo-list in your note.
|
||||
|
||||
```html
|
||||
<details><summary>What I want to do</summary>
|
||||
|
||||
- [x] Create an awesome feature X
|
||||
- [ ] Do my homework
|
||||
|
||||
</details>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Other questions
|
||||
|
||||
You can ask [here][ISSUES]
|
||||
|
||||
[ISSUES]: https://github.com/BoostIO/Boostnote/issues
|
||||
@@ -5,7 +5,11 @@ import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||
import convertModeName from 'browser/lib/convertModeName'
|
||||
import { options, TableEditor, Alignment } from '@susisu/mte-kernel'
|
||||
import {
|
||||
options,
|
||||
TableEditor,
|
||||
Alignment
|
||||
} from '@susisu/mte-kernel'
|
||||
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import iconv from 'iconv-lite'
|
||||
@@ -13,17 +17,25 @@ import crypto from 'crypto'
|
||||
import consts from 'browser/lib/consts'
|
||||
import styles from '../components/CodeEditor.styl'
|
||||
import fs from 'fs'
|
||||
const { ipcRenderer, remote } = require('electron')
|
||||
const { ipcRenderer, remote, clipboard } = require('electron')
|
||||
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||
const spellcheck = require('browser/lib/spellcheck')
|
||||
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
|
||||
import TurndownService from 'turndown'
|
||||
import { gfm } from 'turndown-plugin-gfm'
|
||||
import {
|
||||
gfm
|
||||
} from 'turndown-plugin-gfm'
|
||||
|
||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||
|
||||
const buildCMRulers = (rulers, enableRulers) =>
|
||||
(enableRulers ? rulers.map(ruler => ({ column: ruler })) : [])
|
||||
(enableRulers ? rulers.map(ruler => ({
|
||||
column: ruler
|
||||
})) : [])
|
||||
|
||||
function translateHotkey (hotkey) {
|
||||
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
|
||||
}
|
||||
|
||||
export default class CodeEditor extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -34,6 +46,7 @@ export default class CodeEditor extends React.Component {
|
||||
trailing: true
|
||||
})
|
||||
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
|
||||
this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject)
|
||||
this.focusHandler = () => {
|
||||
ipcRenderer.send('editor:focused', true)
|
||||
}
|
||||
@@ -49,14 +62,21 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
this.props.onBlur != null && this.props.onBlur(e)
|
||||
|
||||
const { storageKey, noteKey } = this.props
|
||||
const {
|
||||
storageKey,
|
||||
noteKey
|
||||
} = this.props
|
||||
attachmentManagement.deleteAttachmentsNotPresentInNote(
|
||||
this.editor.getValue(),
|
||||
storageKey,
|
||||
noteKey
|
||||
)
|
||||
}
|
||||
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||
this.pasteHandler = (editor, e) => {
|
||||
e.preventDefault()
|
||||
|
||||
this.handlePaste(editor, false)
|
||||
}
|
||||
this.loadStyleHandler = e => {
|
||||
this.editor.refresh()
|
||||
}
|
||||
@@ -115,7 +135,9 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
handleFormatTable () {
|
||||
this.tableEditor.formatAll(options({textWidthOptions: {}}))
|
||||
this.tableEditor.formatAll(options({
|
||||
textWidthOptions: {}
|
||||
}))
|
||||
}
|
||||
|
||||
handleEditorActivity () {
|
||||
@@ -124,42 +146,9 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
updateTableEditorState () {
|
||||
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
|
||||
if (active) {
|
||||
if (this.extraKeysMode !== 'editor') {
|
||||
this.extraKeysMode = 'editor'
|
||||
this.editor.setOption('extraKeys', this.editorKeyMap)
|
||||
}
|
||||
} else {
|
||||
if (this.extraKeysMode !== 'default') {
|
||||
this.extraKeysMode = 'default'
|
||||
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
||||
this.tableEditor.resetSmartCursor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { rulers, enableRulers, switchPreview } = this.props
|
||||
updateDefaultKeyMap () {
|
||||
const { hotkey } = this.props
|
||||
const expandSnippet = this.expandSnippet.bind(this)
|
||||
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
||||
|
||||
const defaultSnippet = [
|
||||
{
|
||||
id: crypto.randomBytes(16).toString('hex'),
|
||||
name: 'Dummy text',
|
||||
prefix: ['lorem', 'ipsum'],
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||
}
|
||||
]
|
||||
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
||||
fs.writeFileSync(
|
||||
consts.SNIPPET_FILE,
|
||||
JSON.stringify(defaultSnippet, null, 4),
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
|
||||
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
|
||||
Tab: function (cm) {
|
||||
@@ -202,6 +191,9 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
},
|
||||
'Cmd-Left': function (cm) {
|
||||
cm.execCommand('goLineLeft')
|
||||
},
|
||||
'Cmd-T': function (cm) {
|
||||
// Do nothing
|
||||
},
|
||||
@@ -211,13 +203,56 @@ export default class CodeEditor extends React.Component {
|
||||
document.execCommand('copy')
|
||||
}
|
||||
return CodeMirror.Pass
|
||||
},
|
||||
[translateHotkey(hotkey.pasteSmartly)]: cm => {
|
||||
this.handlePaste(cm, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateTableEditorState () {
|
||||
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
|
||||
if (active) {
|
||||
if (this.extraKeysMode !== 'editor') {
|
||||
this.extraKeysMode = 'editor'
|
||||
this.editor.setOption('extraKeys', this.editorKeyMap)
|
||||
}
|
||||
} else {
|
||||
if (this.extraKeysMode !== 'default') {
|
||||
this.extraKeysMode = 'default'
|
||||
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
||||
this.tableEditor.resetSmartCursor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { rulers, enableRulers } = this.props
|
||||
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
||||
|
||||
const defaultSnippet = [
|
||||
{
|
||||
id: crypto.randomBytes(16).toString('hex'),
|
||||
name: 'Dummy text',
|
||||
prefix: ['lorem', 'ipsum'],
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||
}
|
||||
]
|
||||
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
||||
fs.writeFileSync(
|
||||
consts.SNIPPET_FILE,
|
||||
JSON.stringify(defaultSnippet, null, 4),
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
|
||||
this.updateDefaultKeyMap()
|
||||
|
||||
this.value = this.props.value
|
||||
this.editor = CodeMirror(this.refs.root, {
|
||||
rulers: buildCMRulers(rulers, enableRulers),
|
||||
value: this.props.value,
|
||||
linesHighlighted: this.props.linesHighlighted,
|
||||
lineNumbers: this.props.displayLineNumbers,
|
||||
lineWrapping: true,
|
||||
theme: this.props.theme,
|
||||
@@ -231,9 +266,9 @@ export default class CodeEditor extends React.Component {
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
autoCloseBrackets: {
|
||||
pairs: '()[]{}\'\'""$$**``',
|
||||
triples: '```"""\'\'\'',
|
||||
explode: '[]{}``$$',
|
||||
pairs: this.props.matchingPairs,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs,
|
||||
override: true
|
||||
},
|
||||
extraKeys: this.defaultKeyMap
|
||||
@@ -244,8 +279,9 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.on('focus', this.focusHandler)
|
||||
this.editor.on('blur', this.blurHandler)
|
||||
this.editor.on('change', this.changeHandler)
|
||||
this.editor.on('gutterClick', this.highlightHandler)
|
||||
this.editor.on('paste', this.pasteHandler)
|
||||
if (switchPreview !== 'RIGHTCLICK') {
|
||||
if (this.props.switchPreview !== 'RIGHTCLICK') {
|
||||
this.editor.on('contextmenu', this.contextMenuHandler)
|
||||
}
|
||||
eventEmitter.on('top:search', this.searchHandler)
|
||||
@@ -275,43 +311,117 @@ export default class CodeEditor extends React.Component {
|
||||
})
|
||||
|
||||
this.editorKeyMap = CodeMirror.normalizeKeyMap({
|
||||
'Tab': () => { this.tableEditor.nextCell(this.tableEditorOptions) },
|
||||
'Shift-Tab': () => { this.tableEditor.previousCell(this.tableEditorOptions) },
|
||||
'Enter': () => { this.tableEditor.nextRow(this.tableEditorOptions) },
|
||||
'Ctrl-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) },
|
||||
'Cmd-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) },
|
||||
'Ctrl-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) },
|
||||
'Cmd-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) },
|
||||
'Ctrl-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) },
|
||||
'Cmd-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) },
|
||||
'Ctrl-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) },
|
||||
'Cmd-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) },
|
||||
'Ctrl-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) },
|
||||
'Cmd-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) },
|
||||
'Ctrl-K Ctrl-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) },
|
||||
'Cmd-K Cmd-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) },
|
||||
'Ctrl-L Ctrl-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) },
|
||||
'Cmd-L Cmd-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) },
|
||||
'Ctrl-K Ctrl-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) },
|
||||
'Cmd-K Cmd-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) },
|
||||
'Ctrl-L Ctrl-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) },
|
||||
'Cmd-L Cmd-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) }
|
||||
'Tab': () => {
|
||||
this.tableEditor.nextCell(this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Tab': () => {
|
||||
this.tableEditor.previousCell(this.tableEditorOptions)
|
||||
},
|
||||
'Enter': () => {
|
||||
this.tableEditor.nextRow(this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-Enter': () => {
|
||||
this.tableEditor.escape(this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-Enter': () => {
|
||||
this.tableEditor.escape(this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Ctrl-Left': () => {
|
||||
this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Cmd-Left': () => {
|
||||
this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Ctrl-Right': () => {
|
||||
this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Cmd-Right': () => {
|
||||
this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Ctrl-Up': () => {
|
||||
this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Cmd-Up': () => {
|
||||
this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Ctrl-Down': () => {
|
||||
this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
|
||||
},
|
||||
'Shift-Cmd-Down': () => {
|
||||
this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-Left': () => {
|
||||
this.tableEditor.moveFocus(0, -1, this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-Left': () => {
|
||||
this.tableEditor.moveFocus(0, -1, this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-Right': () => {
|
||||
this.tableEditor.moveFocus(0, 1, this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-Right': () => {
|
||||
this.tableEditor.moveFocus(0, 1, this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-Up': () => {
|
||||
this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-Up': () => {
|
||||
this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-Down': () => {
|
||||
this.tableEditor.moveFocus(1, 0, this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-Down': () => {
|
||||
this.tableEditor.moveFocus(1, 0, this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-K Ctrl-I': () => {
|
||||
this.tableEditor.insertRow(this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-K Cmd-I': () => {
|
||||
this.tableEditor.insertRow(this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-L Ctrl-I': () => {
|
||||
this.tableEditor.deleteRow(this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-L Cmd-I': () => {
|
||||
this.tableEditor.deleteRow(this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-K Ctrl-J': () => {
|
||||
this.tableEditor.insertColumn(this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-K Cmd-J': () => {
|
||||
this.tableEditor.insertColumn(this.tableEditorOptions)
|
||||
},
|
||||
'Ctrl-L Ctrl-J': () => {
|
||||
this.tableEditor.deleteColumn(this.tableEditorOptions)
|
||||
},
|
||||
'Cmd-L Cmd-J': () => {
|
||||
this.tableEditor.deleteColumn(this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Ctrl-Left': () => {
|
||||
this.tableEditor.moveColumn(-1, this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Cmd-Left': () => {
|
||||
this.tableEditor.moveColumn(-1, this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Ctrl-Right': () => {
|
||||
this.tableEditor.moveColumn(1, this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Cmd-Right': () => {
|
||||
this.tableEditor.moveColumn(1, this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Ctrl-Up': () => {
|
||||
this.tableEditor.moveRow(-1, this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Cmd-Up': () => {
|
||||
this.tableEditor.moveRow(-1, this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Ctrl-Down': () => {
|
||||
this.tableEditor.moveRow(1, this.tableEditorOptions)
|
||||
},
|
||||
'Alt-Shift-Cmd-Down': () => {
|
||||
this.tableEditor.moveRow(1, this.tableEditorOptions)
|
||||
}
|
||||
})
|
||||
|
||||
if (this.props.enableTableEditor) {
|
||||
@@ -322,6 +432,8 @@ export default class CodeEditor extends React.Component {
|
||||
this.setState({
|
||||
clientWidth: this.refs.root.clientWidth
|
||||
})
|
||||
|
||||
this.initialHighlighting()
|
||||
}
|
||||
|
||||
expandSnippet (line, cursor, cm, snippets) {
|
||||
@@ -398,8 +510,14 @@ export default class CodeEditor extends React.Component {
|
||||
return {
|
||||
text: wordBeforeCursor,
|
||||
range: {
|
||||
from: { line: lineNumber, ch: originCursorPosition },
|
||||
to: { line: lineNumber, ch: cursorPosition }
|
||||
from: {
|
||||
line: lineNumber,
|
||||
ch: originCursorPosition
|
||||
},
|
||||
to: {
|
||||
line: lineNumber,
|
||||
ch: cursorPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,7 +543,10 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
let needRefresh = false
|
||||
const { rulers, enableRulers } = this.props
|
||||
const {
|
||||
rulers,
|
||||
enableRulers
|
||||
} = this.props
|
||||
if (prevProps.mode !== this.props.mode) {
|
||||
this.setMode(this.props.mode)
|
||||
}
|
||||
@@ -466,6 +587,18 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
||||
}
|
||||
|
||||
if (prevProps.matchingPairs !== this.props.matchingPairs ||
|
||||
prevProps.matchingTriples !== this.props.matchingTriples ||
|
||||
prevProps.explodingPairs !== this.props.explodingPairs) {
|
||||
const bracketObject = {
|
||||
pairs: this.props.matchingPairs,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs,
|
||||
override: true
|
||||
}
|
||||
this.editor.setOption('autoCloseBrackets', bracketObject)
|
||||
}
|
||||
|
||||
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
|
||||
if (this.props.enableTableEditor) {
|
||||
this.editor.on('cursorActivity', this.editorActivityHandler)
|
||||
@@ -479,6 +612,14 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
||||
}
|
||||
|
||||
if (prevProps.hotkey !== this.props.hotkey) {
|
||||
this.updateDefaultKeyMap()
|
||||
|
||||
if (this.extraKeysMode === 'default') {
|
||||
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.clientWidth !== this.refs.root.clientWidth) {
|
||||
this.setState({
|
||||
clientWidth: this.refs.root.clientWidth
|
||||
@@ -490,7 +631,7 @@ export default class CodeEditor extends React.Component {
|
||||
if (prevProps.spellCheck !== this.props.spellCheck) {
|
||||
if (this.props.spellCheck === false) {
|
||||
spellcheck.setLanguage(this.editor, spellcheck.SPELLCHECK_DISABLED)
|
||||
let elem = document.getElementById('editor-bottom-panel')
|
||||
const elem = document.getElementById('editor-bottom-panel')
|
||||
elem.parentNode.removeChild(elem)
|
||||
} else {
|
||||
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
|
||||
@@ -512,12 +653,96 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
handleChange (editor, changeObject) {
|
||||
spellcheck.handleChange(editor, changeObject)
|
||||
|
||||
this.updateHighlight(editor, changeObject)
|
||||
|
||||
this.value = editor.getValue()
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(editor)
|
||||
}
|
||||
}
|
||||
|
||||
incrementLines (start, linesAdded, linesRemoved, editor) {
|
||||
let highlightedLines = editor.options.linesHighlighted
|
||||
|
||||
const totalHighlightedLines = highlightedLines.length
|
||||
|
||||
let offset = linesAdded - linesRemoved
|
||||
|
||||
// Store new items to be added as we're changing the lines
|
||||
let newLines = []
|
||||
|
||||
let i = totalHighlightedLines
|
||||
|
||||
while (i--) {
|
||||
const lineNumber = highlightedLines[i]
|
||||
|
||||
// Interval that will need to be updated
|
||||
// Between start and (start + offset) remove highlight
|
||||
if (lineNumber >= start) {
|
||||
highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)
|
||||
|
||||
// Lines that need to be relocated
|
||||
if (lineNumber >= (start + linesRemoved)) {
|
||||
newLines.push(lineNumber + offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adding relocated lines
|
||||
highlightedLines.push(...newLines)
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(editor)
|
||||
}
|
||||
}
|
||||
|
||||
handleHighlight (editor, changeObject) {
|
||||
const lines = editor.options.linesHighlighted
|
||||
|
||||
if (!lines.includes(changeObject)) {
|
||||
lines.push(changeObject)
|
||||
editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
|
||||
} else {
|
||||
lines.splice(lines.indexOf(changeObject), 1)
|
||||
editor.removeLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
|
||||
}
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(editor)
|
||||
}
|
||||
}
|
||||
|
||||
updateHighlight (editor, changeObject) {
|
||||
const linesAdded = changeObject.text.length - 1
|
||||
const linesRemoved = changeObject.removed.length - 1
|
||||
|
||||
// If no lines added or removed return
|
||||
if (linesAdded === 0 && linesRemoved === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let start = changeObject.from.line
|
||||
|
||||
switch (changeObject.origin) {
|
||||
case '+insert", "undo':
|
||||
start += 1
|
||||
break
|
||||
|
||||
case 'paste':
|
||||
case '+delete':
|
||||
case '+input':
|
||||
if (changeObject.to.ch !== 0 || changeObject.from.ch !== 0) {
|
||||
start += 1
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
this.incrementLines(start, linesAdded, linesRemoved, editor)
|
||||
}
|
||||
|
||||
moveCursorTo (row, col) {}
|
||||
|
||||
scrollToLine (event, num) {
|
||||
@@ -542,6 +767,7 @@ export default class CodeEditor extends React.Component {
|
||||
this.value = this.props.value
|
||||
this.editor.setValue(this.props.value)
|
||||
this.editor.clearHistory()
|
||||
this.restartHighlighting()
|
||||
this.editor.on('change', this.changeHandler)
|
||||
this.editor.refresh()
|
||||
}
|
||||
@@ -554,7 +780,10 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
handleDropImage (dropEvent) {
|
||||
dropEvent.preventDefault()
|
||||
const { storageKey, noteKey } = this.props
|
||||
const {
|
||||
storageKey,
|
||||
noteKey
|
||||
} = this.props
|
||||
attachmentManagement.handleAttachmentDrop(
|
||||
this,
|
||||
storageKey,
|
||||
@@ -567,53 +796,101 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.replaceSelection(imageMd)
|
||||
}
|
||||
|
||||
handlePaste (editor, e) {
|
||||
const clipboardData = e.clipboardData
|
||||
const { storageKey, noteKey } = this.props
|
||||
const dataTransferItem = clipboardData.items[0]
|
||||
const pastedTxt = clipboardData.getData('text')
|
||||
handlePaste (editor, forceSmartPaste) {
|
||||
const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props
|
||||
|
||||
const isURL = str => {
|
||||
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
||||
return matcher.test(str)
|
||||
}
|
||||
|
||||
const isInLinkTag = editor => {
|
||||
const startCursor = editor.getCursor('start')
|
||||
const prevChar = editor.getRange(
|
||||
{ line: startCursor.line, ch: startCursor.ch - 2 },
|
||||
{ line: startCursor.line, ch: startCursor.ch }
|
||||
)
|
||||
const prevChar = editor.getRange({
|
||||
line: startCursor.line,
|
||||
ch: startCursor.ch - 2
|
||||
}, {
|
||||
line: startCursor.line,
|
||||
ch: startCursor.ch
|
||||
})
|
||||
const endCursor = editor.getCursor('end')
|
||||
const nextChar = editor.getRange(
|
||||
{ line: endCursor.line, ch: endCursor.ch },
|
||||
{ line: endCursor.line, ch: endCursor.ch + 1 }
|
||||
)
|
||||
const nextChar = editor.getRange({
|
||||
line: endCursor.line,
|
||||
ch: endCursor.ch
|
||||
}, {
|
||||
line: endCursor.line,
|
||||
ch: endCursor.ch + 1
|
||||
})
|
||||
return prevChar === '](' && nextChar === ')'
|
||||
}
|
||||
|
||||
const pastedHtml = clipboardData.getData('text/html')
|
||||
if (pastedHtml !== '') {
|
||||
this.handlePasteHtml(e, editor, pastedHtml)
|
||||
} else if (dataTransferItem.type.match('image')) {
|
||||
attachmentManagement.handlePastImageEvent(
|
||||
this,
|
||||
storageKey,
|
||||
noteKey,
|
||||
dataTransferItem
|
||||
)
|
||||
} else if (
|
||||
this.props.fetchUrlTitle &&
|
||||
isURL(pastedTxt) &&
|
||||
!isInLinkTag(editor)
|
||||
) {
|
||||
this.handlePasteUrl(e, editor, pastedTxt)
|
||||
const isInFencedCodeBlock = editor => {
|
||||
const cursor = editor.getCursor()
|
||||
|
||||
let token = editor.getTokenAt(cursor)
|
||||
if (token.state.fencedState) {
|
||||
return true
|
||||
}
|
||||
|
||||
let line = line = cursor.line - 1
|
||||
while (line >= 0) {
|
||||
token = editor.getTokenAt({
|
||||
ch: 3,
|
||||
line
|
||||
})
|
||||
|
||||
if (token.start === token.end) {
|
||||
--line
|
||||
} else if (token.type === 'comment') {
|
||||
if (line > 0) {
|
||||
token = editor.getTokenAt({
|
||||
ch: 3,
|
||||
line: line - 1
|
||||
})
|
||||
|
||||
return token.type !== 'comment'
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||
|
||||
const pastedTxt = clipboard.readText()
|
||||
|
||||
if (isInFencedCodeBlock(editor)) {
|
||||
this.handlePasteText(editor, pastedTxt)
|
||||
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
||||
this.handlePasteUrl(editor, pastedTxt)
|
||||
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||
attachmentManagement
|
||||
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
||||
.then(modifiedText => {
|
||||
this.editor.replaceSelection(modifiedText)
|
||||
})
|
||||
e.preventDefault()
|
||||
} else {
|
||||
const image = clipboard.readImage()
|
||||
if (!image.isEmpty()) {
|
||||
attachmentManagement.handlePastNativeImage(
|
||||
this,
|
||||
storageKey,
|
||||
noteKey,
|
||||
image
|
||||
)
|
||||
} else if (enableSmartPaste || forceSmartPaste) {
|
||||
const pastedHtml = clipboard.readHTML()
|
||||
if (pastedHtml.length > 0) {
|
||||
this.handlePasteHtml(editor, pastedHtml)
|
||||
} else {
|
||||
this.handlePasteText(editor, pastedTxt)
|
||||
}
|
||||
} else {
|
||||
this.handlePasteText(editor, pastedTxt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,8 +900,7 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handlePasteUrl (e, editor, pastedTxt) {
|
||||
e.preventDefault()
|
||||
handlePasteUrl (editor, pastedTxt) {
|
||||
const taggedUrl = `<${pastedTxt}>`
|
||||
editor.replaceSelection(taggedUrl)
|
||||
|
||||
@@ -663,12 +939,15 @@ export default class CodeEditor extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handlePasteHtml (e, editor, pastedHtml) {
|
||||
e.preventDefault()
|
||||
handlePasteHtml (editor, pastedHtml) {
|
||||
const markdown = this.turndownService.turndown(pastedHtml)
|
||||
editor.replaceSelection(markdown)
|
||||
}
|
||||
|
||||
handlePasteText (editor, pastedTxt) {
|
||||
editor.replaceSelection(pastedTxt)
|
||||
}
|
||||
|
||||
mapNormalResponse (response, pastedTxt) {
|
||||
return this.decodeResponse(response).then(body => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -689,6 +968,29 @@ export default class CodeEditor extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
initialHighlighting () {
|
||||
if (this.editor.options.linesHighlighted == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const totalHighlightedLines = this.editor.options.linesHighlighted.length
|
||||
const totalAvailableLines = this.editor.lineCount()
|
||||
|
||||
for (let i = 0; i < totalHighlightedLines; i++) {
|
||||
const lineNumber = this.editor.options.linesHighlighted[i]
|
||||
if (lineNumber > totalAvailableLines) {
|
||||
// make sure that we skip the invalid lines althrough this case should not be happened.
|
||||
continue
|
||||
}
|
||||
this.editor.addLineClass(lineNumber, 'text', 'CodeMirror-activeline-background')
|
||||
}
|
||||
}
|
||||
|
||||
restartHighlighting () {
|
||||
this.editor.options.linesHighlighted = this.props.linesHighlighted
|
||||
this.initialHighlighting()
|
||||
}
|
||||
|
||||
mapImageResponse (response, pastedTxt) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
@@ -734,20 +1036,28 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {className, fontSize} = this.props
|
||||
const {
|
||||
className,
|
||||
fontSize
|
||||
} = this.props
|
||||
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
||||
const width = this.props.width
|
||||
return (
|
||||
<div
|
||||
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
|
||||
ref='root'
|
||||
tabIndex='-1'
|
||||
style={{
|
||||
fontFamily,
|
||||
fontSize: fontSize,
|
||||
width: width
|
||||
}}
|
||||
onDrop={e => this.handleDropImage(e)}
|
||||
return (<
|
||||
div className={
|
||||
className == null ? 'CodeEditor' : `CodeEditor ${className}`
|
||||
}
|
||||
ref='root'
|
||||
tabIndex='-1'
|
||||
style={
|
||||
{
|
||||
fontFamily,
|
||||
fontSize: fontSize,
|
||||
width: width
|
||||
}
|
||||
}
|
||||
onDrop={
|
||||
e => this.handleDropImage(e)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||
|
||||
class MarkdownEditor extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -221,6 +222,28 @@ class MarkdownEditor extends React.Component {
|
||||
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
|
||||
}
|
||||
|
||||
handleDropImage (dropEvent) {
|
||||
dropEvent.preventDefault()
|
||||
const { storageKey, noteKey } = this.props
|
||||
|
||||
this.setState({
|
||||
status: 'CODE'
|
||||
}, () => {
|
||||
this.refs.code.focus()
|
||||
|
||||
this.refs.code.editor.execCommand('goDocEnd')
|
||||
this.refs.code.editor.execCommand('goLineEnd')
|
||||
this.refs.code.editor.execCommand('newlineAndIndent')
|
||||
|
||||
attachmentManagement.handleAttachmentDrop(
|
||||
this.refs.code,
|
||||
storageKey,
|
||||
noteKey,
|
||||
dropEvent
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyUp (e) {
|
||||
const keyPressed = this.state.keyPressed
|
||||
keyPressed.delete(e.keyCode)
|
||||
@@ -232,7 +255,7 @@ class MarkdownEditor extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {className, value, config, storageKey, noteKey, getNote} = this.props
|
||||
const {className, value, config, storageKey, noteKey, linesHighlighted, getNote} = this.props
|
||||
|
||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||
@@ -270,14 +293,20 @@ class MarkdownEditor extends React.Component {
|
||||
enableRulers={config.editor.enableRulers}
|
||||
rulers={config.editor.rulers}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
storageKey={storageKey}
|
||||
noteKey={noteKey}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
linesHighlighted={linesHighlighted}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
spellCheck={config.editor.spellcheck}
|
||||
enableSmartPaste={config.editor.enableSmartPaste}
|
||||
hotkey={config.hotkey}
|
||||
switchPreview={config.editor.switchPreview}
|
||||
/>
|
||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||
@@ -314,6 +343,7 @@ class MarkdownEditor extends React.Component {
|
||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||
getNote={getNote}
|
||||
export={config.export}
|
||||
onDrop={(e) => this.handleDropImage(e)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -28,6 +28,8 @@ import uri2path from 'file-uri-to-path'
|
||||
import { remote, shell } from 'electron'
|
||||
import attachmentManagement from '../main/lib/dataApi/attachmentManagement'
|
||||
import filenamify from 'filenamify'
|
||||
import { render } from 'react-dom'
|
||||
import Carousel from 'react-image-carousel'
|
||||
import ConfigManager from '../main/lib/ConfigManager'
|
||||
|
||||
const dialog = remote.dialog
|
||||
@@ -69,7 +71,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||
this.printHandler = () => this.handlePrint()
|
||||
|
||||
this.linkClickHandler = this.handlelinkClick.bind(this)
|
||||
this.linkClickHandler = this.handleLinkClick.bind(this)
|
||||
this.initMarkdown = this.initMarkdown.bind(this)
|
||||
this.initMarkdown()
|
||||
}
|
||||
@@ -224,6 +226,8 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { onDrop } = this.props
|
||||
|
||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||
this.refs.root.contentWindow.document.body.addEventListener(
|
||||
'contextmenu',
|
||||
@@ -261,7 +265,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
)
|
||||
this.refs.root.contentWindow.document.addEventListener(
|
||||
'drop',
|
||||
this.preventImageDroppedHandler
|
||||
onDrop || this.preventImageDroppedHandler
|
||||
)
|
||||
this.refs.root.contentWindow.document.addEventListener(
|
||||
'dragover',
|
||||
@@ -278,6 +282,8 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { onDrop } = this.props
|
||||
|
||||
this.refs.root.contentWindow.document.body.removeEventListener(
|
||||
'contextmenu',
|
||||
this.contextMenuHandler
|
||||
@@ -296,7 +302,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
)
|
||||
this.refs.root.contentWindow.document.removeEventListener(
|
||||
'drop',
|
||||
this.preventImageDroppedHandler
|
||||
onDrop || this.preventImageDroppedHandler
|
||||
)
|
||||
this.refs.root.contentWindow.document.removeEventListener(
|
||||
'dragover',
|
||||
@@ -532,6 +538,34 @@ export default class MarkdownPreview extends React.Component {
|
||||
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme)
|
||||
}
|
||||
)
|
||||
|
||||
_.forEach(
|
||||
this.refs.root.contentWindow.document.querySelectorAll('.gallery'),
|
||||
el => {
|
||||
const images = el.innerHTML.split(/\n/g).filter(i => i.length > 0)
|
||||
el.innerHTML = ''
|
||||
|
||||
const height = el.attributes.getNamedItem('data-height')
|
||||
if (height && height.value !== 'undefined') {
|
||||
el.style.height = height.value + 'vh'
|
||||
}
|
||||
|
||||
let autoplay = el.attributes.getNamedItem('data-autoplay')
|
||||
if (autoplay && autoplay.value !== 'undefined') {
|
||||
autoplay = parseInt(autoplay.value, 10) || 0
|
||||
} else {
|
||||
autoplay = 0
|
||||
}
|
||||
|
||||
render(
|
||||
<Carousel
|
||||
images={images}
|
||||
autoplay={autoplay}
|
||||
/>,
|
||||
el
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
focus () {
|
||||
@@ -574,7 +608,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
return new window.Notification(title, options)
|
||||
}
|
||||
|
||||
handlelinkClick (e) {
|
||||
handleLinkClick (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ class MarkdownSplitEditor extends React.Component {
|
||||
this.refs.code.setValue(value)
|
||||
}
|
||||
|
||||
handleOnChange () {
|
||||
handleOnChange (e) {
|
||||
this.value = this.refs.code.value
|
||||
this.props.onChange()
|
||||
this.props.onChange(e)
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
@@ -136,7 +136,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {config, value, storageKey, noteKey, getNote} = this.props
|
||||
const {config, value, storageKey, noteKey, linesHighlighted, getNote} = this.props
|
||||
const storage = findStorage(storageKey)
|
||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||
@@ -160,6 +160,9 @@ class MarkdownSplitEditor extends React.Component {
|
||||
fontFamily={config.editor.fontFamily}
|
||||
fontSize={editorFontSize}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
indentType={config.editor.indentType}
|
||||
indentSize={editorIndentSize}
|
||||
enableRulers={config.editor.enableRulers}
|
||||
@@ -169,9 +172,12 @@ class MarkdownSplitEditor extends React.Component {
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
storageKey={storageKey}
|
||||
noteKey={noteKey}
|
||||
onChange={this.handleOnChange.bind(this)}
|
||||
linesHighlighted={linesHighlighted}
|
||||
onChange={(e) => this.handleOnChange(e)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
spellCheck={config.editor.spellcheck}
|
||||
enableSmartPaste={config.editor.enableSmartPaste}
|
||||
hotkey={config.hotkey}
|
||||
switchPreview={config.editor.switchPreview}
|
||||
/>
|
||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||
|
||||
@@ -3,19 +3,30 @@
|
||||
flex 1
|
||||
min-width 70px
|
||||
overflow hidden
|
||||
border-left 1px solid $ui-borderColor
|
||||
border-top 1px solid $ui-borderColor
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
.deleteButton
|
||||
color $ui-inactive-text-color
|
||||
&:hover
|
||||
background-color darken($ui-backgroundColor, 15%)
|
||||
&:active
|
||||
color white
|
||||
background-color $ui-active-color
|
||||
color: $ui-text-color
|
||||
visibility visible
|
||||
transition 0.15s
|
||||
.button
|
||||
color: $ui-text-color
|
||||
transition 0.15s
|
||||
|
||||
.root--active
|
||||
@extend .root
|
||||
min-width 100px
|
||||
border-bottom $ui-border
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
.deleteButton
|
||||
visibility visible
|
||||
color: $ui-text-color
|
||||
transition 0.15s
|
||||
.button
|
||||
font-weight bold
|
||||
color: $ui-text-color
|
||||
transition 0.15s
|
||||
|
||||
.button
|
||||
width 100%
|
||||
@@ -27,8 +38,7 @@
|
||||
background-color transparent
|
||||
transition 0.15s
|
||||
border-left 4px solid transparent
|
||||
&:hover
|
||||
background-color $ui-button--hover-backgroundColor
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.deleteButton
|
||||
position absolute
|
||||
@@ -42,6 +52,7 @@
|
||||
color $ui-inactive-text-color
|
||||
background-color transparent
|
||||
border-radius 2px
|
||||
visibility hidden
|
||||
|
||||
.input
|
||||
height 29px
|
||||
@@ -50,76 +61,66 @@
|
||||
width 100%
|
||||
outline none
|
||||
|
||||
body[data-theme="default"], body[data-theme="white"]
|
||||
.root--active
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
color $ui-dark-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
border-top 1px solid $ui-dark-borderColor
|
||||
&:hover
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
transition 0.15s
|
||||
.button
|
||||
color $ui-dark-text-color
|
||||
transition 0.15s
|
||||
.deleteButton
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
transition 0.15s
|
||||
|
||||
.root--active
|
||||
color $ui-dark-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
&:hover
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
.deleteButton
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
border-left 1px solid $ui-dark-borderColor
|
||||
border-top 1px solid $ui-dark-borderColor
|
||||
.button
|
||||
color $ui-dark-text-color
|
||||
.deleteButton
|
||||
color $ui-dark-text-color
|
||||
|
||||
.button
|
||||
border none
|
||||
color $ui-dark-text-color
|
||||
background-color transparent
|
||||
transition color background-color 0.15s
|
||||
border-left 4px solid transparent
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
|
||||
.input
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
.deleteButton
|
||||
color alpha($ui-dark-text-color, 30%)
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
color $ui-solarized-dark-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
.deleteButton
|
||||
color $ui-solarized-dark-text-color
|
||||
&:hover
|
||||
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
|
||||
.root--active
|
||||
color $ui-solarized-dark-text-color
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
transition 0.15s
|
||||
.deleteButton
|
||||
color $ui-solarized-dark-text-color
|
||||
&:hover
|
||||
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-solarized-dark-button--active-color
|
||||
transition 0.15s
|
||||
.button
|
||||
color $ui-solarized-dark-button--active-color
|
||||
transition 0.15s
|
||||
|
||||
.root--active
|
||||
color $ui-solarized-dark-button--active-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.deleteButton
|
||||
color $ui-solarized-dark-button--active-color
|
||||
.button
|
||||
color $ui-solarized-dark-button--active-color
|
||||
|
||||
.button
|
||||
border none
|
||||
@@ -127,101 +128,75 @@ body[data-theme="solarized-dark"]
|
||||
background-color transparent
|
||||
transition color background-color 0.15s
|
||||
border-left 4px solid transparent
|
||||
&:hover
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
.input
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.deleteButton
|
||||
color alpha($ui-solarized-dark-text-color, 30%)
|
||||
color $ui-solarized-dark-button--active-color
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
color $ui-monokai-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
&:hover
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
.deleteButton
|
||||
color $ui-monokai-text-color
|
||||
&:hover
|
||||
background-color darken($ui-monokai-noteDetail-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-monokai-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
|
||||
.root--active
|
||||
color $ui-monokai-text-color
|
||||
border-color $ui-monokai-borderColor
|
||||
&:hover
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
transition 0.15s
|
||||
.deleteButton
|
||||
color $ui-monokai-text-color
|
||||
&:hover
|
||||
background-color darken($ui-monokai-noteDetail-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-monokai-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
transition 0.15s
|
||||
.button
|
||||
color $ui-monokai-text-color
|
||||
transition 0.15s
|
||||
|
||||
.root--active
|
||||
color $ui-monokai-active-color
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
border-color $ui-monokai-borderColor
|
||||
.deleteButton
|
||||
color $ui-monokai-text-color
|
||||
.button
|
||||
color $ui-monokai-active-color
|
||||
|
||||
.button
|
||||
border none
|
||||
color $ui-monokai-text-color
|
||||
color $ui-inactive-text-color
|
||||
background-color transparent
|
||||
transition color background-color 0.15s
|
||||
border-left 4px solid transparent
|
||||
&:hover
|
||||
color $ui-monokai-text-color
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
|
||||
.input
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.deleteButton
|
||||
color alpha($ui-monokai-text-color, 30%)
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
color $ui-dracula-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
&:hover
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
.deleteButton
|
||||
color $ui-dracula-text-color
|
||||
&:hover
|
||||
background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-dracula-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
|
||||
.root--active
|
||||
color $ui-dracula-text-color
|
||||
border-color $ui-dracula-borderColor
|
||||
&:hover
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
transition 0.15s
|
||||
.deleteButton
|
||||
color $ui-dracula-text-color
|
||||
&:hover
|
||||
background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
|
||||
&:active
|
||||
color $ui-dracula-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
transition 0.15s
|
||||
.button
|
||||
color $ui-dracula-text-color
|
||||
transition 0.15s
|
||||
|
||||
.root--active
|
||||
color $ui-dracula-text-color
|
||||
background-color $ui-dracula-button-backgroundColor
|
||||
border-color $ui-dracula-borderColor
|
||||
.deleteButton
|
||||
color $ui-dracula-text-color
|
||||
.button
|
||||
color $ui-dracula-active-color
|
||||
|
||||
.button
|
||||
border none
|
||||
color $ui-dracula-text-color
|
||||
color $ui-inactive-text-color
|
||||
background-color transparent
|
||||
transition color background-color 0.15s
|
||||
border-left 4px solid transparent
|
||||
&:hover
|
||||
color $ui-dracula-text-color
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
.input
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.deleteButton
|
||||
color alpha($ui-dracula-text-color, 30%)
|
||||
color $ui-dracula-text-color
|
||||
@@ -55,11 +55,12 @@ body
|
||||
line-height 1.6
|
||||
overflow-x hidden
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
// do not allow display line breaks
|
||||
.katex-display > .katex
|
||||
white-space nowrap
|
||||
// allow inline line breaks
|
||||
.katex
|
||||
font 400 1.2em 'KaTeX_Main'
|
||||
line-height 1.2em
|
||||
white-space initial
|
||||
text-indent 0
|
||||
.katex .mfrac>.vlist>span:nth-child(2)
|
||||
top 0 !important
|
||||
.katex-error
|
||||
@@ -183,6 +184,10 @@ ul
|
||||
display list-item
|
||||
&.taskListItem
|
||||
list-style none
|
||||
&>input
|
||||
margin-left -1.6em
|
||||
&>p
|
||||
margin-left -1.8em
|
||||
p
|
||||
margin 0
|
||||
&>li>ul, &>li>ol
|
||||
@@ -416,6 +421,26 @@ pre.fence
|
||||
canvas, svg
|
||||
max-width 100% !important
|
||||
|
||||
.gallery
|
||||
width 100%
|
||||
height 50vh
|
||||
|
||||
.carousel
|
||||
.carousel-main img
|
||||
min-width auto
|
||||
max-width 100%
|
||||
min-height auto
|
||||
max-height 100%
|
||||
|
||||
.carousel-footer::-webkit-scrollbar-corner
|
||||
background-color transparent
|
||||
|
||||
.carousel-main, .carousel-footer
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
.prev, .next
|
||||
color $ui-text-color
|
||||
background-color $ui-tag-backgroundColor
|
||||
|
||||
themeDarkBackground = darken(#21252B, 10%)
|
||||
themeDarkText = #f9f9f9
|
||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||
@@ -475,6 +500,14 @@ body[data-theme="dark"]
|
||||
border-color themeDarkBorder
|
||||
background-color themeDarkPreview
|
||||
|
||||
pre.fence
|
||||
.gallery
|
||||
.carousel-main, .carousel-footer
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
.prev, .next
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-tag-backgroundColor
|
||||
|
||||
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
|
||||
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
|
||||
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
|
||||
@@ -510,6 +543,14 @@ body[data-theme="solarized-dark"]
|
||||
border-color themeDarkBorder
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
pre.fence
|
||||
.gallery
|
||||
.carousel-main, .carousel-footer
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
.prev, .next
|
||||
color $ui-solarized-dark-button--active-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
|
||||
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
|
||||
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
||||
themeMonokaiTableHead = themeMonokaiTableEven
|
||||
@@ -538,6 +579,7 @@ body[data-theme="monokai"]
|
||||
border-right solid 1px themeMonokaiTableBorder
|
||||
kbd
|
||||
background-color themeDarkBackground
|
||||
|
||||
dl
|
||||
border-color themeDarkBorder
|
||||
background-color themeMonokaiTableHead
|
||||
@@ -547,6 +589,14 @@ body[data-theme="monokai"]
|
||||
border-color themeDarkBorder
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
|
||||
pre.fence
|
||||
.gallery
|
||||
.carousel-main, .carousel-footer
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
.prev, .next
|
||||
color $ui-monokai-button--active-color
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
|
||||
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
|
||||
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
|
||||
themeDraculaTableHead = themeDraculaTableEven
|
||||
@@ -575,6 +625,7 @@ body[data-theme="dracula"]
|
||||
border-right solid 1px themeDraculaTableBorder
|
||||
kbd
|
||||
background-color themeDarkBackground
|
||||
|
||||
dl
|
||||
border-color themeDarkBorder
|
||||
background-color themeDraculaTableHead
|
||||
@@ -583,3 +634,11 @@ body[data-theme="dracula"]
|
||||
dd
|
||||
border-color themeDarkBorder
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
pre.fence
|
||||
.gallery
|
||||
.carousel-main, .carousel-footer
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
.prev, .next
|
||||
color $ui-dracula-button--active-color
|
||||
background-color $ui-dracula-button-backgroundColor
|
||||
|
||||
@@ -2,51 +2,27 @@
|
||||
* @fileoverview Markdown table of contents generator
|
||||
*/
|
||||
|
||||
import { EOL } from 'os'
|
||||
import toc from 'markdown-toc'
|
||||
import diacritics from 'diacritics-map'
|
||||
import stripColor from 'strip-color'
|
||||
import mdlink from 'markdown-link'
|
||||
import slugify from './slugify'
|
||||
|
||||
const EOL = require('os').EOL
|
||||
const hasProp = Object.prototype.hasOwnProperty
|
||||
|
||||
/**
|
||||
* @caseSensitiveSlugify Custom slugify function
|
||||
* Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js),
|
||||
* but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067
|
||||
* From @enyaxu/markdown-it-anchor
|
||||
*/
|
||||
function caseSensitiveSlugify (str) {
|
||||
function replaceDiacritics (str) {
|
||||
return str.replace(/[À-ž]/g, function (ch) {
|
||||
return diacritics[ch] || ch
|
||||
})
|
||||
}
|
||||
|
||||
function getTitle (str) {
|
||||
if (/^\[[^\]]+\]\(/.test(str)) {
|
||||
var m = /^\[([^\]]+)\]/.exec(str)
|
||||
if (m) return m[1]
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
str = getTitle(str)
|
||||
str = stripColor(str)
|
||||
// str = str.toLowerCase() //let's be case sensitive
|
||||
|
||||
// `.split()` is often (but not always) faster than `.replace()`
|
||||
str = str.split(' ').join('-')
|
||||
str = str.split(/\t/).join('--')
|
||||
str = str.split(/<\/?[^>]+>/).join('')
|
||||
str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('')
|
||||
str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('')
|
||||
str = replaceDiacritics(str)
|
||||
return str
|
||||
function uniqueSlug (slug, slugs, opts) {
|
||||
let uniq = slug
|
||||
let i = opts.uniqueSlugStartIndex
|
||||
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
|
||||
slugs[uniq] = true
|
||||
return uniq
|
||||
}
|
||||
|
||||
function linkify (tok, text, slug, opts) {
|
||||
var uniqeID = opts.num === 0 ? '' : '-' + opts.num
|
||||
tok.content = mdlink(text, '#' + slug + uniqeID)
|
||||
return tok
|
||||
function linkify (token) {
|
||||
token.content = mdlink(token.content, '#' + token.slug)
|
||||
return token
|
||||
}
|
||||
|
||||
const TOC_MARKER_START = '<!-- toc -->'
|
||||
@@ -91,8 +67,23 @@ export function generateInEditor (editor) {
|
||||
* @returns generatedTOC String containing generated TOC
|
||||
*/
|
||||
export function generate (markdownText) {
|
||||
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify, linkify: linkify})
|
||||
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
|
||||
const slugs = {}
|
||||
const opts = {
|
||||
uniqueSlugStartIndex: 1
|
||||
}
|
||||
|
||||
const result = toc(markdownText, {
|
||||
slugify: title => {
|
||||
return uniqueSlug(slugify(title), slugs, opts)
|
||||
},
|
||||
linkify: false
|
||||
})
|
||||
|
||||
const md = toc.bullets(result.json.map(linkify), {
|
||||
highest: result.highest
|
||||
})
|
||||
|
||||
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
|
||||
}
|
||||
|
||||
function wrapTocWithEol (toc, editor) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import katex from 'katex'
|
||||
import { lastFindInArray } from './utils'
|
||||
import anchor from '@enyaxu/markdown-it-anchor'
|
||||
|
||||
function createGutter (str, firstLineNumber) {
|
||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||
@@ -119,14 +118,8 @@ class Markdown {
|
||||
this.md.use(require('markdown-it-imsize'))
|
||||
this.md.use(require('markdown-it-footnote'))
|
||||
this.md.use(require('markdown-it-multimd-table'))
|
||||
this.md.use(anchor, {
|
||||
slugify: (title) => {
|
||||
var slug = encodeURI(title.trim()
|
||||
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||
.replace(/\s+/g, '-'))
|
||||
.replace(/\-+$/, '')
|
||||
return slug
|
||||
}
|
||||
this.md.use(require('@enyaxu/markdown-it-anchor'), {
|
||||
slugify: require('./slugify')
|
||||
})
|
||||
this.md.use(require('markdown-it-kbd'))
|
||||
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
|
||||
@@ -157,6 +150,21 @@ class Markdown {
|
||||
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
gallery: token => {
|
||||
const content = token.content.split('\n').slice(0, -1).map(line => {
|
||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||
if (match) {
|
||||
return match[1]
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
}).join('\n')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="gallery" data-autoplay="${token.parameters.autoplay}" data-height="${token.parameters.height}">${content}</div>
|
||||
</pre>`
|
||||
},
|
||||
mermaid: token => {
|
||||
updatedOptions.onFence('mermaid')
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
|
||||
folder: folder,
|
||||
title: '',
|
||||
tags,
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
})
|
||||
.then(note => {
|
||||
const noteHash = note.key
|
||||
@@ -56,7 +57,8 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
|
||||
{
|
||||
name: '',
|
||||
mode: config.editor.snippetDefaultLanguage || 'text',
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
17
browser/lib/slugify.js
Normal file
17
browser/lib/slugify.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import diacritics from 'diacritics-map'
|
||||
|
||||
function replaceDiacritics (str) {
|
||||
return str.replace(/[À-ž]/g, function (ch) {
|
||||
return diacritics[ch] || ch
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = function slugify (title) {
|
||||
let slug = title.trim()
|
||||
|
||||
slug = replaceDiacritics(slug)
|
||||
|
||||
slug = slug.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
|
||||
|
||||
return encodeURI(slug).replace(/\-+$/, '')
|
||||
}
|
||||
@@ -39,12 +39,14 @@ class MarkdownNoteDetail extends React.Component {
|
||||
isMovingNote: false,
|
||||
note: Object.assign({
|
||||
title: '',
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}, props.note),
|
||||
isLockButtonShown: false,
|
||||
isLocked: false,
|
||||
editorType: props.config.editor.type
|
||||
}
|
||||
|
||||
this.dispatchTimer = null
|
||||
|
||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||
@@ -73,7 +75,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
this.setState({
|
||||
note: Object.assign({}, nextProps.note)
|
||||
note: Object.assign({linesHighlighted: []}, nextProps.note)
|
||||
}, () => {
|
||||
this.refs.content.reload()
|
||||
if (this.refs.tags) this.refs.tags.reset()
|
||||
@@ -367,6 +369,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
@@ -378,6 +381,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
|
||||
@@ -48,7 +48,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
note: Object.assign({
|
||||
description: ''
|
||||
}, props.note, {
|
||||
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||
snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,8 +76,9 @@ class SnippetNoteDetail extends React.Component {
|
||||
const nextNote = Object.assign({
|
||||
description: ''
|
||||
}, nextProps.note, {
|
||||
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||
snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
|
||||
})
|
||||
|
||||
this.setState({
|
||||
snippetIndex: 0,
|
||||
note: nextNote
|
||||
@@ -410,6 +411,8 @@ class SnippetNoteDetail extends React.Component {
|
||||
return (e) => {
|
||||
const snippets = this.state.note.snippets.slice()
|
||||
snippets[index].content = this.refs['code-' + index].value
|
||||
snippets[index].linesHighlighted = e.options.linesHighlighted
|
||||
|
||||
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||
this.setState(state => ({
|
||||
note: state.note
|
||||
@@ -602,7 +605,8 @@ class SnippetNoteDetail extends React.Component {
|
||||
note.snippets = note.snippets.concat([{
|
||||
name: '',
|
||||
mode: config.editor.snippetDefaultLanguage || 'text',
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}])
|
||||
const snippetIndex = note.snippets.length - 1
|
||||
|
||||
@@ -692,10 +696,8 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
const viewList = note.snippets.map((snippet, index) => {
|
||||
const isActive = this.state.snippetIndex === index
|
||||
|
||||
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
|
||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||
|
||||
return <div styleName='tabView'
|
||||
key={index}
|
||||
style={{zIndex: isActive ? 5 : 4}}
|
||||
@@ -704,6 +706,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
? <MarkdownEditor styleName='tabView-content'
|
||||
value={snippet.content}
|
||||
config={config}
|
||||
linesHighlighted={snippet.linesHighlighted}
|
||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||
ref={'code-' + index}
|
||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||
@@ -712,18 +715,24 @@ class SnippetNoteDetail extends React.Component {
|
||||
: <CodeEditor styleName='tabView-content'
|
||||
mode={snippet.mode}
|
||||
value={snippet.content}
|
||||
linesHighlighted={snippet.linesHighlighted}
|
||||
theme={config.editor.theme}
|
||||
fontFamily={config.editor.fontFamily}
|
||||
fontSize={editorFontSize}
|
||||
indentType={config.editor.indentType}
|
||||
indentSize={editorIndentSize}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
keyMap={config.editor.keyMap}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||
ref={'code-' + index}
|
||||
enableSmartPaste={config.editor.enableSmartPaste}
|
||||
hotkey={config.hotkey}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
.tabList
|
||||
absolute left right
|
||||
top 55px
|
||||
top 70px
|
||||
height 30px
|
||||
display flex
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
@@ -57,6 +57,9 @@
|
||||
.tabList .tabButton
|
||||
navWhiteButtonColor()
|
||||
width 30px
|
||||
border-left 1px solid $ui-borderColor
|
||||
border-top 1px solid $ui-borderColor
|
||||
border-right 1px solid $ui-borderColor
|
||||
|
||||
.tabView
|
||||
absolute left right bottom
|
||||
@@ -98,17 +101,34 @@
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="white"]
|
||||
body[data-theme="white"], body[data-theme="default"]
|
||||
.root
|
||||
box-shadow $note-detail-box-shadow
|
||||
border none
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
color $ui-text-color
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-left 1px solid $ui-dark-borderColor
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
box-shadow none
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-dark-borderColor
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
color $ui-dark-text-color
|
||||
transition 0.15s
|
||||
|
||||
.body
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
|
||||
@@ -118,7 +138,6 @@ body[data-theme="dark"]
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
|
||||
.tabList .list
|
||||
@@ -150,6 +169,15 @@ body[data-theme="solarized-dark"]
|
||||
color $ui-solarized-dark-text-color
|
||||
border 1px solid $ui-solarized-dark-borderColor
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
color $ui-solarized-dark-button--active-color
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
transition 0.15s
|
||||
|
||||
.tabList
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
@@ -167,6 +195,14 @@ body[data-theme="monokai"]
|
||||
color $ui-monokai-text-color
|
||||
border 1px solid $ui-monokai-borderColor
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-monokai-borderColor
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
color $ui-monokai-text-color
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
@@ -184,6 +220,14 @@ body[data-theme="dracula"]
|
||||
color $ui-dracula-text-color
|
||||
border 1px solid $ui-dracula-borderColor
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-dracula-borderColor
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
color $ui-dracula-text-color
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
color $ui-dracula-text-color
|
||||
@@ -45,8 +45,14 @@ class TagSelect extends React.Component {
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
value.push(newTag)
|
||||
value = _.uniq(value)
|
||||
|
||||
if (!_.includes(value, newTag)) {
|
||||
value.push(newTag)
|
||||
}
|
||||
|
||||
if (this.props.saveTagsAlphabetically) {
|
||||
value = _.sortBy(value)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
newTag: ''
|
||||
|
||||
@@ -96,12 +96,14 @@ class Main extends React.Component {
|
||||
{
|
||||
name: 'example.html',
|
||||
mode: 'html',
|
||||
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>"
|
||||
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
|
||||
linesHighlighted: []
|
||||
},
|
||||
{
|
||||
name: 'example.js',
|
||||
mode: 'javascript',
|
||||
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)"
|
||||
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)",
|
||||
linesHighlighted: []
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -234,8 +236,8 @@ class Main extends React.Component {
|
||||
if (this.state.isRightSliderFocused) {
|
||||
const offset = this.refs.body.getBoundingClientRect().left
|
||||
let newListWidth = e.pageX - offset
|
||||
if (newListWidth < 10) {
|
||||
newListWidth = 10
|
||||
if (newListWidth < 180) {
|
||||
newListWidth = 180
|
||||
} else if (newListWidth > 600) {
|
||||
newListWidth = 600
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import debounceRender from 'react-debounce-render'
|
||||
import styles from './NoteList.styl'
|
||||
import moment from 'moment'
|
||||
import _ from 'lodash'
|
||||
@@ -766,7 +765,8 @@ class NoteList extends React.Component {
|
||||
type: firstNote.type,
|
||||
folder: folder.key,
|
||||
title: firstNote.title + ' ' + i18n.__('copy'),
|
||||
content: firstNote.content
|
||||
content: firstNote.content,
|
||||
linesHighlighted: firstNote.linesHighlighted
|
||||
})
|
||||
.then((note) => {
|
||||
attachmentManagement.cloneAttachments(firstNote, note)
|
||||
@@ -1184,4 +1184,4 @@ NoteList.propTypes = {
|
||||
})
|
||||
}
|
||||
|
||||
export default debounceRender(CSSModules(NoteList, styles))
|
||||
export default CSSModules(NoteList, styles)
|
||||
|
||||
@@ -6,6 +6,7 @@ import _ from 'lodash'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import NewNoteButton from 'browser/main/NewNoteButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
class TopBar extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -25,6 +26,10 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
this.codeInitHandler = this.handleCodeInit.bind(this)
|
||||
|
||||
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
|
||||
maxWait: 1000 / 8
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -94,7 +99,6 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
handleKeyUp (e) {
|
||||
const { router } = this.context
|
||||
// reset states
|
||||
this.setState({
|
||||
isConfirmTranslation: false
|
||||
@@ -106,21 +110,21 @@ class TopBar extends React.Component {
|
||||
isConfirmTranslation: true
|
||||
})
|
||||
const keyword = this.refs.searchInput.value
|
||||
router.push(`/searched/${encodeURIComponent(keyword)}`)
|
||||
this.setState({
|
||||
search: keyword
|
||||
})
|
||||
this.updateKeyword(keyword)
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
const { router } = this.context
|
||||
const keyword = this.refs.searchInput.value
|
||||
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
|
||||
router.push(`/searched/${encodeURIComponent(keyword)}`)
|
||||
const keyword = this.refs.searchInput.value
|
||||
this.updateKeyword(keyword)
|
||||
} else {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
updateKeyword (keyword) {
|
||||
this.context.router.push(`/searched/${encodeURIComponent(keyword)}`)
|
||||
this.setState({
|
||||
search: keyword
|
||||
})
|
||||
|
||||
@@ -25,7 +25,8 @@ export const DEFAULT_CONFIG = {
|
||||
hotkey: {
|
||||
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
||||
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
|
||||
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace'
|
||||
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
|
||||
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V'
|
||||
},
|
||||
ui: {
|
||||
language: 'en',
|
||||
@@ -44,6 +45,9 @@ export const DEFAULT_CONFIG = {
|
||||
enableRulers: false,
|
||||
rulers: [80, 120],
|
||||
displayLineNumbers: true,
|
||||
matchingPairs: '()[]{}\'\'""$$**``',
|
||||
matchingTriples: '```"""\'\'\'',
|
||||
explodingPairs: '[]{}``$$',
|
||||
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
|
||||
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
|
||||
scrollPastEnd: false,
|
||||
@@ -52,7 +56,8 @@ export const DEFAULT_CONFIG = {
|
||||
enableTableEditor: false,
|
||||
enableFrontMatterTitle: true,
|
||||
frontMatterTitleField: 'title',
|
||||
spellcheck: false
|
||||
spellcheck: false,
|
||||
enableSmartPaste: false
|
||||
},
|
||||
preview: {
|
||||
fontSize: '14',
|
||||
|
||||
@@ -227,7 +227,15 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
|
||||
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
||||
*/
|
||||
function fixLocalURLS (renderedHTML, storagePath) {
|
||||
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', 'g'), function (match) {
|
||||
/*
|
||||
A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`.
|
||||
|
||||
- `STORAGE_FOLDER_PLACEHOLDER` will match `:storage`
|
||||
- `(?:(?:\\\/|%5C)[\\w.]+)+` will match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`
|
||||
- `(?:\\\/|%5C)[\\w.]+` will either match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564` or `/f939b2c3.jpg`
|
||||
- `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows.
|
||||
*/
|
||||
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[\\w.]+)+', 'g'), function (match) {
|
||||
var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
|
||||
return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
||||
})
|
||||
@@ -316,6 +324,44 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
|
||||
reader.readAsDataURL(blob)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code
|
||||
* @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
|
||||
* @param {String} storageKey Key of the current storage
|
||||
* @param {String} noteKey Key of the current note
|
||||
* @param {NativeImage} image The native image
|
||||
*/
|
||||
function handlePastNativeImage (codeEditor, storageKey, noteKey, image) {
|
||||
if (!codeEditor) {
|
||||
throw new Error('codeEditor has to be given')
|
||||
}
|
||||
if (!storageKey) {
|
||||
throw new Error('storageKey has to be given')
|
||||
}
|
||||
|
||||
if (!noteKey) {
|
||||
throw new Error('noteKey has to be given')
|
||||
}
|
||||
if (!image) {
|
||||
throw new Error('image has to be given')
|
||||
}
|
||||
|
||||
const targetStorage = findStorage.findStorage(storageKey)
|
||||
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||
|
||||
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
||||
|
||||
const imageName = `${uniqueSlug()}.png`
|
||||
const imagePath = path.join(destinationDir, imageName)
|
||||
|
||||
const binaryData = image.toPNG()
|
||||
fs.writeFileSync(imagePath, binaryData, 'binary')
|
||||
|
||||
const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName)
|
||||
const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true)
|
||||
codeEditor.insertAttachmentMd(imageMd)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns all attachment paths of the given markdown
|
||||
* @param {String} markdownContent content in which the attachment paths should be found
|
||||
@@ -550,6 +596,7 @@ module.exports = {
|
||||
generateAttachmentMarkdown,
|
||||
handleAttachmentDrop,
|
||||
handlePastImageEvent,
|
||||
handlePastNativeImage,
|
||||
getAttachmentsInMarkdownContent,
|
||||
getAbsolutePathsOfAttachmentsInContent,
|
||||
removeStorageAndNoteReferences,
|
||||
|
||||
@@ -16,6 +16,7 @@ function validateInput (input) {
|
||||
switch (input.type) {
|
||||
case 'MARKDOWN_NOTE':
|
||||
if (!_.isString(input.content)) input.content = ''
|
||||
if (!_.isArray(input.linesHighlighted)) input.linesHighlighted = []
|
||||
break
|
||||
case 'SNIPPET_NOTE':
|
||||
if (!_.isString(input.description)) input.description = ''
|
||||
@@ -23,7 +24,8 @@ function validateInput (input) {
|
||||
input.snippets = [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}]
|
||||
}
|
||||
break
|
||||
|
||||
@@ -9,7 +9,8 @@ function createSnippet (snippetFile) {
|
||||
id: crypto.randomBytes(16).toString('hex'),
|
||||
name: 'Unnamed snippet',
|
||||
prefix: [],
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
fetchSnippet(null, snippetFile).then((snippets) => {
|
||||
snippets.push(newSnippet)
|
||||
|
||||
@@ -69,7 +69,8 @@ function importAll (storage, data) {
|
||||
isStarred: false,
|
||||
title: article.title,
|
||||
content: '# ' + article.title + '\n\n' + article.content,
|
||||
key: noteKey
|
||||
key: noteKey,
|
||||
linesHighlighted: article.linesHighlighted
|
||||
}
|
||||
notes.push(newNote)
|
||||
} else {
|
||||
@@ -87,7 +88,8 @@ function importAll (storage, data) {
|
||||
snippets: [{
|
||||
name: article.mode,
|
||||
mode: article.mode,
|
||||
content: article.content
|
||||
content: article.content,
|
||||
linesHighlighted: article.linesHighlighted
|
||||
}]
|
||||
}
|
||||
notes.push(newNote)
|
||||
|
||||
@@ -39,6 +39,9 @@ function validateInput (input) {
|
||||
if (input.content != null) {
|
||||
if (!_.isString(input.content)) validatedInput.content = ''
|
||||
else validatedInput.content = input.content
|
||||
|
||||
if (!_.isArray(input.linesHighlighted)) validatedInput.linesHighlighted = []
|
||||
else validatedInput.linesHighlighted = input.linesHighlighted
|
||||
}
|
||||
return validatedInput
|
||||
case 'SNIPPET_NOTE':
|
||||
@@ -51,7 +54,8 @@ function validateInput (input) {
|
||||
validatedInput.snippets = [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}]
|
||||
} else {
|
||||
validatedInput.snippets = input.snippets
|
||||
@@ -96,12 +100,14 @@ function updateNote (storageKey, noteKey, input) {
|
||||
snippets: [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}]
|
||||
}
|
||||
: {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
content: ''
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
noteData.title = ''
|
||||
if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.')
|
||||
|
||||
@@ -12,7 +12,8 @@ function updateSnippet (snippet, snippetFile) {
|
||||
if (
|
||||
currentSnippet.name === snippet.name &&
|
||||
currentSnippet.prefix === snippet.prefix &&
|
||||
currentSnippet.content === snippet.content
|
||||
currentSnippet.content === snippet.content &&
|
||||
currentSnippet.linesHighlighted === snippet.linesHighlighted
|
||||
) {
|
||||
// if everything is the same then don't write to disk
|
||||
resolve(snippets)
|
||||
@@ -20,6 +21,7 @@ function updateSnippet (snippet, snippetFile) {
|
||||
currentSnippet.name = snippet.name
|
||||
currentSnippet.prefix = snippet.prefix
|
||||
currentSnippet.content = snippet.content
|
||||
currentSnippet.linesHighlighted = (snippet.linesHighlighted)
|
||||
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||
if (err) reject(err)
|
||||
resolve(snippets)
|
||||
|
||||
@@ -79,7 +79,8 @@ class HotkeyTab extends React.Component {
|
||||
config.hotkey = {
|
||||
toggleMain: this.refs.toggleMain.value,
|
||||
toggleMode: this.refs.toggleMode.value,
|
||||
deleteNote: this.refs.deleteNote.value
|
||||
deleteNote: this.refs.deleteNote.value,
|
||||
pasteSmartly: this.refs.pasteSmartly.value
|
||||
}
|
||||
this.setState({
|
||||
config
|
||||
@@ -149,6 +150,17 @@ class HotkeyTab extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Paste HTML')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
ref='pasteSmartly'
|
||||
value={config.hotkey.pasteSmartly}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-control'>
|
||||
<button styleName='group-control-leftButton'
|
||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||
|
||||
@@ -73,6 +73,11 @@ class InfoTab extends React.Component {
|
||||
<div styleName='header--sub'>{i18n.__('Community')}</div>
|
||||
<div styleName='top'>
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
<a href='https://issuehunt.io/repos/53266139'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Bounty on IssueHunt')}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://boostnote.io/#subscribe'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
|
||||
@@ -28,9 +28,9 @@ class SnippetEditor extends React.Component {
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
autoCloseBrackets: {
|
||||
pairs: '()[]{}\'\'""$$**``',
|
||||
triples: '```"""\'\'\'',
|
||||
explode: '[]{}``$$',
|
||||
pairs: this.props.matchingPairs,
|
||||
triples: this.props.matchingTriples,
|
||||
explode: this.props.explodingPairs,
|
||||
override: true
|
||||
},
|
||||
mode: 'null'
|
||||
|
||||
@@ -136,6 +136,9 @@ class SnippetTab extends React.Component {
|
||||
enableRulers={config.editor.enableRulers}
|
||||
rulers={config.editor.rulers}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
onRef={ref => { this.snippetEditor = ref }} />
|
||||
</div>
|
||||
|
||||
@@ -96,7 +96,11 @@ class UiTab extends React.Component {
|
||||
enableTableEditor: this.refs.enableTableEditor.checked,
|
||||
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
|
||||
frontMatterTitleField: this.refs.frontMatterTitleField.value,
|
||||
spellcheck: this.refs.spellcheck.checked
|
||||
matchingPairs: this.refs.matchingPairs.value,
|
||||
matchingTriples: this.refs.matchingTriples.value,
|
||||
explodingPairs: this.refs.explodingPairs.value,
|
||||
spellcheck: this.refs.spellcheck.checked,
|
||||
enableSmartPaste: this.refs.enableSmartPaste.checked
|
||||
},
|
||||
preview: {
|
||||
fontSize: this.refs.previewFontSize.value,
|
||||
@@ -552,6 +556,18 @@ class UiTab extends React.Component {
|
||||
{i18n.__('Enable smart table editor')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.editor.enableSmartPaste}
|
||||
ref='enableSmartPaste'
|
||||
type='checkbox'
|
||||
/>
|
||||
{i18n.__('Enable HTML paste')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
@@ -563,6 +579,48 @@ 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'>
|
||||
<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'>
|
||||
{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-header2'>{i18n.__('Preview')}</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
@@ -590,6 +648,7 @@ class UiTab extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Code Block Theme')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
|
||||
@@ -240,10 +240,8 @@ navWhiteButtonColor()
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
transition 0.15s
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color $ui-text-color
|
||||
transition 0.15s
|
||||
|
||||
// UI Button
|
||||
|
||||
10
package.json
10
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "boost",
|
||||
"productName": "Boostnote",
|
||||
"version": "0.11.11",
|
||||
"version": "0.11.12",
|
||||
"main": "index.js",
|
||||
"description": "Boostnote",
|
||||
"license": "GPL-3.0",
|
||||
@@ -99,8 +99,10 @@
|
||||
"react-codemirror": "^0.3.0",
|
||||
"react-debounce-render": "^4.0.1",
|
||||
"react-dom": "^15.0.2",
|
||||
"react-image-carousel": "^2.0.18",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-sortable-hoc": "^0.6.7",
|
||||
"react-transition-group": "^2.5.0",
|
||||
"redux": "^3.5.2",
|
||||
"sander": "^0.5.1",
|
||||
"sanitize-html": "^1.18.2",
|
||||
@@ -142,6 +144,7 @@
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-electron-installer": "2.1.0",
|
||||
"history": "^1.17.0",
|
||||
"husky": "^1.1.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^22.4.3",
|
||||
"jest-localstorage-mock": "^2.2.0",
|
||||
@@ -189,5 +192,10 @@
|
||||
"<rootDir>/tests/jest.js",
|
||||
"jest-localstorage-mock"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
|
||||
|
||||
Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer:
|
||||
|
||||
[](https://issuehunt.io/repos/53266139)
|
||||
[](https://issuehunt.io/repos/53266139)
|
||||
|
||||
## Community
|
||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||
|
||||
@@ -25,13 +25,16 @@ test.serial('Create a note', (t) => {
|
||||
const storageKey = t.context.storage.cache.key
|
||||
const folderKey = t.context.storage.json.folders[0].key
|
||||
|
||||
const randLinesHighlightedArray = new Array(10).fill().map(() => Math.round(Math.random() * 10))
|
||||
|
||||
const input1 = {
|
||||
type: 'SNIPPET_NOTE',
|
||||
description: faker.lorem.lines(),
|
||||
snippets: [{
|
||||
name: faker.system.fileName(),
|
||||
mode: 'text',
|
||||
content: faker.lorem.lines()
|
||||
content: faker.lorem.lines(),
|
||||
linesHighlighted: randLinesHighlightedArray
|
||||
}],
|
||||
tags: faker.lorem.words().split(' '),
|
||||
folder: folderKey
|
||||
@@ -42,7 +45,8 @@ test.serial('Create a note', (t) => {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
content: faker.lorem.lines(),
|
||||
tags: faker.lorem.words().split(' '),
|
||||
folder: folderKey
|
||||
folder: folderKey,
|
||||
linesHighlighted: randLinesHighlightedArray
|
||||
}
|
||||
input2.title = input2.content.split('\n').shift()
|
||||
|
||||
@@ -59,6 +63,7 @@ test.serial('Create a note', (t) => {
|
||||
|
||||
t.is(storageKey, data1.storage)
|
||||
const jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson'))
|
||||
|
||||
t.is(input1.title, data1.title)
|
||||
t.is(input1.title, jsonData1.title)
|
||||
t.is(input1.description, data1.description)
|
||||
@@ -71,6 +76,8 @@ test.serial('Create a note', (t) => {
|
||||
t.is(input1.snippets[0].content, jsonData1.snippets[0].content)
|
||||
t.is(input1.snippets[0].name, data1.snippets[0].name)
|
||||
t.is(input1.snippets[0].name, jsonData1.snippets[0].name)
|
||||
t.deepEqual(input1.snippets[0].linesHighlighted, data1.snippets[0].linesHighlighted)
|
||||
t.deepEqual(input1.snippets[0].linesHighlighted, jsonData1.snippets[0].linesHighlighted)
|
||||
|
||||
t.is(storageKey, data2.storage)
|
||||
const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson'))
|
||||
@@ -80,6 +87,8 @@ test.serial('Create a note', (t) => {
|
||||
t.is(input2.content, jsonData2.content)
|
||||
t.is(input2.tags.length, data2.tags.length)
|
||||
t.is(input2.tags.length, jsonData2.tags.length)
|
||||
t.deepEqual(input2.linesHighlighted, data2.linesHighlighted)
|
||||
t.deepEqual(input2.linesHighlighted, jsonData2.linesHighlighted)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ test.serial('Create a snippet', (t) => {
|
||||
t.is(snippet.name, data.name)
|
||||
t.deepEqual(snippet.prefix, data.prefix)
|
||||
t.is(snippet.content, data.content)
|
||||
t.deepEqual(snippet.linesHighlighted, data.linesHighlighted)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -26,13 +26,17 @@ test.serial('Update a note', (t) => {
|
||||
const storageKey = t.context.storage.cache.key
|
||||
const folderKey = t.context.storage.json.folders[0].key
|
||||
|
||||
const randLinesHighlightedArray = new Array(10).fill().map(() => Math.round(Math.random() * 10))
|
||||
const randLinesHighlightedArray2 = new Array(15).fill().map(() => Math.round(Math.random() * 15))
|
||||
|
||||
const input1 = {
|
||||
type: 'SNIPPET_NOTE',
|
||||
description: faker.lorem.lines(),
|
||||
snippets: [{
|
||||
name: faker.system.fileName(),
|
||||
mode: 'text',
|
||||
content: faker.lorem.lines()
|
||||
content: faker.lorem.lines(),
|
||||
linesHighlighted: randLinesHighlightedArray
|
||||
}],
|
||||
tags: faker.lorem.words().split(' '),
|
||||
folder: folderKey
|
||||
@@ -43,7 +47,8 @@ test.serial('Update a note', (t) => {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
content: faker.lorem.lines(),
|
||||
tags: faker.lorem.words().split(' '),
|
||||
folder: folderKey
|
||||
folder: folderKey,
|
||||
linesHighlighted: randLinesHighlightedArray
|
||||
}
|
||||
input2.title = input2.content.split('\n').shift()
|
||||
|
||||
@@ -53,7 +58,8 @@ test.serial('Update a note', (t) => {
|
||||
snippets: [{
|
||||
name: faker.system.fileName(),
|
||||
mode: 'text',
|
||||
content: faker.lorem.lines()
|
||||
content: faker.lorem.lines(),
|
||||
linesHighlighted: randLinesHighlightedArray2
|
||||
}],
|
||||
tags: faker.lorem.words().split(' ')
|
||||
}
|
||||
@@ -62,7 +68,8 @@ test.serial('Update a note', (t) => {
|
||||
const input4 = {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
content: faker.lorem.lines(),
|
||||
tags: faker.lorem.words().split(' ')
|
||||
tags: faker.lorem.words().split(' '),
|
||||
linesHighlighted: randLinesHighlightedArray2
|
||||
}
|
||||
input4.title = input4.content.split('\n').shift()
|
||||
|
||||
@@ -99,6 +106,8 @@ test.serial('Update a note', (t) => {
|
||||
t.is(input3.snippets[0].content, jsonData1.snippets[0].content)
|
||||
t.is(input3.snippets[0].name, data1.snippets[0].name)
|
||||
t.is(input3.snippets[0].name, jsonData1.snippets[0].name)
|
||||
t.deepEqual(input3.snippets[0].linesHighlighted, data1.snippets[0].linesHighlighted)
|
||||
t.deepEqual(input3.snippets[0].linesHighlighted, jsonData1.snippets[0].linesHighlighted)
|
||||
|
||||
const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson'))
|
||||
t.is(input4.title, data2.title)
|
||||
@@ -107,6 +116,8 @@ test.serial('Update a note', (t) => {
|
||||
t.is(input4.content, jsonData2.content)
|
||||
t.is(input4.tags.length, data2.tags.length)
|
||||
t.is(input4.tags.length, jsonData2.tags.length)
|
||||
t.deepEqual(input4.linesHighlighted, data2.linesHighlighted)
|
||||
t.deepEqual(input4.linesHighlighted, jsonData2.linesHighlighted)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
155
yarn.lock
155
yarn.lock
@@ -61,7 +61,6 @@
|
||||
"@enyaxu/markdown-it-anchor@^5.0.2":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@enyaxu/markdown-it-anchor/-/markdown-it-anchor-5.0.2.tgz#d173f7b60b492aabc17dfba864c4d071f5595f72"
|
||||
integrity sha512-HBQ+by3IFHh2i5nw8fzn9qrdA+6uwzre68EzHpBX/WrwgnKrfvckPzdi7MphKp2C617edfpeibucslHDNPYkvQ==
|
||||
|
||||
"@ladjs/time-require@^0.1.4":
|
||||
version "0.1.4"
|
||||
@@ -1690,6 +1689,10 @@ ci-info@^1.0.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2"
|
||||
|
||||
ci-info@^1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
|
||||
|
||||
circular-json@^0.3.1:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
|
||||
@@ -2107,6 +2110,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
||||
cosmiconfig@^5.0.6:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.6.tgz#dca6cf680a0bd03589aff684700858c81abeeb39"
|
||||
dependencies:
|
||||
is-directory "^0.3.1"
|
||||
js-yaml "^3.9.0"
|
||||
parse-json "^4.0.0"
|
||||
|
||||
create-error-class@^3.0.0, create-error-class@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
|
||||
@@ -2749,6 +2760,10 @@ doctrine@^2.0.0, doctrine@^2.0.2:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-helpers@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
|
||||
|
||||
dom-serializer@0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
||||
@@ -3364,6 +3379,18 @@ execa@^0.7.0:
|
||||
signal-exit "^3.0.0"
|
||||
strip-eof "^1.0.0"
|
||||
|
||||
execa@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.9.0.tgz#adb7ce62cf985071f60580deb4a88b9e34712d01"
|
||||
dependencies:
|
||||
cross-spawn "^5.0.1"
|
||||
get-stream "^3.0.0"
|
||||
is-stream "^1.1.0"
|
||||
npm-run-path "^2.0.0"
|
||||
p-finally "^1.0.0"
|
||||
signal-exit "^3.0.0"
|
||||
strip-eof "^1.0.0"
|
||||
|
||||
exit-hook@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
|
||||
@@ -3670,6 +3697,12 @@ find-up@^2.0.0, find-up@^2.1.0:
|
||||
dependencies:
|
||||
locate-path "^2.0.0"
|
||||
|
||||
find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
findup-sync@~0.1.2:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.1.3.tgz#7f3e7a97b82392c653bf06589bd85190e93c3683"
|
||||
@@ -3915,6 +3948,10 @@ get-stdin@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
|
||||
|
||||
get-stdin@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
|
||||
|
||||
get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
@@ -4452,6 +4489,21 @@ humanize-plus@^1.8.1:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
|
||||
|
||||
husky@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-1.1.0.tgz#7271e85f5d98b54349788839b720c9a60cd95dba"
|
||||
dependencies:
|
||||
cosmiconfig "^5.0.6"
|
||||
execa "^0.9.0"
|
||||
find-up "^3.0.0"
|
||||
get-stdin "^6.0.0"
|
||||
is-ci "^1.2.1"
|
||||
pkg-dir "^3.0.0"
|
||||
please-upgrade-node "^3.1.1"
|
||||
read-pkg "^4.0.1"
|
||||
run-node "^1.0.0"
|
||||
slash "^2.0.0"
|
||||
|
||||
i18n-2@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/i18n-2/-/i18n-2-0.7.2.tgz#7efb1a7adc67869adea0688951577464aa793aaf"
|
||||
@@ -4662,6 +4714,12 @@ is-ci@^1.0.10, is-ci@^1.0.7:
|
||||
dependencies:
|
||||
ci-info "^1.0.0"
|
||||
|
||||
is-ci@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
|
||||
dependencies:
|
||||
ci-info "^1.5.0"
|
||||
|
||||
is-data-descriptor@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
|
||||
@@ -4694,6 +4752,10 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
|
||||
is-data-descriptor "^1.0.0"
|
||||
kind-of "^6.0.2"
|
||||
|
||||
is-directory@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
|
||||
|
||||
is-dotfile@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
|
||||
@@ -5324,6 +5386,10 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
|
||||
js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
|
||||
@@ -5331,7 +5397,7 @@ js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
js-yaml@^3.12.0, js-yaml@^3.8.1:
|
||||
js-yaml@^3.12.0, js-yaml@^3.8.1, js-yaml@^3.9.0:
|
||||
version "3.12.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
|
||||
dependencies:
|
||||
@@ -5688,6 +5754,13 @@ locate-path@^2.0.0:
|
||||
p-locate "^2.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
locate-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||
dependencies:
|
||||
p-locate "^3.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash-es@^4.2.1:
|
||||
version "4.17.10"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05"
|
||||
@@ -5805,6 +5878,12 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
|
||||
dependencies:
|
||||
js-tokens "^3.0.0"
|
||||
|
||||
loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
loud-rejection@^1.0.0, loud-rejection@^1.2.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||
@@ -6681,16 +6760,32 @@ p-limit@^1.1.0:
|
||||
dependencies:
|
||||
p-try "^1.0.0"
|
||||
|
||||
p-limit@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec"
|
||||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-locate@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
|
||||
dependencies:
|
||||
p-limit "^1.1.0"
|
||||
|
||||
p-locate@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
|
||||
dependencies:
|
||||
p-limit "^2.0.0"
|
||||
|
||||
p-try@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
|
||||
|
||||
package-hash@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44"
|
||||
@@ -6886,12 +6981,24 @@ pkg-dir@^2.0.0:
|
||||
dependencies:
|
||||
find-up "^2.1.0"
|
||||
|
||||
pkg-dir@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
pkg-up@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
|
||||
dependencies:
|
||||
find-up "^2.1.0"
|
||||
|
||||
please-upgrade-node@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz#ed320051dfcc5024fae696712c8288993595e8ac"
|
||||
dependencies:
|
||||
semver-compare "^1.0.0"
|
||||
|
||||
plist@^2.0.0, plist@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025"
|
||||
@@ -7241,6 +7348,13 @@ prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8,
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
prop-types@^15.6.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||
dependencies:
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
proxy-addr@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
||||
@@ -7420,6 +7534,10 @@ react-dom@^15.0.2:
|
||||
object-assign "^4.1.0"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react-image-carousel@^2.0.18:
|
||||
version "2.0.18"
|
||||
resolved "https://registry.yarnpkg.com/react-image-carousel/-/react-image-carousel-2.0.18.tgz#5868ea09bd9cca09c4467d3d02695cd4e7792f28"
|
||||
|
||||
react-input-autosize@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.2.0.tgz#87241071159f742123897691da6796ec33b57d05"
|
||||
@@ -7427,6 +7545,10 @@ react-input-autosize@^1.1.0:
|
||||
create-react-class "^15.5.2"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
|
||||
react-proxy@^1.1.7:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a"
|
||||
@@ -7492,6 +7614,15 @@ react-transform-hmr@^1.0.3:
|
||||
global "^4.3.0"
|
||||
react-proxy "^1.1.7"
|
||||
|
||||
react-transition-group@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874"
|
||||
dependencies:
|
||||
dom-helpers "^3.3.1"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react@^15.5.4:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
|
||||
@@ -7545,6 +7676,14 @@ read-pkg@^2.0.0:
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^2.0.0"
|
||||
|
||||
read-pkg@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237"
|
||||
dependencies:
|
||||
normalize-package-data "^2.3.2"
|
||||
parse-json "^4.0.0"
|
||||
pify "^3.0.0"
|
||||
|
||||
readable-stream@^1.1.8, readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
@@ -7895,6 +8034,10 @@ run-async@^0.1.0:
|
||||
dependencies:
|
||||
once "^1.3.0"
|
||||
|
||||
run-node@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e"
|
||||
|
||||
run-parallel@^1.1.2:
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
|
||||
@@ -7994,6 +8137,10 @@ section-iterator@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
|
||||
semver-diff@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||
@@ -8153,6 +8300,10 @@ slash@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
||||
|
||||
slash@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
|
||||
|
||||
slice-ansi@0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
|
||||
|
||||
Reference in New Issue
Block a user