1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Merge branch 'master' into drop-browser-image

This commit is contained in:
Baptiste Augrain
2019-02-04 13:36:32 +01:00
88 changed files with 2504 additions and 776 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,7 +2,7 @@ GPL-3.0
Boostnote - an open source note-taking app made for programmers just like you. Boostnote - an open source note-taking app made for programmers just like you.
Copyright (C) 2017 - 2018 BoostIO Copyright (C) 2017 - 2019 BoostIO
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

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'
@@ -13,17 +18,104 @@ import crypto from 'crypto'
import consts from 'browser/lib/consts' import consts from 'browser/lib/consts'
import styles from '../components/CodeEditor.styl' import styles from '../components/CodeEditor.styl'
import fs from 'fs' import fs from 'fs'
const { ipcRenderer, remote } = require('electron') const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' 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) {
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) {
@@ -34,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)
} }
@@ -49,14 +142,21 @@ 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,
noteKey noteKey
) )
} }
this.pasteHandler = (editor, e) => this.handlePaste(editor, e) this.pasteHandler = (editor, e) => {
e.preventDefault()
this.handlePaste(editor, false)
}
this.loadStyleHandler = e => { this.loadStyleHandler = e => {
this.editor.refresh() this.editor.refresh()
} }
@@ -65,12 +165,16 @@ export default class CodeEditor extends React.Component {
this.scrollToLineHandeler = this.scrollToLine.bind(this) this.scrollToLineHandeler = this.scrollToLine.bind(this)
this.formatTable = () => this.handleFormatTable() this.formatTable = () => this.handleFormatTable()
this.contextMenuHandler = function (editor, event) {
const menu = buildEditorContextMenu(editor, event) if (props.switchPreview !== 'RIGHTCLICK') {
if (menu != null) { this.contextMenuHandler = function (editor, event) {
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30) const menu = buildEditorContextMenu(editor, event)
if (menu != null) {
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
}
} }
} }
this.editorActivityHandler = () => this.handleEditorActivity() this.editorActivityHandler = () => this.handleEditorActivity()
this.turndownService = new TurndownService() this.turndownService = new TurndownService()
@@ -111,7 +215,9 @@ export default class CodeEditor extends React.Component {
} }
handleFormatTable () { handleFormatTable () {
this.tableEditor.formatAll(options({textWidthOptions: {}})) this.tableEditor.formatAll(options({
textWidthOptions: {}
}))
} }
handleEditorActivity () { handleEditorActivity () {
@@ -120,42 +226,9 @@ export default class CodeEditor extends React.Component {
} }
} }
updateTableEditorState () { updateDefaultKeyMap () {
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) const { hotkey } = this.props
if (active) {
if (this.extraKeysMode !== 'editor') {
this.extraKeysMode = 'editor'
this.editor.setOption('extraKeys', this.editorKeyMap)
}
} else {
if (this.extraKeysMode !== 'default') {
this.extraKeysMode = 'default'
this.editor.setOption('extraKeys', this.defaultKeyMap)
this.tableEditor.resetSmartCursor()
}
}
}
componentDidMount () {
const { rulers, enableRulers } = this.props
const expandSnippet = this.expandSnippet.bind(this) const expandSnippet = this.expandSnippet.bind(this)
eventEmitter.on('line:jump', this.scrollToLineHandeler)
const defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
if (!fs.existsSync(consts.SNIPPET_FILE)) {
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(defaultSnippet, null, 4),
'utf8'
)
}
this.defaultKeyMap = CodeMirror.normalizeKeyMap({ this.defaultKeyMap = CodeMirror.normalizeKeyMap({
Tab: function (cm) { Tab: function (cm) {
@@ -198,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
}, },
@@ -207,13 +283,56 @@ export default class CodeEditor extends React.Component {
document.execCommand('copy') document.execCommand('copy')
} }
return CodeMirror.Pass return CodeMirror.Pass
},
[translateHotkey(hotkey.pasteSmartly)]: cm => {
this.handlePaste(cm, true)
} }
}) })
}
updateTableEditorState () {
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
if (active) {
if (this.extraKeysMode !== 'editor') {
this.extraKeysMode = 'editor'
this.editor.setOption('extraKeys', this.editorKeyMap)
}
} else {
if (this.extraKeysMode !== 'default') {
this.extraKeysMode = 'default'
this.editor.setOption('extraKeys', this.defaultKeyMap)
this.tableEditor.resetSmartCursor()
}
}
}
componentDidMount () {
const { rulers, enableRulers } = this.props
eventEmitter.on('line:jump', this.scrollToLineHandeler)
const defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
if (!fs.existsSync(consts.SNIPPET_FILE)) {
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(defaultSnippet, null, 4),
'utf8'
)
}
this.updateDefaultKeyMap()
this.value = this.props.value this.value = this.props.value
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,
@@ -227,21 +346,28 @@ 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)
this.editor.on('contextmenu', this.contextMenuHandler) if (this.props.switchPreview !== 'RIGHTCLICK') {
this.editor.on('contextmenu', this.contextMenuHandler)
}
eventEmitter.on('top:search', this.searchHandler) eventEmitter.on('top:search', this.searchHandler)
eventEmitter.emit('code:init') eventEmitter.emit('code:init')
@@ -269,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) {
@@ -316,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) {
@@ -392,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
}
} }
} }
} }
@@ -419,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)
} }
@@ -460,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)
@@ -473,6 +696,14 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('extraKeys', this.defaultKeyMap) this.editor.setOption('extraKeys', this.defaultKeyMap)
} }
if (prevProps.hotkey !== this.props.hotkey) {
this.updateDefaultKeyMap()
if (this.extraKeysMode === 'default') {
this.editor.setOption('extraKeys', this.defaultKeyMap)
}
}
if (this.state.clientWidth !== this.refs.root.clientWidth) { if (this.state.clientWidth !== this.refs.root.clientWidth) {
this.setState({ this.setState({
clientWidth: this.refs.root.clientWidth clientWidth: this.refs.root.clientWidth
@@ -484,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'})
@@ -497,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)
@@ -506,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) {
@@ -536,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()
} }
@@ -548,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,
@@ -561,53 +880,107 @@ export default class CodeEditor extends React.Component {
this.editor.replaceSelection(imageMd) this.editor.replaceSelection(imageMd)
} }
handlePaste (editor, e) { autoDetectLanguage (content) {
const clipboardData = e.clipboardData const res = hljs.highlightAuto(content, Object.keys(languageMaps))
const { storageKey, noteKey } = this.props this.setMode(languageMaps[res.language])
const dataTransferItem = clipboardData.items[0] }
const pastedTxt = clipboardData.getData('text')
const isURL = str => { handlePaste (editor, forceSmartPaste) {
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props
return matcher.test(str)
} const isURL = str => /(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
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 === ')'
} }
const pastedHtml = clipboardData.getData('text/html') const isInFencedCodeBlock = editor => {
if (pastedHtml !== '') { const cursor = editor.getCursor()
this.handlePasteHtml(e, editor, pastedHtml)
} else if (dataTransferItem.type.match('image')) { let token = editor.getTokenAt(cursor)
attachmentManagement.handlePastImageEvent( if (token.state.fencedState) {
this, return true
storageKey, }
noteKey,
dataTransferItem let line = line = cursor.line - 1
) while (line >= 0) {
} else if ( token = editor.getTokenAt({
this.props.fetchUrlTitle && ch: 3,
isURL(pastedTxt) && line
!isInLinkTag(editor) })
) {
this.handlePasteUrl(e, editor, pastedTxt) if (token.start === token.end) {
--line
} else if (token.type === 'comment') {
if (line > 0) {
token = editor.getTokenAt({
ch: 3,
line: line - 1
})
return token.type !== 'comment'
} else {
return true
}
} else {
return false
}
}
return false
} }
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
const pastedTxt = clipboard.readText()
if (isInFencedCodeBlock(editor)) {
this.handlePasteText(editor, pastedTxt)
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt)
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
attachmentManagement attachmentManagement
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
.then(modifiedText => { .then(modifiedText => {
this.editor.replaceSelection(modifiedText) this.editor.replaceSelection(modifiedText)
}) })
e.preventDefault() } else {
const image = clipboard.readImage()
if (!image.isEmpty()) {
attachmentManagement.handlePasteNativeImage(
this,
storageKey,
noteKey,
image
)
} else if (enableSmartPaste || forceSmartPaste) {
const pastedHtml = clipboard.readHTML()
if (pastedHtml.length > 0) {
this.handlePasteHtml(editor, pastedHtml)
} else {
this.handlePasteText(editor, pastedTxt)
}
} else {
this.handlePasteText(editor, pastedTxt)
}
}
if (!this.props.mode && this.props.autoDetect) {
this.autoDetectLanguage(editor.doc.getValue())
} }
} }
@@ -617,8 +990,7 @@ export default class CodeEditor extends React.Component {
} }
} }
handlePasteUrl (e, editor, pastedTxt) { handlePasteUrl (editor, pastedTxt) {
e.preventDefault()
const taggedUrl = `<${pastedTxt}>` const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl) editor.replaceSelection(taggedUrl)
@@ -657,12 +1029,15 @@ export default class CodeEditor extends React.Component {
}) })
} }
handlePasteHtml (e, editor, pastedHtml) { handlePasteHtml (editor, pastedHtml) {
e.preventDefault()
const markdown = this.turndownService.turndown(pastedHtml) const markdown = this.turndownService.turndown(pastedHtml)
editor.replaceSelection(markdown) editor.replaceSelection(markdown)
} }
handlePasteText (editor, pastedTxt) {
editor.replaceSelection(pastedTxt)
}
mapNormalResponse (response, pastedTxt) { mapNormalResponse (response, pastedTxt) {
return this.decodeResponse(response).then(body => { return this.decodeResponse(response).then(body => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -683,6 +1058,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 {
@@ -708,7 +1106,7 @@ export default class CodeEditor extends React.Component {
iconv.encodingExists(_charset) iconv.encodingExists(_charset)
? _charset ? _charset
: 'utf-8' : 'utf-8'
resolve(iconv.decode(new Buffer(buff), charset).toString()) resolve(iconv.decode(Buffer.from(buff), charset).toString())
} catch (e) { } catch (e) {
reject(e) reject(e)
} }
@@ -728,20 +1126,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)
}
/> />
) )
} }
@@ -775,6 +1181,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
} }
@@ -786,5 +1193,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

@@ -0,0 +1,68 @@
import React from 'react'
import PropTypes from 'prop-types'
import { SketchPicker } from 'react-color'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ColorPicker.styl'
const componentHeight = 330
class ColorPicker extends React.Component {
constructor (props) {
super(props)
this.state = {
color: this.props.color || '#939395'
}
this.onColorChange = this.onColorChange.bind(this)
this.handleConfirm = this.handleConfirm.bind(this)
}
componentWillReceiveProps (nextProps) {
this.onColorChange(nextProps.color)
}
onColorChange (color) {
this.setState({
color
})
}
handleConfirm () {
this.props.onConfirm(this.state.color)
}
render () {
const { onReset, onCancel, targetRect } = this.props
const { color } = this.state
const clientHeight = document.body.clientHeight
const alignX = targetRect.right + 4
let alignY = targetRect.top
if (targetRect.top + componentHeight > clientHeight) {
alignY = targetRect.bottom - componentHeight
}
return (
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
<div styleName='cover' onClick={onCancel} />
<SketchPicker color={color} onChange={this.onColorChange} />
<div styleName='footer'>
<button styleName='btn-reset' onClick={onReset}>Reset</button>
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
</div>
</div>
)
}
}
ColorPicker.propTypes = {
color: PropTypes.string,
targetRect: PropTypes.object,
onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired
}
export default CSSModules(ColorPicker, styles)

View File

@@ -0,0 +1,39 @@
.colorPicker
position fixed
z-index 2
display flex
flex-direction column
.cover
position fixed
top 0
right 0
bottom 0
left 0
.footer
display flex
justify-content center
z-index 2
align-items center
& > button + button
margin-left 10px
.btn-cancel,
.btn-confirm,
.btn-reset
vertical-align middle
height 25px
margin-top 2.5px
border-radius 2px
border none
padding 0 5px
background-color $default-button-background
&:hover
background-color $default-button-background--hover
.btn-confirm
background-color #1EC38B
&:hover
background-color darken(#1EC38B, 25%)

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) {
@@ -19,10 +20,10 @@ class MarkdownEditor extends React.Component {
this.supportMdSelectionBold = [16, 17, 186] this.supportMdSelectionBold = [16, 17, 186]
this.state = { this.state = {
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'PREVIEW', status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE',
renderValue: props.value, renderValue: props.value,
keyPressed: new Set(), keyPressed: new Set(),
isLocked: false isLocked: props.isLocked
} }
this.lockEditorCode = () => this.handleLockEditor() this.lockEditorCode = () => this.handleLockEditor()
@@ -75,6 +76,7 @@ class MarkdownEditor extends React.Component {
} }
handleContextMenu (e) { handleContextMenu (e) {
if (this.state.isLocked) return
const { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') { if (config.editor.switchPreview === 'RIGHTCLICK') {
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW' const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
@@ -221,6 +223,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 +256,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,14 +294,21 @@ 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}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
/> />
<MarkdownPreview styleName={this.state.status === 'PREVIEW' <MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview' ? 'preview'
@@ -311,6 +342,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()
} }
@@ -291,26 +294,7 @@ export default class MarkdownPreview extends React.Component {
} }
handleSaveAsMd () { handleSaveAsMd () {
this.exportAsDocument('md', (noteContent, exportTasks) => { this.exportAsDocument('md')
let result = noteContent
if (this.props && this.props.storagePath && this.props.noteKey) {
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent,
this.props.storagePath
)
attachmentsAbsolutePaths.forEach(attachment => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
result = attachmentManagement.removeStorageAndNoteReferences(
noteContent,
this.props.noteKey
)
}
return result
})
} }
handleSaveAsHtml () { handleSaveAsHtml () {
@@ -339,11 +323,6 @@ export default class MarkdownPreview extends React.Component {
) )
let body = this.markdown.render(noteContent) let body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent,
this.props.storagePath
)
files.forEach(file => { files.forEach(file => {
if (global.process.platform === 'win32') { if (global.process.platform === 'win32') {
file = file.replace('file:///', '') file = file.replace('file:///', '')
@@ -355,16 +334,6 @@ export default class MarkdownPreview extends React.Component {
dst: 'css' dst: 'css'
}) })
}) })
attachmentsAbsolutePaths.forEach(attachment => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
body = attachmentManagement.removeStorageAndNoteReferences(
body,
this.props.noteKey
)
let styles = '' let styles = ''
files.forEach(file => { files.forEach(file => {
@@ -397,8 +366,9 @@ export default class MarkdownPreview extends React.Component {
if (filename) { if (filename) {
const content = this.props.value const content = this.props.value
const storage = this.props.storagePath const storage = this.props.storagePath
const nodeKey = this.props.noteKey
exportNote(storage, content, filename, contentFormatter) exportNote(nodeKey, storage, content, filename, contentFormatter)
.then(res => { .then(res => {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info', type: 'info',
@@ -428,6 +398,31 @@ export default class MarkdownPreview extends React.Component {
} }
} }
/**
* @description Convert special characters between three ```
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
* @returns {string} HTML in which special characters between three ``` have been converted
*/
escapeHtmlCharactersInCodeTag (splitWithCodeTag) {
for (let index = 0; index < splitWithCodeTag.length; index++) {
const codeTagRequired = (splitWithCodeTag[index] !== '\`\`\`' && index < splitWithCodeTag.length - 1)
if (codeTagRequired) {
splitWithCodeTag.splice((index + 1), 0, '\`\`\`')
}
}
let inCodeTag = false
let result = ''
for (let content of splitWithCodeTag) {
if (content === '\`\`\`') {
inCodeTag = !inCodeTag
} else if (inCodeTag) {
content = escapeHtmlCharacters(content)
}
result += content
}
return result
}
getScrollBarStyle () { getScrollBarStyle () {
const { theme } = this.props const { theme } = this.props
@@ -443,6 +438,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',
@@ -480,7 +477,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',
@@ -497,6 +494,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
@@ -515,7 +514,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',
@@ -658,11 +657,16 @@ export default class MarkdownPreview extends React.Component {
indentSize, indentSize,
showCopyNotification, showCopyNotification,
storagePath, storagePath,
noteKey noteKey,
sanitize
} = this.props } = this.props
let { value, codeBlockTheme } = this.props let { value, codeBlockTheme } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme) this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
if (sanitize === 'NONE') {
const splitWithCodeTag = value.split('```')
value = this.escapeHtmlCharactersInCodeTag(splitWithCodeTag)
}
const renderedHTML = this.markdown.render(value) const renderedHTML = this.markdown.render(value)
attachmentManagement.migrateAttachments(value, storagePath, noteKey) attachmentManagement.migrateAttachments(value, storagePath, noteKey)
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS( this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
@@ -800,6 +804,109 @@ 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
)
}
)
const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect()
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
for (const img of imgList) {
img.onclick = () => {
const widthMagnification = document.body.clientWidth / img.width
const heightMagnification = document.body.clientHeight / img.height
const baseOnWidth = widthMagnification < heightMagnification
const magnification = baseOnWidth ? widthMagnification : heightMagnification
const zoomImgWidth = img.width * magnification
const zoomImgHeight = img.height * magnification
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
const originalImgTop = img.y + rect.top
const originalImgLeft = img.x + rect.left
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const zoomImg = document.createElement('img')
zoomImg.src = img.src
zoomImg.style = `
position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px;
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = `
position: absolute;
top: ${originalImgTop}px;
left: ${originalImgLeft}px;
width: ${img.width}px;
height: ${img.height}px;
`
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
}
}
} }
focus () { focus () {
@@ -842,7 +949,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,9 +172,13 @@ 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}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
/> />
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} > <div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
<div styleName='slider-hitbox' /> <div styleName='slider-hitbox' />

View File

@@ -4,6 +4,7 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import { isArray } from 'lodash' import { isArray } from 'lodash'
import invertColor from 'invert-color'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus' import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl' import styles from './NoteItem.styl'
@@ -13,29 +14,38 @@ import i18n from 'browser/lib/i18n'
/** /**
* @description Tag element component. * @description Tag element component.
* @param {string} tagName * @param {string} tagName
* @param {string} color
* @return {React.Component} * @return {React.Component}
*/ */
const TagElement = ({ tagName }) => ( const TagElement = ({ tagName, color }) => {
<span styleName='item-bottom-tagList-item' key={tagName}> const style = {}
#{tagName} if (color) {
</span> style.backgroundColor = color
) style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 })
}
return (
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
#{tagName}
</span>
)
}
/** /**
* @description Tag element list component. * @description Tag element list component.
* @param {Array|null} tags * @param {Array|null} tags
* @param {boolean} showTagsAlphabetically * @param {boolean} showTagsAlphabetically
* @param {Object} coloredTags
* @return {React.Component} * @return {React.Component}
*/ */
const TagElementList = (tags, showTagsAlphabetically) => { const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
if (!isArray(tags)) { if (!isArray(tags)) {
return [] return []
} }
if (showTagsAlphabetically) { if (showTagsAlphabetically) {
return _.sortBy(tags).map(tag => TagElement({ tagName: tag })) return _.sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
} else { } else {
return tags.map(tag => TagElement({ tagName: tag })) return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
} }
} }
@@ -46,6 +56,7 @@ const TagElementList = (tags, showTagsAlphabetically) => {
* @param {Function} handleNoteClick * @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu * @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart * @param {Function} handleDragStart
* @param {Object} coloredTags
* @param {string} dateDisplay * @param {string} dateDisplay
*/ */
const NoteItem = ({ const NoteItem = ({
@@ -59,7 +70,8 @@ const NoteItem = ({
storageName, storageName,
folderName, folderName,
viewType, viewType,
showTagsAlphabetically showTagsAlphabetically,
coloredTags
}) => ( }) => (
<div <div
styleName={isActive ? 'item--active' : 'item'} styleName={isActive ? 'item--active' : 'item'}
@@ -97,7 +109,7 @@ const NoteItem = ({
<div styleName='item-bottom'> <div styleName='item-bottom'>
<div styleName='item-bottom-tagList'> <div styleName='item-bottom-tagList'>
{note.tags.length > 0 {note.tags.length > 0
? TagElementList(note.tags, showTagsAlphabetically) ? TagElementList(note.tags, showTagsAlphabetically, coloredTags)
: <span : <span
style={{ fontStyle: 'italic', opacity: 0.5 }} style={{ fontStyle: 'italic', opacity: 0.5 }}
styleName='item-bottom-tagList-empty' styleName='item-bottom-tagList-empty'
@@ -127,6 +139,7 @@ const NoteItem = ({
NoteItem.propTypes = { NoteItem.propTypes = {
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
dateDisplay: PropTypes.string.isRequired, dateDisplay: PropTypes.string.isRequired,
coloredTags: PropTypes.object,
note: PropTypes.shape({ note: PropTypes.shape({
storage: PropTypes.string.isRequired, storage: PropTypes.string.isRequired,
key: PropTypes.string.isRequired, key: PropTypes.string.isRequired,

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

@@ -10,11 +10,12 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {string} name * @param {string} name
* @param {Function} handleClickTagListItem * @param {Function} handleClickTagListItem
* @param {Function} handleClickNarrowToTag * @param {Function} handleClickNarrowToTag
* @param {bool} isActive * @param {boolean} isActive
* @param {bool} isRelated * @param {boolean} isRelated
* @param {string} bgColor tab backgroundColor
*/ */
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => ( const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => (
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}> <div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
{isRelated {isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}> ? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
@@ -23,6 +24,7 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, hand
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} /> : <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
} }
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}> <button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-color' style={{backgroundColor: color || 'transparent'}} />
<span styleName='tagList-item-name'> <span styleName='tagList-item-name'>
{`# ${name}`} {`# ${name}`}
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span> <span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
@@ -33,7 +35,8 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, hand
TagListItem.propTypes = { TagListItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
handleClickTagListItem: PropTypes.func.isRequired handleClickTagListItem: PropTypes.func.isRequired,
color: PropTypes.string
} }
export default CSSModules(TagListItem, styles) export default CSSModules(TagListItem, styles)

View File

@@ -71,6 +71,11 @@
padding-right 15px padding-right 15px
font-size 13px font-size 13px
.tagList-item-color
height 26px
width 3px
display inline-block
body[data-theme="white"] body[data-theme="white"]
.tagList-item .tagList-item
color $ui-inactive-text-color color $ui-inactive-text-color

View File

@@ -55,11 +55,14 @@ 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 .katex-html
display inline-flex
.katex .mfrac>.vlist>span:nth-child(2) .katex .mfrac>.vlist>span:nth-child(2)
top 0 !important top 0 !important
.katex-error .katex-error
@@ -162,6 +165,7 @@ p
white-space pre-line white-space pre-line
word-wrap break-word word-wrap break-word
img img
cursor zoom-in
max-width 100% max-width 100%
strong, b strong, b
font-weight bold font-weight bold
@@ -183,6 +187,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 +424,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 +503,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 +546,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 +582,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 +592,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 +628,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 +637,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

@@ -5,15 +5,17 @@ import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
const FullscreenButton = ({ const FullscreenButton = ({
onClick onClick
}) => ( }) => {
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}> const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' /> return (
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span> <button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
</button> <img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
) <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
</button>
)
}
FullscreenButton.propTypes = { FullscreenButton.propTypes = {
onClick: PropTypes.func.isRequired onClick: PropTypes.func.isRequired

View File

@@ -17,6 +17,10 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 35px
body[data-theme="dark"] body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()

View File

@@ -70,22 +70,22 @@ class InfoPanel extends React.Component {
<hr /> <hr />
<div id='export-wrap'> <div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}> <button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
<i className='fa fa-file-code-o' /> <i className='fa fa-file-code-o' />
<p>{i18n.__('.md')}</p> <p>{i18n.__('.md')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}> <button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
<i className='fa fa-file-text-o' /> <i className='fa fa-file-text-o' />
<p>{i18n.__('.txt')}</p> <p>{i18n.__('.txt')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}> <button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
<i className='fa fa-html5' /> <i className='fa fa-html5' />
<p>{i18n.__('.html')}</p> <p>{i18n.__('.html')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => print(e)}> <button styleName='export--enable' onClick={(e) => print(e, 'print')}>
<i className='fa fa-print' /> <i className='fa fa-print' />
<p>{i18n.__('Print')}</p> <p>{i18n.__('Print')}</p>
</button> </button>

View File

@@ -31,17 +31,17 @@ const InfoPanelTrashed = ({
</div> </div>
<div id='export-wrap'> <div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}> <button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
<i className='fa fa-file-code-o' /> <i className='fa fa-file-code-o' />
<p>.md</p> <p>.md</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}> <button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
<i className='fa fa-file-text-o' /> <i className='fa fa-file-text-o' />
<p>.txt</p> <p>.txt</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}> <button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
<i className='fa fa-html5' /> <i className='fa fa-html5' />
<p>.html</p> <p>.html</p>
</button> </button>

View File

@@ -39,12 +39,15 @@ 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: props.config.editor.type !== 'SPLIT',
isLocked: false, isLocked: false,
editorType: props.config.editor.type editorType: props.config.editor.type,
switchPreview: props.config.editor.switchPreview
} }
this.dispatchTimer = null this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this) this.toggleLockButton = this.handleToggleLockButton.bind(this)
@@ -63,6 +66,9 @@ class MarkdownNoteDetail extends React.Component {
}) })
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this)) ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
ee.on('code:generate-toc', this.generateToc) ee.on('code:generate-toc', this.generateToc)
// Focus content if using blur or double click
if (this.state.switchPreview === 'BLUR' || this.state.switchPreview === 'DBL_CLICK') this.focus()
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
@@ -71,7 +77,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()
@@ -292,7 +298,7 @@ class MarkdownNoteDetail extends React.Component {
handleToggleLockButton (event, noteStatus) { handleToggleLockButton (event, noteStatus) {
// first argument event is not used // first argument event is not used
if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') { if (noteStatus === 'CODE') {
this.setState({isLockButtonShown: true}) this.setState({isLockButtonShown: true})
} else { } else {
this.setState({isLockButtonShown: false}) this.setState({isLockButtonShown: false})
@@ -318,7 +324,8 @@ class MarkdownNoteDetail extends React.Component {
} }
handleSwitchMode (type) { handleSwitchMode (type) {
this.setState({ editorType: type }, () => { // If in split mode, hide the lock button
this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
this.focus() this.focus()
const newConfig = Object.assign({}, this.props.config) const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type newConfig.editor.type = type
@@ -361,7 +368,9 @@ 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)}
isLocked={this.state.isLocked}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
} else { } else {
@@ -371,6 +380,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}
/> />
@@ -433,6 +443,7 @@ class MarkdownNoteDetail extends React.Component {
showTagsAlphabetically={config.ui.showTagsAlphabetically} showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data} data={data}
onChange={this.handleUpdateTag.bind(this)} onChange={this.handleUpdateTag.bind(this)}
coloredTags={config.coloredTags}
/> />
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} /> <TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div> </div>

View File

@@ -20,6 +20,7 @@ import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle' import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName' import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import FullscreenButton from './FullscreenButton'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton' import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton' import PermanentDeleteButton from './PermanentDeleteButton'
@@ -48,7 +49,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 +77,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 +412,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 +600,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
@@ -645,11 +652,18 @@ class SnippetNoteDetail extends React.Component {
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none' if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
} }
showWarning () { showWarning (e, msg) {
const warningMessage = (msg) => ({
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'print': 'Print'
})[msg]
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Sorry!'), message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'), detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
buttons: [i18n.__('OK')] buttons: [i18n.__('OK')]
}) })
} }
@@ -661,6 +675,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)
@@ -685,10 +701,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}}
@@ -697,26 +709,34 @@ 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}
enableTableEditor={config.editor.enableTableEditor} enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
autoDetect={autoDetect}
/> />
} }
</div> </div>
@@ -772,6 +792,7 @@ class SnippetNoteDetail extends React.Component {
showTagsAlphabetically={config.ui.showTagsAlphabetically} showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data} data={data}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
coloredTags={config.coloredTags}
/> />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
@@ -780,11 +801,7 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred} isActive={note.isStarred}
/> />
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} <FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -800,7 +817,9 @@ class SnippetNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning}
type={note.type} type={note.type}
print={this.showWarning}
/> />
</div> </div>
</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

@@ -54,7 +54,7 @@ class StarButton extends React.Component {
: '../resources/icon/icon-star.svg' : '../resources/icon/icon-star.svg'
} }
/> />
<span styleName='tooltip'>{i18n.__('Star')}</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Star')}</span>
</button> </button>
) )
} }

View File

@@ -21,6 +21,11 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 103px
width 70px
.root--active .root--active
@extend .root @extend .root
transition 0.15s transition 0.15s

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import invertColor from 'invert-color'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl' import styles from './TagSelect.styl'
import _ from 'lodash' import _ from 'lodash'
@@ -45,8 +46,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: ''
@@ -179,19 +186,34 @@ class TagSelect extends React.Component {
} }
render () { render () {
const { value, className, showTagsAlphabetically } = this.props const { value, className, showTagsAlphabetically, coloredTags } = this.props
const tagList = _.isArray(value) const tagList = _.isArray(value)
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => { ? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
const wrapperStyle = {}
const textStyle = {}
const BLACK = '#333333'
const WHITE = '#f1f1f1'
const color = coloredTags[tag]
const invertedColor = color && invertColor(color, { black: BLACK, white: WHITE })
let iconRemove = '../resources/icon/icon-x.svg'
if (color) {
wrapperStyle.backgroundColor = color
textStyle.color = invertedColor
}
if (invertedColor === WHITE) {
iconRemove = '../resources/icon/icon-x-light.svg'
}
return ( return (
<span styleName='tag' <span styleName='tag'
key={tag} key={tag}
style={wrapperStyle}
> >
<span styleName='tag-label' onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span> <span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
<button styleName='tag-removeButton' <button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)} onClick={(e) => this.handleTagRemoveButtonClick(tag)}
> >
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' /> <img className='tag-removeButton-icon' src={iconRemove} width='8px' />
</button> </button>
</span> </span>
) )
@@ -240,7 +262,8 @@ TagSelect.contextTypes = {
TagSelect.propTypes = { TagSelect.propTypes = {
className: PropTypes.string, className: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string), value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func onChange: PropTypes.func,
coloredTags: PropTypes.object
} }
export default CSSModules(TagSelect, styles) export default CSSModules(TagSelect, styles)

View File

@@ -3,19 +3,18 @@
align-items center align-items center
user-select none user-select none
vertical-align middle vertical-align middle
width 100% width 96%
overflow-x scroll overflow-x auto
white-space nowrap white-space nowrap
margin-top 31px top 50px
position absolute position absolute
&::-webkit-scrollbar
.root::-webkit-scrollbar height 8px
display none
.tag .tag
display flex display flex
align-items center align-items center
margin 0px 2px margin 0px 2px 2px
padding 2px 4px padding 2px 4px
background-color alpha($ui-tag-backgroundColor, 3%) background-color alpha($ui-tag-backgroundColor, 3%)
border-radius 4px border-radius 4px

View File

@@ -14,7 +14,7 @@ const ToggleModeButton = ({
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}> <div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} /> <img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div> </div>
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
</div> </div>
) )

View File

@@ -40,6 +40,11 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
left -8px
width 70px
body[data-theme="dark"] body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()

View File

@@ -11,7 +11,7 @@ const TrashButton = ({
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Trash')}</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
</button> </button>
) )

View File

@@ -17,6 +17,10 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 46px
.control-trashButton--in-trash .control-trashButton--in-trash
top 60px top 60px
topBarButtonRight() topBarButtonRight()

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: []
} }
] ]
}) })
@@ -170,10 +172,21 @@ class Main extends React.Component {
delete CodeMirror.keyMap.emacs['Ctrl-V'] delete CodeMirror.keyMap.emacs['Ctrl-V']
eventEmitter.on('editor:fullscreen', this.toggleFullScreen) eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
eventEmitter.on('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
} }
componentWillUnmount () { componentWillUnmount () {
eventEmitter.off('editor:fullscreen', this.toggleFullScreen) eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
eventEmitter.off('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
}
toggleMenuBarVisible () {
const { config } = this.props
const { ui } = config
const newUI = Object.assign(ui, {showMenuBar: !ui.showMenuBar})
const newConfig = Object.assign(config, newUI)
ConfigManager.set(newConfig)
} }
handleLeftSlideMouseDown (e) { handleLeftSlideMouseDown (e) {
@@ -234,8 +247,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'
@@ -64,13 +63,14 @@ class NoteList extends React.Component {
this.focusHandler = () => { this.focusHandler = () => {
this.refs.list.focus() this.refs.list.focus()
} }
this.alertIfSnippetHandler = () => { this.alertIfSnippetHandler = (event, msg) => {
this.alertIfSnippet() this.alertIfSnippet(msg)
} }
this.importFromFileHandler = this.importFromFile.bind(this) this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this) this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this) this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this) this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
this.cloneNote = this.cloneNote.bind(this)
this.deleteNote = this.deleteNote.bind(this) this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this) this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this) this.pinToTop = this.pinToTop.bind(this)
@@ -96,6 +96,7 @@ class NoteList extends React.Component {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000) this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
ee.on('list:next', this.selectNextNoteHandler) ee.on('list:next', this.selectNextNoteHandler)
ee.on('list:prior', this.selectPriorNoteHandler) ee.on('list:prior', this.selectPriorNoteHandler)
ee.on('list:clone', this.cloneNote)
ee.on('list:focus', this.focusHandler) ee.on('list:focus', this.focusHandler)
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler) ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.on('import:file', this.importFromFileHandler) ee.on('import:file', this.importFromFileHandler)
@@ -118,6 +119,7 @@ class NoteList extends React.Component {
ee.off('list:next', this.selectNextNoteHandler) ee.off('list:next', this.selectNextNoteHandler)
ee.off('list:prior', this.selectPriorNoteHandler) ee.off('list:prior', this.selectPriorNoteHandler)
ee.off('list:clone', this.cloneNote)
ee.off('list:focus', this.focusHandler) ee.off('list:focus', this.focusHandler)
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler) ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.off('import:file', this.importFromFileHandler) ee.off('import:file', this.importFromFileHandler)
@@ -173,16 +175,15 @@ class NoteList extends React.Component {
} }
} }
focusNote (selectedNoteKeys, noteKey) { focusNote (selectedNoteKeys, noteKey, pathname) {
const { router } = this.context const { router } = this.context
const { location } = this.props
this.setState({ this.setState({
selectedNoteKeys selectedNoteKeys
}) })
router.push({ router.push({
pathname: location.pathname, pathname,
query: { query: {
key: noteKey key: noteKey
} }
@@ -201,6 +202,7 @@ class NoteList extends React.Component {
} }
let { selectedNoteKeys } = this.state let { selectedNoteKeys } = this.state
const { shiftKeyDown } = this.state const { shiftKeyDown } = this.state
const { location } = this.props
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
@@ -217,7 +219,7 @@ class NoteList extends React.Component {
selectedNoteKeys.push(priorNoteKey) selectedNoteKeys.push(priorNoteKey)
} }
this.focusNote(selectedNoteKeys, priorNoteKey) this.focusNote(selectedNoteKeys, priorNoteKey, location.pathname)
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -228,6 +230,7 @@ class NoteList extends React.Component {
} }
let { selectedNoteKeys } = this.state let { selectedNoteKeys } = this.state
const { shiftKeyDown } = this.state const { shiftKeyDown } = this.state
const { location } = this.props
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
const isTargetLastNote = targetIndex === this.notes.length - 1 const isTargetLastNote = targetIndex === this.notes.length - 1
@@ -250,7 +253,7 @@ class NoteList extends React.Component {
selectedNoteKeys.push(nextNoteKey) selectedNoteKeys.push(nextNoteKey)
} }
this.focusNote(selectedNoteKeys, nextNoteKey) this.focusNote(selectedNoteKeys, nextNoteKey, location.pathname)
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -262,7 +265,7 @@ class NoteList extends React.Component {
} }
const selectedNoteKeys = [noteHash] const selectedNoteKeys = [noteHash]
this.focusNote(selectedNoteKeys, noteHash) this.focusNote(selectedNoteKeys, noteHash, '/home')
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -276,12 +279,6 @@ class NoteList extends React.Component {
ee.emit('top:new-note') ee.emit('top:new-note')
} }
// D key
if (e.keyCode === 68) {
e.preventDefault()
this.deleteNote()
}
// E key // E key
if (e.keyCode === 69) { if (e.keyCode === 69) {
e.preventDefault() e.preventDefault()
@@ -494,14 +491,21 @@ class NoteList extends React.Component {
}) })
} }
alertIfSnippet () { alertIfSnippet (msg) {
const warningMessage = (msg) => ({
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'print': 'Print'
})[msg]
const targetIndex = this.getTargetIndex() const targetIndex = this.getTargetIndex()
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') { if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Sorry!'), message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'), detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
buttons: [i18n.__('OK'), i18n.__('Cancel')] buttons: [i18n.__('OK')]
}) })
} }
} }
@@ -652,14 +656,18 @@ class NoteList extends React.Component {
}) })
) )
.then((data) => { .then((data) => {
data.forEach((item) => { const dispatchHandler = () => {
dispatch({ data.forEach((item) => {
type: 'DELETE_NOTE', dispatch({
storageKey: item.storageKey, type: 'DELETE_NOTE',
noteKey: item.noteKey storageKey: item.storageKey,
noteKey: item.noteKey
})
}) })
}) }
ee.once('list:next', dispatchHandler)
}) })
.then(() => ee.emit('list:next'))
.catch((err) => { .catch((err) => {
console.error('Cannot Delete note: ' + err) console.error('Cannot Delete note: ' + err)
}) })
@@ -683,6 +691,7 @@ class NoteList extends React.Component {
}) })
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE') AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
}) })
.then(() => ee.emit('list:next'))
.catch((err) => { .catch((err) => {
console.error('Notes could not go to trash: ' + err) console.error('Notes could not go to trash: ' + err)
}) })
@@ -706,7 +715,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)
@@ -1042,6 +1052,7 @@ class NoteList extends React.Component {
storageName={this.getNoteStorage(note).name} storageName={this.getNoteStorage(note).name}
viewType={viewType} viewType={viewType}
showTagsAlphabetically={config.ui.showTagsAlphabetically} showTagsAlphabetically={config.ui.showTagsAlphabetically}
coloredTags={config.coloredTags}
/> />
) )
} }
@@ -1124,4 +1135,4 @@ NoteList.propTypes = {
}) })
} }
export default debounceRender(CSSModules(NoteList, styles)) export default CSSModules(NoteList, styles)

View File

@@ -25,7 +25,8 @@ class StorageItem extends React.Component {
const { storage } = this.props const { storage } = this.props
this.state = { this.state = {
isOpen: !!storage.isOpen isOpen: !!storage.isOpen,
draggedOver: null
} }
} }
@@ -204,6 +205,20 @@ class StorageItem extends React.Component {
folderKey: data.folderKey, folderKey: data.folderKey,
fileType: data.fileType fileType: data.fileType
}) })
return data
})
.then(data => {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info',
message: 'Exported to "' + data.exportDir + '"'
})
})
.catch(err => {
dialog.showErrorBox(
'Export error',
err ? err.message || err : 'Unexpected error during export'
)
throw err
}) })
} }
}) })
@@ -231,14 +246,20 @@ class StorageItem extends React.Component {
} }
} }
handleDragEnter (e) { handleDragEnter (e, key) {
e.dataTransfer.setData('defaultColor', e.target.style.backgroundColor) e.preventDefault()
e.target.style.backgroundColor = 'rgba(129, 130, 131, 0.08)' if (this.state.draggedOver === key) { return }
this.setState({
draggedOver: key
})
} }
handleDragLeave (e) { handleDragLeave (e) {
e.target.style.opacity = '1' e.preventDefault()
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') if (this.state.draggedOver === null) { return }
this.setState({
draggedOver: null
})
} }
dropNote (storage, folder, dispatch, location, noteData) { dropNote (storage, folder, dispatch, location, noteData) {
@@ -263,8 +284,12 @@ class StorageItem extends React.Component {
} }
handleDrop (e, storage, folder, dispatch, location) { handleDrop (e, storage, folder, dispatch, location) {
e.target.style.opacity = '1' e.preventDefault()
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') if (this.state.draggedOver !== null) {
this.setState({
draggedOver: null
})
}
const noteData = JSON.parse(e.dataTransfer.getData('note')) const noteData = JSON.parse(e.dataTransfer.getData('note'))
this.dropNote(storage, folder, dispatch, location, noteData) this.dropNote(storage, folder, dispatch, location, noteData)
} }
@@ -291,16 +316,22 @@ class StorageItem extends React.Component {
<SortableStorageItemChild <SortableStorageItemChild
key={folder.key} key={folder.key}
index={index} index={index}
isActive={isActive} isActive={isActive || folder.key === this.state.draggedOver}
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)} handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)} handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
folderName={folder.name} folderName={folder.name}
folderColor={folder.color} folderColor={folder.color}
isFolded={isFolded} isFolded={isFolded}
noteCount={noteCount} noteCount={noteCount}
handleDrop={(e) => this.handleDrop(e, storage, folder, dispatch, location)} handleDrop={(e) => {
handleDragEnter={this.handleDragEnter} this.handleDrop(e, storage, folder, dispatch, location)
handleDragLeave={this.handleDragLeave} }}
handleDragEnter={(e) => {
this.handleDragEnter(e, folder.key)
}}
handleDragLeave={(e) => {
this.handleDragLeave(e, folder)
}}
/> />
) )
}) })

View File

@@ -20,6 +20,7 @@ import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import { remote } from 'electron' import { remote } from 'electron'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import ColorPicker from 'browser/components/ColorPicker'
function matchActiveTags (tags, activeTags) { function matchActiveTags (tags, activeTags) {
return _.every(activeTags, v => tags.indexOf(v) >= 0) return _.every(activeTags, v => tags.indexOf(v) >= 0)
@@ -27,6 +28,22 @@ function matchActiveTags (tags, activeTags) {
class SideNav extends React.Component { class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7 // TODO: should not use electron stuff v0.7
constructor (props) {
super(props)
this.state = {
colorPicker: {
show: false,
color: null,
tagName: null,
targetRect: null
}
}
this.dismissColorPicker = this.dismissColorPicker.bind(this)
this.handleColorPickerConfirm = this.handleColorPickerConfirm.bind(this)
this.handleColorPickerReset = this.handleColorPickerReset.bind(this)
}
componentDidMount () { componentDidMount () {
EventEmitter.on('side:preferences', this.handleMenuButtonClick) EventEmitter.on('side:preferences', this.handleMenuButtonClick)
@@ -104,9 +121,64 @@ class SideNav extends React.Component {
click: this.deleteTag.bind(this, tag) click: this.deleteTag.bind(this, tag)
}) })
menu.push({
label: i18n.__('Customize Color'),
click: this.displayColorPicker.bind(this, tag, e.target.getBoundingClientRect())
})
context.popup(menu) context.popup(menu)
} }
dismissColorPicker () {
this.setState({
colorPicker: {
show: false
}
})
}
displayColorPicker (tagName, rect) {
const { config } = this.props
this.setState({
colorPicker: {
show: true,
color: config.coloredTags[tagName],
tagName,
targetRect: rect
}
})
}
handleColorPickerConfirm (color) {
const { dispatch, config: {coloredTags} } = this.props
const { colorPicker: { tagName } } = this.state
const newColoredTags = Object.assign({}, coloredTags, {[tagName]: color.hex})
const config = { coloredTags: newColoredTags }
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
this.dismissColorPicker()
}
handleColorPickerReset () {
const { dispatch, config: {coloredTags} } = this.props
const { colorPicker: { tagName } } = this.state
const newColoredTags = Object.assign({}, coloredTags)
delete newColoredTags[tagName]
const config = { coloredTags: newColoredTags }
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
this.dismissColorPicker()
}
handleToggleButtonClick (e) { handleToggleButtonClick (e) {
const { dispatch, config } = this.props const { dispatch, config } = this.props
@@ -207,6 +279,7 @@ class SideNav extends React.Component {
tagListComponent () { tagListComponent () {
const { data, location, config } = this.props const { data, location, config } = this.props
const { colorPicker } = this.state
const activeTags = this.getActiveTags(location.pathname) const activeTags = this.getActiveTags(location.pathname)
const relatedTags = this.getRelatedTags(activeTags, data.noteMap) const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
let tagList = _.sortBy(data.tagNoteMap.map( let tagList = _.sortBy(data.tagNoteMap.map(
@@ -237,10 +310,11 @@ class SideNav extends React.Component {
handleClickTagListItem={this.handleClickTagListItem.bind(this)} handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)} handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
handleContextMenu={this.handleTagContextMenu.bind(this)} handleContextMenu={this.handleTagContextMenu.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)} isActive={this.getTagActive(location.pathname, tag.name) || (colorPicker.tagName === tag.name)}
isRelated={tag.related} isRelated={tag.related}
key={tag.name} key={tag.name}
count={tag.size} count={tag.size}
color={config.coloredTags[tag.name]}
/> />
) )
}) })
@@ -333,6 +407,7 @@ class SideNav extends React.Component {
render () { render () {
const { data, location, config, dispatch } = this.props const { data, location, config, dispatch } = this.props
const { colorPicker: colorPickerState } = this.state
const isFolded = config.isSideNavFolded const isFolded = config.isSideNavFolded
@@ -349,6 +424,20 @@ class SideNav extends React.Component {
useDragHandle useDragHandle
/> />
}) })
let colorPicker
if (colorPickerState.show) {
colorPicker = (
<ColorPicker
color={colorPickerState.color}
targetRect={colorPickerState.targetRect}
onConfirm={this.handleColorPickerConfirm}
onCancel={this.dismissColorPicker}
onReset={this.handleColorPickerReset}
/>
)
}
const style = {} const style = {}
if (!isFolded) style.width = this.props.width if (!isFolded) style.width = this.props.width
const isTagActive = location.pathname.match(/tag/) const isTagActive = location.pathname.match(/tag/)
@@ -368,6 +457,7 @@ class SideNav extends React.Component {
</div> </div>
</div> </div>
{this.SideNavComponent(isFolded, storageList)} {this.SideNavComponent(isFolded, storageList)}
{colorPicker}
</div> </div>
) )
} }

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

@@ -97,6 +97,7 @@ modalBackColor = white
body[data-theme="dark"] body[data-theme="dark"]
background-color $ui-dark-backgroundColor
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3) background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
@@ -148,6 +149,7 @@ body[data-theme="dark"]
z-index modalZIndex + 5 z-index modalZIndex + 5
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-backgroundColor
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3) background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
@@ -157,6 +159,7 @@ body[data-theme="solarized-dark"]
color: $ui-solarized-dark-text-color color: $ui-solarized-dark-text-color
body[data-theme="monokai"] body[data-theme="monokai"]
background-color $ui-monokai-backgroundColor
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3) background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
@@ -166,6 +169,7 @@ body[data-theme="monokai"]
color: $ui-monokai-text-color color: $ui-monokai-text-color
body[data-theme="dracula"] body[data-theme="dracula"]
background-color $ui-dracula-backgroundColor
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3) background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase

View File

@@ -25,25 +25,31 @@ export const DEFAULT_CONFIG = {
hotkey: { hotkey: {
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E', toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M', toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace' deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
toggleMenuBar: 'Alt'
}, },
ui: { ui: {
language: 'en', language: 'en',
theme: 'default', theme: 'default',
showCopyNotification: true, showCopyNotification: true,
disableDirectWrite: false, disableDirectWrite: false,
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE' defaultNote: 'ALWAYS_ASK', // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
showMenuBar: false
}, },
editor: { editor: {
theme: 'base16-light', theme: 'base16-light',
keyMap: 'sublime', keyMap: 'sublime',
fontSize: '14', fontSize: '14',
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas', fontFamily: win ? 'Consolas' : 'Monaco',
indentType: 'space', indentType: 'space',
indentSize: '2', indentSize: '2',
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,
@@ -52,7 +58,8 @@ export const DEFAULT_CONFIG = {
enableTableEditor: false, enableTableEditor: false,
enableFrontMatterTitle: true, enableFrontMatterTitle: true,
frontMatterTitleField: 'title', frontMatterTitleField: 'title',
spellcheck: false spellcheck: false,
enableSmartPaste: false
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',
@@ -81,7 +88,8 @@ export const DEFAULT_CONFIG = {
token: '', token: '',
username: '', username: '',
password: '' password: ''
} },
coloredTags: {}
} }
function validate (config) { function validate (config) {
@@ -203,7 +211,7 @@ function assignConfigValues (originalConfig, rcConfig) {
function rewriteHotkey (config) { function rewriteHotkey (config) {
const keys = [...Object.keys(config.hotkey)] const keys = [...Object.keys(config.hotkey)]
keys.forEach(key => { keys.forEach(key => {
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command') config.hotkey[key] = config.hotkey[key].replace(/Cmd\s/g, 'Command ')
config.hotkey[key] = config.hotkey[key].replace(/Opt\s/g, 'Option ') config.hotkey[key] = config.hotkey[key].replace(/Opt\s/g, 'Option ')
}) })
return config return config

View File

@@ -241,7 +241,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))
}) })
@@ -270,7 +278,7 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
let promise let promise
if (dropEvent.dataTransfer.files.length > 0) { if (dropEvent.dataTransfer.files.length > 0) {
promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => { promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => {
if (file['type'].startsWith('image')) { if (file['type'].startsWith('image') && !file['type'].endsWith('gif')) {
return fixRotate(file) return fixRotate(file)
.then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey) .then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey)
.then(fileName => ({ .then(fileName => ({
@@ -330,7 +338,7 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
* @param {String} noteKey Key of the current note * @param {String} noteKey Key of the current note
* @param {DataTransferItem} dataTransferItem Part of the past-event * @param {DataTransferItem} dataTransferItem Part of the past-event
*/ */
function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) { function handlePasteImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) {
if (!codeEditor) { if (!codeEditor) {
throw new Error('codeEditor has to be given') throw new Error('codeEditor has to be given')
} }
@@ -367,6 +375,44 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
reader.readAsDataURL(blob) reader.readAsDataURL(blob)
} }
/**
* @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code
* @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
* @param {String} storageKey Key of the current storage
* @param {String} noteKey Key of the current note
* @param {NativeImage} image The native image
*/
function handlePasteNativeImage (codeEditor, storageKey, noteKey, image) {
if (!codeEditor) {
throw new Error('codeEditor has to be given')
}
if (!storageKey) {
throw new Error('storageKey has to be given')
}
if (!noteKey) {
throw new Error('noteKey has to be given')
}
if (!image) {
throw new Error('image has to be given')
}
const targetStorage = findStorage.findStorage(storageKey)
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
createAttachmentDestinationFolder(targetStorage.path, noteKey)
const imageName = `${uniqueSlug()}.png`
const imagePath = path.join(destinationDir, imageName)
const binaryData = image.toPNG()
fs.writeFileSync(imagePath, binaryData, 'binary')
const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName)
const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true)
codeEditor.insertAttachmentMd(imageMd)
}
/** /**
* @description Returns all attachment paths of the given markdown * @description Returns all attachment paths of the given markdown
* @param {String} markdownContent content in which the attachment paths should be found * @param {String} markdownContent content in which the attachment paths should be found
@@ -434,7 +480,14 @@ function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
* @returns {String} Input without the references * @returns {String} Input without the references
*/ */
function removeStorageAndNoteReferences (input, noteKey) { function removeStorageAndNoteReferences (input, noteKey) {
return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER) return input.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?("|])', 'g'), function (match) {
const temp = match
.replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), path.sep)
.replace(new RegExp(mdurl.encode(path.posix.sep), 'g'), path.sep)
.replace(new RegExp(escapeStringRegexp(path.win32.sep), 'g'), path.sep)
.replace(new RegExp(escapeStringRegexp(path.posix.sep), 'g'), path.sep)
return temp.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER)
})
} }
/** /**
@@ -589,7 +642,8 @@ module.exports = {
fixLocalURLS, fixLocalURLS,
generateAttachmentMarkdown, generateAttachmentMarkdown,
handleAttachmentDrop, handleAttachmentDrop,
handlePastImageEvent, handlePasteImageEvent,
handlePasteNativeImage,
getAttachmentsInMarkdownContent, getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences, removeStorageAndNoteReferences,

View File

@@ -16,7 +16,7 @@ function copyFile (srcPath, dstPath) {
const dstFolder = path.dirname(dstPath) const dstFolder = path.dirname(dstPath)
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder) if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
const input = fs.createReadStream(srcPath) const input = fs.createReadStream(decodeURI(srcPath))
const output = fs.createWriteStream(dstPath) const output = fs.createWriteStream(dstPath)
output.on('error', reject) output.on('error', reject)

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

@@ -1,9 +1,9 @@
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData' import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes' import resolveStorageNotes from './resolveStorageNotes'
import exportNote from './exportNote'
import filenamify from 'filenamify' import filenamify from 'filenamify'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs'
/** /**
* @param {String} storageKey * @param {String} storageKey
@@ -45,9 +45,9 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
notes notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE') .filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(snippet => { .forEach(note => {
const notePath = path.join(exportDir, `${filenamify(snippet.title, {replacement: '_'})}.${fileType}`) const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
fs.writeFileSync(notePath, snippet.content) exportNote(note.key, storage.path, note.content, notePath, null)
}) })
return { return {

View File

@@ -4,27 +4,43 @@ import { findStorage } from 'browser/lib/findStorage'
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const attachmentManagement = require('./attachmentManagement')
/** /**
* Export note together with images * Export note together with attachments
* *
* If images is stored in the storage, creates 'images' subfolder in target directory * If attachments are stored in the storage, creates 'attachments' subfolder in target directory
* and copies images to it. Changes links to images in the content of the note * and copies attachments to it. Changes links to images in the content of the note
* *
* @param {String} nodeKey key of the node that should be exported
* @param {String} storageKey or storage path * @param {String} storageKey or storage path
* @param {String} noteContent Content to export * @param {String} noteContent Content to export
* @param {String} targetPath Path to exported file * @param {String} targetPath Path to exported file
* @param {function} outputFormatter * @param {function} outputFormatter
* @return {Promise.<*[]>} * @return {Promise.<*[]>}
*/ */
function exportNote (storageKey, noteContent, targetPath, outputFormatter) { function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatter) {
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
const exportTasks = [] const exportTasks = []
if (!storagePath) { if (!storagePath) {
throw new Error('Storage path is not found') throw new Error('Storage path is not found')
} }
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent,
storagePath
)
attachmentsAbsolutePaths.forEach(attachment => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
let exportedData = noteContent let exportedData = attachmentManagement.removeStorageAndNoteReferences(
noteContent,
nodeKey
)
if (outputFormatter) { if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks) exportedData = outputFormatter(exportedData, exportTasks)

View File

@@ -4,6 +4,7 @@ const resolveStorageData = require('./resolveStorageData')
const resolveStorageNotes = require('./resolveStorageNotes') const resolveStorageNotes = require('./resolveStorageNotes')
const consts = require('browser/lib/consts') const consts = require('browser/lib/consts')
const path = require('path') const path = require('path')
const fs = require('fs')
const CSON = require('@rokt33r/season') const CSON = require('@rokt33r/season')
/** /**
* @return {Object} all storages and notes * @return {Object} all storages and notes
@@ -19,11 +20,14 @@ const CSON = require('@rokt33r/season')
* 2. legacy * 2. legacy
* 3. empty directory * 3. empty directory
*/ */
function init () { function init () {
const fetchStorages = function () { const fetchStorages = function () {
let rawStorages let rawStorages
try { try {
rawStorages = JSON.parse(window.localStorage.getItem('storages')) rawStorages = JSON.parse(window.localStorage.getItem('storages'))
// Remove storages who's location is inaccesible.
rawStorages = rawStorages.filter(storage => fs.existsSync(storage.path))
if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.') if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.')
} catch (e) { } catch (e) {
console.warn('Failed to parse cached data from localStorage', e) console.warn('Failed to parse cached data from localStorage', e)
@@ -36,6 +40,7 @@ function init () {
const fetchNotes = function (storages) { const fetchNotes = function (storages) {
const findNotesFromEachStorage = storages const findNotesFromEachStorage = storages
.filter(storage => fs.existsSync(storage.path))
.map((storage) => { .map((storage) => {
return resolveStorageNotes(storage) return resolveStorageNotes(storage)
.then((notes) => { .then((notes) => {
@@ -51,7 +56,11 @@ function init () {
} }
}) })
if (unknownCount > 0) { if (unknownCount > 0) {
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) try {
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
} catch (e) {
console.log('Error writting boostnote.json: ' + e + ' from init.js')
}
} }
return notes return notes
}) })

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

@@ -6,5 +6,8 @@ module.exports = {
}, },
'deleteNote': () => { 'deleteNote': () => {
ee.emit('hotkey:deletenote') ee.emit('hotkey:deletenote')
},
'toggleMenuBar': () => {
ee.emit('menubar:togglemenubar')
} }
} }

View File

@@ -34,7 +34,7 @@ class Crowdfunding extends React.Component {
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p> <p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
<br /> <br />
<p>{i18n.__('### We believe Meritocracy')}</p> <p>{i18n.__('### We believe Meritocracy')}</p>
<p>{i18n.__('We think developers who has skill and did great things must be rewarded properly.')}</p> <p>{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}</p>
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p> <p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
<p>{i18n.__('It sometimes looks like exploitation.')}</p> <p>{i18n.__('It sometimes looks like exploitation.')}</p>
<p>{i18n.__('Weve realized IssueHunt could enhance sustainability of open-source ecosystem.')}</p> <p>{i18n.__('Weve realized IssueHunt could enhance sustainability of open-source ecosystem.')}</p>

View File

@@ -62,7 +62,7 @@
.folderItem-right-button .folderItem-right-button
vertical-align middle vertical-align middle
height 25px height 25px
margin-top 2.5px margin-top 2px
colorDefaultButton() colorDefaultButton()
border-radius 2px border-radius 2px
border $ui-border border $ui-border

View File

@@ -79,7 +79,9 @@ class HotkeyTab extends React.Component {
config.hotkey = { config.hotkey = {
toggleMain: this.refs.toggleMain.value, toggleMain: this.refs.toggleMain.value,
toggleMode: this.refs.toggleMode.value, toggleMode: this.refs.toggleMode.value,
deleteNote: this.refs.deleteNote.value deleteNote: this.refs.deleteNote.value,
pasteSmartly: this.refs.pasteSmartly.value,
toggleMenuBar: this.refs.toggleMenuBar.value
} }
this.setState({ this.setState({
config config
@@ -127,6 +129,17 @@ class HotkeyTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Show/Hide Menu Bar')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}
ref='toggleMenuBar'
value={config.hotkey.toggleMenuBar}
type='text'
/>
</div>
</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Toggle Editor Mode')}</div> <div styleName='group-section-label'>{i18n.__('Toggle Editor Mode')}</div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
@@ -149,6 +162,17 @@ class HotkeyTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Paste HTML')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}
ref='pasteSmartly'
value={config.hotkey.pasteSmartly}
type='text'
/>
</div>
</div>
<div styleName='group-control'> <div styleName='group-control'>
<button styleName='group-control-leftButton' <button styleName='group-control-leftButton'
onClick={(e) => this.handleHintToggleButtonClick(e)} onClick={(e) => this.handleHintToggleButtonClick(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)}
@@ -129,7 +134,7 @@ class InfoTab extends React.Component {
>{i18n.__('Development')}</a>{i18n.__(' : Development configurations for Boostnote.')} >{i18n.__('Development')}</a>{i18n.__(' : Development configurations for Boostnote.')}
</li> </li>
<li styleName='cc'> <li styleName='cc'>
{i18n.__('Copyright (C) 2017 - 2018 BoostIO')} {i18n.__('Copyright (C) 2017 - 2019 BoostIO')}
</li> </li>
<li styleName='cc'> <li styleName='cc'>
{i18n.__('License: GPL v3')} {i18n.__('License: GPL v3')}

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

@@ -75,6 +75,7 @@ class UiTab extends React.Component {
showTagsAlphabetically: this.refs.showTagsAlphabetically.checked, showTagsAlphabetically: this.refs.showTagsAlphabetically.checked,
saveTagsAlphabetically: this.refs.saveTagsAlphabetically.checked, saveTagsAlphabetically: this.refs.saveTagsAlphabetically.checked,
enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked, enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked,
showMenuBar: this.refs.showMenuBar.checked,
disableDirectWrite: this.refs.uiD2w != null disableDirectWrite: this.refs.uiD2w != null
? this.refs.uiD2w.checked ? this.refs.uiD2w.checked
: false : false
@@ -96,7 +97,11 @@ 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,
spellcheck: this.refs.spellcheck.checked matchingPairs: this.refs.matchingPairs.value,
matchingTriples: this.refs.matchingTriples.value,
explodingPairs: this.refs.explodingPairs.value,
spellcheck: this.refs.spellcheck.checked,
enableSmartPaste: this.refs.enableSmartPaste.checked
}, },
preview: { preview: {
fontSize: this.refs.previewFontSize.value, fontSize: this.refs.previewFontSize.value,
@@ -234,6 +239,16 @@ class UiTab extends React.Component {
</div> </div>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.showMenuBar}
ref='showMenuBar'
type='checkbox'
/>&nbsp;
{i18n.__('Show menu bar')}
</label>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}
@@ -477,6 +492,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>))
} }
@@ -552,6 +568,18 @@ class UiTab extends React.Component {
{i18n.__('Enable smart table editor')} {i18n.__('Enable smart table editor')}
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.enableSmartPaste}
ref='enableSmartPaste'
type='checkbox'
/>&nbsp;
{i18n.__('Enable HTML paste')}
</label>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}
@@ -563,6 +591,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'>
@@ -590,6 +660,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

@@ -4,8 +4,10 @@
border none border none
background-color transparent background-color transparent
outline none outline none
padding 0 4px padding 2px 4px
margin 0px 2px 2px
font-size 13px font-size 13px
height 23px
ul ul
position fixed position fixed

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,10 +1,11 @@
# Build # Build
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md), [Portuguese](https://github.com/BoostIO/Boostnote/blob/master/docs/pt_BR/build.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## Environments ## Environments
* npm: 6.x - npm: 6.x
* node: 8.x - node: 8.x
## Development ## Development
@@ -24,10 +25,40 @@ $ yarn run dev
``` ```
> ### Notice > ### Notice
>
> There are some cases where you have to refresh the app manually. > There are some cases where you have to refresh the app manually.
>
> 1. When editing a constructor method of a component > 1. When editing a constructor method of a component
> 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.) > 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.)
## Accessing code used in Pull Requests
Visit the page for the pull request and look at the end of the url for the PR number
<pre>
https://github.com/BoostIO/Boostnote/pull/2794
</pre>
In the following, replace \<PR> with that number (no brackets).
For the above url, you would replace \<PR> with 2794
_If you do not have a local copy of the master branch yet_
```
git clone https://github.com/BoostIO/Boostnote.git
cd Boostnote
git fetch origin pull/<PR>/head:<PR>
git checkout <PR>
```
_If you already have the master branch_
```
git fetch origin pull/<PR>/head:<PR>
git checkout <PR>
```
_To compile and run the code_
```
yarn
yarn run dev
```
## Deploy ## Deploy
We use Grunt to automate deployment. We use Grunt to automate deployment.
@@ -51,7 +82,6 @@ Distribution packages are created by exec `grunt build` on Linux platform (e.g.
After installing the supported version of `node` and `npm`, install build dependency packages. After installing the supported version of `node` and `npm`, install build dependency packages.
Ubuntu/Debian: Ubuntu/Debian:
``` ```

View File

@@ -1,10 +1,11 @@
# Build # Build
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md), [Portugiesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/pt_BR/build.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## Umgebungen ## Umgebungen
* npm: 6.x - npm: 6.x
* node: 8.x - node: 8.x
## Entwicklung ## Entwicklung
@@ -24,7 +25,9 @@ $ yarn run dev
``` ```
> ### Notiz > ### Notiz
>
> Es gibt einige Fälle bei denen die App manuell zu refreshen ist. > Es gibt einige Fälle bei denen die App manuell zu refreshen ist.
>
> 1. Wenn eine "constructor method" einer Komponente manuell editiert wird. > 1. Wenn eine "constructor method" einer Komponente manuell editiert wird.
> 2. Wenn eine neue CSS Klasse ergänzt wird (ähnlich wie 1: die CSS Klasse wird von jeder Komponenete neu geschrieben. Dieser Prozess passiert in der "Constructor method".) > 2. Wenn eine neue CSS Klasse ergänzt wird (ähnlich wie 1: die CSS Klasse wird von jeder Komponenete neu geschrieben. Dieser Prozess passiert in der "Constructor method".)
@@ -51,7 +54,6 @@ Distributions Pakete können mittels `grunt build` auf Linux Plattformen (e.g. U
Nach der Installation der supporteten Version von `node` and `npm`, installiere auch build dependency packages. Nach der Installation der supporteten Version von `node` and `npm`, installiere auch build dependency packages.
Ubuntu/Debian: Ubuntu/Debian:
``` ```

View File

@@ -1,9 +1,8 @@
# How to debug Boostnote (Electron app) # How to debug Boostnote (Electron app)
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md). Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md), [Portugiesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/pt_BR/debug.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
Boostnote ist eine Electron App und basiert auf Chromium.
Boostnote ist eine Electron App und basiert auf Chromium.
Zum Entwicklen verwendest du am Besten die `Developer Tools` von Google Chrome verwenden. Diese kannst du ganz einfach im unter dem Menüpunkt `View` mit `Toggle Developer Tools` aktivieren: Zum Entwicklen verwendest du am Besten die `Developer Tools` von Google Chrome verwenden. Diese kannst du ganz einfach im unter dem Menüpunkt `View` mit `Toggle Developer Tools` aktivieren:
@@ -13,10 +12,9 @@ Die Anzeige der `Developer Tools` sieht in etwa so aus:
![Developer_Tools](https://cloud.githubusercontent.com/assets/11307908/24343545/eff9f3a6-127b-11e7-94cf-cb67bfda634a.png) ![Developer_Tools](https://cloud.githubusercontent.com/assets/11307908/24343545/eff9f3a6-127b-11e7-94cf-cb67bfda634a.png)
## Debugging ## Debugging
Fehlermeldungen werden in der Regel in der `console` ausgegeben, die du über den gleichnamigen Reiter der `Developer Tools` anzeigen lassen kannst. Zum Debuggen kannst du beispielsweise über den `debugger` Haltepunkte im Code setzen. Fehlermeldungen werden in der Regel in der `console` ausgegeben, die du über den gleichnamigen Reiter der `Developer Tools` anzeigen lassen kannst. Zum Debuggen kannst du beispielsweise über den `debugger` Haltepunkte im Code setzen.
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png) ![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png)
@@ -24,8 +22,8 @@ Du kannst aber natürlich auch die Art von Debugging verwenden mit der du am bes
## Referenz ## Referenz
* [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools) - [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools)
--- ---
Special thanks: Translated by [gino909](https://github.com/gino909), [mdeuerlein](https://github.com/mdeuerlein) Special thanks: Translated by [gino909](https://github.com/gino909), [mdeuerlein](https://github.com/mdeuerlein)

View File

@@ -1,8 +1,9 @@
# How to debug Boostnote (Electron app) # How to debug Boostnote (Electron app)
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md). This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md), [Portuguese](https://github.com/BoostIO/Boostnote/blob/master/docs/pt_BR/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
## Debug with Google Chrome developer Tools ## Debug with Google Chrome developer Tools
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome. Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.
You can toggle the `Developer Tools` like this: You can toggle the `Developer Tools` like this:
@@ -14,6 +15,7 @@ The `Developer Tools` will look like this:
When errors occur, the error messages are displayed at the `console`. When errors occur, the error messages are displayed at the `console`.
### Debugging ### Debugging
For example, you can use the `debugger` to set a breakpoint in the code like this: For example, you can use the `debugger` to set a breakpoint in the code like this:
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png) ![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png)
@@ -21,16 +23,18 @@ For example, you can use the `debugger` to set a breakpoint in the code like thi
This is just an illustrative example, you should find a way to debug which fits your style. This is just an illustrative example, you should find a way to debug which fits your style.
### References ### References
* [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools)
- [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools)
## Debug with Visual Studio Code ## Debug with Visual Studio Code
1. Install **[Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome "Install Debugger for Chrome")** plugin for Visual Studio Code. Then restart it. 1. Install **[Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome 'Install Debugger for Chrome')** plugin for Visual Studio Code. Then restart it.
2. Pressing **Shift+Command+B** or running **Run Build Task** from the global **Terminal** menu, then pick the task named **Build Boostnote**. Or run `yarn run watch` from the terminal. 2. Pressing **Shift+Command+B** or running **Run Build Task** from the global **Terminal** menu, then pick the task named **Build Boostnote**. Or run `yarn run watch` from the terminal.
3. When above task is running, open **Debug view** in **Activity Bar** on the side of VS Code or use shortcut **Shift+Command+D**. 3. When above task is running, open **Debug view** in **Activity Bar** on the side of VS Code or use shortcut **Shift+Command+D**.
4. Select the configuration named **Boostnote All** from the **Debug configuration**, then click the green arrow button or press **F5** to start debugging. 4. Select the configuration named **Boostnote All** from the **Debug configuration**, then click the green arrow button or press **F5** to start debugging.
5. Now you should find **Boostnote** is running. You will see two processes running, one named **Boostnote Main** and the other named **Boostnote Renderer**. Now you can set **debug breakpoints** in vscode. If you find your **breakpoints** is unverified, you need to switch to the appropriate process between **Boostnote Renderer** and **Boostnote Main**. 5. Now you should find **Boostnote** is running. You will see two processes running, one named **Boostnote Main** and the other named **Boostnote Renderer**. Now you can set **debug breakpoints** in vscode. If you find your **breakpoints** is unverified, you need to switch to the appropriate process between **Boostnote Renderer** and **Boostnote Main**.
### References ### References
* [Electron application debugging](https://electronjs.org/docs/tutorial/application-debugging)
* [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) - [Electron application debugging](https://electronjs.org/docs/tutorial/application-debugging)
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)

View File

@@ -1,10 +1,11 @@
# Build # Build
Cette page est également disponible en [Anglais](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md)
Cette page est également disponible en [Anglais](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Portugais](https://github.com/BoostIO/Boostnote/blob/master/docs/pt_BR/build.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md)
## Environnements ## Environnements
* npm: 6.x - npm: 6.x
* node: 8.x - node: 8.x
## Développement ## Développement
@@ -16,6 +17,7 @@ Installez les paquets requis à l'aide de `yarn`.
``` ```
$ yarn $ yarn
``` ```
Build et start Build et start
``` ```
@@ -23,7 +25,9 @@ $ yarn run dev
``` ```
> ### Notice > ### Notice
>
> Il y a certains cas où vous voudrez relancer l'application manuellement. > Il y a certains cas où vous voudrez relancer l'application manuellement.
>
> 1. Quand vous éditez la méthode constructeur dans un composant > 1. Quand vous éditez la méthode constructeur dans un composant
> 2. Quand vous ajoutez une nouvelle classe css. (Comme pour 1: la classe est réécrite pour chaque composant. Le process intervient dans la méthode constructeur) > 2. Quand vous ajoutez une nouvelle classe css. (Comme pour 1: la classe est réécrite pour chaque composant. Le process intervient dans la méthode constructeur)
@@ -37,6 +41,7 @@ Nous avons donc préparé un script séparé qui va rendre un fichier exécutabl
``` ```
grunt pre-build grunt pre-build
``` ```
Vous trouverez l'exécutable dans le dossier `dist`. Vous trouverez l'exécutable dans le dossier `dist`.
Note : l'auto updater ne marchera pas car l'application n'est pas signée. Note : l'auto updater ne marchera pas car l'application n'est pas signée.
@@ -50,7 +55,6 @@ Les paquets sont créés en exécutant `grunt build` sur une plateforme Linux (e
Après avoir installé la version supportée de `node` et de `npm`, installer les paquets de builds. Après avoir installé la version supportée de `node` et de `npm`, installer les paquets de builds.
Ubuntu/Debian: Ubuntu/Debian:
``` ```

75
docs/pt_BR/build.md Normal file
View File

@@ -0,0 +1,75 @@
# Build
Esta página também está disponível em [Japônes](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coreano](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russo](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinês simplificado](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Francês](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) e [Alemão](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## Ambiente
- npm: 6.x
- node: 8.x
## Desenvolvimento
Nós usamos o Webpack HMR para desenvolver o Boostnote.
Ao executar os seguintes comandos no diretório raiz do projeto, o Boostnote será iniciado com as configurações padrão.
Instala os pacotes necessários usando o yarn.
```
$ yarn
```
Gerar e iniciar.
```
$ yarn run dev
```
> ### Notice
>
> Existe alguns casos onde você precisa atualizar o app manualmente.
>
> 1. Quando editar um método construtor de um componente
> 2. Quando adicionar uma nova classe de css (similiar ao 1: a classe do css é reescrita por cada componente. Esse processo ocorre através do método construtor)
## Deploy
Nós usamos o Grunt para automatizar o desenvolvimento.
Você pode gerar o programa usando `grunt`. Contudo, nós não recomendamos isso porque a tarefa padrão inclui _codedesign_ e _authenticode_.
Então nós preparamos um _script_ separado, o qual somente cria um executável.
```
grunt pre-build
```
Você irá encontrar o executável na pasta `dist`. Nota: o atualizador automático não funciona porque o app não está certificado.
Se você achar isto necessário, você pode usar o _codesign_ ou o _authenticode_ com esse executável.
## Faça seus próprios pacotes de distribuição (deb, rpm)
Pacotes de distribuição são gerados através do comando `grunt build` em plataforma Linux (e.g. Ubuntu, Fedora).
> Nota: você pode criar `.deb` e `.rpm` em um mesmo ambiente.
Depois de instalar uma versão suportada do `node` e do `npm`, deve-se instalar as dependências para gerar os pacotes.
Ubuntu/Debian:
```
$ sudo apt-get install -y rpm fakeroot
```
Fedora:
```
$ sudo dnf install -y dpkg dpkg-dev rpm-build fakeroot
```
Então execute `grunt build`.
```
$ grunt build
```
Você vai encontrar o `.deb` e o `.rpm` na pasta`dist`.

40
docs/pt_BR/debug.md Normal file
View File

@@ -0,0 +1,40 @@
# Como debugar Boostnote (app Electron)
Esta página também está disponível em [Japônes](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Coreano](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russo](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Chinês simplificado](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [Francês](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) e [Alemão](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
## Debugar com o Google Chrome developer Tools
Boostnote é um app Electron, por isso ele é baseado no Chromium; desenvolvedores podem usar o `Developer Tools` igual no Google Chrome.
Você pode habilitar e desabilitar o `Developer Tools` assim:
![how_to_toggle_devTools](https://cloud.githubusercontent.com/assets/11307908/24343585/162187e2-127c-11e7-9c01-23578db03ecf.png)
O `Developer Tools` deve parecer assim:
![Developer_Tools](https://cloud.githubusercontent.com/assets/11307908/24343545/eff9f3a6-127b-11e7-94cf-cb67bfda634a.png)
Quando erros acontecem, eles são apresentados na aba `console`.
### Debugando
Por exemplo, você pode usar o `debugger` para adicionar um _breakpoint_ (ponto de parada) no código dessa forma:
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png)
Isso é só um exemplo ilustrativo, mas você deve encontrar um jeito de debugar que encaixe no seu estilo.
### Referências
- [Documentação do Google Chrome sobre como debugar](https://developer.chrome.com/devtools)
## Debugar com o Visual Studio Code (VS Code)
1. Instale o plugin **[Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome 'Instale o pacote Debugger for Chrome')** para Visual Studio Code. Então reinicie-o.
2. Pressione **Shift+Command+B** ou execute **Run Build Task** do menu global **Terminal**, então seleciona a task **Build Boostnote**. Ou execute `yarn run watch` no terminal.
3. Quando a task acima estiver rodando, abra o **Debug view** na **Activity Bar** no lado do seu VS Code ou use o atalho **Shift+Command+D**.
4. Selecione a configuração **Boostnote All** no **Debug configuration**, então clique na seta verde ou aperte **F5** para começar a debugar.
5. Agora você deve encontrar seu **Boostnote** rodando. Você vai ver dois processos rodando, um com nome de **Boostnote Main** e outro com nome de **Boostnote Renderer**. Agora você pode adicionar os **debug breakpoints** no vscode. Se os seus **breakpoints** não forem alertados você pode precisar altenrar entre os processos **Boostnote Renderer** e **Boostnote Main**.
### Referências
- [Debugando uma aplicação Electron](https://electronjs.org/docs/tutorial/application-debugging)
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)

View File

@@ -1,10 +1,11 @@
# 編譯 # 編譯
此文件還提供下列的語言 [日文](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [韓文](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [俄文](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [簡體中文](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [法文](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [德文](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
此文件還提供下列的語言 [日文](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [韓文](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [俄文](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [簡體中文](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [法文](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md), [葡萄牙](https://github.com/BoostIO/Boostnote/blob/master/docs/pt_BR/build.md) and [德文](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## 環境 ## 環境
* npm: 6.x - npm: 6.x
* node: 8.x - node: 8.x
## 開發 ## 開發
@@ -25,7 +26,9 @@ $ yarn run dev
``` ```
> ### Notice > ### Notice
>
> There are some cases where you have to refresh the app manually. > There are some cases where you have to refresh the app manually.
>
> 1. When editing a constructor method of a component > 1. When editing a constructor method of a component
> 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.) > 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.)
@@ -52,7 +55,6 @@ Distribution packages are created by exec `grunt build` on Linux platform (e.g.
After installing the supported version of `node` and `npm`, install build dependency packages. After installing the supported version of `node` and `npm`, install build dependency packages.
Ubuntu/Debian: Ubuntu/Debian:
``` ```

View File

@@ -44,9 +44,46 @@
? match[2].replace('x', ' ') ? match[2].replace('x', ' ')
: (parseInt(match[3], 10) + 1) + match[4] : (parseInt(match[3], 10) + 1) + match[4]
replacements[i] = '\n' + indent + bullet + after replacements[i] = '\n' + indent + bullet + after
if (bullet) incrementRemainingMarkdownListNumbers(cm, pos)
} }
} }
cm.replaceSelections(replacements) cm.replaceSelections(replacements)
} }
// Auto-updating Markdown list numbers when a new item is added to the
// middle of a list
function incrementRemainingMarkdownListNumbers(cm, pos) {
var startLine = pos.line, lookAhead = 0, skipCount = 0
var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1]
do {
lookAhead += 1
var nextLineNumber = startLine + lookAhead
var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine)
if (nextItem) {
var nextIndent = nextItem[1]
var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount)
var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber
if (startIndent === nextIndent && !isNaN(nextNumber)) {
if (newNumber === nextNumber) itemNumber = nextNumber + 1
if (newNumber > nextNumber) itemNumber = newNumber + 1
cm.replaceRange(
nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]),
{
line: nextLineNumber, ch: 0
}, {
line: nextLineNumber, ch: nextLine.length
})
} else {
if (startIndent.length > nextIndent.length) return
// This doesn't run if the next line immediatley indents, as it is
// not clear of the users intention (new indented item or same level)
if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return
skipCount += 1
}
}
} while (nextItem)
}
}) })

View File

@@ -32,6 +32,7 @@ ipcMain.on('config-renew', (e, payload) => {
globalShortcut.unregisterAll() globalShortcut.unregisterAll()
var { config } = payload var { config } = payload
mainWindow.setMenuBarVisibility(config.ui.showMenuBar)
var errors = [] var errors = []
try { try {
globalShortcut.register(config.hotkey.toggleMain, toggleMainWindow) globalShortcut.register(config.hotkey.toggleMain, toggleMainWindow)

View File

@@ -3,6 +3,7 @@ const app = electron.app
const Menu = electron.Menu const Menu = electron.Menu
const ipc = electron.ipcMain const ipc = electron.ipcMain
const GhReleases = require('electron-gh-releases') const GhReleases = require('electron-gh-releases')
const isDev = process.env.NODE_ENV !== 'production'
// electron.crashReporter.start() // electron.crashReporter.start()
var ipcServer = null var ipcServer = null
@@ -35,6 +36,10 @@ const updater = new GhReleases(ghReleasesOpts)
// Check for updates // Check for updates
// `status` returns true if there is a new update available // `status` returns true if there is a new update available
function checkUpdate () { function checkUpdate () {
if (isDev) { // Prevents app from attempting to update when in dev mode.
console.log('Updates are disabled in Development mode, see main-app.js')
return true
}
if (process.platform === 'linux' || isUpdateReady) { if (process.platform === 'linux' || isUpdateReady) {
return true return true
} }
@@ -94,12 +99,12 @@ app.on('ready', function () {
// Check update every day // Check update every day
setInterval(function () { setInterval(function () {
checkUpdate() if (!isDev) checkUpdate()
}, 1000 * 60 * 60 * 24) }, 1000 * 60 * 60 * 24)
// Check update after 10 secs to prevent file locking of Windows // Check update after 10 secs to prevent file locking of Windows
setTimeout(() => { setTimeout(() => {
checkUpdate() if (!isDev) checkUpdate()
ipc.on('update-check', function (event, msg) { ipc.on('update-check', function (event, msg) {
if (isUpdateReady) { if (isUpdateReady) {

View File

@@ -85,39 +85,24 @@ const file = {
}, },
{ {
label: 'Focus Note', label: 'Focus Note',
accelerator: 'Control+E', accelerator: macOS ? 'Command+E' : 'Control+E',
click () { click () {
mainWindow.webContents.send('detail:focus') mainWindow.webContents.send('detail:focus')
} }
}, },
{ {
type: 'separator' label: 'Delete Note',
accelerator: macOS ? 'Command+Shift+Backspace' : 'Control+Shift+Backspace',
click () {
mainWindow.webContents.send('detail:delete')
}
}, },
{ {
label: 'Export as', label: 'Clone Note',
submenu: [ accelerator: macOS ? 'Command+D' : 'Control+D',
{ click () {
label: 'Plain Text (.txt)', mainWindow.webContents.send('list:clone')
click () { }
mainWindow.webContents.send('list:isMarkdownNote')
mainWindow.webContents.send('export:save-text')
}
},
{
label: 'MarkDown (.md)',
click () {
mainWindow.webContents.send('list:isMarkdownNote')
mainWindow.webContents.send('export:save-md')
}
},
{
label: 'HTML (.html)',
click () {
mainWindow.webContents.send('list:isMarkdownNote')
mainWindow.webContents.send('export:save-html')
}
}
]
}, },
{ {
type: 'separator' type: 'separator'
@@ -134,13 +119,30 @@ const file = {
] ]
}, },
{ {
type: 'separator' label: 'Export as',
}, submenu: [
{ {
label: 'Format Table', label: 'Plain Text (.txt)',
click () { click () {
mainWindow.webContents.send('code:format-table') mainWindow.webContents.send('list:isMarkdownNote', 'export-txt')
} mainWindow.webContents.send('export:save-text')
}
},
{
label: 'MarkDown (.md)',
click () {
mainWindow.webContents.send('list:isMarkdownNote', 'export-md')
mainWindow.webContents.send('export:save-md')
}
},
{
label: 'HTML (.html)',
click () {
mainWindow.webContents.send('list:isMarkdownNote', 'export-html')
mainWindow.webContents.send('export:save-html')
}
}
]
}, },
{ {
type: 'separator' type: 'separator'
@@ -153,24 +155,20 @@ const file = {
} }
}, },
{ {
type: 'separator' label: 'Format Table',
},
{
label: 'Print',
accelerator: 'CommandOrControl+P',
click () { click () {
mainWindow.webContents.send('list:isMarkdownNote') mainWindow.webContents.send('code:format-table')
mainWindow.webContents.send('print')
} }
}, },
{ {
type: 'separator' type: 'separator'
}, },
{ {
label: 'Delete Note', label: 'Print',
accelerator: macOS ? 'Control+Backspace' : 'Control+Delete', accelerator: 'CommandOrControl+P',
click () { click () {
mainWindow.webContents.send('detail:delete') mainWindow.webContents.send('list:isMarkdownNote', 'print')
mainWindow.webContents.send('print')
} }
} }
] ]
@@ -296,9 +294,6 @@ const view = {
mainWindow.setFullScreen(!mainWindow.isFullScreen()) mainWindow.setFullScreen(!mainWindow.isFullScreen())
} }
}, },
{
type: 'separator'
},
{ {
label: 'Toggle Side Bar', label: 'Toggle Side Bar',
accelerator: 'CommandOrControl+B', accelerator: 'CommandOrControl+B',
@@ -388,6 +383,27 @@ const help = {
{ {
label: 'Changelog', label: 'Changelog',
click () { shell.openExternal('https://github.com/BoostIO/boost-releases') } click () { shell.openExternal('https://github.com/BoostIO/boost-releases') }
},
{
label: 'Cheatsheets',
submenu: [
{
label: 'Markdown',
click () { shell.openExternal('https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet') }
},
{
label: 'Latex',
click () { shell.openExternal('https://katex.org/docs/supported.html') }
},
{
label: 'HTML',
click () { shell.openExternal('https://htmlcheatsheet.com/') }
},
{
label: 'Boostnote',
click () { shell.openExternal('https://github.com/TobseF/boostnote-markdown-cheatsheet/blob/master/BOOSTNOTE_MARKDOWN_CHEAT_SHEET.md') }
}
]
} }
] ]
} }

View File

@@ -6,7 +6,6 @@ const Config = require('electron-config')
const config = new Config() const config = new Config()
const _ = require('lodash') const _ = require('lodash')
var showMenu = process.platform !== 'win32'
const windowSize = config.get('windowsize') || { const windowSize = config.get('windowsize') || {
x: null, x: null,
y: null, y: null,
@@ -22,7 +21,6 @@ const mainWindow = new BrowserWindow({
useContentSize: true, useContentSize: true,
minWidth: 500, minWidth: 500,
minHeight: 320, minHeight: 320,
autoHideMenuBar: showMenu,
webPreferences: { webPreferences: {
zoomFactor: 1.0, zoomFactor: 1.0,
enableBlinkFeatures: 'OverlayScrollbars' enableBlinkFeatures: 'OverlayScrollbars'
@@ -33,6 +31,7 @@ const mainWindow = new BrowserWindow({
const url = path.resolve(__dirname, './main.html') const url = path.resolve(__dirname, './main.html')
mainWindow.loadURL('file://' + url) mainWindow.loadURL('file://' + url)
mainWindow.setMenuBarVisibility(false)
mainWindow.webContents.on('new-window', function (e) { mainWindow.webContents.on('new-window', function (e) {
e.preventDefault() e.preventDefault()

View File

@@ -8,7 +8,10 @@
"to create a new note": "ノートを新規に作成", "to create a new note": "ノートを新規に作成",
"Toggle Mode": "モード切替", "Toggle Mode": "モード切替",
"Add tag...": "タグを追加...", "Add tag...": "タグを追加...",
"Star": "お気に入り",
"Fullscreen": "全画面",
"Trash": "ゴミ箱", "Trash": "ゴミ箱",
"Info": "情報",
"MODIFICATION DATE": "修正日", "MODIFICATION DATE": "修正日",
"Words": "ワード", "Words": "ワード",
"Letters": "文字", "Letters": "文字",
@@ -26,6 +29,9 @@
"Storage Locations": "ストレージ", "Storage Locations": "ストレージ",
"Add Storage Location": "ストレージロケーションを追加", "Add Storage Location": "ストレージロケーションを追加",
"Add Folder": "フォルダを追加", "Add Folder": "フォルダを追加",
"Create new folder": "新規フォルダ作成",
"Folder name": "フォルダ名",
"Create": "作成",
"Select Folder": "フォルダを選択", "Select Folder": "フォルダを選択",
"Open Storage folder": "ストレージフォルダを開く", "Open Storage folder": "ストレージフォルダを開く",
"Unlink": "リンク解除", "Unlink": "リンク解除",
@@ -37,9 +43,15 @@
"White": "白", "White": "白",
"Solarized Dark": "明灰", "Solarized Dark": "明灰",
"Dark": "暗灰", "Dark": "暗灰",
"Default New Note": "新規ノートの形式",
"Always Ask": "作成時に聞く",
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する", "Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)", "Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
"Save tags of a note in alphabetical order": "ノートのタグをアルファベット順に保存する",
"Show tags of a note in alphabetical order": "ノートのタグをアルファベット順に表示する",
"Show only related tags": "関連するタグのみ表示する", "Show only related tags": "関連するタグのみ表示する",
"Enable live count of notes": "タグ選択時にノート数を再計算して表示する",
"New notes are tagged with the filtering tags": "新規ノートに選択中のタグを付与する",
"Editor Theme": "エディタのテーマ", "Editor Theme": "エディタのテーマ",
"Editor Font Size": "エディタのフォントサイズ", "Editor Font Size": "エディタのフォントサイズ",
"Editor Font Family": "エディタのフォント", "Editor Font Family": "エディタのフォント",
@@ -55,21 +67,32 @@
"vim": "vim", "vim": "vim",
"emacs": "emacs", "emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください", "⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください",
"Snippet Default Language": "スニペットのデフォルト言語",
"Extract title from front matter": "Front matterからタイトルを抽出する",
"Show line numbers in the editor": "エディタ内に行番号を表示", "Show line numbers in the editor": "エディタ内に行番号を表示",
"Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする", "Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする",
"Enable smart quotes": "スマートクォートを有効にする", "Enable smart quotes": "スマートクォートを有効にする",
"Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する", "Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する",
"Enable smart table editor": "スマートテーブルエディタを有効にする",
"Enable HTML paste": "HTML貼り付けを有効にする",
"Matching character pairs": "自動補完する括弧ペアの列記",
"Matching character triples": "自動補完する3文字括弧の列記",
"Exploding character pairs": "改行時に空行を挿入する括弧ペアの列記",
"Preview": "プレビュー", "Preview": "プレビュー",
"Preview Font Size": "プレビュー時フォントサイズ", "Preview Font Size": "プレビュー時フォントサイズ",
"Preview Font Family": "プレビュー時フォント", "Preview Font Family": "プレビュー時フォント",
"Code Block Theme": "コードブロックのテーマ", "Code Block Theme": "コードブロックのテーマ",
"Allow line through checkbox": "チェック済みチェックボックスのテキストに取り消し線を付与する",
"Allow preview to scroll past the last line": "プレビュー時に最終行以降にスクロールできるようにする", "Allow preview to scroll past the last line": "プレビュー時に最終行以降にスクロールできるようにする",
"When scrolling, synchronize preview with editor": "エディタとプレビューのスクロールを同期する",
"Show line numbers for preview code blocks": "プレビュー時のコードブロック内に行番号を表示する", "Show line numbers for preview code blocks": "プレビュー時のコードブロック内に行番号を表示する",
"LaTeX Inline Open Delimiter": "LaTeX 開始デリミタ(インライン)", "LaTeX Inline Open Delimiter": "LaTeX 開始デリミタ(インライン)",
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)", "LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)", "LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)", "LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
"PlantUML Server": "PlantUML サーバー", "PlantUML Server": "PlantUML サーバー",
"Custom CSS": "カスタムCSS",
"Allow custom CSS for preview": "プレビュー用のカスタムCSSを許可する",
"Community": "コミュニティ", "Community": "コミュニティ",
"Subscribe to Newsletter": "ニュースレターを購読する", "Subscribe to Newsletter": "ニュースレターを購読する",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -105,7 +128,19 @@
"French": "フランス語", "French": "フランス語",
"Show \"Saved to Clipboard\" notification when copying": "クリップボードコピー時に \"クリップボードに保存\" 通知を表示する", "Show \"Saved to Clipboard\" notification when copying": "クリップボードコピー時に \"クリップボードに保存\" 通知を表示する",
"All Notes": "すべてのノート", "All Notes": "すべてのノート",
"Pin to Top": "一番上にピン留め",
"Remove pin": "ピン削除",
"Clone Note": "ノート複製",
"Copy Note Link": "ノートのリンクをコピー",
"Publish Blog": "ブログを公開",
"Starred": "スター付き", "Starred": "スター付き",
"Empty Trash": "ゴミ箱を空にする",
"Restore Note": "ノート復元",
"Rename Folder": "フォルダの名称変更",
"Export Folder": "フォルダの書き出し",
"Export as txt": ".txtで書き出す",
"Export as md": ".mdで書き出す",
"Delete Folder": "フォルダ削除",
"Are you sure to ": "本当に ", "Are you sure to ": "本当に ",
" delete": "このフォルダを", " delete": "このフォルダを",
"this folder?": "削除しますか?", "this folder?": "削除しますか?",
@@ -135,6 +170,8 @@
"Hotkeys": "ホットキー", "Hotkeys": "ホットキー",
"Show/Hide Boostnote": "Boostnote の表示/非表示", "Show/Hide Boostnote": "Boostnote の表示/非表示",
"Toggle Editor Mode": "エディタモードの切替", "Toggle Editor Mode": "エディタモードの切替",
"Delete Note": "ノート削除",
"Paste HTML": "HTMLで貼り付け",
"Restore": "リストア", "Restore": "リストア",
"Permanent Delete": "永久に削除", "Permanent Delete": "永久に削除",
"Confirm note deletion": "ノート削除確認", "Confirm note deletion": "ノート削除確認",
@@ -165,6 +202,7 @@
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup", "Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "ロケーション", "Location": "ロケーション",
"Add": "追加", "Add": "追加",
"Export Storage": "ストレージの書き出し",
"Unlink Storage": "ストレージのリンクを解除", "Unlink Storage": "ストレージのリンクを解除",
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "リンクの解除ではBoostnoteからリンクされたストレージを削除しますが、データは削除されません。データを削除する場合はご自身でハードドライブからフォルダを削除してください。", "Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "リンクの解除ではBoostnoteからリンクされたストレージを削除しますが、データは削除されません。データを削除する場合はご自身でハードドライブからフォルダを削除してください。",
"Editor Rulers": "罫線", "Editor Rulers": "罫線",

View File

@@ -33,7 +33,7 @@
"White": "Branco", "White": "Branco",
"Solarized Dark": "Escuro Solarizado", "Solarized Dark": "Escuro Solarizado",
"Dark": "Escuro", "Dark": "Escuro",
"Show a confirmation dialog when deleting notes": "Mostrar um diálogo de confirmação ao escluir notas", "Show a confirmation dialog when deleting notes": "Mostrar um diálogo de confirmação ao excluir notas",
"Editor Theme": "Tema do Editor", "Editor Theme": "Tema do Editor",
"Editor Font Size": "Tamanho da Fonte do Editor", "Editor Font Size": "Tamanho da Fonte do Editor",
"Editor Font Family": "Família da Fonte do Editor", "Editor Font Family": "Família da Fonte do Editor",

View File

@@ -1,7 +1,7 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.11.11", "version": "0.11.13",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -31,7 +31,7 @@
"storage", "storage",
"electron" "electron"
], ],
"author": "Dick Choi <fluke8259@gmail.com> (https://github.com/Rokt33r)", "author": "Junyoung Choi <fluke8259@gmail.com> (https://github.com/Rokt33r)",
"contributors": [ "contributors": [
"Kazu Yokomizo (https://github.com/kazup01)", "Kazu Yokomizo (https://github.com/kazup01)",
"dojineko (https://github.com/dojineko)", "dojineko (https://github.com/dojineko)",
@@ -41,7 +41,8 @@
"Yoshihisa Mochihara (https://github.com/yosmoc)", "Yoshihisa Mochihara (https://github.com/yosmoc)",
"Mike Resoli (https://github.com/mikeres0)", "Mike Resoli (https://github.com/mikeres0)",
"tjado (https://github.com/tejado)", "tjado (https://github.com/tejado)",
"Sota Sugiura (https://github.com/sota1235)" "Sota Sugiura (https://github.com/sota1235)",
"Milo Todt (https://github.com/MiloTodt)"
], ],
"bugs": { "bugs": {
"url": "https://github.com/BoostIO/Boostnote/issues" "url": "https://github.com/BoostIO/Boostnote/issues"
@@ -62,13 +63,15 @@
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
"file-uri-to-path": "^1.0.0", "file-uri-to-path": "^1.0.0",
"file-url": "^2.0.2", "file-url": "^2.0.2",
"filenamify": "^2.0.0", "filenamify": "^2.1.0",
"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",
"invert-color": "^2.0.0",
"js-sequence-diagrams": "^1000000.0.6", "js-sequence-diagrams": "^1000000.0.6",
"js-yaml": "^3.12.0", "js-yaml": "^3.12.0",
"katex": "^0.9.0", "katex": "^0.9.0",
@@ -90,17 +93,20 @@
"mdurl": "^1.0.1", "mdurl": "^1.0.1",
"mermaid": "^8.0.0-rc.8", "mermaid": "^8.0.0-rc.8",
"moment": "^2.10.3", "moment": "^2.10.3",
"mousetrap": "^1.6.1", "mousetrap": "^1.6.2",
"mousetrap-global-bind": "^1.1.0", "mousetrap-global-bind": "^1.1.0",
"node-ipc": "^8.1.0", "node-ipc": "^8.1.0",
"raphael": "^2.2.7", "raphael": "^2.2.7",
"react": "^15.5.4", "react": "^15.5.4",
"react-autosuggest": "^9.4.0", "react-autosuggest": "^9.4.0",
"react-codemirror": "^0.3.0", "react-codemirror": "^0.3.0",
"react-color": "^2.2.2",
"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 +148,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",
@@ -150,7 +157,6 @@
"merge-stream": "^1.0.0", "merge-stream": "^1.0.0",
"mock-require": "^3.0.1", "mock-require": "^3.0.1",
"nib": "^1.1.0", "nib": "^1.1.0",
"react-color": "^2.2.2",
"react-css-modules": "^3.7.6", "react-css-modules": "^3.7.6",
"react-input-autosize": "^1.1.0", "react-input-autosize": "^1.1.0",
"react-router": "^2.4.0", "react-router": "^2.4.0",
@@ -189,5 +195,10 @@
"<rootDir>/tests/jest.js", "<rootDir>/tests/jest.js",
"jest-localstorage-mock" "jest-localstorage-mock"
] ]
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
} }
} }

View File

@@ -5,10 +5,14 @@
<h4 align="center">Note-taking app for programmers. </h4> <h4 align="center">Note-taking app for programmers. </h4>
<h5 align="center">Apps available for Mac, Windows, Linux, Android, and iOS.</h5> <h5 align="center">Apps available for Mac, Windows, Linux, Android, and iOS.</h5>
<h5 align="center">Built with Electron, React + Redux, Webpack, and CSSModules.</h5> <h5 align="center">Built with Electron, React + Redux, Webpack, and CSSModules.</h5>
<p align="center">
[![Build Status](https://travis-ci.org/BoostIO/Boostnote.svg?branch=master)](https://travis-ci.org/BoostIO/Boostnote) <a href="https://travis-ci.org/BoostIO/Boostnote">
<img src="https://travis-ci.org/BoostIO/Boostnote.svg?branch=master" alt="Build Status" />
</a>
</p>
## Authors & Maintainers ## Authors & Maintainers
- [Rokt33r](https://github.com/rokt33r) - [Rokt33r](https://github.com/rokt33r)
- [Sosuke](https://github.com/sosukesuzuki) - [Sosuke](https://github.com/sosukesuzuki)
- [Kazz](https://github.com/kazup01) - [Kazz](https://github.com/kazup01)
@@ -20,11 +24,11 @@ Thank you to all the people who have contributed to Boostnote!
<a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a> <a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a>
## Supporting Boostnote ## Supporting Boostnote
Boostnote is an open source project. It's an independent project with its ongoing development made possible thanks to the support by our amazing backers. Boostnote is an open source project. It's an independent project with its ongoing development made possible thanks to the support by our amazing backers.
Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer: Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer:
[![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/)
@@ -38,7 +42,7 @@ Issues on Boostnote can be funded by anyone and the money will be distributed to
* Website: https://boostnote.io * Website: https://boostnote.io
* Newsletters: https://boostnote.io/#subscribe * Newsletters: https://boostnote.io/#subscribe
* [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote. * [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
* Copyright (C) 2016 - 2018 BoostIO, Inc. * Copyright (C) 2016 - 2019 BoostIO, Inc.
#### License #### License

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
<title>x</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="Feather" transform="translate(-1910.000000, -644.000000)" stroke="#c1c1c1" stroke-width="2">
<g id="Group" transform="translate(175.000000, 332.000000)">
<g id="x" transform="translate(1736.000000, 313.000000)">
<path d="M12,0 L0,12" id="Shape"></path>
<path d="M0,0 L12,12" id="Shape"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 917 B

View File

@@ -3,7 +3,7 @@ import renderer from 'react-test-renderer'
import TagListItem from 'browser/components/TagListItem' import TagListItem from 'browser/components/TagListItem'
it('TagListItem renders correctly', () => { it('TagListItem renders correctly', () => {
const tagListItem = renderer.create(<TagListItem name='Test' handleClickTagListItem={jest.fn()} />) const tagListItem = renderer.create(<TagListItem name='Test' handleClickTagListItem={jest.fn()} color='#000' />)
expect(tagListItem.toJSON()).toMatchSnapshot() expect(tagListItem.toJSON()).toMatchSnapshot()
}) })

View File

@@ -12,6 +12,14 @@ exports[`TagListItem renders correctly 1`] = `
className="tagList-item" className="tagList-item"
onClick={[Function]} onClick={[Function]}
> >
<span
className="tagList-item-color"
style={
Object {
"backgroundColor": "#000",
}
}
/>
<span <span
className="tagList-item-name" className="tagList-item-name"
> >

View File

@@ -268,6 +268,7 @@ it('should test that copyAttachment with url (without extension, with query)', f
it('should replace the all ":storage" path with the actual storage path', function () { it('should replace the all ":storage" path with the actual storage path', function () {
const storageFolder = systemUnderTest.DESTINATION_FOLDER const storageFolder = systemUnderTest.DESTINATION_FOLDER
const noteKey = '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c'
const testInput = const testInput =
'<html>\n' + '<html>\n' +
' <head>\n' + ' <head>\n' +
@@ -276,14 +277,18 @@ it('should replace the all ":storage" path with the actual storage path', functi
' <body data-theme="default">\n' + ' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + ' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' + ' <p data-line="2">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + ' <img src=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="4">\n' + ' <p data-line="4">\n' +
' <a href=":storage' + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' + ' <a href=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="6">\n' + ' <p data-line="6">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' + ' <img src=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
' </p>\n' + ' </p>\n' +
' <pre class="fence" data-line="8">\n' +
' <span class="filename"></span>\n' +
' <div class="gallery" data-autoplay="undefined" data-height="undefined">:storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + 'f939b2c3.jpg</div>\n' +
' </pre>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'
const storagePath = '<<dummyStoragePath>>' const storagePath = '<<dummyStoragePath>>'
@@ -295,14 +300,18 @@ it('should replace the all ":storage" path with the actual storage path', functi
' <body data-theme="default">\n' + ' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + ' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' + ' <p data-line="2">\n' +
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + ' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="4">\n' + ' <p data-line="4">\n' +
' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' + ' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="6">\n' + ' <p data-line="6">\n' +
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' + ' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
' </p>\n' + ' </p>\n' +
' <pre class="fence" data-line="8">\n' +
' <span class="filename"></span>\n' +
' <div class="gallery" data-autoplay="undefined" data-height="undefined">file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + 'f939b2c3.jpg</div>\n' +
' </pre>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'
const actual = systemUnderTest.fixLocalURLS(testInput, storagePath) const actual = systemUnderTest.fixLocalURLS(testInput, storagePath)
@@ -311,6 +320,7 @@ it('should replace the all ":storage" path with the actual storage path', functi
it('should replace the ":storage" path with the actual storage path when they have different path separators', function () { it('should replace the ":storage" path with the actual storage path when they have different path separators', function () {
const storageFolder = systemUnderTest.DESTINATION_FOLDER const storageFolder = systemUnderTest.DESTINATION_FOLDER
const noteKey = '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c'
const testInput = const testInput =
'<html>\n' + '<html>\n' +
' <head>\n' + ' <head>\n' +
@@ -319,10 +329,10 @@ it('should replace the ":storage" path with the actual storage path when they ha
' <body data-theme="default">\n' + ' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + ' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' + ' <p data-line="2">\n' +
' <img src=":storage' + mdurl.encode(path.win32.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + ' <img src=":storage' + mdurl.encode(path.win32.sep) + noteKey + mdurl.encode(path.win32.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="4">\n' + ' <p data-line="4">\n' +
' <a href=":storage' + mdurl.encode(path.posix.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' + ' <a href=":storage' + mdurl.encode(path.posix.sep) + noteKey + mdurl.encode(path.posix.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' + ' </p>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'
@@ -335,10 +345,10 @@ it('should replace the ":storage" path with the actual storage path when they ha
' <body data-theme="default">\n' + ' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + ' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' + ' <p data-line="2">\n' +
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + ' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="4">\n' + ' <p data-line="4">\n' +
' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' + ' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' + ' </p>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'
@@ -391,28 +401,17 @@ it('should test that getAttachmentsInMarkdownContent finds all attachments when
it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () { it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () {
const dummyStoragePath = 'dummyStoragePath' const dummyStoragePath = 'dummyStoragePath'
const testInput = const noteKey = '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c'
'<html>\n' + const testInput = '"# Test\n' +
' <head>\n' + '\n' +
' //header\n' + '![Screenshot1](:storage' + path.win32.sep + noteKey + path.win32.sep + '0.6r4zdgc22xp.png)\n' +
' </head>\n' + '![Screenshot2](:storage' + path.posix.sep + noteKey + path.posix.sep + '0.q2i4iw0fyx.pdf)\n' +
' <body data-theme="default">\n' + '![Screenshot3](:storage' + path.win32.sep + noteKey + path.posix.sep + 'd6c5ee92.jpg)"'
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' +
' <p data-line="4">\n' +
' <a href=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' +
' <p data-line="6">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
' </p>\n' +
' </body>\n' +
'</html>'
const actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath) const actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath)
const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp.png', const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + noteKey + path.sep + '0.6r4zdgc22xp.png',
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx.pdf', dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + noteKey + path.sep + '0.q2i4iw0fyx.pdf',
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + noteKey + path.sep + 'd6c5ee92.jpg']
expect(actual).toEqual(expect.arrayContaining(expected)) expect(actual).toEqual(expect.arrayContaining(expected))
}) })
@@ -427,13 +426,13 @@ it('should remove the all ":storage" and noteKey references', function () {
' <body data-theme="default">\n' + ' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + ' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' + ' <p data-line="2">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + ' <img src=":storage' + mdurl.encode(path.win32.sep) + noteKey + mdurl.encode(path.win32.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="4">\n' + ' <p data-line="4">\n' +
' <a href=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' + ' <a href=":storage' + mdurl.encode(path.posix.sep) + noteKey + mdurl.encode(path.posix.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="6">\n' + ' <p data-line="6">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' + ' <img src=":storage' + mdurl.encode(path.win32.sep) + noteKey + mdurl.encode(path.posix.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
' </p>\n' + ' </p>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'
@@ -463,8 +462,8 @@ it('should make sure that "removeStorageAndNoteReferences" works with markdown c
const noteKey = 'noteKey' const noteKey = 'noteKey'
const testInput = const testInput =
'Test input' + 'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'image.jpg](imageName}) \n' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + noteKey + path.win32.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'pdf.pdf](pdf})' '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + noteKey + path.posix.sep + 'pdf.pdf](pdf})'
const expectedOutput = const expectedOutput =
'Test input' + 'Test input' +
@@ -616,8 +615,8 @@ it('should test that moveAttachments returns a correct modified content version'
const newNoteKey = 'newNoteKey' const newNoteKey = 'newNoteKey'
const testInput = const testInput =
'Test input' + 'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNoteKey + path.sep + 'image.jpg](imageName}) \n' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + oldNoteKey + path.win32.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNoteKey + path.sep + 'pdf.pdf](pdf})' '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + oldNoteKey + path.posix.sep + 'pdf.pdf](pdf})'
const expectedOutput = const expectedOutput =
'Test input' + 'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + 'image.jpg](imageName}) \n' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + 'image.jpg](imageName}) \n' +
@@ -632,8 +631,8 @@ it('should test that cloneAttachments modifies the content of the new note corre
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'} const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
const testInput = const testInput =
'Test input' + 'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + oldNote.key + path.win32.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})' '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + oldNote.key + path.posix.sep + 'pdf.pdf](pdf})'
newNote.content = testInput newNote.content = testInput
findStorage.findStorage = jest.fn() findStorage.findStorage = jest.fn()
findStorage.findStorage.mockReturnValue({path: 'dummyStoragePath'}) findStorage.findStorage.mockReturnValue({path: 'dummyStoragePath'})
@@ -656,8 +655,8 @@ it('should test that cloneAttachments finds all attachments and copies them to t
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'MARKDOWN_NOTE'} const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'MARKDOWN_NOTE'}
const testInput = const testInput =
'Test input' + 'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + oldNote.key + path.win32.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})' '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + oldNote.key + path.posix.sep + 'pdf.pdf](pdf})'
oldNote.content = testInput oldNote.content = testInput
newNote.content = testInput newNote.content = testInput
@@ -706,14 +705,22 @@ it('should test that isAttachmentLink works correctly', function () {
expect(systemUnderTest.isAttachmentLink('text')).toBe(false) expect(systemUnderTest.isAttachmentLink('text')).toBe(false)
expect(systemUnderTest.isAttachmentLink('text [linkText](link)')).toBe(false) expect(systemUnderTest.isAttachmentLink('text [linkText](link)')).toBe(false)
expect(systemUnderTest.isAttachmentLink('text ![linkText](link)')).toBe(false) expect(systemUnderTest.isAttachmentLink('text ![linkText](link)')).toBe(false)
expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true) expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf)')).toBe(true)
expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf )')).toBe(true) expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf )')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text [ linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true) expect(systemUnderTest.isAttachmentLink('text [ linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf)')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text ![linkText ](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true) expect(systemUnderTest.isAttachmentLink('text ![linkText ](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf)')).toBe(true)
expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true)
expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text [linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) expect(systemUnderTest.isAttachmentLink('text [linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text ![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) expect(systemUnderTest.isAttachmentLink('text ![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true)
expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf)')).toBe(true)
expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf )')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text [ linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf)')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text ![linkText ](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf)')).toBe(true)
expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true)
expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text [linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true)
expect(systemUnderTest.isAttachmentLink('text ![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true)
}) })
it('should test that handleAttachmentLinkPaste copies the attachments to the new location', function () { it('should test that handleAttachmentLinkPaste copies the attachments to the new location', function () {
@@ -721,7 +728,7 @@ it('should test that handleAttachmentLinkPaste copies the attachments to the new
findStorage.findStorage = jest.fn(() => dummyStorage) findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
@@ -736,12 +743,53 @@ it('should test that handleAttachmentLinkPaste copies the attachments to the new
}) })
}) })
it('should test that handleAttachmentLinkPaste don\'t try to copy the file if it does not exist', function () { it('should test that handleAttachmentLinkPaste copies the attachments to the new location - win32 path', function () {
const dummyStorage = {path: 'dummyStoragePath'} const dummyStorage = {path: 'dummyStoragePath'}
findStorage.findStorage = jest.fn(() => dummyStorage) findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf)'
const storageKey = 'storageKey'
const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
sander.exists = jest.fn(() => Promise.resolve(true))
systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve('dummyNewFileName'))
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
.then(() => {
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePath)
expect(systemUnderTest.copyAttachment).toHaveBeenCalledWith(expectedSourceFilePath, storageKey, newNoteKey)
})
})
it('should test that handleAttachmentLinkPaste don\'t try to copy the file if it does not exist - win32 path', function () {
const dummyStorage = {path: 'dummyStoragePath'}
findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf)'
const storageKey = 'storageKey'
const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
sander.exists = jest.fn(() => Promise.resolve(false))
systemUnderTest.copyAttachment = jest.fn()
systemUnderTest.generateFileNotFoundMarkdown = jest.fn()
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
.then(() => {
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePath)
expect(systemUnderTest.copyAttachment).not.toHaveBeenCalled()
})
})
it('should test that handleAttachmentLinkPaste don\'t try to copy the file if it does not exist -- posix', function () {
const dummyStorage = {path: 'dummyStoragePath'}
findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
@@ -762,8 +810,8 @@ it('should test that handleAttachmentLinkPaste copies multiple attachments if mu
findStorage.findStorage = jest.fn(() => dummyStorage) findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ..' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf) ..' +
'![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'img.jpg)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg') const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg')
@@ -787,7 +835,7 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const dummyNewFileName = 'dummyNewFileName' const dummyNewFileName = 'dummyNewFileName'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf)'
const expectedText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileName + ')' const expectedText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileName + ')'
const storageKey = 'storageKey' const storageKey = 'storageKey'
@@ -807,8 +855,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const dummyNewFileNameOne = 'dummyNewFileName' const dummyNewFileNameOne = 'dummyNewFileName'
const dummyNewFileNameTwo = 'dummyNewFileNameTwo' const dummyNewFileNameTwo = 'dummyNewFileNameTwo'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf) ' +
'![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'img.jpg)'
const expectedText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileNameOne + ') ' + const expectedText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileNameOne + ') ' +
'![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileNameTwo + ')' '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileNameTwo + ')'
const storageKey = 'storageKey' const storageKey = 'storageKey'
@@ -829,8 +877,8 @@ it('should test that handleAttachmentLinkPaste calls the copy method correct if
findStorage.findStorage = jest.fn(() => dummyStorage) findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ..' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf) ..' +
'![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'img.jpg)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg') const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg')
@@ -856,7 +904,7 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past
findStorage.findStorage = jest.fn(() => dummyStorage) findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt.png](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' const pasteText = 'text ![alt.png](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const fileNotFoundMD = 'file not found' const fileNotFoundMD = 'file not found'
const expectedPastText = 'text ' + fileNotFoundMD const expectedPastText = 'text ' + fileNotFoundMD
@@ -875,8 +923,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past
findStorage.findStorage = jest.fn(() => dummyStorage) findStorage.findStorage = jest.fn(() => dummyStorage)
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf) ' +
'![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'img.jpg)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const fileNotFoundMD = 'file not found' const fileNotFoundMD = 'file not found'
const expectedPastText = 'text ' + fileNotFoundMD + ' ' + fileNotFoundMD const expectedPastText = 'text ' + fileNotFoundMD + ' ' + fileNotFoundMD
@@ -897,8 +945,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const dummyFoundFileName = 'dummyFileName' const dummyFoundFileName = 'dummyFileName'
const fileNotFoundMD = 'file not found' const fileNotFoundMD = 'file not found'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) .. ' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf) .. ' +
'![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'img.jpg)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const expectedPastText = 'text ' + fileNotFoundMD + ' .. ![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyFoundFileName + ')' const expectedPastText = 'text ' + fileNotFoundMD + ' .. ![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyFoundFileName + ')'
@@ -921,8 +969,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
const dummyFoundFileName = 'dummyFileName' const dummyFoundFileName = 'dummyFileName'
const fileNotFoundMD = 'file not found' const fileNotFoundMD = 'file not found'
const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) .. ' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf) .. ' +
'![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'img.jpg)'
const storageKey = 'storageKey' const storageKey = 'storageKey'
const expectedPastText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyFoundFileName + ') .. ' + fileNotFoundMD const expectedPastText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyFoundFileName + ') .. ' + fileNotFoundMD

View File

@@ -0,0 +1,35 @@
const test = require('ava')
const copyFile = require('browser/main/lib/dataApi/copyFile')
const path = require('path')
const fs = require('fs')
const testFile = 'test.txt'
const srcFolder = path.join(__dirname, '🤔')
const srcPath = path.join(srcFolder, testFile)
const dstFolder = path.join(__dirname, '😇')
const dstPath = path.join(dstFolder, testFile)
test.before((t) => {
if (!fs.existsSync(srcFolder)) fs.mkdirSync(srcFolder)
fs.writeFileSync(srcPath, 'test')
})
test('`copyFile` should handle encoded URI on src path', (t) => {
return copyFile(encodeURI(srcPath), dstPath)
.then(() => {
t.true(true)
})
.catch(() => {
t.true(false)
})
})
test.after((t) => {
fs.unlinkSync(srcPath)
fs.unlinkSync(dstPath)
fs.rmdirSync(srcFolder)
fs.rmdirSync(dstFolder)
})

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)
}) })
}) })

178
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"
@@ -3591,9 +3618,9 @@ filename-reserved-regex@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
filenamify@^2.0.0: filenamify@^2.1.0:
version "2.0.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.0.0.tgz#bd162262c0b6e94bfbcdcf19a3bbb3764f785695" resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9"
dependencies: dependencies:
filename-reserved-regex "^2.0.0" filename-reserved-regex "^2.0.0"
strip-outer "^1.0.0" strip-outer "^1.0.0"
@@ -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"
@@ -4300,6 +4337,11 @@ he@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "http://registry.npm.taobao.org/he/download/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" resolved "http://registry.npm.taobao.org/he/download/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
highlight.js@^9.13.1:
version "9.13.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
highlight.js@^9.3.0: highlight.js@^9.3.0:
version "9.12.0" version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
@@ -4452,6 +4494,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"
@@ -4600,6 +4657,11 @@ invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2:
dependencies: dependencies:
loose-envify "^1.0.0" loose-envify "^1.0.0"
invert-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/invert-color/-/invert-color-2.0.0.tgz#894ab1f7494a6e45f5e74c2f99ec042cd67dd23e"
integrity sha512-9s6IATlhOAr0/0MPUpLdMpk81ixIu8IqwPwORssXBauFT/4ff/iyEOcojd0UYuPwkDbJvL1+blIZGhqVIaAm5Q==
invert-kv@^1.0.0: invert-kv@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
@@ -4662,6 +4724,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 +4762,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 +5396,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 +5407,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 +5764,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 +5888,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"
@@ -6237,9 +6326,10 @@ mousetrap-global-bind@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd" resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd"
mousetrap@^1.6.1: mousetrap@^1.6.2:
version "1.6.1" version "1.6.2"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.1.tgz#2a085f5c751294c75e7e81f6ec2545b29cbf42d9" resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.2.tgz#caadd9cf886db0986fb2fee59a82f6bd37527587"
integrity sha512-jDjhi7wlHwdO6q6DS7YRmSHcuI+RVxadBkLt3KHrhd3C2b+w5pKefg3oj5beTcHZyVFA9Aksf+yEE1y5jxUjVA==
ms@2.0.0: ms@2.0.0:
version "2.0.0" version "2.0.0"
@@ -6681,16 +6771,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 +6992,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 +7359,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 +7545,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 +7556,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 +7625,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 +7687,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 +8045,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 +8148,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 +8311,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"