1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00
This commit is contained in:
Nguyễn Việt Hưng
2019-01-06 09:35:01 +07:00
40 changed files with 1136 additions and 394 deletions

View File

@@ -1,4 +1,4 @@
# EditorConfig is awesome: http://EditorConfig.org # EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file # top-most EditorConfig file
root = true root = true

2
.vscode/launch.json vendored
View File

@@ -17,7 +17,7 @@
"${workspaceFolder}/index.js" "${workspaceFolder}/index.js"
], ],
"windows": { "windows": {
"runtimeExecutable": "${workspaceFolder}/node_modeules/.bin/electron.cmd" "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
} }
}, },
{ {

View File

@@ -1,72 +0,0 @@
<h1 align="center">Sponsors &amp; 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
View 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

View File

@@ -2,10 +2,15 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import _ from 'lodash' import _ from 'lodash'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import hljs from 'highlight.js'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName' 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 TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite' import iconv from 'iconv-lite'
@@ -18,17 +23,100 @@ import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck') const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder') const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
import TurndownService from 'turndown' 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' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const buildCMRulers = (rulers, enableRulers) => const buildCMRulers = (rulers, enableRulers) =>
(enableRulers ? rulers.map(ruler => ({ column: ruler })) : []) (enableRulers ? rulers.map(ruler => ({
column: ruler
})) : [])
function translateHotkey (hotkey) { function translateHotkey (hotkey) {
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl') return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
} }
const languageMaps = {
brainfuck: 'Brainfuck',
cpp: 'C++',
cs: 'C#',
clojure: 'Clojure',
'clojure-repl': 'ClojureScript',
cmake: 'CMake',
coffeescript: 'CoffeeScript',
crystal: 'Crystal',
css: 'CSS',
d: 'D',
dart: 'Dart',
delphi: 'Pascal',
diff: 'Diff',
django: 'Django',
dockerfile: 'Dockerfile',
ebnf: 'EBNF',
elm: 'Elm',
erlang: 'Erlang',
'erlang-repl': 'Erlang',
fortran: 'Fortran',
fsharp: 'F#',
gherkin: 'Gherkin',
go: 'Go',
groovy: 'Groovy',
haml: 'HAML',
haskell: 'Haskell',
haxe: 'Haxe',
http: 'HTTP',
ini: 'toml',
java: 'Java',
javascript: 'JavaScript',
json: 'JSON',
julia: 'Julia',
kotlin: 'Kotlin',
less: 'LESS',
livescript: 'LiveScript',
lua: 'Lua',
markdown: 'Markdown',
mathematica: 'Mathematica',
nginx: 'Nginx',
nsis: 'NSIS',
objectivec: 'Objective-C',
ocaml: 'Ocaml',
perl: 'Perl',
php: 'PHP',
powershell: 'PowerShell',
properties: 'Properties files',
protobuf: 'ProtoBuf',
python: 'Python',
puppet: 'Puppet',
q: 'Q',
r: 'R',
ruby: 'Ruby',
rust: 'Rust',
sas: 'SAS',
scala: 'Scala',
scheme: 'Scheme',
scss: 'SCSS',
shell: 'Shell',
smalltalk: 'Smalltalk',
sml: 'SML',
sql: 'SQL',
stylus: 'Stylus',
swift: 'Swift',
tcl: 'Tcl',
tex: 'LaTex',
typescript: 'TypeScript',
twig: 'Twig',
vbnet: 'VB.NET',
vbscript: 'VBScript',
verilog: 'Verilog',
vhdl: 'VHDL',
xml: 'HTML',
xquery: 'XQuery',
yaml: 'YAML',
elixir: 'Elixir'
}
export default class CodeEditor extends React.Component { export default class CodeEditor extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -38,6 +126,7 @@ export default class CodeEditor extends React.Component {
trailing: true trailing: true
}) })
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject) this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject)
this.focusHandler = () => { this.focusHandler = () => {
ipcRenderer.send('editor:focused', true) ipcRenderer.send('editor:focused', true)
} }
@@ -53,7 +142,10 @@ export default class CodeEditor extends React.Component {
} }
this.props.onBlur != null && this.props.onBlur(e) this.props.onBlur != null && this.props.onBlur(e)
const { storageKey, noteKey } = this.props const {
storageKey,
noteKey
} = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote( attachmentManagement.deleteAttachmentsNotPresentInNote(
this.editor.getValue(), this.editor.getValue(),
storageKey, storageKey,
@@ -123,7 +215,9 @@ export default class CodeEditor extends React.Component {
} }
handleFormatTable () { handleFormatTable () {
this.tableEditor.formatAll(options({textWidthOptions: {}})) this.tableEditor.formatAll(options({
textWidthOptions: {}
}))
} }
handleEditorActivity () { handleEditorActivity () {
@@ -177,6 +271,9 @@ export default class CodeEditor extends React.Component {
} }
} }
}, },
'Cmd-Left': function (cm) {
cm.execCommand('goLineLeft')
},
'Cmd-T': function (cm) { 'Cmd-T': function (cm) {
// Do nothing // Do nothing
}, },
@@ -235,6 +332,7 @@ export default class CodeEditor extends React.Component {
this.editor = CodeMirror(this.refs.root, { this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers), rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value, value: this.props.value,
linesHighlighted: this.props.linesHighlighted,
lineNumbers: this.props.displayLineNumbers, lineNumbers: this.props.displayLineNumbers,
lineWrapping: true, lineWrapping: true,
theme: this.props.theme, theme: this.props.theme,
@@ -248,19 +346,24 @@ export default class CodeEditor extends React.Component {
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: { autoCloseBrackets: {
pairs: '()[]{}\'\'""$$**``', pairs: this.props.matchingPairs,
triples: '```"""\'\'\'', triples: this.props.matchingTriples,
explode: '[]{}``$$', explode: this.props.explodingPairs,
override: true override: true
}, },
extraKeys: this.defaultKeyMap extraKeys: this.defaultKeyMap
}) })
this.setMode(this.props.mode) if (!this.props.mode && this.props.value && this.props.autoDetect) {
this.autoDetectLanguage(this.props.value)
} else {
this.setMode(this.props.mode)
}
this.editor.on('focus', this.focusHandler) this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler) this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler) this.editor.on('change', this.changeHandler)
this.editor.on('gutterClick', this.highlightHandler)
this.editor.on('paste', this.pasteHandler) this.editor.on('paste', this.pasteHandler)
if (this.props.switchPreview !== 'RIGHTCLICK') { if (this.props.switchPreview !== 'RIGHTCLICK') {
this.editor.on('contextmenu', this.contextMenuHandler) this.editor.on('contextmenu', this.contextMenuHandler)
@@ -292,43 +395,117 @@ export default class CodeEditor extends React.Component {
}) })
this.editorKeyMap = CodeMirror.normalizeKeyMap({ this.editorKeyMap = CodeMirror.normalizeKeyMap({
'Tab': () => { this.tableEditor.nextCell(this.tableEditorOptions) }, 'Tab': () => {
'Shift-Tab': () => { this.tableEditor.previousCell(this.tableEditorOptions) }, this.tableEditor.nextCell(this.tableEditorOptions)
'Enter': () => { this.tableEditor.nextRow(this.tableEditorOptions) }, },
'Ctrl-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) }, 'Shift-Tab': () => {
'Cmd-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) }, this.tableEditor.previousCell(this.tableEditorOptions)
'Shift-Ctrl-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) }, },
'Shift-Cmd-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) }, 'Enter': () => {
'Shift-Ctrl-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) }, this.tableEditor.nextRow(this.tableEditorOptions)
'Shift-Cmd-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) }, },
'Shift-Ctrl-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) }, 'Ctrl-Enter': () => {
'Shift-Cmd-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) }, this.tableEditor.escape(this.tableEditorOptions)
'Shift-Ctrl-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) }, },
'Shift-Cmd-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) }, 'Cmd-Enter': () => {
'Ctrl-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) }, this.tableEditor.escape(this.tableEditorOptions)
'Cmd-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) }, },
'Ctrl-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) }, 'Shift-Ctrl-Left': () => {
'Cmd-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) }, this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
'Ctrl-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) }, },
'Cmd-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) }, 'Shift-Cmd-Left': () => {
'Ctrl-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) }, this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
'Cmd-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) }, },
'Ctrl-K Ctrl-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) }, 'Shift-Ctrl-Right': () => {
'Cmd-K Cmd-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) }, this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
'Ctrl-L Ctrl-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) }, },
'Cmd-L Cmd-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) }, 'Shift-Cmd-Right': () => {
'Ctrl-K Ctrl-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) }, this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
'Cmd-K Cmd-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) }, },
'Ctrl-L Ctrl-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) }, 'Shift-Ctrl-Up': () => {
'Cmd-L Cmd-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) }, this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
'Alt-Shift-Ctrl-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) }, },
'Alt-Shift-Cmd-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) }, 'Shift-Cmd-Up': () => {
'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) }, this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
'Alt-Shift-Cmd-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) }, },
'Alt-Shift-Ctrl-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) }, 'Shift-Ctrl-Down': () => {
'Alt-Shift-Cmd-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) }, this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
'Alt-Shift-Ctrl-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) }, },
'Alt-Shift-Cmd-Down': () => { this.tableEditor.moveRow(1, 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) { if (this.props.enableTableEditor) {
@@ -339,6 +516,8 @@ export default class CodeEditor extends React.Component {
this.setState({ this.setState({
clientWidth: this.refs.root.clientWidth clientWidth: this.refs.root.clientWidth
}) })
this.initialHighlighting()
} }
expandSnippet (line, cursor, cm, snippets) { expandSnippet (line, cursor, cm, snippets) {
@@ -415,8 +594,14 @@ export default class CodeEditor extends React.Component {
return { return {
text: wordBeforeCursor, text: wordBeforeCursor,
range: { range: {
from: { line: lineNumber, ch: originCursorPosition }, from: {
to: { line: lineNumber, ch: cursorPosition } line: lineNumber,
ch: originCursorPosition
},
to: {
line: lineNumber,
ch: cursorPosition
}
} }
} }
} }
@@ -442,7 +627,10 @@ export default class CodeEditor extends React.Component {
componentDidUpdate (prevProps, prevState) { componentDidUpdate (prevProps, prevState) {
let needRefresh = false let needRefresh = false
const { rulers, enableRulers } = this.props const {
rulers,
enableRulers
} = this.props
if (prevProps.mode !== this.props.mode) { if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode) this.setMode(this.props.mode)
} }
@@ -483,6 +671,18 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd) 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 (prevProps.enableTableEditor !== this.props.enableTableEditor) {
if (this.props.enableTableEditor) { if (this.props.enableTableEditor) {
this.editor.on('cursorActivity', this.editorActivityHandler) this.editor.on('cursorActivity', this.editorActivityHandler)
@@ -515,7 +715,7 @@ export default class CodeEditor extends React.Component {
if (prevProps.spellCheck !== this.props.spellCheck) { if (prevProps.spellCheck !== this.props.spellCheck) {
if (this.props.spellCheck === false) { if (this.props.spellCheck === false) {
spellcheck.setLanguage(this.editor, spellcheck.SPELLCHECK_DISABLED) 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) elem.parentNode.removeChild(elem)
} else { } else {
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'}) this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
@@ -528,7 +728,7 @@ export default class CodeEditor extends React.Component {
} }
setMode (mode) { setMode (mode) {
let syntax = CodeMirror.findModeByName(convertModeName(mode)) let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime) this.editor.setOption('mode', syntax.mime)
@@ -537,12 +737,96 @@ export default class CodeEditor extends React.Component {
handleChange (editor, changeObject) { handleChange (editor, changeObject) {
spellcheck.handleChange(editor, changeObject) spellcheck.handleChange(editor, changeObject)
this.updateHighlight(editor, changeObject)
this.value = editor.getValue() this.value = editor.getValue()
if (this.props.onChange) { if (this.props.onChange) {
this.props.onChange(editor) 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) {} moveCursorTo (row, col) {}
scrollToLine (event, num) { scrollToLine (event, num) {
@@ -567,6 +851,7 @@ export default class CodeEditor extends React.Component {
this.value = this.props.value this.value = this.props.value
this.editor.setValue(this.props.value) this.editor.setValue(this.props.value)
this.editor.clearHistory() this.editor.clearHistory()
this.restartHighlighting()
this.editor.on('change', this.changeHandler) this.editor.on('change', this.changeHandler)
this.editor.refresh() this.editor.refresh()
} }
@@ -579,7 +864,10 @@ export default class CodeEditor extends React.Component {
handleDropImage (dropEvent) { handleDropImage (dropEvent) {
dropEvent.preventDefault() dropEvent.preventDefault()
const { storageKey, noteKey } = this.props const {
storageKey,
noteKey
} = this.props
attachmentManagement.handleAttachmentDrop( attachmentManagement.handleAttachmentDrop(
this, this,
storageKey, storageKey,
@@ -592,6 +880,11 @@ export default class CodeEditor extends React.Component {
this.editor.replaceSelection(imageMd) this.editor.replaceSelection(imageMd)
} }
autoDetectLanguage (content) {
const res = hljs.highlightAuto(content, Object.keys(languageMaps))
this.setMode(languageMaps[res.language])
}
handlePaste (editor, forceSmartPaste) { handlePaste (editor, forceSmartPaste) {
const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props
@@ -602,15 +895,21 @@ export default class CodeEditor extends React.Component {
const isInLinkTag = editor => { const isInLinkTag = editor => {
const startCursor = editor.getCursor('start') const startCursor = editor.getCursor('start')
const prevChar = editor.getRange( const prevChar = editor.getRange({
{ line: startCursor.line, ch: startCursor.ch - 2 }, line: startCursor.line,
{ line: startCursor.line, ch: startCursor.ch } ch: startCursor.ch - 2
) }, {
line: startCursor.line,
ch: startCursor.ch
})
const endCursor = editor.getCursor('end') const endCursor = editor.getCursor('end')
const nextChar = editor.getRange( const nextChar = editor.getRange({
{ line: endCursor.line, ch: endCursor.ch }, line: endCursor.line,
{ line: endCursor.line, ch: endCursor.ch + 1 } ch: endCursor.ch
) }, {
line: endCursor.line,
ch: endCursor.ch + 1
})
return prevChar === '](' && nextChar === ')' return prevChar === '](' && nextChar === ')'
} }
@@ -656,7 +955,13 @@ export default class CodeEditor extends React.Component {
this.handlePasteText(editor, pastedTxt) this.handlePasteText(editor, pastedTxt)
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { } else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt) this.handlePasteUrl(editor, pastedTxt)
} else if (enableSmartPaste || forceSmartPaste) { } else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
attachmentManagement
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
.then(modifiedText => {
this.editor.replaceSelection(modifiedText)
})
} else {
const image = clipboard.readImage() const image = clipboard.readImage()
if (!image.isEmpty()) { if (!image.isEmpty()) {
attachmentManagement.handlePastNativeImage( attachmentManagement.handlePastNativeImage(
@@ -665,22 +970,20 @@ export default class CodeEditor extends React.Component {
noteKey, noteKey,
image image
) )
} else { } else if (enableSmartPaste || forceSmartPaste) {
const pastedHtml = clipboard.readHTML() const pastedHtml = clipboard.readHTML()
if (pastedHtml.length > 0) { if (pastedHtml.length > 0) {
this.handlePasteHtml(editor, pastedHtml) this.handlePasteHtml(editor, pastedHtml)
} else { } else {
this.handlePasteText(editor, pastedTxt) this.handlePasteText(editor, pastedTxt)
} }
} else {
this.handlePasteText(editor, pastedTxt)
} }
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) { }
attachmentManagement
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) if (!this.props.mode && this.props.autoDetect) {
.then(modifiedText => { this.autoDetectLanguage(editor.doc.getValue())
this.editor.replaceSelection(modifiedText)
})
} else {
this.handlePasteText(editor, pastedTxt)
} }
} }
@@ -758,6 +1061,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) { mapImageResponse (response, pastedTxt) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
@@ -803,20 +1129,28 @@ export default class CodeEditor extends React.Component {
} }
render () { render () {
const {className, fontSize} = this.props const {
className,
fontSize
} = this.props
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
const width = this.props.width const width = this.props.width
return ( return (<
<div div className={
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`} className == null ? 'CodeEditor' : `CodeEditor ${className}`
ref='root' }
tabIndex='-1' ref='root'
style={{ tabIndex='-1'
fontFamily, style={
fontSize: fontSize, {
width: width fontFamily,
}} fontSize: fontSize,
onDrop={e => this.handleDropImage(e)} width: width
}
}
onDrop={
e => this.handleDropImage(e)
}
/> />
) )
} }
@@ -850,6 +1184,7 @@ CodeEditor.propTypes = {
onBlur: PropTypes.func, onBlur: PropTypes.func,
onChange: PropTypes.func, onChange: PropTypes.func,
readOnly: PropTypes.bool, readOnly: PropTypes.bool,
autoDetect: PropTypes.bool,
spellCheck: PropTypes.bool spellCheck: PropTypes.bool
} }
@@ -861,5 +1196,6 @@ CodeEditor.defaultProps = {
fontFamily: 'Monaco, Consolas', fontFamily: 'Monaco, Consolas',
indentSize: 4, indentSize: 4,
indentType: 'space', indentType: 'space',
autoDetect: false,
spellCheck: false spellCheck: false
} }

View File

@@ -7,6 +7,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
class MarkdownEditor extends React.Component { class MarkdownEditor extends React.Component {
constructor (props) { constructor (props) {
@@ -221,6 +222,28 @@ class MarkdownEditor extends React.Component {
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`) 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) { handleKeyUp (e) {
const keyPressed = this.state.keyPressed const keyPressed = this.state.keyPressed
keyPressed.delete(e.keyCode) keyPressed.delete(e.keyCode)
@@ -232,7 +255,7 @@ class MarkdownEditor extends React.Component {
} }
render () { render () {
const {className, value, config, storageKey, noteKey} = this.props const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -270,11 +293,15 @@ class MarkdownEditor extends React.Component {
enableRulers={config.editor.enableRulers} enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers} rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey} noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor} enableTableEditor={config.editor.enableTableEditor}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)} onBlur={(e) => this.handleBlur(e)}
spellCheck={config.editor.spellcheck} spellCheck={config.editor.spellcheck}
@@ -314,6 +341,7 @@ class MarkdownEditor extends React.Component {
customCSS={config.preview.customCSS} customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS} allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox} lineThroughCheckbox={config.preview.lineThroughCheckbox}
onDrop={(e) => this.handleDropImage(e)}
/> />
</div> </div>
) )

View File

@@ -21,6 +21,8 @@ import yaml from 'js-yaml'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import fs from 'fs' import fs from 'fs'
import { render } from 'react-dom'
import Carousel from 'react-image-carousel'
import ConfigManager from '../main/lib/ConfigManager' import ConfigManager from '../main/lib/ConfigManager'
const { remote, shell } = require('electron') const { remote, shell } = require('electron')
@@ -40,7 +42,8 @@ const appPath = fileUrl(
) )
const CSS_FILES = [ const CSS_FILES = [
`${appPath}/node_modules/katex/dist/katex.min.css`, `${appPath}/node_modules/katex/dist/katex.min.css`,
`${appPath}/node_modules/codemirror/lib/codemirror.css` `${appPath}/node_modules/codemirror/lib/codemirror.css`,
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
] ]
function buildStyle ( function buildStyle (
@@ -207,7 +210,7 @@ export default class MarkdownPreview extends React.Component {
this.saveAsHtmlHandler = () => this.handleSaveAsHtml() this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.printHandler = () => this.handlePrint() this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handlelinkClick.bind(this) this.linkClickHandler = this.handleLinkClick.bind(this)
this.initMarkdown = this.initMarkdown.bind(this) this.initMarkdown = this.initMarkdown.bind(this)
this.initMarkdown() this.initMarkdown()
} }
@@ -410,6 +413,8 @@ export default class MarkdownPreview extends React.Component {
} }
componentDidMount () { componentDidMount () {
const { onDrop } = this.props
this.refs.root.setAttribute('sandbox', 'allow-scripts') this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener( this.refs.root.contentWindow.document.body.addEventListener(
'contextmenu', 'contextmenu',
@@ -447,7 +452,7 @@ export default class MarkdownPreview extends React.Component {
) )
this.refs.root.contentWindow.document.addEventListener( this.refs.root.contentWindow.document.addEventListener(
'drop', 'drop',
this.preventImageDroppedHandler onDrop || this.preventImageDroppedHandler
) )
this.refs.root.contentWindow.document.addEventListener( this.refs.root.contentWindow.document.addEventListener(
'dragover', 'dragover',
@@ -464,6 +469,8 @@ export default class MarkdownPreview extends React.Component {
} }
componentWillUnmount () { componentWillUnmount () {
const { onDrop } = this.props
this.refs.root.contentWindow.document.body.removeEventListener( this.refs.root.contentWindow.document.body.removeEventListener(
'contextmenu', 'contextmenu',
this.contextMenuHandler this.contextMenuHandler
@@ -482,7 +489,7 @@ export default class MarkdownPreview extends React.Component {
) )
this.refs.root.contentWindow.document.removeEventListener( this.refs.root.contentWindow.document.removeEventListener(
'drop', 'drop',
this.preventImageDroppedHandler onDrop || this.preventImageDroppedHandler
) )
this.refs.root.contentWindow.document.removeEventListener( this.refs.root.contentWindow.document.removeEventListener(
'dragover', 'dragover',
@@ -767,6 +774,34 @@ export default class MarkdownPreview extends React.Component {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme) 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 () { focus () {
@@ -809,7 +844,7 @@ export default class MarkdownPreview extends React.Component {
return new window.Notification(title, options) return new window.Notification(title, options)
} }
handlelinkClick (e) { handleLinkClick (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()

View File

@@ -24,9 +24,9 @@ class MarkdownSplitEditor extends React.Component {
this.refs.code.setValue(value) this.refs.code.setValue(value)
} }
handleOnChange () { handleOnChange (e) {
this.value = this.refs.code.value this.value = this.refs.code.value
this.props.onChange() this.props.onChange(e)
} }
handleScroll (e) { handleScroll (e) {
@@ -136,7 +136,7 @@ class MarkdownSplitEditor extends React.Component {
} }
render () { render () {
const {config, value, storageKey, noteKey} = this.props const {config, value, storageKey, noteKey, linesHighlighted} = this.props
const storage = findStorage(storageKey) const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -160,6 +160,9 @@ class MarkdownSplitEditor extends React.Component {
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorFontSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers} enableRulers={config.editor.enableRulers}
@@ -169,7 +172,8 @@ class MarkdownSplitEditor extends React.Component {
enableTableEditor={config.editor.enableTableEditor} enableTableEditor={config.editor.enableTableEditor}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey} noteKey={noteKey}
onChange={this.handleOnChange.bind(this)} linesHighlighted={linesHighlighted}
onChange={(e) => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)} onScroll={this.handleScroll.bind(this)}
spellCheck={config.editor.spellcheck} spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste} enableSmartPaste={config.editor.enableSmartPaste}

View File

@@ -3,19 +3,30 @@
flex 1 flex 1
min-width 70px min-width 70px
overflow hidden overflow hidden
border-left 1px solid $ui-borderColor
border-top 1px solid $ui-borderColor
&:hover &:hover
background-color alpha($ui-button--active-backgroundColor, 20%)
.deleteButton .deleteButton
color $ui-inactive-text-color color: $ui-text-color
&:hover visibility visible
background-color darken($ui-backgroundColor, 15%) transition 0.15s
&:active .button
color white color: $ui-text-color
background-color $ui-active-color transition 0.15s
.root--active .root--active
@extend .root @extend .root
min-width 100px 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 .button
width 100% width 100%
@@ -27,8 +38,7 @@
background-color transparent background-color transparent
transition 0.15s transition 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover color $ui-inactive-text-color
background-color $ui-button--hover-backgroundColor
.deleteButton .deleteButton
position absolute position absolute
@@ -42,6 +52,7 @@
color $ui-inactive-text-color color $ui-inactive-text-color
background-color transparent background-color transparent
border-radius 2px border-radius 2px
visibility hidden
.input .input
height 29px height 29px
@@ -50,76 +61,66 @@
width 100% width 100%
outline none 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"] body[data-theme="dark"]
.root .root
color $ui-dark-text-color
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
border-top 1px solid $ui-dark-borderColor
&:hover &: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 .deleteButton
color $ui-dark-inactive-text-color color $ui-dark-text-color
&:hover transition 0.15s
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active .root--active
color $ui-dark-text-color background-color $ui-dark-button--active-backgroundColor
border-color $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
&:hover border-top 1px solid $ui-dark-borderColor
background-color $ui-dark-button--hover-backgroundColor .button
.deleteButton color $ui-dark-text-color
color $ui-dark-inactive-text-color .deleteButton
&:hover color $ui-dark-text-color
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.button .button
border none border none
color $ui-dark-text-color
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-dark-text-color
background-color $ui-dark-button--hover-backgroundColor
.input .input
background-color $ui-dark-button--hover-backgroundColor background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
transition 0.15s
.deleteButton
color alpha($ui-dark-text-color, 30%)
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .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 border-color $ui-solarized-dark-borderColor
&:hover &:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
transition 0.15s
.deleteButton .deleteButton
color $ui-solarized-dark-text-color color $ui-solarized-dark-button--active-color
&:hover transition 0.15s
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%) .button
&:active color $ui-solarized-dark-button--active-color
color $ui-solarized-dark-text-color transition 0.15s
background-color $ui-dark-button--active-backgroundColor
.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 .button
border none border none
@@ -127,101 +128,75 @@ body[data-theme="solarized-dark"]
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-noteDetail-backgroundColor
.input .input
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color color $ui-solarized-dark-button--active-color
transition 0.15s
.deleteButton
color alpha($ui-solarized-dark-text-color, 30%)
body[data-theme="monokai"] body[data-theme="monokai"]
.root .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 border-color $ui-monokai-borderColor
&:hover &:hover
background-color $ui-monokai-noteDetail-backgroundColor background-color $ui-monokai-noteDetail-backgroundColor
transition 0.15s
.deleteButton .deleteButton
color $ui-monokai-text-color color $ui-monokai-text-color
&:hover transition 0.15s
background-color darken($ui-monokai-noteDetail-backgroundColor, 15%) .button
&:active color $ui-monokai-text-color
color $ui-monokai-text-color transition 0.15s
background-color $ui-dark-button--active-backgroundColor
.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 .button
border none border none
color $ui-monokai-text-color color $ui-inactive-text-color
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-noteDetail-backgroundColor
.input .input
background-color $ui-monokai-noteDetail-backgroundColor background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color color $ui-monokai-text-color
transition 0.15s
.deleteButton
color alpha($ui-monokai-text-color, 30%)
body[data-theme="dracula"] body[data-theme="dracula"]
.root .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 border-color $ui-dracula-borderColor
&:hover &:hover
background-color $ui-dracula-noteDetail-backgroundColor background-color $ui-dracula-noteDetail-backgroundColor
transition 0.15s
.deleteButton .deleteButton
color $ui-dracula-text-color color $ui-dracula-text-color
&:hover transition 0.15s
background-color darken($ui-dracula-noteDetail-backgroundColor, 15%) .button
&:active color $ui-dracula-text-color
color $ui-dracula-text-color transition 0.15s
background-color $ui-dark-button--active-backgroundColor
.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 .button
border none border none
color $ui-dracula-text-color color $ui-inactive-text-color
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-noteDetail-backgroundColor
.input .input
background-color $ui-dracula-noteDetail-backgroundColor background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color color $ui-dracula-text-color
.deleteButton
color alpha($ui-dracula-text-color, 30%)

View File

@@ -55,11 +55,12 @@ body
line-height 1.6 line-height 1.6
overflow-x hidden overflow-x hidden
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
// do not allow display line breaks
.katex-display > .katex
white-space nowrap
// allow inline line breaks
.katex .katex
font 400 1.2em 'KaTeX_Main'
line-height 1.2em
white-space initial white-space initial
text-indent 0
.katex .mfrac>.vlist>span:nth-child(2) .katex .mfrac>.vlist>span:nth-child(2)
top 0 !important top 0 !important
.katex-error .katex-error
@@ -183,6 +184,10 @@ ul
display list-item display list-item
&.taskListItem &.taskListItem
list-style none list-style none
&>input
margin-left -1.6em
&>p
margin-left -1.8em
p p
margin 0 margin 0
&>li>ul, &>li>ol &>li>ul, &>li>ol
@@ -416,6 +421,26 @@ pre.fence
canvas, svg canvas, svg
max-width 100% !important 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%) themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9 themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%) themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -475,6 +500,14 @@ body[data-theme="dark"]
border-color themeDarkBorder border-color themeDarkBorder
background-color themeDarkPreview 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 themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%) themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
@@ -510,6 +543,14 @@ body[data-theme="solarized-dark"]
border-color themeDarkBorder border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor 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 themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%) themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
themeMonokaiTableHead = themeMonokaiTableEven themeMonokaiTableHead = themeMonokaiTableEven
@@ -538,6 +579,7 @@ body[data-theme="monokai"]
border-right solid 1px themeMonokaiTableBorder border-right solid 1px themeMonokaiTableBorder
kbd kbd
background-color themeDarkBackground background-color themeDarkBackground
dl dl
border-color themeDarkBorder border-color themeDarkBorder
background-color themeMonokaiTableHead background-color themeMonokaiTableHead
@@ -547,6 +589,14 @@ body[data-theme="monokai"]
border-color themeDarkBorder border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor 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 themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%) themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
themeDraculaTableHead = themeDraculaTableEven themeDraculaTableHead = themeDraculaTableEven
@@ -575,6 +625,7 @@ body[data-theme="dracula"]
border-right solid 1px themeDraculaTableBorder border-right solid 1px themeDraculaTableBorder
kbd kbd
background-color themeDarkBackground background-color themeDarkBackground
dl dl
border-color themeDarkBorder border-color themeDarkBorder
background-color themeDraculaTableHead background-color themeDraculaTableHead
@@ -583,3 +634,11 @@ body[data-theme="dracula"]
dd dd
border-color themeDarkBorder border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor 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

View File

@@ -2,51 +2,27 @@
* @fileoverview Markdown table of contents generator * @fileoverview Markdown table of contents generator
*/ */
import { EOL } from 'os'
import toc from 'markdown-toc' import toc from 'markdown-toc'
import diacritics from 'diacritics-map'
import stripColor from 'strip-color'
import mdlink from 'markdown-link' import mdlink from 'markdown-link'
import slugify from './slugify'
const EOL = require('os').EOL const hasProp = Object.prototype.hasOwnProperty
/** /**
* @caseSensitiveSlugify Custom slugify function * From @enyaxu/markdown-it-anchor
* 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
*/ */
function caseSensitiveSlugify (str) { function uniqueSlug (slug, slugs, opts) {
function replaceDiacritics (str) { let uniq = slug
return str.replace(/[À-ž]/g, function (ch) { let i = opts.uniqueSlugStartIndex
return diacritics[ch] || ch while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
}) slugs[uniq] = true
} return uniq
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 linkify (tok, text, slug, opts) { function linkify (token) {
var uniqeID = opts.num === 0 ? '' : '-' + opts.num token.content = mdlink(token.content, '#' + token.slug)
tok.content = mdlink(text, '#' + slug + uniqeID) return token
return tok
} }
const TOC_MARKER_START = '<!-- toc -->' const TOC_MARKER_START = '<!-- toc -->'
@@ -91,8 +67,23 @@ export function generateInEditor (editor) {
* @returns generatedTOC String containing generated TOC * @returns generatedTOC String containing generated TOC
*/ */
export function generate (markdownText) { export function generate (markdownText) {
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify, linkify: linkify}) const slugs = {}
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END 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) { function wrapTocWithEol (toc, editor) {

View File

@@ -7,7 +7,6 @@ import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex' import katex from 'katex'
import { lastFindInArray } from './utils' import { lastFindInArray } from './utils'
import anchor from '@enyaxu/markdown-it-anchor'
function createGutter (str, firstLineNumber) { function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1 if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
@@ -118,14 +117,8 @@ class Markdown {
this.md.use(require('markdown-it-imsize')) this.md.use(require('markdown-it-imsize'))
this.md.use(require('markdown-it-footnote')) this.md.use(require('markdown-it-footnote'))
this.md.use(require('markdown-it-multimd-table')) this.md.use(require('markdown-it-multimd-table'))
this.md.use(anchor, { this.md.use(require('@enyaxu/markdown-it-anchor'), {
slugify: (title) => { slugify: require('./slugify')
var slug = encodeURI(title.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
return slug
}
}) })
this.md.use(require('markdown-it-kbd')) this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']}) this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
@@ -152,6 +145,21 @@ class Markdown {
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div> <div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
</pre>` </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 => { mermaid: token => {
return `<pre class="fence" data-line="${token.map[0]}"> return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span> <span class="filename">${token.fileName}</span>

View File

@@ -18,7 +18,8 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
folder: folder, folder: folder,
title: '', title: '',
tags, tags,
content: '' content: '',
linesHighlighted: []
}) })
.then(note => { .then(note => {
const noteHash = note.key const noteHash = note.key
@@ -45,6 +46,8 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
tags = params.tagname.split(' ') tags = params.tagname.split(' ')
} }
const defaultLanguage = config.editor.snippetDefaultLanguage === 'Auto Detect' ? null : config.editor.snippetDefaultLanguage
return dataApi return dataApi
.createNote(storage, { .createNote(storage, {
type: 'SNIPPET_NOTE', type: 'SNIPPET_NOTE',
@@ -55,8 +58,9 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
snippets: [ snippets: [
{ {
name: '', name: '',
mode: config.editor.snippetDefaultLanguage || 'text', mode: defaultLanguage,
content: '' content: '',
linesHighlighted: []
} }
] ]
}) })

17
browser/lib/slugify.js Normal file
View 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(/\-+$/, '')
}

View File

@@ -39,12 +39,14 @@ class MarkdownNoteDetail extends React.Component {
isMovingNote: false, isMovingNote: false,
note: Object.assign({ note: Object.assign({
title: '', title: '',
content: '' content: '',
linesHighlighted: []
}, props.note), }, props.note),
isLockButtonShown: false, isLockButtonShown: false,
isLocked: false, isLocked: false,
editorType: props.config.editor.type editorType: props.config.editor.type
} }
this.dispatchTimer = null this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this) this.toggleLockButton = this.handleToggleLockButton.bind(this)
@@ -71,7 +73,7 @@ class MarkdownNoteDetail extends React.Component {
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) { if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
this.setState({ this.setState({
note: Object.assign({}, nextProps.note) note: Object.assign({linesHighlighted: []}, nextProps.note)
}, () => { }, () => {
this.refs.content.reload() this.refs.content.reload()
if (this.refs.tags) this.refs.tags.reset() if (this.refs.tags) this.refs.tags.reset()
@@ -361,6 +363,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key} noteKey={note.key}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
@@ -371,6 +374,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key} noteKey={note.key}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />

View File

@@ -48,7 +48,7 @@ class SnippetNoteDetail extends React.Component {
note: Object.assign({ note: Object.assign({
description: '' description: ''
}, props.note, { }, 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({ const nextNote = Object.assign({
description: '' description: ''
}, nextProps.note, { }, nextProps.note, {
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet)) snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
}) })
this.setState({ this.setState({
snippetIndex: 0, snippetIndex: 0,
note: nextNote note: nextNote
@@ -410,6 +411,8 @@ class SnippetNoteDetail extends React.Component {
return (e) => { return (e) => {
const snippets = this.state.note.snippets.slice() const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value 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: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({ this.setState(state => ({
note: state.note note: state.note
@@ -596,13 +599,16 @@ class SnippetNoteDetail extends React.Component {
} }
addSnippet () { addSnippet () {
const { config } = this.props const { config: { editor: { snippetDefaultLanguage } } } = this.props
const { note } = this.state const { note } = this.state
const defaultLanguage = snippetDefaultLanguage === 'Auto Detect' ? null : snippetDefaultLanguage
note.snippets = note.snippets.concat([{ note.snippets = note.snippets.concat([{
name: '', name: '',
mode: config.editor.snippetDefaultLanguage || 'text', mode: defaultLanguage,
content: '' content: '',
linesHighlighted: []
}]) }])
const snippetIndex = note.snippets.length - 1 const snippetIndex = note.snippets.length - 1
@@ -668,6 +674,8 @@ class SnippetNoteDetail extends React.Component {
const storageKey = note.storage const storageKey = note.storage
const folderKey = note.folder const folderKey = note.folder
const autoDetect = config.editor.snippetDefaultLanguage === 'Auto Detect'
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
@@ -692,10 +700,6 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, index) => { const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === 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' return <div styleName='tabView'
key={index} key={index}
style={{zIndex: isActive ? 5 : 4}} style={{zIndex: isActive ? 5 : 4}}
@@ -704,20 +708,25 @@ class SnippetNoteDetail extends React.Component {
? <MarkdownEditor styleName='tabView-content' ? <MarkdownEditor styleName='tabView-content'
value={snippet.content} value={snippet.content}
config={config} config={config}
linesHighlighted={snippet.linesHighlighted}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents} ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
storageKey={storageKey} storageKey={storageKey}
/> />
: <CodeEditor styleName='tabView-content' : <CodeEditor styleName='tabView-content'
mode={snippet.mode} mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)}
value={snippet.content} value={snippet.content}
linesHighlighted={snippet.linesHighlighted}
theme={config.editor.theme} theme={config.editor.theme}
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorFontSize}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
@@ -726,6 +735,7 @@ class SnippetNoteDetail extends React.Component {
ref={'code-' + index} ref={'code-' + index}
enableSmartPaste={config.editor.enableSmartPaste} enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey} hotkey={config.hotkey}
autoDetect={autoDetect}
/> />
} }
</div> </div>

View File

@@ -31,7 +31,7 @@
.tabList .tabList
absolute left right absolute left right
top 55px top 70px
height 30px height 30px
display flex display flex
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
@@ -57,6 +57,9 @@
.tabList .tabButton .tabList .tabButton
navWhiteButtonColor() navWhiteButtonColor()
width 30px width 30px
border-left 1px solid $ui-borderColor
border-top 1px solid $ui-borderColor
border-right 1px solid $ui-borderColor
.tabView .tabView
absolute left right bottom absolute left right bottom
@@ -98,17 +101,34 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
body[data-theme="white"] body[data-theme="white"], body[data-theme="default"]
.root .root
box-shadow $note-detail-box-shadow box-shadow $note-detail-box-shadow
border none border none
.tabButton
&:hover
background-color alpha($ui-button--active-backgroundColor, 20%)
color $ui-text-color
transition 0.15s
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-left 1px solid $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
box-shadow none 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 .body
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
@@ -118,7 +138,6 @@ body[data-theme="dark"]
border 1px solid $ui-dark-borderColor border 1px solid $ui-dark-borderColor
.tabList .tabList
background-color $ui-button--active-backgroundColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
.tabList .list .tabList .list
@@ -150,6 +169,15 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
border 1px solid $ui-solarized-dark-borderColor 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 .tabList
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
@@ -167,6 +195,14 @@ body[data-theme="monokai"]
color $ui-monokai-text-color color $ui-monokai-text-color
border 1px solid $ui-monokai-borderColor 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 .tabList
background-color $ui-monokai-noteDetail-backgroundColor background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color color $ui-monokai-text-color
@@ -184,6 +220,14 @@ body[data-theme="dracula"]
color $ui-dracula-text-color color $ui-dracula-text-color
border 1px solid $ui-dracula-borderColor 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 .tabList
background-color $ui-dracula-noteDetail-backgroundColor background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color color $ui-dracula-text-color

View File

@@ -45,8 +45,14 @@ class TagSelect extends React.Component {
value = _.isArray(value) value = _.isArray(value)
? value.slice() ? value.slice()
: [] : []
value.push(newTag)
value = _.uniq(value) if (!_.includes(value, newTag)) {
value.push(newTag)
}
if (this.props.saveTagsAlphabetically) {
value = _.sortBy(value)
}
this.setState({ this.setState({
newTag: '' newTag: ''

View File

@@ -96,12 +96,14 @@ class Main extends React.Component {
{ {
name: 'example.html', name: 'example.html',
mode: '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', name: 'example.js',
mode: 'javascript', 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) { if (this.state.isRightSliderFocused) {
const offset = this.refs.body.getBoundingClientRect().left const offset = this.refs.body.getBoundingClientRect().left
let newListWidth = e.pageX - offset let newListWidth = e.pageX - offset
if (newListWidth < 10) { if (newListWidth < 180) {
newListWidth = 10 newListWidth = 180
} else if (newListWidth > 600) { } else if (newListWidth > 600) {
newListWidth = 600 newListWidth = 600
} }

View File

@@ -2,7 +2,6 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import debounceRender from 'react-debounce-render'
import styles from './NoteList.styl' import styles from './NoteList.styl'
import moment from 'moment' import moment from 'moment'
import _ from 'lodash' import _ from 'lodash'
@@ -711,7 +710,8 @@ class NoteList extends React.Component {
type: firstNote.type, type: firstNote.type,
folder: folder.key, folder: folder.key,
title: firstNote.title + ' ' + i18n.__('copy'), title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content content: firstNote.content,
linesHighlighted: firstNote.linesHighlighted
}) })
.then((note) => { .then((note) => {
attachmentManagement.cloneAttachments(firstNote, note) attachmentManagement.cloneAttachments(firstNote, note)
@@ -1129,4 +1129,4 @@ NoteList.propTypes = {
}) })
} }
export default debounceRender(CSSModules(NoteList, styles)) export default CSSModules(NoteList, styles)

View File

@@ -6,6 +6,7 @@ import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton' import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import debounce from 'lodash/debounce'
class TopBar extends React.Component { class TopBar extends React.Component {
constructor (props) { constructor (props) {
@@ -25,6 +26,10 @@ class TopBar extends React.Component {
} }
this.codeInitHandler = this.handleCodeInit.bind(this) this.codeInitHandler = this.handleCodeInit.bind(this)
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
maxWait: 1000 / 8
})
} }
componentDidMount () { componentDidMount () {
@@ -94,7 +99,6 @@ class TopBar extends React.Component {
} }
handleKeyUp (e) { handleKeyUp (e) {
const { router } = this.context
// reset states // reset states
this.setState({ this.setState({
isConfirmTranslation: false isConfirmTranslation: false
@@ -106,21 +110,21 @@ class TopBar extends React.Component {
isConfirmTranslation: true isConfirmTranslation: true
}) })
const keyword = this.refs.searchInput.value const keyword = this.refs.searchInput.value
router.push(`/searched/${encodeURIComponent(keyword)}`) this.updateKeyword(keyword)
this.setState({
search: keyword
})
} }
} }
handleSearchChange (e) { handleSearchChange (e) {
const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) { if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push(`/searched/${encodeURIComponent(keyword)}`) const keyword = this.refs.searchInput.value
this.updateKeyword(keyword)
} else { } else {
e.preventDefault() e.preventDefault()
} }
}
updateKeyword (keyword) {
this.context.router.push(`/searched/${encodeURIComponent(keyword)}`)
this.setState({ this.setState({
search: keyword search: keyword
}) })

View File

@@ -45,6 +45,9 @@ export const DEFAULT_CONFIG = {
enableRulers: false, enableRulers: false,
rulers: [80, 120], rulers: [80, 120],
displayLineNumbers: true, displayLineNumbers: true,
matchingPairs: '()[]{}\'\'""$$**``',
matchingTriples: '```"""\'\'\'',
explodingPairs: '[]{}``$$',
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK' switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE' delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
scrollPastEnd: false, scrollPastEnd: false,

View File

@@ -227,7 +227,15 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths. * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/ */
function fixLocalURLS (renderedHTML, storagePath) { 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') 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)) return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
}) })

View File

@@ -16,6 +16,7 @@ function validateInput (input) {
switch (input.type) { switch (input.type) {
case 'MARKDOWN_NOTE': case 'MARKDOWN_NOTE':
if (!_.isString(input.content)) input.content = '' if (!_.isString(input.content)) input.content = ''
if (!_.isArray(input.linesHighlighted)) input.linesHighlighted = []
break break
case 'SNIPPET_NOTE': case 'SNIPPET_NOTE':
if (!_.isString(input.description)) input.description = '' if (!_.isString(input.description)) input.description = ''
@@ -23,7 +24,8 @@ function validateInput (input) {
input.snippets = [{ input.snippets = [{
name: '', name: '',
mode: 'text', mode: 'text',
content: '' content: '',
linesHighlighted: []
}] }]
} }
break break

View File

@@ -9,7 +9,8 @@ function createSnippet (snippetFile) {
id: crypto.randomBytes(16).toString('hex'), id: crypto.randomBytes(16).toString('hex'),
name: 'Unnamed snippet', name: 'Unnamed snippet',
prefix: [], prefix: [],
content: '' content: '',
linesHighlighted: []
} }
fetchSnippet(null, snippetFile).then((snippets) => { fetchSnippet(null, snippetFile).then((snippets) => {
snippets.push(newSnippet) snippets.push(newSnippet)

View File

@@ -69,7 +69,8 @@ function importAll (storage, data) {
isStarred: false, isStarred: false,
title: article.title, title: article.title,
content: '# ' + article.title + '\n\n' + article.content, content: '# ' + article.title + '\n\n' + article.content,
key: noteKey key: noteKey,
linesHighlighted: article.linesHighlighted
} }
notes.push(newNote) notes.push(newNote)
} else { } else {
@@ -87,7 +88,8 @@ function importAll (storage, data) {
snippets: [{ snippets: [{
name: article.mode, name: article.mode,
mode: article.mode, mode: article.mode,
content: article.content content: article.content,
linesHighlighted: article.linesHighlighted
}] }]
} }
notes.push(newNote) notes.push(newNote)

View File

@@ -39,6 +39,9 @@ function validateInput (input) {
if (input.content != null) { if (input.content != null) {
if (!_.isString(input.content)) validatedInput.content = '' if (!_.isString(input.content)) validatedInput.content = ''
else validatedInput.content = input.content else validatedInput.content = input.content
if (!_.isArray(input.linesHighlighted)) validatedInput.linesHighlighted = []
else validatedInput.linesHighlighted = input.linesHighlighted
} }
return validatedInput return validatedInput
case 'SNIPPET_NOTE': case 'SNIPPET_NOTE':
@@ -51,7 +54,8 @@ function validateInput (input) {
validatedInput.snippets = [{ validatedInput.snippets = [{
name: '', name: '',
mode: 'text', mode: 'text',
content: '' content: '',
linesHighlighted: []
}] }]
} else { } else {
validatedInput.snippets = input.snippets validatedInput.snippets = input.snippets
@@ -96,12 +100,14 @@ function updateNote (storageKey, noteKey, input) {
snippets: [{ snippets: [{
name: '', name: '',
mode: 'text', mode: 'text',
content: '' content: '',
linesHighlighted: []
}] }]
} }
: { : {
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
content: '' content: '',
linesHighlighted: []
} }
noteData.title = '' noteData.title = ''
if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.') if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.')

View File

@@ -12,7 +12,8 @@ function updateSnippet (snippet, snippetFile) {
if ( if (
currentSnippet.name === snippet.name && currentSnippet.name === snippet.name &&
currentSnippet.prefix === snippet.prefix && 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 // if everything is the same then don't write to disk
resolve(snippets) resolve(snippets)
@@ -20,6 +21,7 @@ function updateSnippet (snippet, snippetFile) {
currentSnippet.name = snippet.name currentSnippet.name = snippet.name
currentSnippet.prefix = snippet.prefix currentSnippet.prefix = snippet.prefix
currentSnippet.content = snippet.content currentSnippet.content = snippet.content
currentSnippet.linesHighlighted = (snippet.linesHighlighted)
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err) if (err) reject(err)
resolve(snippets) resolve(snippets)

View File

@@ -151,7 +151,7 @@ class HotkeyTab extends React.Component {
</div> </div>
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Paste Smartly')}</div> <div styleName='group-section-label'>{i18n.__('Paste HTML')}</div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)} onChange={(e) => this.handleHotkeyChange(e)}

View File

@@ -73,6 +73,11 @@ class InfoTab extends React.Component {
<div styleName='header--sub'>{i18n.__('Community')}</div> <div styleName='header--sub'>{i18n.__('Community')}</div>
<div styleName='top'> <div styleName='top'>
<ul styleName='list'> <ul styleName='list'>
<li>
<a href='https://issuehunt.io/repos/53266139'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Bounty on IssueHunt')}</a>
</li>
<li> <li>
<a href='https://boostnote.io/#subscribe' <a href='https://boostnote.io/#subscribe'
onClick={(e) => this.handleLinkClick(e)} onClick={(e) => this.handleLinkClick(e)}

View File

@@ -28,9 +28,9 @@ class SnippetEditor extends React.Component {
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: { autoCloseBrackets: {
pairs: '()[]{}\'\'""$$**``', pairs: this.props.matchingPairs,
triples: '```"""\'\'\'', triples: this.props.matchingTriples,
explode: '[]{}``$$', explode: this.props.explodingPairs,
override: true override: true
}, },
mode: 'null' mode: 'null'

View File

@@ -136,6 +136,9 @@ class SnippetTab extends React.Component {
enableRulers={config.editor.enableRulers} enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers} rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
onRef={ref => { this.snippetEditor = ref }} /> onRef={ref => { this.snippetEditor = ref }} />
</div> </div>

View File

@@ -96,6 +96,9 @@ class UiTab extends React.Component {
enableTableEditor: this.refs.enableTableEditor.checked, enableTableEditor: this.refs.enableTableEditor.checked,
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked, enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
frontMatterTitleField: this.refs.frontMatterTitleField.value, frontMatterTitleField: this.refs.frontMatterTitleField.value,
matchingPairs: this.refs.matchingPairs.value,
matchingTriples: this.refs.matchingTriples.value,
explodingPairs: this.refs.explodingPairs.value,
spellcheck: this.refs.spellcheck.checked, spellcheck: this.refs.spellcheck.checked,
enableSmartPaste: this.refs.enableSmartPaste.checked enableSmartPaste: this.refs.enableSmartPaste.checked
}, },
@@ -478,6 +481,7 @@ class UiTab extends React.Component {
ref='editorSnippetDefaultLanguage' ref='editorSnippetDefaultLanguage'
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
> >
<option key='Auto Detect' value='Auto Detect'>Auto Detect</option>
{ {
_.sortBy(CodeMirror.modeInfo.map(mode => mode.name)).map(name => (<option key={name} value={name}>{name}</option>)) _.sortBy(CodeMirror.modeInfo.map(mode => mode.name)).map(name => (<option key={name} value={name}>{name}</option>))
} }
@@ -561,7 +565,7 @@ class UiTab extends React.Component {
ref='enableSmartPaste' ref='enableSmartPaste'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
{i18n.__('Enable smart paste')} {i18n.__('Enable HTML paste')}
</label> </label>
</div> </div>
@@ -576,6 +580,48 @@ class UiTab extends React.Component {
</label> </label>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Matching character pairs')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={this.state.config.editor.matchingPairs}
ref='matchingPairs'
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{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-header2'>{i18n.__('Preview')}</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
@@ -603,6 +649,7 @@ class UiTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Code Block Theme')}</div> <div styleName='group-section-label'>{i18n.__('Code Block Theme')}</div>
<div styleName='group-section-control'> <div styleName='group-section-control'>

View File

@@ -240,10 +240,8 @@ navWhiteButtonColor()
&:hover &:hover
background-color alpha($ui-button--active-backgroundColor, 20%) background-color alpha($ui-button--active-backgroundColor, 20%)
transition 0.15s transition 0.15s
color $ui-text-color
&:active, &:active:hover &:active, &:active:hover
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
color $ui-text-color
transition 0.15s transition 0.15s
// UI Button // UI Button

View File

@@ -1,7 +1,7 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.11.11", "version": "0.11.12",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -66,6 +66,7 @@
"flowchart.js": "^1.6.5", "flowchart.js": "^1.6.5",
"font-awesome": "^4.3.0", "font-awesome": "^4.3.0",
"fs-extra": "^5.0.0", "fs-extra": "^5.0.0",
"highlight.js": "^9.13.1",
"i18n-2": "^0.7.2", "i18n-2": "^0.7.2",
"iconv-lite": "^0.4.19", "iconv-lite": "^0.4.19",
"immutable": "^3.8.1", "immutable": "^3.8.1",
@@ -99,8 +100,10 @@
"react-codemirror": "^0.3.0", "react-codemirror": "^0.3.0",
"react-debounce-render": "^4.0.1", "react-debounce-render": "^4.0.1",
"react-dom": "^15.0.2", "react-dom": "^15.0.2",
"react-image-carousel": "^2.0.18",
"react-redux": "^4.4.5", "react-redux": "^4.4.5",
"react-sortable-hoc": "^0.6.7", "react-sortable-hoc": "^0.6.7",
"react-transition-group": "^2.5.0",
"redux": "^3.5.2", "redux": "^3.5.2",
"sander": "^0.5.1", "sander": "^0.5.1",
"sanitize-html": "^1.18.2", "sanitize-html": "^1.18.2",
@@ -142,6 +145,7 @@
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-electron-installer": "2.1.0", "grunt-electron-installer": "2.1.0",
"history": "^1.17.0", "history": "^1.17.0",
"husky": "^1.1.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^22.4.3", "jest": "^22.4.3",
"jest-localstorage-mock": "^2.2.0", "jest-localstorage-mock": "^2.2.0",
@@ -189,5 +193,10 @@
"<rootDir>/tests/jest.js", "<rootDir>/tests/jest.js",
"jest-localstorage-mock" "jest-localstorage-mock"
] ]
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
} }
} }

View File

@@ -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: 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:
[![issuehunt-image](https://github.com/BoostIO/issuehunt-materials/blob/master/issuehunt-badge@1x.png?raw=true)](https://issuehunt.io/repos/53266139) [![Let's fund issues in this repository](https://issuehunt.io/static/embed/issuehunt-button-v1.svg)](https://issuehunt.io/repos/53266139)
## Community ## Community
- [Facebook Group](https://www.facebook.com/groups/boostnote/) - [Facebook Group](https://www.facebook.com/groups/boostnote/)

View File

@@ -25,13 +25,16 @@ test.serial('Create a note', (t) => {
const storageKey = t.context.storage.cache.key const storageKey = t.context.storage.cache.key
const folderKey = t.context.storage.json.folders[0].key const folderKey = t.context.storage.json.folders[0].key
const randLinesHighlightedArray = new Array(10).fill().map(() => Math.round(Math.random() * 10))
const input1 = { const input1 = {
type: 'SNIPPET_NOTE', type: 'SNIPPET_NOTE',
description: faker.lorem.lines(), description: faker.lorem.lines(),
snippets: [{ snippets: [{
name: faker.system.fileName(), name: faker.system.fileName(),
mode: 'text', mode: 'text',
content: faker.lorem.lines() content: faker.lorem.lines(),
linesHighlighted: randLinesHighlightedArray
}], }],
tags: faker.lorem.words().split(' '), tags: faker.lorem.words().split(' '),
folder: folderKey folder: folderKey
@@ -42,7 +45,8 @@ test.serial('Create a note', (t) => {
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
content: faker.lorem.lines(), content: faker.lorem.lines(),
tags: faker.lorem.words().split(' '), tags: faker.lorem.words().split(' '),
folder: folderKey folder: folderKey,
linesHighlighted: randLinesHighlightedArray
} }
input2.title = input2.content.split('\n').shift() input2.title = input2.content.split('\n').shift()
@@ -59,6 +63,7 @@ test.serial('Create a note', (t) => {
t.is(storageKey, data1.storage) t.is(storageKey, data1.storage)
const jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson')) const jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson'))
t.is(input1.title, data1.title) t.is(input1.title, data1.title)
t.is(input1.title, jsonData1.title) t.is(input1.title, jsonData1.title)
t.is(input1.description, data1.description) 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].content, jsonData1.snippets[0].content)
t.is(input1.snippets[0].name, data1.snippets[0].name) t.is(input1.snippets[0].name, data1.snippets[0].name)
t.is(input1.snippets[0].name, jsonData1.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) t.is(storageKey, data2.storage)
const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson')) 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.content, jsonData2.content)
t.is(input2.tags.length, data2.tags.length) t.is(input2.tags.length, data2.tags.length)
t.is(input2.tags.length, jsonData2.tags.length) t.is(input2.tags.length, jsonData2.tags.length)
t.deepEqual(input2.linesHighlighted, data2.linesHighlighted)
t.deepEqual(input2.linesHighlighted, jsonData2.linesHighlighted)
}) })
}) })

View File

@@ -26,6 +26,7 @@ test.serial('Create a snippet', (t) => {
t.is(snippet.name, data.name) t.is(snippet.name, data.name)
t.deepEqual(snippet.prefix, data.prefix) t.deepEqual(snippet.prefix, data.prefix)
t.is(snippet.content, data.content) t.is(snippet.content, data.content)
t.deepEqual(snippet.linesHighlighted, data.linesHighlighted)
}) })
}) })

View File

@@ -26,13 +26,17 @@ test.serial('Update a note', (t) => {
const storageKey = t.context.storage.cache.key const storageKey = t.context.storage.cache.key
const folderKey = t.context.storage.json.folders[0].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 = { const input1 = {
type: 'SNIPPET_NOTE', type: 'SNIPPET_NOTE',
description: faker.lorem.lines(), description: faker.lorem.lines(),
snippets: [{ snippets: [{
name: faker.system.fileName(), name: faker.system.fileName(),
mode: 'text', mode: 'text',
content: faker.lorem.lines() content: faker.lorem.lines(),
linesHighlighted: randLinesHighlightedArray
}], }],
tags: faker.lorem.words().split(' '), tags: faker.lorem.words().split(' '),
folder: folderKey folder: folderKey
@@ -43,7 +47,8 @@ test.serial('Update a note', (t) => {
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
content: faker.lorem.lines(), content: faker.lorem.lines(),
tags: faker.lorem.words().split(' '), tags: faker.lorem.words().split(' '),
folder: folderKey folder: folderKey,
linesHighlighted: randLinesHighlightedArray
} }
input2.title = input2.content.split('\n').shift() input2.title = input2.content.split('\n').shift()
@@ -53,7 +58,8 @@ test.serial('Update a note', (t) => {
snippets: [{ snippets: [{
name: faker.system.fileName(), name: faker.system.fileName(),
mode: 'text', mode: 'text',
content: faker.lorem.lines() content: faker.lorem.lines(),
linesHighlighted: randLinesHighlightedArray2
}], }],
tags: faker.lorem.words().split(' ') tags: faker.lorem.words().split(' ')
} }
@@ -62,7 +68,8 @@ test.serial('Update a note', (t) => {
const input4 = { const input4 = {
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
content: faker.lorem.lines(), content: faker.lorem.lines(),
tags: faker.lorem.words().split(' ') tags: faker.lorem.words().split(' '),
linesHighlighted: randLinesHighlightedArray2
} }
input4.title = input4.content.split('\n').shift() 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].content, jsonData1.snippets[0].content)
t.is(input3.snippets[0].name, data1.snippets[0].name) t.is(input3.snippets[0].name, data1.snippets[0].name)
t.is(input3.snippets[0].name, jsonData1.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')) const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson'))
t.is(input4.title, data2.title) 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.content, jsonData2.content)
t.is(input4.tags.length, data2.tags.length) t.is(input4.tags.length, data2.tags.length)
t.is(input4.tags.length, jsonData2.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
View File

@@ -61,7 +61,6 @@
"@enyaxu/markdown-it-anchor@^5.0.2": "@enyaxu/markdown-it-anchor@^5.0.2":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@enyaxu/markdown-it-anchor/-/markdown-it-anchor-5.0.2.tgz#d173f7b60b492aabc17dfba864c4d071f5595f72" 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": "@ladjs/time-require@^0.1.4":
version "0.1.4" version "0.1.4"
@@ -1690,6 +1689,10 @@ ci-info@^1.0.0:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" 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: circular-json@^0.3.1:
version "0.3.3" version "0.3.3"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" 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" version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 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: create-error-class@^3.0.0, create-error-class@^3.0.1:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" 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: dependencies:
esutils "^2.0.2" 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: dom-serializer@0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" 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" signal-exit "^3.0.0"
strip-eof "^1.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: exit-hook@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" 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: dependencies:
locate-path "^2.0.0" 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: findup-sync@~0.1.2:
version "0.1.3" version "0.1.3"
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.1.3.tgz#7f3e7a97b82392c653bf06589bd85190e93c3683" 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" version "5.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" 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: get-stream@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" 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" version "1.8.2"
resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030" 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: i18n-2@^0.7.2:
version "0.7.2" version "0.7.2"
resolved "https://registry.yarnpkg.com/i18n-2/-/i18n-2-0.7.2.tgz#7efb1a7adc67869adea0688951577464aa793aaf" 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: dependencies:
ci-info "^1.0.0" 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: is-data-descriptor@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" 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" is-data-descriptor "^1.0.0"
kind-of "^6.0.2" 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: is-dotfile@^1.0.0:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" 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" version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 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: js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
version "3.11.0" version "3.11.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" 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" argparse "^1.0.7"
esprima "^4.0.0" 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" version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies: dependencies:
@@ -5688,6 +5754,13 @@ locate-path@^2.0.0:
p-locate "^2.0.0" p-locate "^2.0.0"
path-exists "^3.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: lodash-es@^4.2.1:
version "4.17.10" version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" 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: dependencies:
js-tokens "^3.0.0" 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: loud-rejection@^1.0.0, loud-rejection@^1.2.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -6681,16 +6760,32 @@ p-limit@^1.1.0:
dependencies: dependencies:
p-try "^1.0.0" 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: p-locate@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
dependencies: dependencies:
p-limit "^1.1.0" 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: p-try@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" 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: package-hash@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44" resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44"
@@ -6886,12 +6981,24 @@ pkg-dir@^2.0.0:
dependencies: dependencies:
find-up "^2.1.0" 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: pkg-up@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
dependencies: dependencies:
find-up "^2.1.0" 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: plist@^2.0.0, plist@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025" 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" loose-envify "^1.3.1"
object-assign "^4.1.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: proxy-addr@~2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" 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" object-assign "^4.1.0"
prop-types "^15.5.10" 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: react-input-autosize@^1.1.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.2.0.tgz#87241071159f742123897691da6796ec33b57d05" 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" create-react-class "^15.5.2"
prop-types "^15.5.8" 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: react-proxy@^1.1.7:
version "1.1.8" version "1.1.8"
resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a" 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" global "^4.3.0"
react-proxy "^1.1.7" 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: react@^15.5.4:
version "15.6.2" version "15.6.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" 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" normalize-package-data "^2.3.2"
path-type "^2.0.0" 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: readable-stream@^1.1.8, readable-stream@~1.1.9:
version "1.1.14" version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@@ -7895,6 +8034,10 @@ run-async@^0.1.0:
dependencies: dependencies:
once "^1.3.0" 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: run-parallel@^1.1.2:
version "1.1.9" version "1.1.9"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" 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" version "2.0.0"
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a" 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: semver-diff@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" 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" version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" 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: slice-ansi@0.0.4:
version "0.0.4" version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"