1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 02:06:29 +00:00

Compare commits

...

79 Commits

Author SHA1 Message Date
Junyoung Choi
c5b4c327fa v0.16.0 2020-07-20 20:42:18 +09:00
Junyoung Choi
4c39922ead Merge pull request #2405 from daiyam/fix-scroll
Better scroll sync between the editor and the preview in the SplitEditor
2020-07-20 19:48:29 +09:00
Junyoung Choi
1cdc74a2f0 Merge pull request #2594 from daiyam/fix-autocomplete-codeblock
improve autocomplete within code blocks
2020-07-20 19:47:48 +09:00
Junyoung Choi
6213a820e6 Merge pull request #3600 from ZeroX-DG/date-iso8601
Add Date shortcut ISO 8601 format as an option in preference
2020-07-20 19:46:53 +09:00
Junyoung Choi
ce81b26d1d Merge branch 'master' into date-iso8601 2020-07-20 19:42:08 +09:00
dependabot[bot]
fae91255f9 Bump lodash from 4.17.13 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.13 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.13...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-20 19:28:17 +09:00
dependabot[bot]
a82a3efb14 Bump websocket-extensions from 0.1.3 to 0.1.4
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-20 19:27:17 +09:00
Junyoung Choi
9556417447 Merge pull request #2923 from LuisReinoso/master
Add Wakatime integration
2020-07-20 19:26:53 +09:00
Baptiste Augrain
60fbb7db5d fix unclickable links 2020-07-20 19:25:53 +09:00
Baptiste Augrain
e504f8e63e fix broken nord and vulcan themes 2020-07-20 19:25:27 +09:00
Baptiste Augrain
071ce12a7e add updated yarn.lock 2020-07-20 19:23:25 +09:00
Baptiste Augrain
0decaf187c update mermaid due to missing arrowheads 2020-07-20 19:23:25 +09:00
Junyoung Choi
961644747e Update readme.md 2020-07-09 13:28:02 +09:00
ZeroX-DG
d1a81984fb fixed eslint 2020-07-02 11:46:13 +12:00
Baptiste Augrain
540d72696c Merge branch 'master' into fix-autocomplete-codeblock 2020-06-26 02:40:37 +02:00
ehhc
7f3fdedb5d Update readme.md 2020-06-25 10:03:02 +09:00
ehhc
3a80706938 Update readme.md
Added link to a project developing a mobile version of boostnote
2020-06-25 10:03:02 +09:00
ZeroX-DG
2cf5f8e966 updated slack invite link 2020-06-08 18:48:55 +09:00
Luis Reinoso
8ede1a4989 refactor: Change UI according requested changes.
NOTE:
Just a simple section with title Wakatime and bellow is a check box saying enable wakatime? and below it is the text box for wakatime key.
2020-06-07 11:17:02 -05:00
Luis Reinoso
76da76ae76 fix: Wakatime command name. 2020-06-07 11:01:44 -05:00
Luis Reinoso
c02ab033f4 fix: Remove extra calling function. Now call directly to check wakatime plugin. 2020-05-23 12:25:37 -05:00
Luis Reinoso
1aaba74e24 refactor: Improve sendWakatimeHeartBeat. 2020-05-23 12:21:43 -05:00
Luis Reinoso
6fe6794796 feat: Add checkbox validation to active or deactive plugin. 2020-05-16 11:13:19 -05:00
Luis Reinoso
fd3e243855 feat: Check for missing wakatime cli. And display modal and alert message. 2020-05-16 10:08:51 -05:00
Luis Reinoso
938b075bf6 fix: Lint errors. 2020-05-16 08:37:35 -05:00
Baptiste Augrain
81ac3d1748 fix scrolling with only one big paragraph 2020-05-16 14:19:22 +02:00
Baptiste Augrain
40d10eae04 Merge branch 'master' into fix-scroll 2020-05-15 02:59:00 +02:00
Baptiste Augrain
9b6a61a91c fix cursor sync 2020-05-15 02:57:00 +02:00
Baptiste Augrain
7116c305ca Merge branch 'master' into fix-scroll 2020-05-15 02:02:38 +02:00
ZeroX-DG
4fbbb4651d updated Boostnote name to Boostnote Legacy 2020-05-11 03:44:21 +09:00
Luis Reinoso
2ac38e9644 fix: Update prettier config with master. 2020-05-08 17:11:16 -05:00
Luis Reinoso
98d4fa0603 fix: Lint issues. 2020-05-08 10:01:30 -05:00
Luis Reinoso
2ea0514bbe Merge remote-tracking branch 'upstream/master' 2020-05-08 06:55:48 -05:00
Junyoung Choi
137aa692bc Merge pull request #3547 from ZeroX-DG/migrate-to-jest
[WIP] Migrate to jest
2020-05-08 18:49:27 +09:00
ZeroX-DG
634fec39c0 migrate more tests to jest 2020-05-08 20:13:39 +12:00
ZeroX-DG
d269f1e8fd migrate more tests to jest 2020-05-06 14:57:35 +12:00
Junyoung Choi
4b67026bbf Merge pull request #2936 from callumbooth/fix-2903
fixes #2903 - Rearrange layout of columns
2020-04-24 01:04:23 +09:00
Alexander Wolf
8b13ec4f0e Issue 1706 tag rename - finishing #2989 (#3469)
* allow a tag to be renamed and update all notes that use that tag

• repurpose RenameFolderModal.styl to RenameModal so it is more generic

* call handleConfirmButtonClick directly instead of sending through a confirm method

* better name for method to confirm the rename

* use close prop instead of a new method

* use callback ref instead of legacy string refs

* bind the handleChange in the constructor to allow for direct function assignment

* update the tag in the URL upon change

* use the eventEmitter to update the tags in the SnippetNoteDetail header via the TagSelect component

* respect themes when modal is opened

* show error message when trying to rename to an existing tag

* lint fix, const over let

* add missing letter

* fix routing and add merge warning dialog

* fix space-before-parens lint error

* change theming

* add check if tag changed

Co-authored-by: Khaliq Gant <khaliqgant@gmail.com>
2020-04-21 20:55:56 +09:00
Junyoung Choi
7fef7660e4 Add roadmap 2020-04-21 20:45:05 +09:00
Junyoung Choi
01d021cc4c Add boosthub intro 2020-04-21 18:33:57 +09:00
Callum Booth
c355f81525 remove redundant icons 2020-04-20 08:43:01 +01:00
Callum Booth
d138a54dfd fix accidental deletion of rtl state 2020-04-20 08:42:45 +01:00
Callum Booth
a7ead67c2d moves orientation button to view menu 2020-04-18 14:17:08 +01:00
Callum Booth
2f16784a20 Merge branch 'master' of https://github.com/BoostIO/Boostnote into fix-2903 2020-04-18 13:54:27 +01:00
ZeroX-DG
8ca3ba21ee Merge branch 'master' of https://github.com/BoostIO/Boostnote into migrate-to-jest 2020-04-17 21:49:38 +12:00
ZeroX-DG
58ae6419f0 fixed markdown test error 2020-04-17 21:43:05 +12:00
ZeroX-DG
3f320f4337 resolved conflict 2020-04-12 18:45:49 +12:00
amedora
fc08d2f8c3 Merge branch 'master' into migrate-to-jest
# Conflicts:
#	tests/lib/snapshots/markdown-test.js.md
#	tests/lib/snapshots/markdown-test.js.snap
2019-10-14 07:41:19 +09:00
Callum Booth
59e361cb37 move config value into ui 2019-10-09 12:59:53 +01:00
Callum Booth
1993a6588d Cleans up toggle button component 2019-10-09 12:45:52 +01:00
Callum Booth
218fba1aa1 fix formatting 2019-10-09 12:41:26 +01:00
Callum Booth
4de6c69f5d merge from upstream/master 2019-10-08 13:21:56 +01:00
lockee14
2b3538d3b1 add indentation and closing brace
missing indentation and brace between line 229 - 234:
before:
'Shift-Cmd-/': function (cm) {
        if (global.process.platform !== 'darwin') { return }
      [translateHotkey(hotkey.insertDateTime)]: function (cm) {
        const dateNow = new Date()
        cm.replaceSelection(dateNow.toLocaleString())
},

after:
'Shift-Cmd-/': function (cm) {
        if (global.process.platform !== 'darwin') { return }
        [translateHotkey(hotkey.insertDateTime)]: function (cm) {
          const dateNow = new Date()
          cm.replaceSelection(dateNow.toLocaleString())
        }
},
2019-09-12 08:30:53 +09:00
lockee14
b84f1173b7 add a comma at the end of line 114 2019-09-12 08:15:42 +09:00
lockee14
bdfe8c0445 add a comma at the end of line 73 2019-09-12 08:14:39 +09:00
lockee14
f64d0b35e1 Merge branch 'master' into Insert_Date_ISO_8601_Style 2019-08-30 21:54:38 +09:00
amedora
3921655157 replace the snapshot file 2019-08-07 15:22:27 +09:00
amedora
e4e10d523f fix how handle the storage as context data 2019-08-07 14:52:17 +09:00
amedora
404dddcb86 rename files to suit Jest 2019-08-07 14:28:38 +09:00
amedora
ffb2603485 jest-codemods tests/lib 2019-08-07 14:17:48 +09:00
mehdi
928e0edf4d forgot to remove commented code 2019-07-29 16:24:42 +09:00
mehdi
80a63f7404 refactor: move the config down to editor setting 2019-07-08 19:27:23 +09:00
mehdi
6e45ee6a38 add Date ISO 8601 format 2019-06-24 06:10:31 +09:00
Callum Booth
ba34458feb changes if state to be more readable 2019-06-10 20:27:55 +01:00
Callum Booth
a2fb50a71c adds destructed fontFamily from props 2019-06-10 19:21:33 +01:00
Callum booth
b15a4007ee merge from master 2019-06-10 19:13:58 +01:00
Callum booth
93f0d3c1cf fixes #2903 - Rearrange layout of columns 2019-03-19 20:39:34 +00:00
Luis Reinoso
8ec7d19f30 Remove unnecessary console.log 2019-03-12 22:38:10 -05:00
Luis Reinoso
a0f5a06c73 Add send wakatimeHeartBeat on constructor CodeEditor to fix when change from Markdown to Snippet #2810 2019-03-12 22:14:10 -05:00
Luis Reinoso
39a98e795f Add preferences plugins wakatime key #2810 2019-03-12 22:05:30 -05:00
Luis Reinoso
57705cf41b Add less WakatimeHeartBeat requests #2810 2019-03-12 20:40:27 -05:00
Luis Reinoso
052c70bb38 Add support to snippetNote #2810 2019-03-12 20:38:55 -05:00
Luis Reinoso
6dc88262c9 Add wakatime-plugin #2810 2019-03-12 20:02:24 -05:00
Baptiste Augrain
12f9b9342d Merge branch 'master' into fix-autocomplete-codeblock 2018-12-27 22:57:05 +01:00
Baptiste Augrain
e76bc72667 - data-line attributes might not be directly under the body
- support checkbox preference `When scrolling, synchronize preview with editor`
2018-12-24 11:44:02 +01:00
Baptiste Augrain
9310e5e86c Merge branch 'master' into fix-scroll 2018-12-24 10:33:47 +01:00
Baptiste Augrain
540c608cc6 Merge branch 'master' into fix-scroll 2018-11-28 14:01:18 +01:00
Baptiste Augrain
87515dbd3f separate autocomplete configuration between markdown and within code blocks 2018-11-10 12:20:51 +01:00
Baptiste Augrain
696c2f29b5 implements better positioning between the editor and the preview in the SplitEditor (particularly with lot of images) 2018-09-17 16:59:27 +02:00
53 changed files with 2007 additions and 636 deletions

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ node_modules/*
.idea .idea
.vscode .vscode
package-lock.json package-lock.json
config.json

View File

@@ -21,6 +21,8 @@ const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
import { createTurndownService } from '../lib/turndown' import { createTurndownService } from '../lib/turndown'
import { languageMaps } from '../lib/CMLanguageList' import { languageMaps } from '../lib/CMLanguageList'
import snippetManager from '../lib/SnippetManager' import snippetManager from '../lib/SnippetManager'
import { findStorage } from 'browser/lib/findStorage'
import { sendWakatimeHeartBeat } from 'browser/lib/wakatime-plugin'
import { import {
generateInEditor, generateInEditor,
tocExistsInEditor tocExistsInEditor
@@ -113,6 +115,16 @@ export default class CodeEditor extends React.Component {
this.editorActivityHandler = () => this.handleEditorActivity() this.editorActivityHandler = () => this.handleEditorActivity()
this.turndownService = createTurndownService() this.turndownService = createTurndownService()
// wakatime
const { storageKey, noteKey } = this.props
const storage = findStorage(storageKey)
if (storage)
sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
isWrite: false,
hasFileChanges: false,
isFileChange: true
})
} }
handleSearch(msg) { handleSearch(msg) {
@@ -158,6 +170,10 @@ export default class CodeEditor extends React.Component {
} }
handleEditorActivity() { handleEditorActivity() {
if (this.props.onCursorActivity) {
this.props.onCursorActivity(this.editor)
}
if (!this.textEditorInterface.transaction) { if (!this.textEditorInterface.transaction) {
this.updateTableEditorState() this.updateTableEditorState()
} }
@@ -219,11 +235,19 @@ export default class CodeEditor extends React.Component {
}, },
[translateHotkey(hotkey.insertDate)]: function(cm) { [translateHotkey(hotkey.insertDate)]: function(cm) {
const dateNow = new Date() const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString()) if (self.props.dateFormatISO8601) {
cm.replaceSelection(dateNow.toISOString().split('T')[0])
} else {
cm.replaceSelection(dateNow.toLocaleDateString())
}
}, },
[translateHotkey(hotkey.insertDateTime)]: function(cm) { [translateHotkey(hotkey.insertDateTime)]: function(cm) {
const dateNow = new Date() const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString()) if (self.props.dateFormatISO8601) {
cm.replaceSelection(dateNow.toISOString())
} else {
cm.replaceSelection(dateNow.toLocaleString())
}
}, },
Enter: 'boostNewLineAndIndentContinueMarkdownList', Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => { 'Ctrl-C': cm => {
@@ -321,10 +345,18 @@ export default class CodeEditor extends React.Component {
'CodeMirror-lint-markers' 'CodeMirror-lint-markers'
], ],
autoCloseBrackets: { autoCloseBrackets: {
pairs: this.props.matchingPairs, codeBlock: {
triples: this.props.matchingTriples, pairs: this.props.codeBlockMatchingPairs,
explode: this.props.explodingPairs, closeBefore: this.props.codeBlockMatchingCloseBefore,
override: true triples: this.props.codeBlockMatchingTriples,
explode: this.props.codeBlockExplodingPairs
},
markdown: {
pairs: this.props.matchingPairs,
closeBefore: this.props.matchingCloseBefore,
triples: this.props.matchingTriples,
explode: this.props.explodingPairs
}
}, },
extraKeys: this.defaultKeyMap, extraKeys: this.defaultKeyMap,
prettierConfig: this.props.prettierConfig prettierConfig: this.props.prettierConfig
@@ -352,6 +384,7 @@ export default class CodeEditor extends React.Component {
eventEmitter.emit('code:init') eventEmitter.emit('code:init')
this.editor.on('scroll', this.scrollHandler) this.editor.on('scroll', this.scrollHandler)
this.editor.on('cursorActivity', this.editorActivityHandler)
const editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler) editorTheme.addEventListener('load', this.loadStyleHandler)
@@ -489,7 +522,6 @@ export default class CodeEditor extends React.Component {
}) })
if (this.props.enableTableEditor) { if (this.props.enableTableEditor) {
this.editor.on('cursorActivity', this.editorActivityHandler)
this.editor.on('changes', this.editorActivityHandler) this.editor.on('changes', this.editorActivityHandler)
} }
@@ -548,12 +580,18 @@ export default class CodeEditor extends React.Component {
this.editor.off('paste', this.pasteHandler) this.editor.off('paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler) eventEmitter.off('top:search', this.searchHandler)
this.editor.off('scroll', this.scrollHandler) this.editor.off('scroll', this.scrollHandler)
this.editor.off('cursorActivity', this.editorActivityHandler)
this.editor.off('contextmenu', this.contextMenuHandler) this.editor.off('contextmenu', this.contextMenuHandler)
const editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler) editorTheme.removeEventListener('load', this.loadStyleHandler)
spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED) spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
eventEmitter.off('code:format-table', this.formatTable) eventEmitter.off('code:format-table', this.formatTable)
if (this.props.enableTableEditor) {
this.editor.off('changes', this.editorActivityHandler)
}
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@@ -629,16 +667,32 @@ export default class CodeEditor extends React.Component {
if ( if (
prevProps.matchingPairs !== this.props.matchingPairs || prevProps.matchingPairs !== this.props.matchingPairs ||
prevProps.matchingCloseBefore !== this.props.matchingCloseBefore ||
prevProps.matchingTriples !== this.props.matchingTriples || prevProps.matchingTriples !== this.props.matchingTriples ||
prevProps.explodingPairs !== this.props.explodingPairs prevProps.explodingPairs !== this.props.explodingPairs ||
prevProps.codeBlockMatchingPairs !== this.props.codeBlockMatchingPairs ||
prevProps.codeBlockMatchingCloseBefore !==
this.props.codeBlockMatchingCloseBefore ||
prevProps.codeBlockMatchingTriples !==
this.props.codeBlockMatchingTriples ||
prevProps.codeBlockExplodingPairs !== this.props.codeBlockExplodingPairs
) { ) {
const bracketObject = { const autoCloseBrackets = {
pairs: this.props.matchingPairs, codeBlock: {
triples: this.props.matchingTriples, pairs: this.props.codeBlockMatchingPairs,
explode: this.props.explodingPairs, closeBefore: this.props.codeBlockMatchingCloseBefore,
override: true triples: this.props.codeBlockMatchingTriples,
explode: this.props.codeBlockExplodingPairs
},
markdown: {
pairs: this.props.matchingPairs,
closeBefore: this.props.matchingCloseBefore,
triples: this.props.matchingTriples,
explode: this.props.explodingPairs
}
} }
this.editor.setOption('autoCloseBrackets', bracketObject)
this.editor.setOption('autoCloseBrackets', autoCloseBrackets)
} }
if (prevProps.enableTableEditor !== this.props.enableTableEditor) { if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
@@ -793,9 +847,23 @@ export default class CodeEditor extends React.Component {
this.updateHighlight(editor, changeObject) this.updateHighlight(editor, changeObject)
this.value = editor.getValue() this.value = editor.getValue()
const { storageKey, noteKey } = this.props
const storage = findStorage(storageKey)
if (this.props.onChange) { if (this.props.onChange) {
this.props.onChange(editor) this.props.onChange(editor)
} }
const isWrite = !!this.props.onChange
const hasFileChanges = isWrite
if (storage) {
sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
isWrite,
hasFileChanges,
isFileChange: false
})
}
} }
linePossibleContainsHeadline(currentLine) { linePossibleContainsHeadline(currentLine) {
@@ -923,6 +991,16 @@ export default class CodeEditor extends React.Component {
this.restartHighlighting() this.restartHighlighting()
this.editor.on('change', this.changeHandler) this.editor.on('change', this.changeHandler)
this.editor.refresh() this.editor.refresh()
// wakatime
const { storageKey, noteKey } = this.props
const storage = findStorage(storageKey)
if (storage)
sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
isWrite: false,
hasFileChanges: false,
isFileChange: true
})
} }
setValue(value) { setValue(value) {
@@ -1240,18 +1318,19 @@ export default class CodeEditor extends React.Component {
} }
render() { render() {
const { className, fontSize } = this.props const { className, fontSize, fontFamily, width, height } = this.props
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const normalisedFontFamily = normalizeEditorFontFamily(fontFamily)
const width = this.props.width
return ( return (
<div <div
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`} className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
ref='root' ref='root'
tabIndex='-1' tabIndex='-1'
style={{ style={{
fontFamily, fontFamily: normalisedFontFamily,
fontSize: fontSize, fontSize,
width: width width,
height
}} }}
onDrop={e => this.handleDropImage(e)} onDrop={e => this.handleDropImage(e)}
/> />

View File

@@ -139,7 +139,7 @@ class MarkdownEditor extends React.Component {
}, },
() => { () => {
this.previewRef.current.focus() this.previewRef.current.focus()
this.previewRef.current.scrollToRow(cursorPosition.line) this.previewRef.current.scrollToLine(cursorPosition.line)
} }
) )
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
@@ -365,8 +365,15 @@ class MarkdownEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping lineWrapping
matchingPairs={config.editor.matchingPairs} matchingPairs={config.editor.matchingPairs}
matchingCloseBefore={config.editor.matchingCloseBefore}
matchingTriples={config.editor.matchingTriples} matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs} explodingPairs={config.editor.explodingPairs}
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
codeBlockMatchingCloseBefore={
config.editor.codeBlockMatchingCloseBefore
}
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey} noteKey={noteKey}
@@ -381,6 +388,7 @@ class MarkdownEditor extends React.Component {
switchPreview={config.editor.switchPreview} switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint} enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig} customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
dateFormatISO8601={config.editor.dateFormatISO8601}
prettierConfig={config.editor.prettierConfig} prettierConfig={config.editor.prettierConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments} deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
RTL={RTL} RTL={RTL}

View File

@@ -1145,17 +1145,18 @@ class MarkdownPreview extends React.Component {
/** /**
* @public * @public
* @param {Number} targetRow * @param {Number} targetLine
*/ */
scrollToRow(targetRow) { scrollToLine(targetLine) {
const blocks = this.getWindow().document.querySelectorAll( const blocks = this.getWindow().document.querySelectorAll(
'body>[data-line]' 'body [data-line]'
) )
for (let index = 0; index < blocks.length; index++) { for (let index = 0; index < blocks.length; index++) {
let block = blocks[index] let block = blocks[index]
const row = parseInt(block.getAttribute('data-line')) const line = parseInt(block.getAttribute('data-line'))
if (row > targetRow || index === blocks.length - 1) {
if (line > targetLine || index === blocks.length - 1) {
block = blocks[index - 1] block = blocks[index - 1]
block != null && this.scrollTo(0, block.offsetTop) block != null && this.scrollTo(0, block.offsetTop)
break break
@@ -1192,7 +1193,10 @@ class MarkdownPreview extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const rawHref = e.target.getAttribute('href') const el = e.target.closest('a[href]')
if (!el) return
const rawHref = el.getAttribute('href')
const { dispatch } = this.props const { dispatch } = this.props
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]() if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()

View File

@@ -13,10 +13,77 @@ class MarkdownSplitEditor extends React.Component {
this.value = props.value this.value = props.value
this.focus = () => this.refs.code.focus() this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload() this.reload = () => this.refs.code.reload()
this.userScroll = true this.userScroll = props.config.preview.scrollSync
this.state = { this.state = {
isSliderFocused: false, isSliderFocused: false,
codeEditorWidthInPercent: 50 codeEditorWidthInPercent: 50,
codeEditorHeightInPercent: 50
}
}
componentDidUpdate(prevProps) {
if (
this.props.config.preview.scrollSync !==
prevProps.config.preview.scrollSync
) {
this.userScroll = this.props.config.preview.scrollSync
}
}
handleCursorActivity(editor) {
if (this.userScroll) {
const previewDoc = _.get(
this,
'refs.preview.refs.root.contentWindow.document'
)
const previewTop = _.get(previewDoc, 'body.scrollTop')
const line = editor.doc.getCursor().line
let top
if (line === 0) {
top = 0
} else {
const blockElements = previewDoc.querySelectorAll('body [data-line]')
const blocks = []
for (const block of blockElements) {
const l = parseInt(block.getAttribute('data-line'))
blocks.push({
line: l,
top: block.offsetTop
})
if (l > line) {
break
}
}
if (blocks.length === 1) {
const block = blockElements[blockElements.length - 1]
blocks.push({
line: editor.doc.size,
top: block.offsetTop + block.offsetHeight
})
}
const i = blocks.length - 1
const ratio =
(blocks[i].top - blocks[i - 1].top) /
(blocks[i].line - blocks[i - 1].line)
const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
top =
blocks[i - 1].top +
Math.floor((line - blocks[i - 1].line) * ratio) -
delta
}
this.scrollTo(previewTop, top, y =>
_.set(previewDoc, 'body.scrollTop', y)
)
} }
} }
@@ -29,59 +96,125 @@ class MarkdownSplitEditor extends React.Component {
this.props.onChange(e) this.props.onChange(e)
} }
handleScroll(e) { handleEditorScroll(e) {
if (!this.props.config.preview.scrollSync) return
const previewDoc = _.get(
this,
'refs.preview.refs.root.contentWindow.document'
)
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
if (this.userScroll) { if (this.userScroll) {
if (e.doc) { const previewDoc = _.get(
srcTop = _.get(e, 'doc.scrollTop') this,
srcHeight = _.get(e, 'doc.height') 'refs.preview.refs.root.contentWindow.document'
targetTop = _.get(previewDoc, 'body.scrollTop') )
targetHeight = _.get(previewDoc, 'body.scrollHeight') const codeDoc = _.get(this, 'refs.code.editor.doc')
const from = codeDoc.cm.coordsChar({ left: 0, top: 0 }).line
const to = codeDoc.cm.coordsChar({
left: 0,
top: codeDoc.cm.display.lastWrapHeight * 1.125
}).line
const previewTop = _.get(previewDoc, 'body.scrollTop')
let top
if (from === 0) {
top = 0
} else if (to === codeDoc.lastLine()) {
top =
_.get(previewDoc, 'body.scrollHeight') -
_.get(previewDoc, 'body.clientHeight')
} else { } else {
srcTop = _.get(previewDoc, 'body.scrollTop') const line = from + Math.floor((to - from) / 3)
srcHeight = _.get(previewDoc, 'body.scrollHeight')
targetTop = _.get(codeDoc, 'scrollTop') const blockElements = previewDoc.querySelectorAll('body [data-line]')
targetHeight = _.get(codeDoc, 'height') const blocks = []
for (const block of blockElements) {
const l = parseInt(block.getAttribute('data-line'))
blocks.push({
line: l,
top: block.offsetTop
})
if (l > line) {
break
}
}
if (blocks.length === 1) {
const block = blockElements[blockElements.length - 1]
blocks.push({
line: codeDoc.size,
top: block.offsetTop + block.offsetHeight
})
}
const i = blocks.length - 1
const ratio =
(blocks[i].top - blocks[i - 1].top) /
(blocks[i].line - blocks[i - 1].line)
top =
blocks[i - 1].top + Math.floor((line - blocks[i - 1].line) * ratio)
} }
const distance = (targetHeight * srcTop) / srcHeight - targetTop this.scrollTo(previewTop, top, y =>
const framerate = 1000 / 60 _.set(previewDoc, 'body.scrollTop', y)
const frames = 20 )
const refractory = frames * framerate }
}
this.userScroll = false handlePreviewScroll(e) {
if (this.userScroll) {
const previewDoc = _.get(
this,
'refs.preview.refs.root.contentWindow.document'
)
const codeDoc = _.get(this, 'refs.code.editor.doc')
let frame = 0 const srcTop = _.get(previewDoc, 'body.scrollTop')
let scrollPos, time const editorTop = _.get(codeDoc, 'scrollTop')
const timer = setInterval(() => {
time = frame / frames let top
scrollPos = if (srcTop === 0) {
time < 0.5 top = 0
? 2 * time * time // ease in } else {
: -1 + (4 - 2 * time) * time // ease out const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
if (e.doc) const previewTop = srcTop + delta
_.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else const blockElements = previewDoc.querySelectorAll('body [data-line]')
_.get(this, 'refs.code.editor').scrollTo( const blocks = []
0, for (const block of blockElements) {
targetTop + scrollPos * distance const top = block.offsetTop
)
if (frame >= frames) { blocks.push({
clearInterval(timer) line: parseInt(block.getAttribute('data-line')),
setTimeout(() => { top
this.userScroll = true })
}, refractory)
if (top > previewTop) {
break
}
} }
frame++
}, framerate) if (blocks.length === 1) {
const block = blockElements[blockElements.length - 1]
blocks.push({
line: codeDoc.size,
top: block.offsetTop + block.offsetHeight
})
}
const i = blocks.length - 1
const from = codeDoc.cm.heightAtLine(blocks[i - 1].line, 'local')
const to = codeDoc.cm.heightAtLine(blocks[i].line, 'local')
const ratio =
(previewTop - blocks[i - 1].top) / (blocks[i].top - blocks[i - 1].top)
top = from + Math.floor((to - from) * ratio) - delta
}
this.scrollTo(editorTop, top, y => codeDoc.cm.scrollTo(0, y))
} }
} }
@@ -114,22 +247,42 @@ class MarkdownSplitEditor extends React.Component {
handleMouseMove(e) { handleMouseMove(e) {
if (this.state.isSliderFocused) { if (this.state.isSliderFocused) {
const rootRect = this.refs.root.getBoundingClientRect() const rootRect = this.refs.root.getBoundingClientRect()
const rootWidth = rootRect.width if (this.props.isStacking) {
const offset = rootRect.left const rootHeight = rootRect.height
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100 const offset = rootRect.top
let newCodeEditorHeightInPercent =
((e.pageY - offset) / rootHeight) * 100
// limit minSize to 10%, maxSize to 90% // limit minSize to 10%, maxSize to 90%
if (newCodeEditorWidthInPercent <= 10) { if (newCodeEditorHeightInPercent <= 10) {
newCodeEditorWidthInPercent = 10 newCodeEditorHeightInPercent = 10
}
if (newCodeEditorHeightInPercent >= 90) {
newCodeEditorHeightInPercent = 90
}
this.setState({
codeEditorHeightInPercent: newCodeEditorHeightInPercent
})
} else {
const rootWidth = rootRect.width
const offset = rootRect.left
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
// limit minSize to 10%, maxSize to 90%
if (newCodeEditorWidthInPercent <= 10) {
newCodeEditorWidthInPercent = 10
}
if (newCodeEditorWidthInPercent >= 90) {
newCodeEditorWidthInPercent = 90
}
this.setState({
codeEditorWidthInPercent: newCodeEditorWidthInPercent
})
} }
if (newCodeEditorWidthInPercent >= 90) {
newCodeEditorWidthInPercent = 90
}
this.setState({
codeEditorWidthInPercent: newCodeEditorWidthInPercent
})
} }
} }
@@ -147,6 +300,35 @@ class MarkdownSplitEditor extends React.Component {
}) })
} }
scrollTo(from, to, scroller) {
const distance = to - from
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
this.userScroll = false
let frame = 0
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos =
time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
scroller(from + scrollPos * distance)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => {
this.userScroll = true
}, refractory)
}
frame++
}, framerate)
}
render() { render() {
const { const {
config, config,
@@ -154,6 +336,7 @@ class MarkdownSplitEditor extends React.Component {
storageKey, storageKey,
noteKey, noteKey,
linesHighlighted, linesHighlighted,
isStacking,
RTL RTL
} = this.props } = this.props
let storage let storage
@@ -162,14 +345,62 @@ class MarkdownSplitEditor extends React.Component {
} catch (e) { } catch (e) {
return <div /> return <div />
} }
let editorStyle = {}
let previewStyle = {}
let sliderStyle = {}
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
editorStyle.fontSize = editorFontSize
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorStyle.fontSize > 0 && editorStyle.fontSize < 132))
const previewStyle = {} editorIndentSize = 4
previewStyle.width = 100 - this.state.codeEditorWidthInPercent + '%' editorStyle.indentSize = editorIndentSize
editorStyle = Object.assign(
editorStyle,
isStacking
? {
width: '100%',
height: `${this.state.codeEditorHeightInPercent}%`
}
: {
width: `${this.state.codeEditorWidthInPercent}%`,
height: '100%'
}
)
previewStyle = Object.assign(
previewStyle,
isStacking
? {
width: '100%',
height: `${100 - this.state.codeEditorHeightInPercent}%`
}
: {
width: `${100 - this.state.codeEditorWidthInPercent}%`,
height: '100%'
}
)
sliderStyle = Object.assign(
sliderStyle,
isStacking
? {
left: 0,
top: `${this.state.codeEditorHeightInPercent}%`
}
: {
left: `${this.state.codeEditorWidthInPercent}%`,
top: 0
}
)
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
previewStyle.pointerEvents = 'none' previewStyle.pointerEvents = 'none'
return ( return (
<div <div
styleName='root' styleName='root'
@@ -179,20 +410,28 @@ class MarkdownSplitEditor extends React.Component {
> >
<CodeEditor <CodeEditor
ref='code' ref='code'
width={this.state.codeEditorWidthInPercent + '%'} width={editorStyle.width}
height={editorStyle.height}
mode='Boost Flavored Markdown' mode='Boost Flavored Markdown'
value={value} value={value}
theme={config.editor.theme} theme={config.editor.theme}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorStyle.fontSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping lineWrapping
matchingPairs={config.editor.matchingPairs} matchingPairs={config.editor.matchingPairs}
matchingCloseBefore={config.editor.matchingCloseBefore}
matchingTriples={config.editor.matchingTriples} matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs} explodingPairs={config.editor.explodingPairs}
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
codeBlockMatchingCloseBefore={
config.editor.codeBlockMatchingCloseBefore
}
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorStyle.indentSize}
enableRulers={config.editor.enableRulers} enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers} rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
@@ -202,19 +441,21 @@ class MarkdownSplitEditor extends React.Component {
noteKey={noteKey} noteKey={noteKey}
linesHighlighted={linesHighlighted} linesHighlighted={linesHighlighted}
onChange={e => this.handleOnChange(e)} onChange={e => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)} onScroll={e => this.handleEditorScroll(e)}
onCursorActivity={e => this.handleCursorActivity(e)}
spellCheck={config.editor.spellcheck} spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste} enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey} hotkey={config.hotkey}
switchPreview={config.editor.switchPreview} switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint} enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig} customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
dateFormatISO8601={config.editor.dateFormatISO8601}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments} deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
RTL={RTL} RTL={RTL}
/> />
<div <div
styleName='slider' styleName={isStacking ? 'slider-hoz' : 'slider'}
style={{ left: this.state.codeEditorWidthInPercent + '%' }} style={{ left: sliderStyle.left, top: sliderStyle.top }}
onMouseDown={e => this.handleMouseDown(e)} onMouseDown={e => this.handleMouseDown(e)}
> >
<div styleName='slider-hitbox' /> <div styleName='slider-hitbox' />
@@ -238,7 +479,7 @@ class MarkdownSplitEditor extends React.Component {
tabInde='0' tabInde='0'
value={value} value={value}
onCheckboxClick={e => this.handleCheckboxClick(e)} onCheckboxClick={e => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)} onScroll={e => this.handlePreviewScroll(e)}
showCopyNotification={config.ui.showCopyNotification} showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
noteKey={noteKey} noteKey={noteKey}

View File

@@ -3,6 +3,7 @@
height 100% height 100%
font-size 30px font-size 30px
display flex display flex
flex-wrap wrap
.slider .slider
absolute top bottom absolute top bottom
top -2px top -2px
@@ -15,6 +16,14 @@
left -3px left -3px
z-index 10 z-index 10
cursor col-resize cursor col-resize
.slider-hoz
absolute left right
.slider-hitbox
absolute left right
width: 100%
height 7px
cursor row-resize
apply-theme(theme) apply-theme(theme)
body[data-theme={theme}] body[data-theme={theme}]

View File

@@ -1,4 +1,4 @@
import mermaidAPI from 'mermaid' import mermaidAPI from 'mermaid/dist/mermaid.min.js'
import uiThemes from 'browser/lib/ui-themes' import uiThemes from 'browser/lib/ui-themes'
// fixes bad styling in the mermaid dark theme // fixes bad styling in the mermaid dark theme
@@ -61,7 +61,6 @@ function render(element, content, theme, enableHTMLLabel) {
el.setAttribute('ratio', ratio) el.setAttribute('ratio', ratio)
el.setAttribute('height', el.parentNode.clientWidth / ratio) el.setAttribute('height', el.parentNode.clientWidth / ratio)
console.log(el)
} }
}) })
} catch (e) { } catch (e) {

View File

@@ -0,0 +1,49 @@
import config from 'browser/main/lib/ConfigManager'
const exec = require('child_process').exec
const path = require('path')
let lastHeartbeat = 0
function sendWakatimeHeartBeat(
storagePath,
noteKey,
storageName,
{ isWrite, hasFileChanges, isFileChange }
) {
if (
config.get().wakatime.isActive &&
!!config.get().wakatime.key &&
(new Date().getTime() - lastHeartbeat > 120000 || isFileChange)
) {
const notePath = path.join(storagePath, 'notes', noteKey + '.cson')
if (!isWrite && !hasFileChanges && !isFileChange) {
return
}
lastHeartbeat = new Date()
const wakatimeKey = config.get().wakatime.key
if (wakatimeKey) {
exec(
`wakatime --file ${notePath} --project '${storageName}' --key ${wakatimeKey} --plugin Boostnote-wakatime`,
(error, stdOut, stdErr) => {
if (error) {
console.log(error)
lastHeartbeat = 0
} else {
console.log(
'wakatime',
'isWrite',
isWrite,
'hasChanges',
hasFileChanges,
'isFileChange',
isFileChange
)
}
}
)
}
}
}
export { sendWakatimeHeartBeat }

View File

@@ -60,6 +60,7 @@ class MarkdownNoteDetail extends React.Component {
this.toggleLockButton = this.handleToggleLockButton.bind(this) this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.generateToc = this.handleGenerateToc.bind(this) this.generateToc = this.handleGenerateToc.bind(this)
this.handleUpdateContent = this.handleUpdateContent.bind(this) this.handleUpdateContent = this.handleUpdateContent.bind(this)
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
} }
focus() { focus() {
@@ -67,6 +68,7 @@ class MarkdownNoteDetail extends React.Component {
} }
componentDidMount() { componentDidMount() {
ee.on('editor:orientation', this.handleSwitchStackDirection)
ee.on('topbar:togglelockbutton', this.toggleLockButton) ee.on('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection()) ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
ee.on('topbar:togglemodebutton', () => { ee.on('topbar:togglemodebutton', () => {
@@ -383,7 +385,7 @@ class MarkdownNoteDetail extends React.Component {
handleSwitchMode(type) { handleSwitchMode(type) {
// If in split mode, hide the lock button // If in split mode, hide the lock button
this.setState( this.setState(
{ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, { editorType: type, isLockButtonShown: type !== 'SPLIT' },
() => { () => {
this.focus() this.focus()
const newConfig = Object.assign({}, this.props.config) const newConfig = Object.assign({}, this.props.config)
@@ -393,6 +395,18 @@ class MarkdownNoteDetail extends React.Component {
) )
} }
handleSwitchStackDirection() {
this.setState(
prevState => ({ isStacking: !prevState.isStacking }),
() => {
this.focus()
const newConfig = Object.assign({}, this.props.config)
newConfig.ui.isStacking = this.state.isStacking
ConfigManager.set(newConfig)
}
)
}
handleSwitchDirection() { handleSwitchDirection() {
if (!this.props.config.editor.rtlEnabled) { if (!this.props.config.editor.rtlEnabled) {
return return
@@ -429,7 +443,7 @@ class MarkdownNoteDetail extends React.Component {
renderEditor() { renderEditor() {
const { config, ignorePreviewPointerEvents } = this.props const { config, ignorePreviewPointerEvents } = this.props
const { note } = this.state const { note, isStacking } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') { if (this.state.editorType === 'EDITOR_PREVIEW') {
return ( return (
@@ -455,6 +469,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key} noteKey={note.key}
isStacking={isStacking}
linesHighlighted={note.linesHighlighted} linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent} onChange={this.handleUpdateContent}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}

View File

@@ -859,8 +859,15 @@ class SnippetNoteDetail extends React.Component {
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs} matchingPairs={config.editor.matchingPairs}
matchingCloseBefore={config.editor.matchingCloseBefore}
matchingTriples={config.editor.matchingTriples} matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs} explodingPairs={config.editor.explodingPairs}
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
codeBlockMatchingCloseBefore={
config.editor.codeBlockMatchingCloseBefore
}
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
@@ -870,6 +877,9 @@ class SnippetNoteDetail extends React.Component {
enableSmartPaste={config.editor.enableSmartPaste} enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey} hotkey={config.hotkey}
autoDetect={autoDetect} autoDetect={autoDetect}
dateFormatISO8601={config.editor.dateFormatISO8601}
storageKey={storageKey}
noteKey={note.key}
/> />
)} )}
</div> </div>

View File

@@ -20,6 +20,7 @@ class TagSelect extends React.Component {
} }
this.handleAddTag = this.handleAddTag.bind(this) this.handleAddTag = this.handleAddTag.bind(this)
this.handleRenameTag = this.handleRenameTag.bind(this)
this.onInputBlur = this.onInputBlur.bind(this) this.onInputBlur = this.onInputBlur.bind(this)
this.onInputChange = this.onInputChange.bind(this) this.onInputChange = this.onInputChange.bind(this)
this.onInputKeyDown = this.onInputKeyDown.bind(this) this.onInputKeyDown = this.onInputKeyDown.bind(this)
@@ -88,6 +89,7 @@ class TagSelect extends React.Component {
this.buildSuggestions() this.buildSuggestions()
ee.on('editor:add-tag', this.handleAddTag) ee.on('editor:add-tag', this.handleAddTag)
ee.on('sidebar:rename-tag', this.handleRenameTag)
} }
componentDidUpdate() { componentDidUpdate() {
@@ -96,12 +98,23 @@ class TagSelect extends React.Component {
componentWillUnmount() { componentWillUnmount() {
ee.off('editor:add-tag', this.handleAddTag) ee.off('editor:add-tag', this.handleAddTag)
ee.off('sidebar:rename-tag', this.handleRenameTag)
} }
handleAddTag() { handleAddTag() {
this.refs.newTag.input.focus() this.refs.newTag.input.focus()
} }
handleRenameTag(event, tagChange) {
const { value } = this.props
const { tag, updatedTag } = tagChange
const newTags = value.slice()
newTags[value.indexOf(tag)] = updatedTag
this.value = newTags
this.props.onChange()
}
handleTagLabelClick(tag) { handleTagLabelClick(tag) {
const { dispatch } = this.props const { dispatch } = this.props

View File

@@ -6,6 +6,7 @@ import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl' import styles from './SideNav.styl'
import { openModal } from 'browser/main/lib/modal' import { openModal } from 'browser/main/lib/modal'
import PreferencesModal from '../modals/PreferencesModal' import PreferencesModal from '../modals/PreferencesModal'
import RenameTagModal from 'browser/main/modals/RenameTagModal'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import StorageItem from './StorageItem' import StorageItem from './StorageItem'
import TagListItem from 'browser/components/TagListItem' import TagListItem from 'browser/components/TagListItem'
@@ -170,6 +171,11 @@ class SideNav extends React.Component {
) )
}) })
menu.push({
label: i18n.__('Rename Tag'),
click: this.handleRenameTagClick.bind(this, tag)
})
context.popup(menu) context.popup(menu)
} }
@@ -193,6 +199,16 @@ class SideNav extends React.Component {
}) })
} }
handleRenameTagClick(tagName) {
const { data, dispatch } = this.props
openModal(RenameTagModal, {
tagName,
data,
dispatch
})
}
handleColorPickerConfirm(color) { handleColorPickerConfirm(color) {
const { const {
dispatch, dispatch,

View File

@@ -71,7 +71,8 @@ export const DEFAULT_CONFIG = {
disableDirectWrite: false, disableDirectWrite: false,
showScrollBar: true, showScrollBar: true,
defaultNote: 'ALWAYS_ASK', // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE' defaultNote: 'ALWAYS_ASK', // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
showMenuBar: false showMenuBar: false,
isStacking: false
}, },
editor: { editor: {
theme: 'base16-light', theme: 'base16-light',
@@ -85,8 +86,13 @@ export const DEFAULT_CONFIG = {
rulers: [80, 120], rulers: [80, 120],
displayLineNumbers: true, displayLineNumbers: true,
matchingPairs: '()[]{}\'\'""$$**``~~__', matchingPairs: '()[]{}\'\'""$$**``~~__',
matchingCloseBefore: ')]}\'":;>',
matchingTriples: '```"""\'\'\'', matchingTriples: '```"""\'\'\'',
explodingPairs: '[]{}``$$', explodingPairs: '[]{}``$$',
codeBlockMatchingPairs: '()[]{}\'\'""``',
codeBlockMatchingCloseBefore: ')]}\'":;>',
codeBlockMatchingTriples: '',
codeBlockExplodingPairs: '[]{}``',
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK' switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE' delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
scrollPastEnd: false, scrollPastEnd: false,
@@ -99,6 +105,7 @@ export const DEFAULT_CONFIG = {
enableSmartPaste: false, enableSmartPaste: false,
enableMarkdownLint: false, enableMarkdownLint: false,
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG, customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG,
dateFormatISO8601: false,
prettierConfig: `{ prettierConfig: `{
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 2, "tabWidth": 2,
@@ -137,7 +144,10 @@ export const DEFAULT_CONFIG = {
username: '', username: '',
password: '' password: ''
}, },
coloredTags: {} coloredTags: {},
wakatime: {
key: null
}
} }
function validate(config) { function validate(config) {
@@ -253,6 +263,12 @@ function assignConfigValues(originalConfig, rcConfig) {
originalConfig.hotkey, originalConfig.hotkey,
rcConfig.hotkey rcConfig.hotkey
) )
config.wakatime = Object.assign(
{},
DEFAULT_CONFIG.wakatime,
originalConfig.wakatime,
rcConfig.wakatime
)
config.blog = Object.assign( config.blog = Object.assign(
{}, {},
DEFAULT_CONFIG.blog, DEFAULT_CONFIG.blog,

View File

@@ -1,4 +1,5 @@
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import uiThemes from 'browser/lib/ui-themes'
const saveChanges = newConfig => { const saveChanges = newConfig => {
ConfigManager.set(newConfig) ConfigManager.set(newConfig)
@@ -40,14 +41,7 @@ const chooseTheme = config => {
} }
const applyTheme = theme => { const applyTheme = theme => {
const supportedThemes = [ if (uiThemes.some(item => item.name === theme)) {
'dark',
'white',
'solarized-dark',
'monokai',
'dracula'
]
if (supportedThemes.indexOf(theme) !== -1) {
document.body.setAttribute('data-theme', theme) document.body.setAttribute('data-theme', theme)
if (document.body.querySelector('.MarkdownPreview')) { if (document.body.querySelector('.MarkdownPreview')) {
document.body document.body

View File

@@ -139,6 +139,13 @@ div[id^="firstRow"]
margin-right 10px margin-right 10px
font-size 14px font-size 14px
.group-section-label-right
width 200px
text-align right
margin-right 10px
font-size 14px
padding-right 1.5rem
.group-section-control .group-section-control
flex 1 flex 1
margin-left 5px margin-left 5px

View File

@@ -145,9 +145,7 @@ class InfoTab extends React.Component {
height='92' height='92'
/> />
<div styleName='icon-right'> <div styleName='icon-right'>
<div styleName='appId'> <div styleName='appId'>Boostnote Legacy {appVersion}</div>
{i18n.__('Boostnote')} {appVersion}
</div>
<div styleName='description'> <div styleName='description'>
{i18n.__( {i18n.__(
'An open source note-taking app made for programmers just like you.' 'An open source note-taking app made for programmers just like you.'

View File

@@ -0,0 +1,207 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import { store } from 'browser/main/store'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
import { sync as commandExists } from 'command-exists'
const electron = require('electron')
const ipc = electron.ipcRenderer
const { remote } = electron
const { dialog } = remote
class PluginsTab extends React.Component {
constructor(props) {
super(props)
this.state = {
config: props.config
}
}
componentDidMount() {
this.handleSettingDone = () => {
this.setState({
pluginsAlert: {
type: 'success',
message: i18n.__('Successfully applied!')
}
})
}
this.handleSettingError = err => {
this.setState({
pluginsAlert: {
type: 'error',
message:
err.message != null ? err.message : i18n.__('An error occurred!')
}
})
}
this.oldWakatimeConfig = this.state.config.wakatime
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
}
componentWillUnmount() {
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
}
checkWakatimePluginRequirement() {
const { wakatime } = this.state.config
if (wakatime.isActive && !commandExists('wakatime')) {
this.setState({
wakatimePluginAlert: {
type: i18n.__('Warning'),
message: i18n.__('Missing wakatime cli')
}
})
const alertConfig = {
type: 'warning',
message: i18n.__('Missing Wakatime CLI'),
detail: i18n.__(
`Please install Wakatime CLI to use Wakatime tracker feature.`
),
buttons: [i18n.__('OK')]
}
dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
} else {
this.setState({
wakatimePluginAlert: null
})
}
}
handleSaveButtonClick(e) {
const newConfig = {
wakatime: {
isActive: this.state.config.wakatime.isActive,
key: this.state.config.wakatime.key
}
}
ConfigManager.set(newConfig)
store.dispatch({
type: 'SET_CONFIG',
config: newConfig
})
this.clearMessage()
this.props.haveToSave()
this.checkWakatimePluginRequirement()
}
handleIsWakatimePluginActiveChange(e) {
const { config } = this.state
config.wakatime.isActive = !config.wakatime.isActive
this.setState({
config
})
if (_.isEqual(this.oldWakatimeConfig.isActive, config.wakatime.isActive)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'Plugins',
type: 'warning',
message: i18n.__('Unsaved Changes!')
})
}
}
handleWakatimeKeyChange(e) {
const { config } = this.state
config.wakatime = {
isActive: true,
key: this.refs.wakatimeKey.value
}
this.setState({
config
})
if (_.isEqual(this.oldWakatimeConfig.key, config.wakatime.key)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'Plugins',
type: 'warning',
message: i18n.__('Unsaved Changes!')
})
}
}
clearMessage() {
_.debounce(() => {
this.setState({
pluginsAlert: null
})
}, 2000)()
}
render() {
const pluginsAlert = this.state.pluginsAlert
const pluginsAlertElement =
pluginsAlert != null ? (
<p className={`alert ${pluginsAlert.type}`}>{pluginsAlert.message}</p>
) : null
const wakatimeAlert = this.state.wakatimePluginAlert
const wakatimePluginAlertElement =
wakatimeAlert != null ? (
<p className={`alert ${wakatimeAlert.type}`}>{wakatimeAlert.message}</p>
) : null
const { config } = this.state
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>{i18n.__('Plugins')}</div>
<div styleName='group-header2'>{i18n.__('Wakatime')}</div>
<div styleName='group-checkBoxSection'>
<label>
<input
onChange={e => this.handleIsWakatimePluginActiveChange(e)}
checked={config.wakatime.isActive}
ref='wakatimeIsActive'
type='checkbox'
/>
&nbsp;
{i18n.__('Enable Wakatime')}
</label>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Wakatime key')}</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
onChange={e => this.handleWakatimeKeyChange(e)}
disabled={!config.wakatime.isActive}
ref='wakatimeKey'
value={config.wakatime.key}
type='text'
/>
{wakatimePluginAlertElement}
</div>
</div>
<div styleName='group-control'>
<button
styleName='group-control-rightButton'
onClick={e => this.handleSaveButtonClick(e)}
>
{i18n.__('Save')}
</button>
{pluginsAlertElement}
</div>
</div>
</div>
)
}
}
PluginsTab.propTypes = {
dispatch: PropTypes.func,
haveToSave: PropTypes.func
}
export default CSSModules(PluginsTab, styles)

View File

@@ -35,10 +35,18 @@ class SnippetEditor extends React.Component {
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: { autoCloseBrackets: {
pairs: this.props.matchingPairs, codeBlock: {
triples: this.props.matchingTriples, pairs: this.props.codeBlockMatchingPairs,
explode: this.props.explodingPairs, closeBefore: this.props.codeBlockMatchingCloseBefore,
override: true triples: this.props.codeBlockMatchingTriples,
explode: this.props.codeBlockExplodingPairs
},
markdown: {
pairs: this.props.matchingPairs,
closeBefore: this.props.matchingCloseBefore,
triples: this.props.matchingTriples,
explode: this.props.explodingPairs
}
}, },
mode: 'null' mode: 'null'
}) })

View File

@@ -152,8 +152,15 @@ class SnippetTab extends React.Component {
rulers={config.editor.rulers} rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs} matchingPairs={config.editor.matchingPairs}
matchingCloseBefore={config.editor.matchingCloseBefore}
matchingTriples={config.editor.matchingTriples} matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs} explodingPairs={config.editor.explodingPairs}
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
codeBlockMatchingCloseBefore={
config.editor.codeBlockMatchingCloseBefore
}
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
onRef={ref => { onRef={ref => {
this.snippetEditor = ref this.snippetEditor = ref

View File

@@ -124,14 +124,21 @@ class UiTab extends React.Component {
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked, enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
frontMatterTitleField: this.refs.frontMatterTitleField.value, frontMatterTitleField: this.refs.frontMatterTitleField.value,
matchingPairs: this.refs.matchingPairs.value, matchingPairs: this.refs.matchingPairs.value,
matchingCloseBefore: this.refs.matchingCloseBefore.value,
matchingTriples: this.refs.matchingTriples.value, matchingTriples: this.refs.matchingTriples.value,
explodingPairs: this.refs.explodingPairs.value, explodingPairs: this.refs.explodingPairs.value,
codeBlockMatchingPairs: this.refs.codeBlockMatchingPairs.value,
codeBlockMatchingCloseBefore: this.refs.codeBlockMatchingCloseBefore
.value,
codeBlockMatchingTriples: this.refs.codeBlockMatchingTriples.value,
codeBlockExplodingPairs: this.refs.codeBlockExplodingPairs.value,
spellcheck: this.refs.spellcheck.checked, spellcheck: this.refs.spellcheck.checked,
enableSmartPaste: this.refs.enableSmartPaste.checked, enableSmartPaste: this.refs.enableSmartPaste.checked,
enableMarkdownLint: this.refs.enableMarkdownLint.checked, enableMarkdownLint: this.refs.enableMarkdownLint.checked,
customMarkdownLintConfig: this.customMarkdownLintConfigCM customMarkdownLintConfig: this.customMarkdownLintConfigCM
.getCodeMirror() .getCodeMirror()
.getValue(), .getValue(),
dateFormatISO8601: this.refs.dateFormatISO8601.checked,
prettierConfig: this.prettierConfigCM.getCodeMirror().getValue(), prettierConfig: this.prettierConfigCM.getCodeMirror().getValue(),
deleteUnusedAttachments: this.refs.deleteUnusedAttachments.checked, deleteUnusedAttachments: this.refs.deleteUnusedAttachments.checked,
rtlEnabled: this.refs.rtlEnabled.checked rtlEnabled: this.refs.rtlEnabled.checked
@@ -745,6 +752,126 @@ class UiTab extends React.Component {
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Matching character pairs')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.matchingPairs}
ref='matchingPairs'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label-right'>
{i18n.__('in code blocks')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.codeBlockMatchingPairs}
ref='codeBlockMatchingPairs'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Close pairs before')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.matchingCloseBefore}
ref='matchingCloseBefore'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label-right'>
{i18n.__('in code blocks')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.codeBlockMatchingCloseBefore}
ref='codeBlockMatchingCloseBefore'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Matching character triples')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.matchingTriples}
ref='matchingTriples'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label-right'>
{i18n.__('in code blocks')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.codeBlockMatchingTriples}
ref='codeBlockMatchingTriples'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Exploding character pairs')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.explodingPairs}
ref='explodingPairs'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label-right'>
{i18n.__('in code blocks')}
</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={this.state.config.editor.codeBlockExplodingPairs}
ref='codeBlockExplodingPairs'
onChange={e => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input <input
@@ -875,50 +1002,19 @@ class UiTab extends React.Component {
</label> </label>
</div> </div>
<div styleName='group-section'> <div styleName='group-checkBoxSection'>
<div styleName='group-section-label'> <label>
{i18n.__('Matching character pairs')}
</div>
<div styleName='group-section-control'>
<input <input
styleName='group-section-control-input'
value={this.state.config.editor.matchingPairs}
ref='matchingPairs'
onChange={e => this.handleUIChange(e)} onChange={e => this.handleUIChange(e)}
type='text' checked={this.state.config.editor.dateFormatISO8601}
ref='dateFormatISO8601'
type='checkbox'
/> />
</div> &nbsp;
{i18n.__('Date shortcut use iso 8601 format')}
</label>
</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-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Custom MarkdownLint Rules')} {i18n.__('Custom MarkdownLint Rules')}

View File

@@ -7,6 +7,7 @@ import InfoTab from './InfoTab'
import Crowdfunding from './Crowdfunding' import Crowdfunding from './Crowdfunding'
import StoragesTab from './StoragesTab' import StoragesTab from './StoragesTab'
import SnippetTab from './SnippetTab' import SnippetTab from './SnippetTab'
import PluginsTab from './PluginsTab'
import Blog from './Blog' import Blog from './Blog'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -82,6 +83,14 @@ class Preferences extends React.Component {
) )
case 'SNIPPET': case 'SNIPPET':
return <SnippetTab dispatch={dispatch} config={config} data={data} /> return <SnippetTab dispatch={dispatch} config={config} data={data} />
case 'PLUGINS':
return (
<PluginsTab
dispatch={dispatch}
config={config}
haveToSave={alert => this.setState({ PluginsAlert: alert })}
/>
)
case 'STORAGES': case 'STORAGES':
default: default:
return ( return (
@@ -122,7 +131,8 @@ class Preferences extends React.Component {
{ target: 'INFO', label: i18n.__('About') }, { target: 'INFO', label: i18n.__('About') },
{ target: 'CROWDFUNDING', label: i18n.__('Crowdfunding') }, { target: 'CROWDFUNDING', label: i18n.__('Crowdfunding') },
{ target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert }, { target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert },
{ target: 'SNIPPET', label: i18n.__('Snippets') } { target: 'SNIPPET', label: i18n.__('Snippets') },
{ target: 'PLUGINS', label: i18n.__('Plugins') }
] ]
const navButtons = tabs.map(tab => { const navButtons = tabs.map(tab => {

View File

@@ -1,7 +1,7 @@
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 styles from './RenameFolderModal.styl' import styles from './RenameModal.styl'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import { store } from 'browser/main/store' import { store } from 'browser/main/store'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'

View File

@@ -46,13 +46,18 @@
font-size 14px font-size 14px
colorPrimaryButton() colorPrimaryButton()
.error
text-align center
color #F44336
height 20px
apply-theme(theme) apply-theme(theme)
body[data-theme={theme}] body[data-theme={theme}]
.root .root
background-color transparent background-color transparent
.header .header
background-color get-theme-var(theme, 'button--hover-backgroundColor') background-color transparent
border-color get-theme-var(theme, 'borderColor') border-color get-theme-var(theme, 'borderColor')
color get-theme-var(theme, 'text-color') color get-theme-var(theme, 'text-color')

View File

@@ -0,0 +1,196 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RenameModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'
import { replace } from 'connected-react-router'
import ee from 'browser/main/lib/eventEmitter'
import { isEmpty } from 'lodash'
import electron from 'electron'
const { remote } = electron
const { dialog } = remote
class RenameTagModal extends React.Component {
constructor(props) {
super(props)
this.nameInput = null
this.handleChange = this.handleChange.bind(this)
this.setTextInputRef = el => {
this.nameInput = el
}
this.state = {
name: props.tagName,
oldName: props.tagName
}
}
componentDidMount() {
this.nameInput.focus()
this.nameInput.select()
}
handleChange(e) {
this.setState({
name: this.nameInput.value,
showerror: false,
errormessage: ''
})
}
handleKeyDown(e) {
if (e.keyCode === 27) {
this.props.close()
}
}
handleInputKeyDown(e) {
switch (e.keyCode) {
case 13:
this.handleConfirm()
}
}
handleConfirm() {
if (this.state.name.trim().length > 0) {
const { name, oldName } = this.state
this.renameTag(oldName, name)
}
}
showError(message) {
this.setState({
showerror: true,
errormessage: message
})
}
renameTag(tag, updatedTag) {
const { data, dispatch } = this.props
if (tag === updatedTag) {
// confirm with-out any change - just dismiss the modal
this.props.close()
return
}
if (
data.noteMap
.map(note => note)
.some(note => note.tags.indexOf(updatedTag) !== -1)
) {
const alertConfig = {
type: 'warning',
message: i18n.__('Confirm tag merge'),
detail: i18n.__(
`Tag ${tag} will be merged with existing tag ${updatedTag}`
),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
}
const dialogButtonIndex = dialog.showMessageBox(
remote.getCurrentWindow(),
alertConfig
)
if (dialogButtonIndex === 1) {
return // bail early on cancel click
}
}
const notes = data.noteMap
.map(note => note)
.filter(
note => note.tags.indexOf(tag) !== -1 && note.tags.indexOf(updatedTag)
)
.map(note => {
note = Object.assign({}, note)
note.tags = note.tags.slice()
note.tags[note.tags.indexOf(tag)] = updatedTag
return note
})
if (isEmpty(notes)) {
this.showError(i18n.__('Tag exists'))
return
}
Promise.all(
notes.map(note => dataApi.updateNote(note.storage, note.key, note))
)
.then(updatedNotes => {
updatedNotes.forEach(note => {
dispatch({
type: 'UPDATE_NOTE',
note
})
})
})
.then(() => {
if (window.location.hash.includes(tag)) {
dispatch(replace(`/tags/${updatedTag}`))
}
ee.emit('sidebar:rename-tag', { tag, updatedTag })
this.props.close()
})
}
render() {
const { close } = this.props
const { errormessage } = this.state
return (
<div
styleName='root'
tabIndex='-1'
onKeyDown={e => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>{i18n.__('Rename Tag')}</div>
</div>
<ModalEscButton handleEscButtonClick={close} />
<div styleName='control'>
<input
styleName='control-input'
placeholder={i18n.__('Tag Name')}
ref={this.setTextInputRef}
value={this.state.name}
onChange={this.handleChange}
onKeyDown={e => this.handleInputKeyDown(e)}
/>
<button
styleName='control-confirmButton'
onClick={() => this.handleConfirm()}
>
{i18n.__('Confirm')}
</button>
</div>
<div className='error' styleName='error'>
{errormessage}
</div>
</div>
)
}
}
RenameTagModal.propTypes = {
storage: PropTypes.shape({
key: PropTypes.string
}),
folder: PropTypes.shape({
key: PropTypes.string,
name: PropTypes.string
})
}
export default CSSModules(RenameTagModal, styles)

View File

@@ -0,0 +1,196 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var defaults = {
pairs: "()[]{}''\"\"",
closeBefore: ")]}'\":;>",
triples: "",
explode: "[]{}"
};
var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.removeKeyMap(keyMap);
cm.state.closeBrackets = null;
}
if (val) {
ensureBound(getOption(val.markdown, "pairs"))
cm.state.closeBrackets = val;
cm.addKeyMap(keyMap);
}
});
function getOption(conf, name) {
if (name == "pairs" && typeof conf == "string") return conf;
if (typeof conf == "object" && conf[name] != null) return conf[name];
return defaults[name];
}
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
function ensureBound(chars) {
for (var i = 0; i < chars.length; i++) {
var ch = chars.charAt(i), key = "'" + ch + "'"
if (!keyMap[key]) keyMap[key] = handler(ch)
}
}
ensureBound(defaults.pairs + "`")
function handler(ch) {
return function(cm) { return handleChar(cm, ch); };
}
function getConfig(cm) {
var cursor = cm.getCursor();
var token = cm.getTokenAt(cursor);
var inCodeBlock = !!token.state.fencedEndRE;
if (inCodeBlock) {
return cm.state.closeBrackets.codeBlock
} else {
return cm.state.closeBrackets.markdown
}
}
function handleBackspace(cm) {
var conf = getConfig(cm);
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
var pairs = getOption(conf, "pairs");
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
for (var i = ranges.length - 1; i >= 0; i--) {
var cur = ranges[i].head;
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
}
}
function handleEnter(cm) {
var conf = getConfig(cm);
var explode = conf && getOption(conf, "explode");
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
cm.operation(function() {
var linesep = cm.lineSeparator() || "\n";
cm.replaceSelection(linesep + linesep, null);
cm.execCommand("goCharLeft");
ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var line = ranges[i].head.line;
cm.indentLine(line, null, true);
cm.indentLine(line + 1, null, true);
}
});
}
function contractSelection(sel) {
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
}
function handleChar(cm, ch) {
var conf = getConfig(cm);
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
var pairs = getOption(conf, "pairs");
var pos = pairs.indexOf(ch);
if (pos == -1) return CodeMirror.Pass;
var closeBefore = getOption(conf,"closeBefore");
var triples = getOption(conf, "triples");
var identical = pairs.charAt(pos + 1) == ch;
var ranges = cm.listSelections();
var opening = pos % 2 == 0;
var type;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType;
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
if (opening && !range.empty()) {
curType = "surround";
} else if ((identical || !opening) && next == ch) {
if (identical && stringStartsAfter(cm, cur))
curType = "both";
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
curType = "skipThree";
else
curType = "skip";
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
curType = "addFour";
} else if (identical) {
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
else return CodeMirror.Pass;
} else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
curType = "both";
} else {
return CodeMirror.Pass;
}
if (!type) type = curType;
else if (type != curType) return CodeMirror.Pass;
}
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
cm.operation(function() {
if (type == "skip") {
cm.execCommand("goCharRight");
} else if (type == "skipThree") {
for (var i = 0; i < 3; i++)
cm.execCommand("goCharRight");
} else if (type == "surround") {
var sels = cm.getSelections();
for (var i = 0; i < sels.length; i++)
sels[i] = left + sels[i] + right;
cm.replaceSelections(sels, "around");
sels = cm.listSelections().slice();
for (var i = 0; i < sels.length; i++)
sels[i] = contractSelection(sels[i]);
cm.setSelections(sels);
} else if (type == "both") {
cm.replaceSelection(left + right, null);
cm.triggerElectric(left + right);
cm.execCommand("goCharLeft");
} else if (type == "addFour") {
cm.replaceSelection(left + left + left + left, "before");
cm.execCommand("goCharRight");
}
});
}
function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1));
return str.length == 2 ? str : null;
}
function stringStartsAfter(cm, pos) {
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
return /\bstring/.test(token.type) && token.start == pos.ch &&
(pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
}
});

View File

@@ -55,202 +55,168 @@
} }
} }
CodeMirror.defineMode( CodeMirror.defineMode('bfm', function (config, baseConfig) {
'bfm', baseConfig.name = 'yaml-frontmatter'
function(config, baseConfig) { const baseMode = CodeMirror.getMode(config, baseConfig)
baseConfig.name = 'yaml-frontmatter'
const baseMode = CodeMirror.getMode(config, baseConfig)
return { return {
startState: function() { startState: function() {
return { return {
baseState: CodeMirror.startState(baseMode), baseState: CodeMirror.startState(baseMode),
basePos: 0, basePos: 0,
baseCur: null, baseCur: null,
overlayPos: 0, overlayPos: 0,
overlayCur: null, overlayCur: null,
streamSeen: null, streamSeen: null,
fencedEndRE: null, fencedEndRE: null,
inTable: false, inTable: false,
rowIndex: 0 rowIndex: 0
} }
}, },
copyState: function(s) { copyState: function(s) {
return { return {
baseState: CodeMirror.copyState(baseMode, s.baseState), baseState: CodeMirror.copyState(baseMode, s.baseState),
basePos: s.basePos, basePos: s.basePos,
baseCur: null, baseCur: null,
overlayPos: s.overlayPos, overlayPos: s.overlayPos,
overlayCur: null, overlayCur: null,
fencedMode: s.fencedMode, fencedMode: s.fencedMode,
fencedState: s.fencedMode fencedState: s.fencedMode ? CodeMirror.copyState(s.fencedMode, s.fencedState) : null,
? CodeMirror.copyState(s.fencedMode, s.fencedState)
: null,
fencedEndRE: s.fencedEndRE, fencedEndRE: s.fencedEndRE,
inTable: s.inTable, inTable: s.inTable,
rowIndex: s.rowIndex rowIndex: s.rowIndex
} }
}, },
token: function(stream, state) { token: function(stream, state) {
const initialPos = stream.pos const initialPos = stream.pos
if (state.fencedEndRE && stream.match(state.fencedEndRE)) { if (state.fencedEndRE) {
if (stream.match(state.fencedEndRE)) {
state.fencedEndRE = null state.fencedEndRE = null
state.fencedMode = null state.fencedMode = null
state.fencedState = null state.fencedState = null
stream.pos = initialPos stream.pos = initialPos
} else if (state.fencedMode) {
return state.fencedMode.token(stream, state.fencedState)
} else { } else {
if (state.fencedMode) {
return state.fencedMode.token(stream, state.fencedState)
}
const match = stream.match(fencedCodeRE, true)
if (match) {
state.fencedEndRE = new RegExp(match[1] + '+ *$')
state.fencedMode = getMode(
match[2],
match[3],
config,
stream.lineOracle.doc.cm
)
if (state.fencedMode) {
state.fencedState = CodeMirror.startState(state.fencedMode)
}
stream.pos = initialPos
}
}
if (
stream != state.streamSeen ||
Math.min(state.basePos, state.overlayPos) < stream.start
) {
state.streamSeen = stream
state.basePos = state.overlayPos = stream.start
}
if (stream.start == state.basePos) {
state.baseCur = baseMode.token(stream, state.baseState)
state.basePos = stream.pos
}
if (stream.start == state.overlayPos) {
stream.pos = stream.start
state.overlayCur = this.overlayToken(stream, state) state.overlayCur = this.overlayToken(stream, state)
state.overlayPos = stream.pos state.overlayPos = stream.pos
}
stream.pos = Math.min(state.basePos, state.overlayPos)
if (state.overlayCur == null) {
return state.baseCur
} else if (state.baseCur != null && state.combineTokens) {
return state.baseCur + ' ' + state.overlayCur
} else {
return state.overlayCur return state.overlayCur
} }
}, }
overlayToken: function(stream, state) { else {
state.combineTokens = false
if (state.fencedEndRE && stream.match(state.fencedEndRE)) {
state.fencedEndRE = null
state.localMode = null
state.localState = null
return null
}
if (state.localMode) {
return state.localMode.token(stream, state.localState) || ''
}
const match = stream.match(fencedCodeRE, true) const match = stream.match(fencedCodeRE, true)
if (match) { if (match) {
state.fencedEndRE = new RegExp(match[1] + '+ *$') state.fencedEndRE = new RegExp(match[1] + '+ *$')
state.localMode = getMode( state.fencedMode = getMode(match[2], match[3], config, stream.lineOracle.doc.cm)
match[2], if (state.fencedMode) {
match[3], state.fencedState = CodeMirror.startState(state.fencedMode)
config,
stream.lineOracle.doc.cm
)
if (state.localMode) {
state.localState = CodeMirror.startState(state.localMode)
} }
return null stream.pos = initialPos
}
state.combineTokens = true
if (state.inTable) {
if (stream.match(/^\|/)) {
++state.rowIndex
stream.skipToEnd()
if (state.rowIndex === 1) {
return 'table table-separator'
} else if (state.rowIndex % 2 === 0) {
return 'table table-row table-row-even'
} else {
return 'table table-row table-row-odd'
}
} else {
state.inTable = false
stream.skipToEnd()
return null
}
} else if (stream.match(/^\|/)) {
state.inTable = true
state.rowIndex = 0
stream.skipToEnd()
return 'table table-header'
}
stream.skipToEnd()
return null
},
electricChars: baseMode.electricChars,
innerMode: function(state) {
if (state.fencedMode) {
return {
mode: state.fencedMode,
state: state.fencedState
}
} else {
return {
mode: baseMode,
state: state.baseState
}
}
},
blankLine: function(state) {
state.inTable = false
if (state.fencedMode) {
return (
state.fencedMode.blankLine &&
state.fencedMode.blankLine(state.fencedState)
)
} else {
return baseMode.blankLine(state.baseState)
} }
} }
if (stream != state.streamSeen || Math.min(state.basePos, state.overlayPos) < stream.start) {
state.streamSeen = stream
state.basePos = state.overlayPos = stream.start
}
if (stream.start == state.basePos) {
state.baseCur = baseMode.token(stream, state.baseState)
state.basePos = stream.pos
}
if (stream.start == state.overlayPos) {
stream.pos = stream.start
state.overlayCur = this.overlayToken(stream, state)
state.overlayPos = stream.pos
}
stream.pos = Math.min(state.basePos, state.overlayPos)
if (state.overlayCur == null) {
return state.baseCur
}
else if (state.baseCur != null && state.combineTokens) {
return state.baseCur + ' ' + state.overlayCur
}
else {
return state.overlayCur
}
},
overlayToken: function(stream, state) {
state.combineTokens = false
if (state.localMode) {
return state.localMode.token(stream, state.localState) || ''
}
state.combineTokens = true
if (state.inTable) {
if (stream.match(/^\|/)) {
++state.rowIndex
stream.skipToEnd()
if (state.rowIndex === 1) {
return 'table table-separator'
} else if (state.rowIndex % 2 === 0) {
return 'table table-row table-row-even'
} else {
return 'table table-row table-row-odd'
}
} else {
state.inTable = false
stream.skipToEnd()
return null
}
} else if (stream.match(/^\|/)) {
state.inTable = true
state.rowIndex = 0
stream.skipToEnd()
return 'table table-header'
}
stream.skipToEnd()
return null
},
electricChars: baseMode.electricChars,
innerMode: function(state) {
if (state.fencedMode) {
return {
mode: state.fencedMode,
state: state.fencedState
}
} else {
return {
mode: baseMode,
state: state.baseState
}
}
},
blankLine: function(state) {
state.inTable = false
if (state.fencedMode) {
return state.fencedMode.blankLine && state.fencedMode.blankLine(state.fencedState)
} else {
return baseMode.blankLine(state.baseState)
}
} }
}, }
'yaml-frontmatter' }, 'yaml-frontmatter')
)
CodeMirror.defineMIME('text/x-bfm', 'bfm') CodeMirror.defineMIME('text/x-bfm', 'bfm')
@@ -259,4 +225,4 @@
mime: 'text/x-bfm', mime: 'text/x-bfm',
mode: 'bfm' mode: 'bfm'
}) })
}) })

View File

@@ -314,6 +314,12 @@ const view = {
mainWindow.webContents.send('editor:fullscreen') mainWindow.webContents.send('editor:fullscreen')
} }
}, },
{
label: 'Toggle Editor Orientation',
click() {
mainWindow.webContents.send('editor:orientation')
}
},
{ {
type: 'separator' type: 'separator'
}, },

View File

@@ -72,7 +72,7 @@
border-left-color: rgba(142, 142, 142, 0.5); border-left-color: rgba(142, 142, 142, 0.5);
mix-blend-mode: difference; mix-blend-mode: difference;
} }
.CodeMirror-scroll { .CodeMirror-scroll {
margin-bottom: 0; margin-bottom: 0;
padding-bottom: 0; padding-bottom: 0;
@@ -116,7 +116,7 @@
<script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script> <script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script>
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script> <script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script> <script src="../extra_scripts/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script> <script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
<script src="../node_modules/codemirror/addon/search/search.js"></script> <script src="../node_modules/codemirror/addon/search/search.js"></script>

View File

@@ -112,7 +112,7 @@
<script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script> <script src="../extra_scripts/codemirror/mode/gfm/gfm.js"></script>
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script> <script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script> <script src="../extra_scripts/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script> <script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
<script src="../node_modules/codemirror/addon/search/search.js"></script> <script src="../node_modules/codemirror/addon/search/search.js"></script>

View File

@@ -1,7 +1,7 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.15.3", "version": "0.16.0",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -61,6 +61,7 @@
"chart.js": "^2.7.2", "chart.js": "^2.7.2",
"codemirror": "^5.40.2", "codemirror": "^5.40.2",
"codemirror-mode-elixir": "^1.1.1", "codemirror-mode-elixir": "^1.1.1",
"command-exists": "^1.2.9",
"connected-react-router": "^6.4.0", "connected-react-router": "^6.4.0",
"electron-config": "^1.0.0", "electron-config": "^1.0.0",
"electron-gh-releases": "^2.0.4", "electron-gh-releases": "^2.0.4",
@@ -79,7 +80,7 @@
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"jsonlint-mod": "^1.7.4", "jsonlint-mod": "^1.7.4",
"katex": "^0.10.1", "katex": "^0.10.1",
"lodash": "^4.17.13", "lodash": "^4.17.19",
"lodash-move": "^1.1.1", "lodash-move": "^1.1.1",
"markdown-it": "^6.0.1", "markdown-it": "^6.0.1",
"markdown-it-abbr": "^1.0.4", "markdown-it-abbr": "^1.0.4",
@@ -95,7 +96,7 @@
"markdown-it-sup": "^1.0.0", "markdown-it-sup": "^1.0.0",
"markdown-toc": "^1.2.0", "markdown-toc": "^1.2.0",
"mdurl": "^1.0.1", "mdurl": "^1.0.1",
"mermaid": "^8.4.2", "mermaid": "^8.5.2",
"moment": "^2.10.3", "moment": "^2.10.3",
"mousetrap": "^1.6.2", "mousetrap": "^1.6.2",
"mousetrap-global-bind": "^1.1.0", "mousetrap-global-bind": "^1.1.0",

View File

@@ -1,6 +1,5 @@
{ {
"trailingComma": "es5", "singleQuote": true,
"tabWidth": 2,
"semi": false, "semi": false,
"singleQuote": true "jsxSingleQuote": true
} }

View File

@@ -1,5 +1,11 @@
> [We've launched desktop and mobile app of the new Boost Note now.](https://github.com/BoostIO/BoostNote.next) > [We've launched desktop and mobile app of the new Boost Note now.](https://github.com/BoostIO/BoostNote.next)
> ### [Boost Note for Teams](https://boosthub.io/)
>
> We've developed a collaborative workspace app called "Boost Hub" for developer teams.
>
> It's customizable and easy to optimize for your team like rego blocks and even lets you edit documents together in real-time!
![Boostnote app screenshot](./resources/repository/top.png) ![Boostnote app screenshot](./resources/repository/top.png)
<h4 align="center">Note-taking app for programmers. </h4> <h4 align="center">Note-taking app for programmers. </h4>
@@ -15,6 +21,10 @@
[Find the latest release of Boostnote here!](https://github.com/BoostIO/boost-releases/releases/) [Find the latest release of Boostnote here!](https://github.com/BoostIO/boost-releases/releases/)
## Roadmap
[Boost Note Roadmap 2020](https://medium.com/boostnote/boost-note-roadmap-2020-9f06a642f5f1)
## Authors & Maintainers ## Authors & Maintainers
- [Rokt33r](https://github.com/rokt33r) - [Rokt33r](https://github.com/rokt33r)
@@ -22,11 +32,13 @@
- [ZeroX-DG](https://github.com/ZeroX-DG) - [ZeroX-DG](https://github.com/ZeroX-DG)
## Contributors ## Contributors
Thank you to all the people who have contributed to Boostnote! 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:
@@ -34,18 +46,22 @@ Issues on Boostnote can be funded by anyone and the money will be distributed to
[![Let's fund issues in this repository](https://issuehunt.io/static/embed/issuehunt-button-v1.svg)](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/)
- [Twitter](https://twitter.com/boostnoteapp) - [Twitter](https://twitter.com/boostnoteapp)
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LWQxZTQwNjBlMDI4YjkyYjg2MTRiZGJhNzA1YjQ5ODA5M2M0M2NlMjI5YjhiYWQzNzgzYmU0MDMwOTlmZmZmMGE) - [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/zt-cun7pas3-WwkaezxHBB1lCbUHrwQLXw)
- [Blog](https://medium.com/boostnote) - [Blog](https://medium.com/boostnote)
- [Reddit](https://www.reddit.com/r/Boostnote/) - [Reddit](https://www.reddit.com/r/Boostnote/)
### Boostnote mobile
A community project developing a mobile cross-platform version of boostnote for iOS and Android can be found here: [NoteApp](https://github.com/T0M0F/NoteApp)
#### More Information #### More Information
* Website: https://boostnote.io
* [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
* Copyright (C) 2016 - 2020 BoostIO, Inc.
- Website: https://boostnote.io
- [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
- Copyright (C) 2016 - 2020 BoostIO, Inc.
#### License #### License

View File

@@ -1,4 +1,3 @@
const test = require('ava')
const copyFile = require('browser/main/lib/dataApi/copyFile') const copyFile = require('browser/main/lib/dataApi/copyFile')
const path = require('path') const path = require('path')
@@ -13,23 +12,25 @@ const srcPath = path.join(srcFolder, testFile)
const dstFolder = path.join(__dirname, '😇') const dstFolder = path.join(__dirname, '😇')
const dstPath = path.join(dstFolder, testFile) const dstPath = path.join(dstFolder, testFile)
test.before(t => { beforeAll(() => {
if (!fs.existsSync(srcFolder)) fs.mkdirSync(srcFolder) if (!fs.existsSync(srcFolder)) fs.mkdirSync(srcFolder)
fs.writeFileSync(srcPath, 'test') fs.writeFileSync(srcPath, 'test')
}) })
test('`copyFile` should handle encoded URI on src path', t => { it('`copyFile` should handle encoded URI on src path', done => {
return copyFile(encodeURI(srcPath), dstPath) return copyFile(encodeURI(srcPath), dstPath)
.then(() => { .then(() => {
t.true(true) expect(true).toBe(true)
done()
}) })
.catch(() => { .catch(() => {
t.true(false) expect(false).toBe(true)
done()
}) })
}) })
test.after(t => { afterAll(() => {
fs.unlinkSync(srcPath) fs.unlinkSync(srcPath)
fs.unlinkSync(dstPath) fs.unlinkSync(dstPath)
execSync(removeDirCommand + '"' + srcFolder + '"') execSync(removeDirCommand + '"' + srcFolder + '"')

View File

@@ -1,4 +1,3 @@
const test = require('ava')
const createFolder = require('browser/main/lib/dataApi/createFolder') const createFolder = require('browser/main/lib/dataApi/createFolder')
global.document = require('jsdom').jsdom('<body></body>') global.document = require('jsdom').jsdom('<body></body>')
@@ -19,32 +18,34 @@ const CSON = require('@rokt33r/season')
const storagePath = path.join(os.tmpdir(), 'test/create-folder') const storagePath = path.join(os.tmpdir(), 'test/create-folder')
test.beforeEach(t => { let storageContext
t.context.storage = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) beforeAll(() => {
storageContext = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
}) })
test.serial('Create a folder', t => { it('Create a folder', done => {
const storageKey = t.context.storage.cache.key const storageKey = storageContext.cache.key
const input = { const input = {
name: 'created', name: 'created',
color: '#ff5555' color: '#ff5555'
} }
return Promise.resolve() return Promise.resolve()
.then(function doTest() { .then(() => {
return createFolder(storageKey, input) return createFolder(storageKey, input)
}) })
.then(function assert(data) { .then(data => {
t.true(_.find(data.storage.folders, input) != null) expect(_.find(data.storage.folders, input)).not.toBeNull()
const jsonData = CSON.readFileSync( const jsonData = CSON.readFileSync(
path.join(data.storage.path, 'boostnote.json') path.join(data.storage.path, 'boostnote.json')
) )
console.log(path.join(data.storage.path, 'boostnote.json')) expect(_.find(jsonData.folders, input)).not.toBeNull()
t.true(_.find(jsonData.folders, input) != null) done()
}) })
}) })
test.after(function after() { afterAll(() => {
localStorage.clear() localStorage.clear()
sander.rimrafSync(storagePath) sander.rimrafSync(storagePath)
}) })

View File

@@ -1,4 +1,3 @@
const test = require('ava')
const createNote = require('browser/main/lib/dataApi/createNote') const createNote = require('browser/main/lib/dataApi/createNote')
global.document = require('jsdom').jsdom('<body></body>') global.document = require('jsdom').jsdom('<body></body>')
@@ -19,14 +18,16 @@ const faker = require('faker')
const storagePath = path.join(os.tmpdir(), 'test/create-note') const storagePath = path.join(os.tmpdir(), 'test/create-note')
test.beforeEach(t => { let storageContext
t.context.storage = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) beforeEach(() => {
storageContext = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
}) })
test.serial('Create a note', t => { it('Create a note', done => {
const storageKey = t.context.storage.cache.key const storageKey = storageContext.cache.key
const folderKey = t.context.storage.json.folders[0].key const folderKey = storageContext.json.folders[0].key
const randLinesHighlightedArray = new Array(10) const randLinesHighlightedArray = new Array(10)
.fill() .fill()
@@ -58,58 +59,58 @@ test.serial('Create a note', t => {
input2.title = input2.content.split('\n').shift() input2.title = input2.content.split('\n').shift()
return Promise.resolve() return Promise.resolve()
.then(function doTest() { .then(() => {
return Promise.all([ return Promise.all([
createNote(storageKey, input1), createNote(storageKey, input1),
createNote(storageKey, input2) createNote(storageKey, input2)
]) ])
}) })
.then(function assert(data) { .then(data => {
const data1 = data[0] const data1 = data[0]
const data2 = data[1] const data2 = data[1]
t.is(storageKey, data1.storage) expect(storageKey).toEqual(data1.storage)
const jsonData1 = CSON.readFileSync( const jsonData1 = CSON.readFileSync(
path.join(storagePath, 'notes', data1.key + '.cson') path.join(storagePath, 'notes', data1.key + '.cson')
) )
t.is(input1.title, data1.title) expect(input1.title).toEqual(data1.title)
t.is(input1.title, jsonData1.title) expect(input1.title).toEqual(jsonData1.title)
t.is(input1.description, data1.description) expect(input1.description).toEqual(data1.description)
t.is(input1.description, jsonData1.description) expect(input1.description).toEqual(jsonData1.description)
t.is(input1.tags.length, data1.tags.length) expect(input1.tags.length).toEqual(data1.tags.length)
t.is(input1.tags.length, jsonData1.tags.length) expect(input1.tags.length).toEqual(jsonData1.tags.length)
t.is(input1.snippets.length, data1.snippets.length) expect(input1.snippets.length).toEqual(data1.snippets.length)
t.is(input1.snippets.length, jsonData1.snippets.length) expect(input1.snippets.length).toEqual(jsonData1.snippets.length)
t.is(input1.snippets[0].content, data1.snippets[0].content) expect(input1.snippets[0].content).toEqual(data1.snippets[0].content)
t.is(input1.snippets[0].content, jsonData1.snippets[0].content) expect(input1.snippets[0].content).toEqual(jsonData1.snippets[0].content)
t.is(input1.snippets[0].name, data1.snippets[0].name) expect(input1.snippets[0].name).toEqual(data1.snippets[0].name)
t.is(input1.snippets[0].name, jsonData1.snippets[0].name) expect(input1.snippets[0].name).toEqual(jsonData1.snippets[0].name)
t.deepEqual( expect(input1.snippets[0].linesHighlighted).toEqual(
input1.snippets[0].linesHighlighted,
data1.snippets[0].linesHighlighted data1.snippets[0].linesHighlighted
) )
t.deepEqual( expect(input1.snippets[0].linesHighlighted).toEqual(
input1.snippets[0].linesHighlighted,
jsonData1.snippets[0].linesHighlighted jsonData1.snippets[0].linesHighlighted
) )
t.is(storageKey, data2.storage) expect(storageKey).toEqual(data2.storage)
const jsonData2 = CSON.readFileSync( const jsonData2 = CSON.readFileSync(
path.join(storagePath, 'notes', data2.key + '.cson') path.join(storagePath, 'notes', data2.key + '.cson')
) )
t.is(input2.title, data2.title) expect(input2.title).toEqual(data2.title)
t.is(input2.title, jsonData2.title) expect(input2.title).toEqual(jsonData2.title)
t.is(input2.content, data2.content) expect(input2.content).toEqual(data2.content)
t.is(input2.content, jsonData2.content) expect(input2.content).toEqual(jsonData2.content)
t.is(input2.tags.length, data2.tags.length) expect(input2.tags.length).toEqual(data2.tags.length)
t.is(input2.tags.length, jsonData2.tags.length) expect(input2.tags.length).toEqual(jsonData2.tags.length)
t.deepEqual(input2.linesHighlighted, data2.linesHighlighted) expect(input2.linesHighlighted).toEqual(data2.linesHighlighted)
t.deepEqual(input2.linesHighlighted, jsonData2.linesHighlighted) expect(input2.linesHighlighted).toEqual(jsonData2.linesHighlighted)
done()
}) })
}) })
test.after(function after() { afterAll(function after() {
localStorage.clear() localStorage.clear()
sander.rimrafSync(storagePath) sander.rimrafSync(storagePath)
}) })

View File

@@ -1,4 +1,3 @@
const test = require('ava')
const createNoteFromUrl = require('browser/main/lib/dataApi/createNoteFromUrl') const createNoteFromUrl = require('browser/main/lib/dataApi/createNoteFromUrl')
global.document = require('jsdom').jsdom('<body></body>') global.document = require('jsdom').jsdom('<body></body>')
@@ -18,32 +17,34 @@ const CSON = require('@rokt33r/season')
const storagePath = path.join(os.tmpdir(), 'test/create-note-from-url') const storagePath = path.join(os.tmpdir(), 'test/create-note-from-url')
test.beforeEach(t => { let storageContext
t.context.storage = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) beforeEach(() => {
storageContext = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
}) })
test.serial('Create a note from URL', t => { it('Create a note from URL', () => {
const storageKey = t.context.storage.cache.key const storageKey = storageContext.cache.key
const folderKey = t.context.storage.json.folders[0].key const folderKey = storageContext.json.folders[0].key
const url = 'https://shapeshed.com/writing-cross-platform-node/' const url = 'https://shapeshed.com/writing-cross-platform-node/'
return createNoteFromUrl(url, storageKey, folderKey).then(function assert({ return createNoteFromUrl(url, storageKey, folderKey).then(function assert({
note note
}) { }) {
t.is(storageKey, note.storage) expect(storageKey).toEqual(note.storage)
const jsonData = CSON.readFileSync( const jsonData = CSON.readFileSync(
path.join(storagePath, 'notes', note.key + '.cson') path.join(storagePath, 'notes', note.key + '.cson')
) )
// Test if saved content is matching the created in memory note // Test if saved content is matching the created in memory note
t.is(note.content, jsonData.content) expect(note.content).toEqual(jsonData.content)
t.is(note.tags.length, jsonData.tags.length) expect(note.tags.length).toEqual(jsonData.tags.length)
}) })
}) })
test.after(function after() { afterAll(function after() {
localStorage.clear() localStorage.clear()
sander.rimrafSync(storagePath) sander.rimrafSync(storagePath)
}) })

View File

@@ -1,4 +1,3 @@
const test = require('ava')
const createSnippet = require('browser/main/lib/dataApi/createSnippet') const createSnippet = require('browser/main/lib/dataApi/createSnippet')
const sander = require('sander') const sander = require('sander')
const os = require('os') const os = require('os')
@@ -7,29 +6,27 @@ const path = require('path')
const snippetFilePath = path.join(os.tmpdir(), 'test', 'create-snippet') const snippetFilePath = path.join(os.tmpdir(), 'test', 'create-snippet')
const snippetFile = path.join(snippetFilePath, 'snippets.json') const snippetFile = path.join(snippetFilePath, 'snippets.json')
test.beforeEach(t => { beforeEach(() => {
sander.writeFileSync(snippetFile, '[]') sander.writeFileSync(snippetFile, '[]')
}) })
test.serial('Create a snippet', t => { it('Create a snippet', () => {
return Promise.resolve() return Promise.resolve()
.then(function doTest() { .then(() => Promise.all([createSnippet(snippetFile)]))
return Promise.all([createSnippet(snippetFile)])
})
.then(function assert(data) { .then(function assert(data) {
data = data[0] data = data[0]
const snippets = JSON.parse(sander.readFileSync(snippetFile)) const snippets = JSON.parse(sander.readFileSync(snippetFile))
const snippet = snippets.find( const snippet = snippets.find(
currentSnippet => currentSnippet.id === data.id currentSnippet => currentSnippet.id === data.id
) )
t.not(snippet, undefined) expect(snippet).not.toBeUndefined()
t.is(snippet.name, data.name) expect(snippet.name).toEqual(data.name)
t.deepEqual(snippet.prefix, data.prefix) expect(snippet.prefix).toEqual(data.prefix)
t.is(snippet.content, data.content) expect(snippet.content).toEqual(data.content)
t.deepEqual(snippet.linesHighlighted, data.linesHighlighted) expect(snippet.linesHighlighted).toEqual(data.linesHighlighted)
}) })
}) })
test.after.always(() => { afterAll(() => {
sander.rimrafSync(snippetFilePath) sander.rimrafSync(snippetFilePath)
}) })

View File

@@ -1,4 +1,3 @@
const test = require('ava')
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder') const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement') const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement')
const createNote = require('browser/main/lib/dataApi/createNote') const createNote = require('browser/main/lib/dataApi/createNote')
@@ -23,14 +22,16 @@ const CSON = require('@rokt33r/season')
const storagePath = path.join(os.tmpdir(), 'test/delete-folder') const storagePath = path.join(os.tmpdir(), 'test/delete-folder')
test.beforeEach(t => { let storageContext
t.context.storage = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) beforeEach(() => {
storageContext = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
}) })
test.serial('Delete a folder', t => { it('Delete a folder', () => {
const storageKey = t.context.storage.cache.key const storageKey = storageContext.cache.key
const folderKey = t.context.storage.json.folders[0].key const folderKey = storageContext.json.folders[0].key
let noteKey let noteKey
const input1 = { const input1 = {
@@ -72,16 +73,15 @@ test.serial('Delete a folder', t => {
return deleteFolder(storageKey, folderKey) return deleteFolder(storageKey, folderKey)
}) })
.then(function assert(data) { .then(function assert(data) {
t.true(_.find(data.storage.folders, { key: folderKey }) == null) expect(_.find(data.storage.folders, { key: folderKey })).toBeUndefined()
const jsonData = CSON.readFileSync( const jsonData = CSON.readFileSync(
path.join(data.storage.path, 'boostnote.json') path.join(data.storage.path, 'boostnote.json')
) )
t.true(_.find(jsonData.folders, { key: folderKey }) == null) expect(_.find(jsonData.folders, { key: folderKey })).toBeUndefined()
const notePaths = sander.readdirSync(data.storage.path, 'notes') const notePaths = sander.readdirSync(data.storage.path, 'notes')
t.is( expect(notePaths.length).toBe(
notePaths.length, storageContext.notes.filter(note => note.folder !== folderKey).length
t.context.storage.notes.filter(note => note.folder !== folderKey).length
) )
const attachmentFolderPath = path.join( const attachmentFolderPath = path.join(
@@ -89,11 +89,11 @@ test.serial('Delete a folder', t => {
attachmentManagement.DESTINATION_FOLDER, attachmentManagement.DESTINATION_FOLDER,
noteKey noteKey
) )
t.false(fs.existsSync(attachmentFolderPath)) expect(fs.existsSync(attachmentFolderPath)).toBe(false)
}) })
}) })
test.after.always(function after() { afterAll(() => {
localStorage.clear() localStorage.clear()
sander.rimrafSync(storagePath) sander.rimrafSync(storagePath)
}) })

View File

@@ -0,0 +1,189 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Markdown.render() should render PlantUML Ditaa correctly 1`] = `
"<img src=\\"http://www.plantuml.com/plantuml/png/SoWkIImgISaiIKpaqjQ50cq51GLj93Q2mrMZ00NQO3cmHX3RJW4cKmDI4v9QKQ805a8nfyObCp6zA34NgCObFxiqDpMl1AIcHj4tCJqpLH5i18evG52TKbk3B8og1kmC0cvMKB1Im0NYkA2ckMRcANWabgQbvYau5YMbPfP0p4UOWmcqkHnIyrB0GG00\\" alt=\\"uml diagram\\" />
"
`;
exports[`Markdown.render() should render PlantUML Gantt correctly 1`] = `
"<img src=\\"http://www.plantuml.com/plantuml/svg/SoWkIImgIK_CAodXYWueoY_9BwaiI5L8IItEJC-BLSX9B2ufLZ0qLKX9h2pcYWv9BIvHA82fWaiRu906crsia5YYW6cqUh52QbuAbmEG0DiE0000\\" alt=\\"uml diagram\\" />
"
`;
exports[`Markdown.render() should render PlantUML MindMaps correctly 1`] = `
"<img src=\\"http://www.plantuml.com/plantuml/svg/JOzD3e8m44Rtd6BMtNW192IM5I29HEDsAbKdeLD2MvNRIsjCMCsRlFd9LpgFipV4Wy4f4o2r8kHC23Yhm3wi9A0X3XzeYNrgwx1H6wvb1KTjqtRJoYhMtexBSAqJUescwoEUq4tn3xp9Fm7XfUS5HiiFO3Gw7SjT4QUCkkKxLy2-WAvl3rkrtEclBdOCXcnMwZN7ByiN\\" alt=\\"uml diagram\\" />
"
`;
exports[`Markdown.render() should render PlantUML Umls correctly 1`] = `
"<img src=\\"http://www.plantuml.com/plantuml/svg/LOzD2eCm44RtESMtj0jx01V5E_G4Gvngo2_912gbTsz4LBfylCV7p5Y4ibJlbEENG2AocHV1P39hCJ6eOar8bCaZaROqyrDMnzWqXTcn8YqnGzSYqNC-q76sweoW5zOsLi57uMpHz-WESslY0jmVw1AjdaE30IPeLoVUceLTslrL3-2tS9ZA_qZRtm_vgh7PzkOF\\" alt=\\"uml diagram\\" />
"
`;
exports[`Markdown.render() should render PlantUML WBS correctly 1`] = `
"<img src=\\"http://www.plantuml.com/plantuml/svg/ZP2_JiD03CRtFeNdRF04fR140gdGeREv-z8plVYYimFYxSabKbaxsR9-ylTdRyxLVpvjrz5XDb6OqR6MqEPRYSXPz4BdmsdNTVJAiuP4da1JBLy8lbmxUYxZbE6Wa_CLgUI8IXymS0rf9NeL5yxKDt24EhiKfMDcRNzVO79HcX8RLdvLfZBGa_KtFx2RKcpK7TZ3dTpZfWgskMAZ9jIXr94rW4PubM1RbBZOb-6NtcS9LpgBjlj_1w9QldbPjZHxQ5pg_GC0\\" alt=\\"uml diagram\\" />
"
`;
exports[`Markdown.render() should render footnote correctly 1`] = `
"<p data-line=\\"1\\"><sup class=\\"footnote-ref\\"><a href=\\"#fn1\\" id=\\"fnref1\\">[1]</a></sup><br />
hello-world: <a href=\\"https://github.com/BoostIO/Boostnote/\\">https://github.com/BoostIO/Boostnote/</a></p>
<hr class=\\"footnotes-sep\\" />
<section class=\\"footnotes\\">
<ol class=\\"footnotes-list\\">
<li id=\\"fn1\\" class=\\"footnote-item\\"><p>hello-world <a href=\\"#fnref1\\" class=\\"footnote-backref\\">↩︎</a></p>
</li>
</ol>
</section>
"
`;
exports[`Markdown.render() should render line breaks correctly 1`] = `
"<p data-line=\\"0\\">This is the first line.<br />
This is the second line.</p>
"
`;
exports[`Markdown.render() should render line breaks correctly 2`] = `
"<p data-line=\\"0\\">This is the first line.
This is the second line.</p>
"
`;
exports[`Markdown.render() should render shortcuts correctly 1`] = `
"<p data-line=\\"0\\"><kbd>Ctrl</kbd></p>
<p data-line=\\"2\\"><kbd>Ctrl</kbd></p>
"
`;
exports[`Markdown.render() should renders [TOC] placholder correctly 1`] = `
"<p data-line=\\"1\\"><div class=\\"markdownIt-TOC-wrapper\\"><ul class=\\"markdownIt-TOC\\">
<li><a href=\\"#H1\\">H1</a>
<ul>
<li><a href=\\"#H2\\">H2</a>
<ul>
<li><a href=\\"#H3\\">H3</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div></p>
<h1 id=\\"H1\\" data-line=\\"2\\">H1</h1>
<h2 id=\\"H2\\" data-line=\\"3\\">H2</h2>
<h3 id=\\"H3\\" data-line=\\"4\\">H3</h3>
<p data-line=\\"5\\">###$ H4</p>
"
`;
exports[`Markdown.render() should renders KaTeX correctly 1`] = `
"<span class=\\"katex-display\\"><span class=\\"katex\\"><span class=\\"katex-mathml\\"><math><semantics><mrow><mi>c</mi><mo>=</mo><mi>p</mi><mi>m</mi><mi>s</mi><mi>q</mi><mi>r</mi><mi>t</mi><mrow><msup><mi>a</mi><mn>2</mn></msup><mo>+</mo><msup><mi>b</mi><mn>2</mn></msup></mrow></mrow><annotation encoding=\\"application/x-tex\\">c = pmsqrt{a^2 + b^2}</annotation></semantics></math></span><span class=\\"katex-html\\" aria-hidden=\\"true\\"><span class=\\"base\\"><span class=\\"strut\\" style=\\"height:0.43056em;vertical-align:0em;\\"></span><span class=\\"mord mathdefault\\">c</span><span class=\\"mspace\\" style=\\"margin-right:0.2777777777777778em;\\"></span><span class=\\"mrel\\">=</span><span class=\\"mspace\\" style=\\"margin-right:0.2777777777777778em;\\"></span></span><span class=\\"base\\"><span class=\\"strut\\" style=\\"height:1.0585479999999998em;vertical-align:-0.19444em;\\"></span><span class=\\"mord mathdefault\\">p</span><span class=\\"mord mathdefault\\">m</span><span class=\\"mord mathdefault\\">s</span><span class=\\"mord mathdefault\\" style=\\"margin-right:0.03588em;\\">q</span><span class=\\"mord mathdefault\\" style=\\"margin-right:0.02778em;\\">r</span><span class=\\"mord mathdefault\\">t</span><span class=\\"mord\\"><span class=\\"mord\\"><span class=\\"mord mathdefault\\">a</span><span class=\\"msupsub\\"><span class=\\"vlist-t\\"><span class=\\"vlist-r\\"><span class=\\"vlist\\" style=\\"height:0.8641079999999999em;\\"><span style=\\"top:-3.113em;margin-right:0.05em;\\"><span class=\\"pstrut\\" style=\\"height:2.7em;\\"></span><span class=\\"sizing reset-size6 size3 mtight\\"><span class=\\"mord mtight\\">2</span></span></span></span></span></span></span></span><span class=\\"mspace\\" style=\\"margin-right:0.2222222222222222em;\\"></span><span class=\\"mbin\\">+</span><span class=\\"mspace\\" style=\\"margin-right:0.2222222222222222em;\\"></span><span class=\\"mord\\"><span class=\\"mord mathdefault\\">b</span><span class=\\"msupsub\\"><span class=\\"vlist-t\\"><span class=\\"vlist-r\\"><span class=\\"vlist\\" style=\\"height:0.8641079999999999em;\\"><span style=\\"top:-3.113em;margin-right:0.05em;\\"><span class=\\"pstrut\\" style=\\"height:2.7em;\\"></span><span class=\\"sizing reset-size6 size3 mtight\\"><span class=\\"mord mtight\\">2</span></span></span></span></span></span></span></span></span></span></span></span></span>
"
`;
exports[`Markdown.render() should renders abbrevations correctly 1`] = `
"<h2 id=\\"abbr\\" data-line=\\"1\\">abbr</h2>
<p data-line=\\"3\\">The <abbr title=\\"Hyper Text Markup Language\\">HTML</abbr> specification<br />
is maintained by the <abbr title=\\"World Wide Web Consortium\\">W3C</abbr>.</p>
"
`;
exports[`Markdown.render() should renders checkboxes 1`] = `
"<ul>
<li class=\\"taskListItem\\" data-line=\\"1\\"><input type=\\"checkbox\\" id=\\"checkbox-2\\" /> Unchecked</li>
<li class=\\"taskListItem checked\\" data-line=\\"2\\"><input type=\\"checkbox\\" checked id=\\"checkbox-3\\" /> Checked</li>
</ul>
"
`;
exports[`Markdown.render() should renders codeblock correctly 1`] = `
"<pre class=\\"code CodeMirror\\" data-line=\\"1\\">
<span class=\\"filename\\">filename.js</span>
<span class=\\"lineNumber CodeMirror-gutters\\"><span class=\\"CodeMirror-linenumber\\">2</span></span>
<code class=\\"js\\">var project = 'boostnote';
</code>
</pre>"
`;
exports[`Markdown.render() should renders definition lists correctly 1`] = `
"<h2 id=\\"definition-list\\" data-line=\\"1\\">definition list</h2>
<h3 id=\\"list-1\\" data-line=\\"3\\">list 1</h3>
<dl>
<dt data-line=\\"5\\">Term 1</dt>
<dd data-line=\\"6\\">Definition 1</dd>
<dt data-line=\\"8\\">Term 2</dt>
<dd data-line=\\"9\\">Definition 2a</dd>
<dd data-line=\\"10\\">Definition 2b</dd>
</dl>
<p data-line=\\"12\\">Term 3<br />
~</p>
<h3 id=\\"list-2\\" data-line=\\"16\\">list 2</h3>
<dl>
<dt data-line=\\"18\\">Term 1</dt>
<dd data-line=\\"20\\">
<p data-line=\\"20\\">Definition 1</p>
</dd>
<dt data-line=\\"22\\">Term 2 with <em>inline markup</em></dt>
<dd data-line=\\"24\\">
<p data-line=\\"24\\">Definition 2</p>
<pre><code> { some code, part of Definition 2 }
</code></pre>
<p data-line=\\"28\\">Third paragraph of definition 2.</p>
</dd>
</dl>
"
`;
exports[`Markdown.render() should renders markdown correctly 1`] = `
"<h1 id=\\"Welcome-to-Boostnote\\" data-line=\\"1\\">Welcome to Boostnote!</h1>
<h2 id=\\"Click-here-to-edit-markdown\\" data-line=\\"2\\">Click here to edit markdown 👋</h2>
<iframe width=\\"560\\" height=\\"315\\" src=\\"https://www.youtube.com/embed/L0qNPLsvmyM\\" frameborder=\\"0\\" allowfullscreen></iframe>
<h2 id=\\"Docs\\" data-line=\\"6\\">Docs 📝</h2>
<ul>
<li data-line=\\"7\\"><a href=\\"https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe\\">Boostnote | Boost your happiness, productivity and creativity.</a></li>
<li data-line=\\"8\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup\\">Cloud Syncing &amp; Backups</a></li>
<li data-line=\\"9\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps\\">How to sync your data across Desktop and Mobile apps</a></li>
<li data-line=\\"10\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Evernote\\">Convert data from <strong>Evernote</strong> to Boostnote.</a></li>
<li data-line=\\"11\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts\\">Keyboard Shortcuts</a></li>
<li data-line=\\"12\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode\\">Keymaps in Editor mode</a></li>
<li data-line=\\"13\\"><a href=\\"https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting\\">How to set syntax highlight in Snippet note</a></li>
</ul>
<hr />
<h2 id=\\"Article-Archive\\" data-line=\\"17\\">Article Archive 📚</h2>
<ul>
<li data-line=\\"18\\"><a href=\\"http://bit.ly/2mOJPu7\\">Reddit English</a></li>
<li data-line=\\"19\\"><a href=\\"https://www.reddit.com/r/boostnote_es/\\">Reddit Spanish</a></li>
<li data-line=\\"20\\"><a href=\\"https://www.reddit.com/r/boostnote_cn/\\">Reddit Chinese</a></li>
<li data-line=\\"21\\"><a href=\\"https://www.reddit.com/r/boostnote_jp/\\">Reddit Japanese</a></li>
</ul>
<hr />
<h2 id=\\"Community\\" data-line=\\"25\\">Community 🍻</h2>
<ul>
<li data-line=\\"26\\"><a href=\\"http://bit.ly/2AWWzkD\\">GitHub</a></li>
<li data-line=\\"27\\"><a href=\\"http://bit.ly/2z8BUJZ\\">Twitter</a></li>
<li data-line=\\"28\\"><a href=\\"http://bit.ly/2jcca8t\\">Facebook Group</a></li>
</ul>
"
`;
exports[`Markdown.render() should renders sub correctly 1`] = `
"<h2 id=\\"sub\\" data-line=\\"1\\">sub</h2>
<p data-line=\\"3\\">H<sub>2</sub>0</p>
"
`;
exports[`Markdown.render() should renders sup correctly 1`] = `
"<h2 id=\\"sup\\" data-line=\\"1\\">sup</h2>
<p data-line=\\"3\\">29<sup>th</sup></p>
"
`;
exports[`Markdown.render() should text with quotes correctly 1`] = `
"<p data-line=\\"0\\">This is a “QUOTE”.</p>
"
`;
exports[`Markdown.render() should text with quotes correctly 2`] = `
"<p data-line=\\"0\\">This is a &quot;QUOTE&quot;.</p>
"
`;

View File

@@ -1,46 +1,45 @@
const { escapeHtmlCharacters } = require('browser/lib/utils') const { escapeHtmlCharacters } = require('browser/lib/utils')
const test = require('ava')
test('escapeHtmlCharacters should return the original string if nothing needed to escape', t => { test('escapeHtmlCharacters should return the original string if nothing needed to escape', () => {
const input = 'Nothing to be escaped' const input = 'Nothing to be escaped'
const expected = 'Nothing to be escaped' const expected = 'Nothing to be escaped'
const actual = escapeHtmlCharacters(input) const actual = escapeHtmlCharacters(input)
t.is(actual, expected) expect(actual).toBe(expected)
}) })
test('escapeHtmlCharacters should skip code block if that option is enabled', t => { test('escapeHtmlCharacters should skip code block if that option is enabled', () => {
const input = ` <no escape> const input = ` <no escape>
<escapeMe>` <escapeMe>`
const expected = ` <no escape> const expected = ` <no escape>
&lt;escapeMe&gt;` &lt;escapeMe&gt;`
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true }) const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
t.is(actual, expected) expect(actual).toBe(expected)
}) })
test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', t => { test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', () => {
const input = '4 spaces &' const input = '4 spaces &'
const expected = '4 spaces &amp;' const expected = '4 spaces &amp;'
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true }) const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
t.is(actual, expected) expect(actual).toBe(expected)
}) })
test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', t => { test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', () => {
const input = ` <no escape> const input = ` <no escape>
<escapeMe>` <escapeMe>`
const expected = ` &lt;no escape&gt; const expected = ` &lt;no escape&gt;
&lt;escapeMe&gt;` &lt;escapeMe&gt;`
const actual = escapeHtmlCharacters(input) const actual = escapeHtmlCharacters(input)
t.is(actual, expected) expect(actual).toBe(expected)
}) })
test("escapeHtmlCharacters should NOT escape & character if it's a part of an escaped character", t => { test("escapeHtmlCharacters should NOT escape & character if it's a part of an escaped character", () => {
const input = 'Do not escape &amp; or &quot; but do escape &' const input = 'Do not escape &amp; or &quot; but do escape &'
const expected = 'Do not escape &amp; or &quot; but do escape &amp;' const expected = 'Do not escape &amp; or &quot; but do escape &amp;'
const actual = escapeHtmlCharacters(input) const actual = escapeHtmlCharacters(input)
t.is(actual, expected) expect(actual).toBe(expected)
}) })
test('escapeHtmlCharacters should skip char if in code block', t => { test('escapeHtmlCharacters should skip char if in code block', () => {
const input = ` const input = `
\`\`\` \`\`\`
<dontescapeme> <dontescapeme>
@@ -62,12 +61,12 @@ dasdasdasd
\`\`\` \`\`\`
` `
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true }) const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
t.is(actual, expected) expect(actual).toBe(expected)
}) })
test('escapeHtmlCharacters should return the correct result', t => { test('escapeHtmlCharacters should return the correct result', () => {
const input = '& < > " \'' const input = '& < > " \''
const expected = '&amp; &lt; &gt; &quot; &#39;' const expected = '&amp; &lt; &gt; &quot; &#39;'
const actual = escapeHtmlCharacters(input) const actual = escapeHtmlCharacters(input)
t.is(actual, expected) expect(actual).toBe(expected)
}) })

View File

@@ -1,4 +1,3 @@
const test = require('ava')
const { findStorage } = require('browser/lib/findStorage') const { findStorage } = require('browser/lib/findStorage')
global.document = require('jsdom').jsdom('<body></body>') global.document = require('jsdom').jsdom('<body></body>')
@@ -16,20 +15,22 @@ const sander = require('sander')
const os = require('os') const os = require('os')
const storagePath = path.join(os.tmpdir(), 'test/find-storage') const storagePath = path.join(os.tmpdir(), 'test/find-storage')
test.beforeEach(t => { let storageContext
t.context.storage = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) beforeEach(() => {
storageContext = TestDummy.dummyStorage(storagePath)
localStorage.setItem('storages', JSON.stringify([storageContext.cache]))
}) })
// Unit test // Unit test
test('findStorage() should return a correct storage path(string)', t => { test('findStorage() should return a correct storage path(string)', () => {
const storageKey = t.context.storage.cache.key const storageKey = storageContext.cache.key
t.is(findStorage(storageKey).key, storageKey) expect(findStorage(storageKey).key).toBe(storageKey)
t.is(findStorage(storageKey).path, storagePath) expect(findStorage(storageKey).path).toBe(storagePath)
}) })
test.after(function after() { afterAll(function after() {
localStorage.clear() localStorage.clear()
sander.rimrafSync(storagePath) sander.rimrafSync(storagePath)
}) })

View File

@@ -2,11 +2,10 @@
* @fileoverview Unit test for browser/lib/findTitle * @fileoverview Unit test for browser/lib/findTitle
*/ */
const test = require('ava')
const { findNoteTitle } = require('browser/lib/findNoteTitle') const { findNoteTitle } = require('browser/lib/findNoteTitle')
// Unit test // Unit test
test('findNoteTitle#find should return a correct title (string)', t => { test('findNoteTitle#find should return a correct title (string)', () => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
['# hoge\nfuga', '# hoge'], ['# hoge\nfuga', '# hoge'],
@@ -20,15 +19,11 @@ test('findNoteTitle#find should return a correct title (string)', t => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(findNoteTitle(input, false)).toBe(expected)
findNoteTitle(input, false),
expected,
`Test for find() input: ${input} expected: ${expected}`
)
}) })
}) })
test('findNoteTitle#find should ignore front matter when enableFrontMatterTitle=false', t => { test('findNoteTitle#find should ignore front matter when enableFrontMatterTitle=false', () => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
['---\nlayout: test\ntitle: hoge hoge hoge \n---\n# fuga', '# fuga'], ['---\nlayout: test\ntitle: hoge hoge hoge \n---\n# fuga', '# fuga'],
@@ -38,15 +33,11 @@ test('findNoteTitle#find should ignore front matter when enableFrontMatterTitle
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(findNoteTitle(input, false)).toBe(expected)
findNoteTitle(input, false),
expected,
`Test for find() input: ${input} expected: ${expected}`
)
}) })
}) })
test('findNoteTitle#find should respect front matter when enableFrontMatterTitle=true', t => { test('findNoteTitle#find should respect front matter when enableFrontMatterTitle=true', () => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
[ [
@@ -59,15 +50,11 @@ test('findNoteTitle#find should respect front matter when enableFrontMatterTitl
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(findNoteTitle(input, true)).toBe(expected)
findNoteTitle(input, true),
expected,
`Test for find() input: ${input} expected: ${expected}`
)
}) })
}) })
test('findNoteTitle#find should respect frontMatterTitleField when provided', t => { test('findNoteTitle#find should respect frontMatterTitleField when provided', () => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
['---\ntitle: hoge\n---\n# fuga', '# fuga'], ['---\ntitle: hoge\n---\n# fuga', '# fuga'],
@@ -76,10 +63,6 @@ test('findNoteTitle#find should respect frontMatterTitleField when provided', t
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(findNoteTitle(input, true, 'custom')).toBe(expected)
findNoteTitle(input, true, 'custom'),
expected,
`Test for find() input: ${input} expected: ${expected}`
)
}) })
}) })

View File

@@ -1,8 +1,7 @@
const test = require('ava')
const { getTodoStatus } = require('browser/lib/getTodoStatus') const { getTodoStatus } = require('browser/lib/getTodoStatus')
// Unit test // Unit test
test('getTodoStatus should return a correct hash object', t => { test('getTodoStatus should return a correct hash object', () => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
['', { total: 0, completed: 0 }], ['', { total: 0, completed: 0 }],
@@ -40,15 +39,7 @@ test('getTodoStatus should return a correct hash object', t => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(getTodoStatus(input).total).toBe(expected.total)
getTodoStatus(input).total, expect(getTodoStatus(input).completed).toBe(expected.completed)
expected.total,
`Test for getTodoStatus() input: ${input} expected: ${expected.total}`
)
t.is(
getTodoStatus(input).completed,
expected.completed,
`Test for getTodoStatus() input: ${input} expected: ${expected.completed}`
)
}) })
}) })

View File

@@ -1,11 +1,10 @@
/** /**
* @fileoverview Unit test for browser/lib/htmlTextHelper * @fileoverview Unit test for browser/lib/htmlTextHelper
*/ */
const test = require('ava')
const htmlTextHelper = require('browser/lib/htmlTextHelper') const htmlTextHelper = require('browser/lib/htmlTextHelper')
// Unit test // Unit test
test('htmlTextHelper#decodeEntities should return encoded text (string)', t => { test('htmlTextHelper#decodeEntities should return encoded text (string)', () => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
['&lt;a href=', '<a href='], ['&lt;a href=', '<a href='],
@@ -21,15 +20,11 @@ test('htmlTextHelper#decodeEntities should return encoded text (string)', t => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(htmlTextHelper.decodeEntities(input)).toBe(expected)
htmlTextHelper.decodeEntities(input),
expected,
`Test for decodeEntities() input: ${input} expected: ${expected}`
)
}) })
}) })
test('htmlTextHelper#decodeEntities() should return decoded text (string)', t => { test('htmlTextHelper#decodeEntities() should return decoded text (string)', () => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
['<a href=', '&lt;a href='], ['<a href=', '&lt;a href='],
@@ -44,16 +39,12 @@ test('htmlTextHelper#decodeEntities() should return decoded text (string)', t =>
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(htmlTextHelper.encodeEntities(input)).toBe(expected)
htmlTextHelper.encodeEntities(input),
expected,
`Test for encodeEntities() input: ${input} expected: ${expected}`
)
}) })
}) })
// Integration test // Integration test
test(t => { test(() => {
const testCases = [ const testCases = [
"var test = 'test'", "var test = 'test'",
"<a href='https://boostnote.io'>Boostnote", "<a href='https://boostnote.io'>Boostnote",
@@ -63,10 +54,6 @@ test(t => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
const encodedText = htmlTextHelper.encodeEntities(testCase) const encodedText = htmlTextHelper.encodeEntities(testCase)
const decodedText = htmlTextHelper.decodeEntities(encodedText) const decodedText = htmlTextHelper.decodeEntities(encodedText)
t.is( expect(decodedText).toBe(testCase)
decodedText,
testCase,
'Integration test through encodedText() and decodedText()'
)
}) })
}) })

View File

@@ -1,10 +1,9 @@
/** /**
* @fileoverview Unit test for browser/lib/markdown * @fileoverview Unit test for browser/lib/markdown
*/ */
const test = require('ava')
const markdown = require('browser/lib/markdownTextHelper') const markdown = require('browser/lib/markdownTextHelper')
test(t => { test(() => {
// [input, expected] // [input, expected]
const testCases = [ const testCases = [
// List // List
@@ -42,10 +41,6 @@ test(t => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is( expect(markdown.strip(input)).toBe(expected)
markdown.strip(input),
expected,
`Test for strip() input: ${input} expected: ${expected}`
)
}) })
}) })

View File

@@ -4,11 +4,10 @@
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
require('codemirror/addon/search/searchcursor.js') require('codemirror/addon/search/searchcursor.js')
const test = require('ava')
const markdownToc = require('browser/lib/markdown-toc-generator') const markdownToc = require('browser/lib/markdown-toc-generator')
const EOL = require('os').EOL const EOL = require('os').EOL
test(t => { test(() => {
/** /**
* Contains array of test cases in format : * Contains array of test cases in format :
* [ * [
@@ -261,15 +260,11 @@ this is a text
const expectedToc = testCase[2].trim() const expectedToc = testCase[2].trim()
const generatedToc = markdownToc.generate(inputMd) const generatedToc = markdownToc.generate(inputMd)
t.is( expect(generatedToc).toBe(expectedToc)
generatedToc,
expectedToc,
`generate test : ${title} , generated : ${EOL}${generatedToc}, expected : ${EOL}${expectedToc}`
)
}) })
}) })
test(t => { test(() => {
/** /**
* Contains array of test cases in format : * Contains array of test cases in format :
* [ * [
@@ -667,10 +662,6 @@ this is a level one text
editor.setCursor(cursor) editor.setCursor(cursor)
markdownToc.generateInEditor(editor) markdownToc.generateInEditor(editor)
t.is( expect(expectedMd).toBe(editor.getValue())
expectedMd,
editor.getValue(),
`generateInEditor test : ${title} , generated : ${EOL}${editor.getValue()}, expected : ${EOL}${expectedMd}`
)
}) })
}) })

View File

@@ -1,4 +1,17 @@
import test from 'ava' jest.mock(
'electron',
() => {
return {
remote: {
app: {
getPath: jest.fn().mockReturnValue('.')
}
}
}
},
{ virtual: true }
)
import Markdown from 'browser/lib/markdown' import Markdown from 'browser/lib/markdown'
import markdownFixtures from '../fixtures/markdowns' import markdownFixtures from '../fixtures/markdowns'
@@ -6,100 +19,100 @@ import markdownFixtures from '../fixtures/markdowns'
// To test markdown options, initialize a new instance in your test case // To test markdown options, initialize a new instance in your test case
const md = new Markdown() const md = new Markdown()
test('Markdown.render() should renders markdown correctly', t => { test('Markdown.render() should renders markdown correctly', () => {
const rendered = md.render(markdownFixtures.basic) const rendered = md.render(markdownFixtures.basic)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should renders codeblock correctly', t => { test('Markdown.render() should renders codeblock correctly', () => {
const rendered = md.render(markdownFixtures.codeblock) const rendered = md.render(markdownFixtures.codeblock)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should renders KaTeX correctly', t => { test('Markdown.render() should renders KaTeX correctly', () => {
const rendered = md.render(markdownFixtures.katex) const rendered = md.render(markdownFixtures.katex)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should renders checkboxes', t => { test('Markdown.render() should renders checkboxes', () => {
const rendered = md.render(markdownFixtures.checkboxes) const rendered = md.render(markdownFixtures.checkboxes)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should text with quotes correctly', t => { test('Markdown.render() should text with quotes correctly', () => {
const renderedSmartQuotes = md.render(markdownFixtures.smartQuotes) const renderedSmartQuotes = md.render(markdownFixtures.smartQuotes)
t.snapshot(renderedSmartQuotes) expect(renderedSmartQuotes).toMatchSnapshot()
const newmd = new Markdown({ typographer: false }) const newmd = new Markdown({ typographer: false })
const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes) const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes)
t.snapshot(renderedNonSmartQuotes) expect(renderedNonSmartQuotes).toMatchSnapshot()
}) })
test('Markdown.render() should render line breaks correctly', t => { test('Markdown.render() should render line breaks correctly', () => {
const renderedBreaks = md.render(markdownFixtures.breaks) const renderedBreaks = md.render(markdownFixtures.breaks)
t.snapshot(renderedBreaks) expect(renderedBreaks).toMatchSnapshot()
const newmd = new Markdown({ breaks: false }) const newmd = new Markdown({ breaks: false })
const renderedNonBreaks = newmd.render(markdownFixtures.breaks) const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
t.snapshot(renderedNonBreaks) expect(renderedNonBreaks).toMatchSnapshot()
}) })
test('Markdown.render() should renders abbrevations correctly', t => { test('Markdown.render() should renders abbrevations correctly', () => {
const rendered = md.render(markdownFixtures.abbrevations) const rendered = md.render(markdownFixtures.abbrevations)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should renders sub correctly', t => { test('Markdown.render() should renders sub correctly', () => {
const rendered = md.render(markdownFixtures.subTexts) const rendered = md.render(markdownFixtures.subTexts)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should renders sup correctly', t => { test('Markdown.render() should renders sup correctly', () => {
const rendered = md.render(markdownFixtures.supTexts) const rendered = md.render(markdownFixtures.supTexts)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should renders definition lists correctly', t => { test('Markdown.render() should renders definition lists correctly', () => {
const rendered = md.render(markdownFixtures.deflists) const rendered = md.render(markdownFixtures.deflists)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should render shortcuts correctly', t => { test('Markdown.render() should render shortcuts correctly', () => {
const rendered = md.render(markdownFixtures.shortcuts) const rendered = md.render(markdownFixtures.shortcuts)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should render footnote correctly', t => { test('Markdown.render() should render footnote correctly', () => {
const rendered = md.render(markdownFixtures.footnote) const rendered = md.render(markdownFixtures.footnote)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should renders [TOC] placholder correctly', t => { test('Markdown.render() should renders [TOC] placholder correctly', () => {
const rendered = md.render(markdownFixtures.tocPlaceholder) const rendered = md.render(markdownFixtures.tocPlaceholder)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should render PlantUML MindMaps correctly', t => { test('Markdown.render() should render PlantUML MindMaps correctly', () => {
const rendered = md.render(markdownFixtures.plantUmlMindMap) const rendered = md.render(markdownFixtures.plantUmlMindMap)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should render PlantUML Gantt correctly', t => { test('Markdown.render() should render PlantUML Gantt correctly', () => {
const rendered = md.render(markdownFixtures.plantUmlGantt) const rendered = md.render(markdownFixtures.plantUmlGantt)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should render PlantUML WBS correctly', t => { test('Markdown.render() should render PlantUML WBS correctly', () => {
const rendered = md.render(markdownFixtures.plantUmlWbs) const rendered = md.render(markdownFixtures.plantUmlWbs)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should render PlantUML Umls correctly', t => { test('Markdown.render() should render PlantUML Umls correctly', () => {
const rendered = md.render(markdownFixtures.plantUmlUml) const rendered = md.render(markdownFixtures.plantUmlUml)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })
test('Markdown.render() should render PlantUML Ditaa correctly', t => { test('Markdown.render() should render PlantUML Ditaa correctly', () => {
const rendered = md.render(markdownFixtures.plantUmlDitaa) const rendered = md.render(markdownFixtures.plantUmlDitaa)
t.snapshot(rendered) expect(rendered).toMatchSnapshot()
}) })

View File

@@ -1,19 +1,17 @@
/** /**
* @fileoverview Unit test for browser/lib/normalizeEditorFontFamily * @fileoverview Unit test for browser/lib/normalizeEditorFontFamily
*/ */
import test from 'ava'
import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily' import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily'
import consts from '../../browser/lib/consts' import consts from '../../browser/lib/consts'
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
test('normalizeEditorFontFamily() should return default font family (string[])', t => { test('normalizeEditorFontFamily() should return default font family (string[])', () => {
t.is(normalizeEditorFontFamily(), defaultEditorFontFamily.join(', ')) expect(normalizeEditorFontFamily()).toBe(defaultEditorFontFamily.join(', '))
}) })
test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', t => { test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', () => {
const arg = 'font1, font2' const arg = 'font1, font2'
t.is( expect(normalizeEditorFontFamily(arg)).toBe(
normalizeEditorFontFamily(arg),
`${arg}, ${defaultEditorFontFamily.join(', ')}` `${arg}, ${defaultEditorFontFamily.join(', ')}`
) )
}) })

View File

@@ -1,9 +1,8 @@
const test = require('ava')
const path = require('path') const path = require('path')
const { parse } = require('browser/lib/RcParser') const { parse } = require('browser/lib/RcParser')
// Unit test // Unit test
test('RcParser should return a json object', t => { test('RcParser should return a json object', () => {
const validJson = { const validJson = {
editor: { keyMap: 'vim', switchPreview: 'BLUR', theme: 'monokai' }, editor: { keyMap: 'vim', switchPreview: 'BLUR', theme: 'monokai' },
hotkey: { toggleMain: 'Control + L' }, hotkey: { toggleMain: 'Control + L' },
@@ -51,20 +50,12 @@ test('RcParser should return a json object', t => {
validTestCases.forEach(validTestCase => { validTestCases.forEach(validTestCase => {
const [input, expected] = validTestCase const [input, expected] = validTestCase
t.is( expect(parse(filePath(input)).editor.keyMap).toBe(expected.editor.keyMap)
parse(filePath(input)).editor.keyMap,
expected.editor.keyMap,
`Test for getTodoStatus() input: ${input} expected: ${expected.keyMap}`
)
}) })
invalidTestCases.forEach(invalidTestCase => { invalidTestCases.forEach(invalidTestCase => {
const [input, expected] = invalidTestCase const [input, expected] = invalidTestCase
t.is( expect(parse(filePath(input)).editor).toBe(expected.editor)
parse(filePath(input)).editor,
expected.editor,
`Test for getTodoStatus() input: ${input} expected: ${expected.editor}`
)
}) })
}) })

View File

@@ -1,4 +1,3 @@
import test from 'ava'
import searchFromNotes from 'browser/lib/search' import searchFromNotes from 'browser/lib/search'
import { dummyNote } from '../fixtures/TestDummy' import { dummyNote } from '../fixtures/TestDummy'
import _ from 'lodash' import _ from 'lodash'
@@ -11,7 +10,7 @@ const pickContents = notes =>
let notes = [] let notes = []
let note1, note2, note3 let note1, note2, note3
test.before(t => { beforeAll(() => {
const data1 = { type: 'MARKDOWN_NOTE', content: 'content1', tags: ['tag1'] } const data1 = { type: 'MARKDOWN_NOTE', content: 'content1', tags: ['tag1'] }
const data2 = { const data2 = {
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
@@ -27,7 +26,7 @@ test.before(t => {
notes = [note1, note2, note3] notes = [note1, note2, note3]
}) })
test('it can find notes by tags and words', t => { test('it can find notes by tags and words', () => {
// [input, expected content (Array)] // [input, expected content (Array)]
const testWithTags = [ const testWithTags = [
['#tag1', [note1.content, note2.content, note3.content]], ['#tag1', [note1.content, note2.content, note3.content]],
@@ -49,6 +48,8 @@ test('it can find notes by tags and words', t => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expectedContents] = testCase const [input, expectedContents] = testCase
const results = searchFromNotes(notes, input) const results = searchFromNotes(notes, input)
t.true(_.isEqual(pickContents(results).sort(), expectedContents.sort())) expect(
_.isEqual(pickContents(results).sort(), expectedContents.sort())
).toBe(true)
}) })
}) })

View File

@@ -1,58 +1,57 @@
import test from 'ava'
import slugify from 'browser/lib/slugify' import slugify from 'browser/lib/slugify'
test('alphabet and digit', t => { test('alphabet and digit', () => {
const upperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' const upperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const lowerAlphabet = 'abcdefghijklmnopqrstuvwxyz' const lowerAlphabet = 'abcdefghijklmnopqrstuvwxyz'
const digit = '0123456789' const digit = '0123456789'
const testCase = upperAlphabet + lowerAlphabet + digit const testCase = upperAlphabet + lowerAlphabet + digit
const decodeSlug = decodeURI(slugify(testCase)) const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === testCase) expect(decodeSlug === testCase).toBe(true)
}) })
test('should delete unavailable symbols', t => { test('should delete unavailable symbols', () => {
const availableSymbols = '_-' const availableSymbols = '_-'
const testCase = availableSymbols + "][!'#$%&()*+,./:;<=>?@\\^{|}~`" const testCase = availableSymbols + "][!'#$%&()*+,./:;<=>?@\\^{|}~`"
const decodeSlug = decodeURI(slugify(testCase)) const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === availableSymbols) expect(decodeSlug === availableSymbols).toBe(true)
}) })
test('should convert from white spaces between words to hyphens', t => { test('should convert from white spaces between words to hyphens', () => {
const testCase = 'This is one' const testCase = 'This is one'
const expectedString = 'This-is-one' const expectedString = 'This-is-one'
const decodeSlug = decodeURI(slugify(testCase)) const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === expectedString) expect(decodeSlug === expectedString).toBe(true)
}) })
test('should remove leading white spaces', t => { test('should remove leading white spaces', () => {
const testCase = ' This is one' const testCase = ' This is one'
const expectedString = 'This-is-one' const expectedString = 'This-is-one'
const decodeSlug = decodeURI(slugify(testCase)) const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === expectedString) expect(decodeSlug === expectedString).toBe(true)
}) })
test('should remove trailing white spaces', t => { test('should remove trailing white spaces', () => {
const testCase = 'This is one ' const testCase = 'This is one '
const expectedString = 'This-is-one' const expectedString = 'This-is-one'
const decodeSlug = decodeURI(slugify(testCase)) const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === expectedString) expect(decodeSlug === expectedString).toBe(true)
}) })
test('2-byte charactor support', t => { test('2-byte charactor support', () => {
const testCase = '菠萝芒果テストÀžƁƵ' const testCase = '菠萝芒果テストÀžƁƵ'
const decodeSlug = decodeURI(slugify(testCase)) const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === testCase) expect(decodeSlug === testCase).toBe(true)
}) })
test('emoji', t => { test('emoji', () => {
const testCase = '🌸' const testCase = '🌸'
const decodeSlug = decodeURI(slugify(testCase)) const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === testCase) expect(decodeSlug === testCase).toBe(true)
}) })

112
yarn.lock
View File

@@ -1966,6 +1966,11 @@ combined-stream@1.0.6, combined-stream@~1.0.5:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
command-exists@^1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
commander@2: commander@2:
version "2.16.0" version "2.16.0"
resolved "http://registry.npm.taobao.org/commander/download/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50" resolved "http://registry.npm.taobao.org/commander/download/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
@@ -2583,7 +2588,44 @@ d3-zoom@1:
d3-selection "1" d3-selection "1"
d3-transition "1" d3-transition "1"
d3@^5.12, d3@^5.7.0: d3@^5.14:
version "5.16.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877"
integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==
dependencies:
d3-array "1"
d3-axis "1"
d3-brush "1"
d3-chord "1"
d3-collection "1"
d3-color "1"
d3-contour "1"
d3-dispatch "1"
d3-drag "1"
d3-dsv "1"
d3-ease "1"
d3-fetch "1"
d3-force "1"
d3-format "1"
d3-geo "1"
d3-hierarchy "1"
d3-interpolate "1"
d3-path "1"
d3-polygon "1"
d3-quadtree "1"
d3-random "1"
d3-scale "2"
d3-scale-chromatic "1"
d3-selection "1"
d3-shape "1"
d3-time "1"
d3-time-format "2"
d3-timer "1"
d3-transition "1"
d3-voronoi "1"
d3-zoom "1"
d3@^5.7.0:
version "5.12.0" version "5.12.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.12.0.tgz#0ddeac879c28c882317cd439b495290acd59ab61" resolved "https://registry.yarnpkg.com/d3/-/d3-5.12.0.tgz#0ddeac879c28c882317cd439b495290acd59ab61"
integrity sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg== integrity sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==
@@ -2626,13 +2668,14 @@ d@1:
dependencies: dependencies:
es5-ext "^0.10.9" es5-ext "^0.10.9"
dagre-d3@dagrejs/dagre-d3: dagre-d3@^0.6.4:
version "0.6.4-pre" version "0.6.4"
resolved "https://codeload.github.com/dagrejs/dagre-d3/tar.gz/e1a00e5cb518f5d2304a35647e024f31d178e55b" resolved "https://registry.yarnpkg.com/dagre-d3/-/dagre-d3-0.6.4.tgz#0728d5ce7f177ca2337df141ceb60fbe6eeb7b29"
integrity sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==
dependencies: dependencies:
d3 "^5.12" d3 "^5.14"
dagre "^0.8.4" dagre "^0.8.5"
graphlib "^2.1.7" graphlib "^2.1.8"
lodash "^4.17.15" lodash "^4.17.15"
dagre@^0.8.4: dagre@^0.8.4:
@@ -2643,6 +2686,14 @@ dagre@^0.8.4:
graphlib "^2.1.7" graphlib "^2.1.7"
lodash "^4.17.4" lodash "^4.17.4"
dagre@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee"
integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==
dependencies:
graphlib "^2.1.8"
lodash "^4.17.15"
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -3130,6 +3181,13 @@ entities@^1.1.1, entities@~1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
entity-decode@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/entity-decode/-/entity-decode-2.0.2.tgz#e4f807e52c3294246e9347d1f2b02b07fd5f92e7"
integrity sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==
dependencies:
he "^1.1.1"
env-paths@^1.0.0: env-paths@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
@@ -4302,6 +4360,13 @@ graphlib@^2.1.7:
dependencies: dependencies:
lodash "^4.17.5" lodash "^4.17.5"
graphlib@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
dependencies:
lodash "^4.17.15"
gray-matter@^2.1.0: gray-matter@^2.1.0:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-2.1.1.tgz#3042d9adec2a1ded6a7707a9ed2380f8a17a430e" resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-2.1.1.tgz#3042d9adec2a1ded6a7707a9ed2380f8a17a430e"
@@ -4490,7 +4555,7 @@ has@^1.0.1:
dependencies: dependencies:
function-bind "^1.0.2" function-bind "^1.0.2"
he@^1.2.0: he@^1.1.1, he@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -6108,15 +6173,10 @@ lodash.uniq@^4.5.0:
version "4.5.0" version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@^4.0.0, lodash@^4.0.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1: lodash@^4.0.0, lodash@^4.0.1, lodash@^4.12.0, lodash@^4.13.0, lodash@^4.13.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
version "4.17.13" version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA== integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
lodash@^4.13.0, lodash@^4.17.11, lodash@^4.17.15:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
lodash@~0.9.2: lodash@~0.9.2:
version "0.9.2" version "0.9.2"
@@ -6400,22 +6460,21 @@ merge@^1.1.3:
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
mermaid@^8.4.2: mermaid@^8.5.2:
version "8.4.2" version "8.5.2"
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.4.2.tgz#91d3d8e9541e72eed7a78d0e882db11564fab3bb" resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.5.2.tgz#0f1914cda53d4ea5377380e5ce07a38bef2ea7e8"
integrity sha512-vYSCP2u4XkOnjliWz/QIYwvzF/znQAq22vWJJ3YV40SnwV2JQyHblnwwNYXCprkXw7XfwBKDpSNaJ3HP4WfnZw== integrity sha512-I+s+8/RzlazF3dGOhDUfU/ERkUV4zfIlTWb3703jNx+2lfACs+4AdY9ULQaw6BPWzW3gB+XlXFOOX/m/vqujIA==
dependencies: dependencies:
"@braintree/sanitize-url" "^3.1.0" "@braintree/sanitize-url" "^3.1.0"
crypto-random-string "^3.0.1" crypto-random-string "^3.0.1"
d3 "^5.7.0" d3 "^5.7.0"
dagre "^0.8.4" dagre "^0.8.4"
dagre-d3 dagrejs/dagre-d3 dagre-d3 "^0.6.4"
entity-decode "^2.0.2"
graphlib "^2.1.7" graphlib "^2.1.7"
he "^1.2.0" he "^1.2.0"
lodash "^4.17.11"
minify "^4.1.1" minify "^4.1.1"
moment-mini "^2.22.1" moment-mini "^2.22.1"
prettier "^1.18.2"
scope-css "^1.2.1" scope-css "^1.2.1"
methods@~1.1.2: methods@~1.1.2:
@@ -10027,8 +10086,9 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1" websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1: websocket-extensions@>=0.1.1:
version "0.1.3" version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
well-known-symbols@^1.0.0: well-known-symbols@^1.0.0:
version "1.0.0" version "1.0.0"