1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 18:26:26 +00:00

Compare commits

..

98 Commits

Author SHA1 Message Date
Junyoung Choi
d010c5532d v0.13.0 2019-10-16 20:18:47 +09:00
hikerpig
f2dc8b8020 optimize: npm 'test' script should contain jest tests 2019-10-15 13:24:44 +09:00
Junyoung Choi
1798353eac Merge pull request #3173 from hikerpig/feature/toc
Suppport auto generating toc content for the [TOC] tag
2019-10-15 13:24:30 +09:00
Kazumasa Yokomizo
772a8b2383 Updated the readme 2019-10-14 13:34:27 +09:00
hikerpig
5690c8361a 🔥 remove obsolete snap file 2019-10-11 10:41:38 +08:00
hikerpig
6d09cf227c Merge remote-tracking branch 'origin/master' into feature/toc
Fix conflicts
2019-10-10 18:23:39 +08:00
Junyoung Choi
8736666e91 Fix default prettier hotkey 2019-10-10 18:01:27 +09:00
Junyoung Choi
d1fd5cfb45 Set prettier external deps 2019-10-10 18:01:09 +09:00
hikerpig
3eabf95fb3 optimize implementation for a0c15182 2019-10-10 16:34:28 +09:00
hikerpig
8ea920ef91 fix: Can't open external browser in Markdown Preview with external link containing '#', close #3213 2019-10-10 16:34:28 +09:00
jhdcruz
3c0f20f364 Single Instance fix #3241
Fixes single instance depreciation
2019-10-10 16:33:21 +09:00
alwxkxk
59f8425c97 fix #2935 2019-10-10 16:29:55 +09:00
hikerpig
f181a7e459 more strict regex pattern in pathname matching, fix #3183 2019-10-10 16:29:27 +09:00
MSSandroid
6b1c595f87 add test for PlantUml Ditaa 2019-10-10 16:26:57 +09:00
MSSandroid
0003de8f08 remove code redundancy in parsing of PlantUml 2019-10-10 16:26:57 +09:00
MSSandroid
5357d8dc04 remove code redundancy in parsing of PlantUml 2019-10-10 16:26:57 +09:00
MSSandroid
d069722bf9 require(\'markdown-it-plantuml\') only once 2019-10-10 16:26:57 +09:00
MSSandroid
3f4dd49a8f added missing newline at end of document 2019-10-10 16:26:57 +09:00
Michael Schuldes
be06b3f7e8 Added plantUML Gantt support 2019-10-10 16:26:57 +09:00
Michael Schuldes
5044bdda00 Added plantUML wbs support 2019-10-10 16:26:57 +09:00
Michael Schuldes
fbeffb0b5d Added plantUML mindmap support 2019-10-10 16:26:57 +09:00
Olcod
ef0af39aa7 Added package-lock file to the gitignore 2019-10-10 16:13:45 +09:00
Olcod
0697bc0a74 Add ability to sort lines with a hot key combination 2019-10-10 16:13:45 +09:00
Aleksei Seletskiy
43d8ebb3c4 Dracula theme buttons in storage settings fix 2019-09-14 13:20:50 +09:00
Robert Weber
68175cd71b Add sidebar collapse button to sidebar while viewing the tags list
Fixes #2097
2019-09-14 13:20:37 +09:00
amedora
f4d87f64ae Fix #3190 - App blanks out after setting HotKey (#3193)
* fix lack of hotkey properties

* Update HotkeyTab.js
2019-09-03 02:24:09 +09:00
Junyoung Choi
68b3077651 Merge pull request #3099 from AWolf81/html-to-md
Html to md feature
2019-09-03 02:03:51 +09:00
Junyoung Choi
1332b337f3 Merge pull request #3136 from hikerpig/feature/scrollbarAppearance
tweak MarkdownPreview style to optimize overflow scrollbar display
2019-09-03 02:03:23 +09:00
hikerpig
e9975d1ea5 fix: HotkeyTab accidentally set incomplete hotkey, related #3190 2019-09-03 01:59:59 +09:00
Thamara Andrade
2c103aca3d Fix #888 - Wrong word count due splitting 2019-09-03 01:54:53 +09:00
Thamara Andrade
c0a5eb0d2b Fix #888 - Wrong word count due splitting 2019-09-03 01:54:53 +09:00
霸气千秋
ff7c4495f0 format locale files 2019-09-03 01:53:45 +09:00
minbaby.zhang
35fe639cd2 optimize translate 2019-09-03 01:53:45 +09:00
minbaby.zhang
59add8982e update lang 2019-09-03 01:53:45 +09:00
minbaby.zhang
8d9c514097 update lang 2019-09-03 01:53:45 +09:00
minbaby.zhang
6f880d0f02 更新翻译 2019-09-03 01:53:45 +09:00
AWolf81
ec47ee8110 Remove manual script tag filter and use turndown remove filter 2019-08-31 21:35:09 +02:00
Nguyễn Việt Hưng
28b8141c6b fixed test 2019-08-30 12:46:40 +09:00
Nguyễn Việt Hưng
5b0b309c49 added test for getAttachmentsPathAndStatus 2019-08-30 12:46:40 +09:00
Nguyễn Việt Hưng
0b84a372f6 re-organize attachment functions and updated comments doc 2019-08-30 12:46:40 +09:00
Nguyễn Việt Hưng
8355e1e006 updated function name and return type 2019-08-30 12:46:40 +09:00
Nguyễn Việt Hưng
c7d33fbd83 Allow user to view attachments and clear unused attachments 2019-08-30 12:46:40 +09:00
ehhc
cf324d93fe Add option to disable the automiatic deletion of un-referenced attachments -> might fix #3203 2019-08-30 12:46:40 +09:00
hikerpig
9a704a2bcb Merge branch 'master' into feature/scrollbarAppearance 2019-08-26 10:38:26 +08:00
hikerpig
1e00651541 feature/toc: upgrade "@hikerpig/markdown-it-toc-and-anchor" package to avoid default anchor lowercase casting 2019-08-25 18:25:17 +08:00
AWolf81
857e75594d Disable Javascript for printout window 2019-08-24 13:43:36 +09:00
AWolf81
2f1dadfc3e Change drag disable styles to be more specific 2019-08-24 13:43:14 +09:00
AWolf81
7d0404657e Fix routing for tag filtering 2019-08-24 13:42:09 +09:00
Junyoung Choi
b9dd651fc1 Merge pull request #3093 from nathan-castlehow/feat-run-prettier-on-markdown
Feat run prettier on markdown
2019-08-24 13:41:41 +09:00
hikerpig
25ef456af2 feat: should scroll to top after selecting another note, also fix #3023 2019-08-24 13:39:28 +09:00
hikerpig
084decaa85 improvement: MarkdownPreview, rewriteIframe attempt can be combined to one call 2019-08-24 13:39:28 +09:00
hikerpig
330a444fc5 optimize: should highlight any non-empty search query, fix #3164 2019-08-24 13:38:28 +09:00
alwxkxk
a47dac2854 fix #3159 2019-08-24 13:38:09 +09:00
Ronald Walker
08070f3e2d fix #3144 2019-08-24 13:37:43 +09:00
hikerpig
2352c78cb6 Add CodeEditor::setLineContent method to manipulate line contents, related #3163
Avoid changing all CodeMirror instance's contents
2019-08-24 13:36:56 +09:00
Antonio Cangiano
6ef9c3865f Fix JavaScript hello world example
The current snippet note example references a non-existent element with id `enjoy`. I updated it to reference the correct id (i.e., `hello`).
2019-08-24 13:36:29 +09:00
sirrah23
ff9789b5a7 Fix 3060
Right now there are only two export types that are using a special
output formatter, pdf and html. Both of these formatters currently populate the
`/html/head/base` portion of the associated html document with the name
of the target directory for the file that the user is exporting.

In order for internal links within the exported document to work
correctly, the value of base must also include the filename. This fix
removes the call to `path.dirname`, which gets rid of the necessary
filename.
2019-08-24 13:36:05 +09:00
Jack Hsieh
f09297f406 Fix 2636 (#3206)
* Fix 2636 Can't scroll to bottom of editor pane

* Fix minor lint issues
2019-08-11 23:22:53 +09:00
nathan-castlehow
2d3c69d178 Fixed eslint issue 2019-08-01 20:13:46 +08:00
nathan-castlehow
b837653cf1 Merged Master into feature branch and fixed conflicts 2019-08-01 20:12:58 +08:00
nathan-castlehow
eeca031c86 Merge upstream into master 2019-08-01 19:56:38 +08:00
nathan-castlehow
918a8627e9 Merge upstream into master 2019-08-01 19:55:21 +08:00
nathan-castlehow
86370edd1e Merge branch 'feat-run-prettier-on-markdown' of https://github.com/nathan-castlehow/Boostnote into feat-run-prettier-on-markdown 2019-08-01 18:36:40 +08:00
nathan-castlehow
1173631255 feat(prettierOnMarkdown): Forced prettier options to always have parser set to markdown when used. 2019-08-01 18:36:22 +08:00
nathan-castlehow
911fd9a004 feat(prettierOnMarkdown): Changed Prettier require to use import 2019-08-01 18:36:21 +08:00
nathan-castlehow
0ad3da5bbc feat(prettierOnMarkdown): Changed default hotkey value 2019-08-01 18:36:21 +08:00
nathan-castlehow
89ae2a9516 feat(prettierOnMarkdown):Fixed incorrect options passed to code mirror instance 2019-08-01 18:36:20 +08:00
nathan-castlehow
70892cae05 feat(prettierOnMarkdown):Tweaked spacing on default Prettier Config Value 2019-08-01 18:36:19 +08:00
nathan-castlehow
de0af153bc feat(prettierOnMarkdown):Added prettier config default to config manager 2019-08-01 18:36:19 +08:00
nathan-castlehow
33161e46e6 feat(prettierOnMarkdown): Added support for prettyifing markdown as well as added hot key option. Partial Implementation of Prettier config in configuration screen. TODO Fix defaulting of prettier configuration 2019-08-01 18:36:18 +08:00
nathan-castlehow
7e3c662374 feat(prettierOnMarkdown): Added Reference to prettier in Code Editor and created config file 2019-08-01 18:36:17 +08:00
nathan-castlehow
a39e9c2da6 feat(prettierOnMarkdown): Added Reference To Prettier 2019-08-01 18:36:16 +08:00
AWolf81
72b8d56245 Merge remote-tracking branch 'upstream/master' into html-to-md 2019-07-28 15:49:16 +02:00
AWolf81
0d36f59036 Create turndown service & use gfm turndown plugin 2019-07-28 15:02:17 +02:00
AWolf81
a3f7d2287a Add dracula theme styles 2019-07-28 11:00:40 +02:00
hikerpig
8edfbd28ed feat: suppport auto generating toc content for the '[TOC]' placeholder, related #3022 2019-07-27 16:55:32 +08:00
amedora
606be4304d Fix 3007 (#3028)
* fix code fences never sanitized

* fix mermaid xss

* Revert "fix mermaid xss"

This reverts commit 1ff179a1bd.

* configuable mermaid HTML label

* add locales for mermaid configuration
2019-07-27 12:39:12 +09:00
hikerpig
c2a26a8547 improvement: refactor buildStyle to NamedParameters style, and add some jsdoc 2019-07-21 15:28:12 +08:00
hikerpig
addf9b920f tweak MarkdownPreview style to optimize overflow scrollbar display, fix #2902 2019-07-21 15:28:12 +08:00
AWolf81
aeb77e5a40 Remove package-lock file & use startsWith for https check 2019-07-08 00:05:26 +02:00
nathan-castlehow
1d59d89588 feat(prettierOnMarkdown): Forced prettier options to always have parser set to markdown when used. 2019-07-03 09:28:36 +08:00
nathan-castlehow
bde357f952 feat(prettierOnMarkdown): Changed Prettier require to use import 2019-07-03 09:03:24 +08:00
AWolf81
558c091205 fix linting 2019-06-30 00:18:52 +02:00
AWolf81
f67175e628 fix test 2019-06-30 00:03:54 +02:00
AWolf81
390f6d545f fix PropTypes 2019-06-30 00:03:25 +02:00
AWolf81
44efb0178c Merge branch 'master' into html-to-md
# Conflicts:
#	browser/main/modals/NewNoteModal.js
#	package-lock.json
#	package.json
#	yarn.lock
2019-06-29 23:25:52 +02:00
AWolf81
37eee26bdf fix linting & routing 2019-06-29 23:21:32 +02:00
nathan-castlehow
ed4a670f0a feat(prettierOnMarkdown): Changed default hotkey value 2019-06-23 13:54:17 +08:00
nathan-castlehow
fbb9afe34f feat(prettierOnMarkdown):Fixed incorrect options passed to code mirror instance 2019-06-23 13:42:14 +08:00
nathan-castlehow
020bc11bd7 feat(prettierOnMarkdown):Tweaked spacing on default Prettier Config Value 2019-06-23 13:41:52 +08:00
nathan-castlehow
ae0837e29b feat(prettierOnMarkdown):Added prettier config default to config manager 2019-06-23 13:41:34 +08:00
nathan-castlehow
f0380ef733 feat(prettierOnMarkdown): Added support for prettyifing markdown as well as added hot key option. Partial Implementation of Prettier config in configuration screen. TODO Fix defaulting of prettier configuration 2019-06-23 13:40:20 +08:00
nathan-castlehow
25bdaf9f00 feat(prettierOnMarkdown): Added Reference to prettier in Code Editor and created config file 2019-06-23 13:34:47 +08:00
nathan-castlehow
ef1809305c feat(prettierOnMarkdown): Added Reference To Prettier 2019-06-23 13:34:47 +08:00
nathan-castlehow
090b5c32f0 feat: Added Context Menu for markdown preview mode and copy url when hyperlink 2019-06-09 13:28:53 +08:00
Storm Burpee
18aae8cf7b getting very close 2018-05-28 22:12:04 +09:30
Storm Burpee
4a9bc69ac2 starting to write a test 2018-05-28 19:45:09 +09:30
Storm Burpee
d97e62f864 Import note from url with markdown 2018-05-26 17:00:12 +09:30
80 changed files with 4990 additions and 3644 deletions

View File

@@ -18,7 +18,9 @@
"globals": { "globals": {
"FileReader": true, "FileReader": true,
"localStorage": true, "localStorage": true,
"fetch": true "fetch": true,
"Image": true,
"MutationObserver": true
}, },
"env": { "env": {
"jest": true "jest": true

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ node_modules/*
*.log *.log
.idea .idea
.vscode .vscode
package-lock.json

View File

@@ -3,7 +3,6 @@ node_js:
- 8 - 8
script: script:
- npm run lint && npm run test - npm run lint && npm run test
- yarn jest
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi' - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi'
after_success: after_success:
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv - openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv

View File

@@ -21,13 +21,14 @@ const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck') const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
import TurndownService from '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 {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator' import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
import markdownlint from 'markdownlint' import markdownlint from 'markdownlint'
import Jsonlint from 'jsonlint-mod' import Jsonlint from 'jsonlint-mod'
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager' import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
import prettier from 'prettier'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -69,8 +70,10 @@ export default class CodeEditor extends React.Component {
storageKey, storageKey,
noteKey noteKey
} = this.props } = this.props
if (this.props.deleteUnusedAttachments === true) {
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey) debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
} }
}
this.pasteHandler = (editor, e) => { this.pasteHandler = (editor, e) => {
e.preventDefault() e.preventDefault()
@@ -98,7 +101,7 @@ export default class CodeEditor extends React.Component {
this.editorActivityHandler = () => this.handleEditorActivity() this.editorActivityHandler = () => this.handleEditorActivity()
this.turndownService = new TurndownService() this.turndownService = createTurndownService()
} }
handleSearch (msg) { handleSearch (msg) {
@@ -106,7 +109,7 @@ export default class CodeEditor extends React.Component {
const component = this const component = this
if (component.searchState) cm.removeOverlay(component.searchState) if (component.searchState) cm.removeOverlay(component.searchState)
if (msg.length < 3) return if (msg.length < 1) return
cm.operation(function () { cm.operation(function () {
component.searchState = makeOverlay(msg, 'searching') component.searchState = makeOverlay(msg, 'searching')
@@ -216,6 +219,37 @@ export default class CodeEditor extends React.Component {
} }
return CodeMirror.Pass return CodeMirror.Pass
}, },
[translateHotkey(hotkey.prettifyMarkdown)]: cm => {
// Default / User configured prettier options
const currentConfig = JSON.parse(self.props.prettierConfig)
// Parser type will always need to be markdown so we override the option before use
currentConfig.parser = 'markdown'
// Get current cursor position
const cursorPos = cm.getCursor()
currentConfig.cursorOffset = cm.doc.indexFromPos(cursorPos)
// Prettify contents of editor
const formattedTextDetails = prettier.formatWithCursor(cm.doc.getValue(), currentConfig)
const formattedText = formattedTextDetails.formatted
const formattedCursorPos = formattedTextDetails.cursorOffset
cm.doc.setValue(formattedText)
// Reset Cursor position to be at the same markdown as was before prettifying
const newCursorPos = cm.doc.posFromIndex(formattedCursorPos)
cm.doc.setCursor(newCursorPos)
},
[translateHotkey(hotkey.sortLines)]: cm => {
const selection = cm.doc.getSelection()
const appendLineBreak = /\n$/.test(selection)
const sorted = _.split(selection.trim(), '\n').sort()
const sortedString = _.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
cm.doc.replaceSelection(sortedString)
},
[translateHotkey(hotkey.pasteSmartly)]: cm => { [translateHotkey(hotkey.pasteSmartly)]: cm => {
this.handlePaste(cm, true) this.handlePaste(cm, true)
} }
@@ -269,7 +303,8 @@ export default class CodeEditor extends React.Component {
explode: this.props.explodingPairs, explode: this.props.explodingPairs,
override: true override: true
}, },
extraKeys: this.defaultKeyMap extraKeys: this.defaultKeyMap,
prettierConfig: this.props.prettierConfig
}) })
document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none' document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none'
@@ -608,6 +643,9 @@ export default class CodeEditor extends React.Component {
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'}) this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
} }
} }
if (prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments) {
this.editor.setOption('deleteUnusedAttachments', this.props.deleteUnusedAttachments)
}
if (needRefresh) { if (needRefresh) {
this.editor.refresh() this.editor.refresh()
@@ -836,6 +874,17 @@ export default class CodeEditor extends React.Component {
this.editor.setCursor(cursor) this.editor.setCursor(cursor)
} }
/**
* Update content of one line
* @param {Number} lineNumber
* @param {String} content
*/
setLineContent (lineNumber, content) {
const prevContent = this.editor.getLine(lineNumber)
const prevContentLength = prevContent ? prevContent.length : 0
this.editor.replaceRange(content, { line: lineNumber, ch: 0 }, { line: lineNumber, ch: prevContentLength })
}
handleDropImage (dropEvent) { handleDropImage (dropEvent) {
dropEvent.preventDefault() dropEvent.preventDefault()
const { const {
@@ -1169,7 +1218,8 @@ CodeEditor.propTypes = {
autoDetect: PropTypes.bool, autoDetect: PropTypes.bool,
spellCheck: PropTypes.bool, spellCheck: PropTypes.bool,
enableMarkdownLint: PropTypes.bool, enableMarkdownLint: PropTypes.bool,
customMarkdownLintConfig: PropTypes.string customMarkdownLintConfig: PropTypes.string,
deleteUnusedAttachments: PropTypes.bool
} }
CodeEditor.defaultProps = { CodeEditor.defaultProps = {
@@ -1183,5 +1233,7 @@ CodeEditor.defaultProps = {
autoDetect: false, autoDetect: false,
spellCheck: false, spellCheck: false,
enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint, enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint,
customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig,
prettierConfig: DEFAULT_CONFIG.editor.prettierConfig,
deleteUnusedAttachments: DEFAULT_CONFIG.editor.deleteUnusedAttachments
} }

View File

@@ -169,14 +169,15 @@ class MarkdownEditor extends React.Component {
.split('\n') .split('\n')
const targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
let newLine = targetLine
if (targetLine.match(checkedMatch)) { if (targetLine.match(checkedMatch)) {
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]') newLine = targetLine.replace(checkReplace, '[ ]')
} }
if (targetLine.match(uncheckedMatch)) { if (targetLine.match(uncheckedMatch)) {
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]') newLine = targetLine.replace(uncheckReplace, '[x]')
} }
this.refs.code.setValue(lines.join('\n')) this.refs.code.setLineContent(lineIndex, newLine)
} }
} }
@@ -322,6 +323,8 @@ 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}
prettierConfig={config.editor.prettierConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
/> />
<MarkdownPreview styleName={this.state.status === 'PREVIEW' <MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview' ? 'preview'
@@ -341,6 +344,7 @@ class MarkdownEditor extends React.Component {
smartArrows={config.preview.smartArrows} smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks} breaks={config.preview.breaks}
sanitize={config.preview.sanitize} sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview' ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)} onDoubleClick={(e) => this.handleDoubleClick(e)}

View File

@@ -41,18 +41,32 @@ const CSS_FILES = [
`${appPath}/node_modules/codemirror/lib/codemirror.css`, `${appPath}/node_modules/codemirror/lib/codemirror.css`,
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css` `${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
] ]
const win = global.process.platform === 'win32'
function buildStyle ( /**
* @param {Object} opts
* @param {String} opts.fontFamily
* @param {Numberl} opts.fontSize
* @param {String} opts.codeBlockFontFamily
* @param {String} opts.theme
* @param {Boolean} [opts.lineNumber] Should show line number
* @param {Boolean} [opts.scrollPastEnd]
* @param {Boolean} [opts.optimizeOverflowScroll] Should tweak body style to optimize overflow scrollbar display
* @param {Boolean} [opts.allowCustomCSS] Should add custom css
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
* @returns {String}
*/
function buildStyle (opts) {
const {
fontFamily, fontFamily,
fontSize, fontSize,
codeBlockFontFamily, codeBlockFontFamily,
lineNumber, lineNumber,
scrollPastEnd, scrollPastEnd,
optimizeOverflowScroll,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS customCSS
) { } = opts
return ` return `
@font-face { @font-face {
font-family: 'Lato'; font-family: 'Lato';
@@ -82,12 +96,14 @@ function buildStyle (
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'), url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype'); url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
} }
${markdownStyle} ${markdownStyle}
body { body {
font-family: '${fontFamily.join("','")}'; font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px; font-size: ${fontSize}px;
${scrollPastEnd && 'padding-bottom: 90vh;'} ${scrollPastEnd ? 'padding-bottom: 90vh;' : ''}
${optimizeOverflowScroll ? 'height: 100%;' : ''}
} }
@media print { @media print {
body { body {
@@ -247,8 +263,11 @@ export default class MarkdownPreview extends React.Component {
handleContextMenu (event) { handleContextMenu (event) {
const menu = buildMarkdownPreviewContextMenu(this, event) const menu = buildMarkdownPreviewContextMenu(this, event)
if (menu != null) { const switchPreview = ConfigManager.get().editor.switchPreview
if (menu != null && switchPreview !== 'RIGHTCLICK') {
menu.popup(remote.getCurrentWindow()) menu.popup(remote.getCurrentWindow())
} else if (_.isFunction(this.props.onContextMenu)) {
this.props.onContextMenu(event)
} }
} }
@@ -310,7 +329,7 @@ export default class MarkdownPreview extends React.Component {
customCSS customCSS
} = this.getStyleParams() } = this.getStyleParams()
const inlineStyles = buildStyle( const inlineStyles = buildStyle({
fontFamily, fontFamily,
fontSize, fontSize,
codeBlockFontFamily, codeBlockFontFamily,
@@ -319,7 +338,7 @@ export default class MarkdownPreview extends React.Component {
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS customCSS
) })
let body = this.markdown.render(noteContent) let body = this.markdown.render(noteContent)
body = attachmentManagement.fixLocalURLS( body = attachmentManagement.fixLocalURLS(
body, body,
@@ -361,7 +380,7 @@ export default class MarkdownPreview extends React.Component {
handleSaveAsPdf () { handleSaveAsPdf () {
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => { this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false}}) const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false, javascript: false}})
printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir)) printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
printout.webContents.on('did-finish-load', () => { printout.webContents.on('did-finish-load', () => {
@@ -556,16 +575,19 @@ export default class MarkdownPreview extends React.Component {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
if (prevProps.value !== this.props.value) this.rewriteIframe() // actual rewriteIframe function should be called only once
let needsRewriteIframe = false
if (prevProps.value !== this.props.value) needsRewriteIframe = true
if ( if (
prevProps.smartQuotes !== this.props.smartQuotes || prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize || prevProps.sanitize !== this.props.sanitize ||
prevProps.mermaidHTMLLabel !== this.props.mermaidHTMLLabel ||
prevProps.smartArrows !== this.props.smartArrows || prevProps.smartArrows !== this.props.smartArrows ||
prevProps.breaks !== this.props.breaks || prevProps.breaks !== this.props.breaks ||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
) { ) {
this.initMarkdown() this.initMarkdown()
this.rewriteIframe() needsRewriteIframe = true
} }
if ( if (
prevProps.fontFamily !== this.props.fontFamily || prevProps.fontFamily !== this.props.fontFamily ||
@@ -580,8 +602,17 @@ export default class MarkdownPreview extends React.Component {
prevProps.customCSS !== this.props.customCSS prevProps.customCSS !== this.props.customCSS
) { ) {
this.applyStyle() this.applyStyle()
needsRewriteIframe = true
}
if (needsRewriteIframe) {
this.rewriteIframe() this.rewriteIframe()
} }
// Should scroll to top after selecting another note
if (prevProps.noteKey !== this.props.noteKey) {
this.getWindow().scrollTo(0, 0)
}
} }
getStyleParams () { getStyleParams () {
@@ -638,16 +669,18 @@ export default class MarkdownPreview extends React.Component {
this.getWindow().document.getElementById( this.getWindow().document.getElementById(
'codeTheme' 'codeTheme'
).href = this.getCodeThemeLink(codeBlockTheme) ).href = this.getCodeThemeLink(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle( this.getWindow().document.getElementById('style').innerHTML = buildStyle({
fontFamily, fontFamily,
fontSize, fontSize,
codeBlockFontFamily, codeBlockFontFamily,
lineNumber, lineNumber,
scrollPastEnd, scrollPastEnd,
optimizeOverflowScroll: true,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS customCSS
) })
this.getWindow().document.documentElement.style.overflowY = 'hidden'
} }
getCodeThemeLink (name) { getCodeThemeLink (name) {
@@ -681,7 +714,8 @@ export default class MarkdownPreview extends React.Component {
showCopyNotification, showCopyNotification,
storagePath, storagePath,
noteKey, noteKey,
sanitize sanitize,
mermaidHTMLLabel
} = this.props } = this.props
let { value, codeBlockTheme } = this.props let { value, codeBlockTheme } = this.props
@@ -813,6 +847,7 @@ export default class MarkdownPreview extends React.Component {
canvas.height = height.value + 'vh' canvas.height = height.value + 'vh'
} }
// eslint-disable-next-line no-unused-vars
const chart = new Chart(canvas, chartConfig) const chart = new Chart(canvas, chartConfig)
} catch (e) { } catch (e) {
el.className = 'chart-error' el.className = 'chart-error'
@@ -823,7 +858,7 @@ export default class MarkdownPreview extends React.Component {
_.forEach( _.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'), this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
el => { el => {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme) mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme, mermaidHTMLLabel)
} }
) )
@@ -951,8 +986,6 @@ export default class MarkdownPreview extends React.Component {
overlay.appendChild(zoomImg) overlay.appendChild(zoomImg)
document.body.appendChild(overlay) document.body.appendChild(overlay)
} }
this.getWindow().scrollTo(0, 0)
} }
focus () { focus () {
@@ -1000,17 +1033,22 @@ export default class MarkdownPreview extends React.Component {
e.stopPropagation() e.stopPropagation()
const rawHref = e.target.getAttribute('href') const rawHref = e.target.getAttribute('href')
const parser = document.createElement('a')
parser.href = e.target.getAttribute('href')
const { href, hash } = parser
const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10
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]()
const extractId = /(main.html)?#/ const parser = document.createElement('a')
const regexNoteInternalLink = new RegExp(`${extractId.source}(.+)`) parser.href = rawHref
if (regexNoteInternalLink.test(linkHash)) { const isStartWithHash = rawHref[0] === '#'
const targetId = mdurl.encode(linkHash.replace(extractId, '')) const { href, hash } = parser
const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10
const extractIdRegex = /file:\/\/.*main.?\w*.html#/ // file://path/to/main(.development.)html
const regexNoteInternalLink = new RegExp(`${extractIdRegex.source}(.+)`)
if (isStartWithHash || regexNoteInternalLink.test(rawHref)) {
const posOfHash = linkHash.indexOf('#')
if (posOfHash > -1) {
const extractedId = linkHash.slice(posOfHash + 1)
const targetId = mdurl.encode(extractedId)
const targetElement = this.refs.root.contentWindow.document.getElementById( const targetElement = this.refs.root.contentWindow.document.getElementById(
targetId targetId
) )
@@ -1020,6 +1058,7 @@ export default class MarkdownPreview extends React.Component {
} }
return return
} }
}
// this will match the new uuid v4 hash and the old hash // this will match the new uuid v4 hash and the old hash
// e.g. // e.g.

View File

@@ -88,14 +88,15 @@ class MarkdownSplitEditor extends React.Component {
.split('\n') .split('\n')
const targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
let newLine = targetLine
if (targetLine.match(checkedMatch)) { if (targetLine.match(checkedMatch)) {
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]') newLine = targetLine.replace(checkReplace, '[ ]')
} }
if (targetLine.match(uncheckedMatch)) { if (targetLine.match(uncheckedMatch)) {
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]') newLine = targetLine.replace(uncheckReplace, '[x]')
} }
this.refs.code.setValue(lines.join('\n')) this.refs.code.setLineContent(lineIndex, newLine)
} }
} }
@@ -181,6 +182,7 @@ class MarkdownSplitEditor 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}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
/> />
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} > <div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
<div styleName='slider-hitbox' /> <div styleName='slider-hitbox' />
@@ -199,6 +201,7 @@ class MarkdownSplitEditor extends React.Component {
smartArrows={config.preview.smartArrows} smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks} breaks={config.preview.breaks}
sanitize={config.preview.sanitize} sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview' ref='preview'
tabInde='0' tabInde='0'
value={value} value={value}

View File

@@ -3,7 +3,7 @@
*/ */
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import { isArray } from 'lodash' import { isArray, sortBy } from 'lodash'
import invertColor from 'invert-color' import invertColor from 'invert-color'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus' import { getTodoStatus } from 'browser/lib/getTodoStatus'
@@ -43,7 +43,7 @@ const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
} }
if (showTagsAlphabetically) { if (showTagsAlphabetically) {
return _.sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] })) return sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
} else { } else {
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] })) return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
} }

View File

@@ -19,7 +19,7 @@ function getId () {
return id return id
} }
function render (element, content, theme) { function render (element, content, theme, enableHTMLLabel) {
try { try {
const height = element.attributes.getNamedItem('data-height') const height = element.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') { if (height && height.value !== 'undefined') {
@@ -29,7 +29,8 @@ function render (element, content, theme) {
mermaidAPI.initialize({ mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default', theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : '', themeCSS: isDarkTheme ? darkThemeStyling : '',
useMaxWidth: false useMaxWidth: false,
flowchart: { htmlLabels: enableHTMLLabel }
}) })
mermaidAPI.render(getId(), content, (svgGraph) => { mermaidAPI.render(getId(), content, (svgGraph) => {
element.innerHTML = svgGraph element.innerHTML = svgGraph

View File

@@ -1,5 +1,4 @@
const crypto = require('crypto') const crypto = require('crypto')
const _ = require('lodash')
const uuidv4 = require('uuid/v4') const uuidv4 = require('uuid/v4')
module.exports = function (uuid) { module.exports = function (uuid) {

View File

@@ -15,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
options options
) )
} }
if (state.tokens[tokenIdx].type === '_fence') { if (state.tokens[tokenIdx].type.match(/.*_fence$/)) {
// escapeHtmlCharacters has better performance // escapeHtmlCharacters has better performance
state.tokens[tokenIdx].content = escapeHtmlCharacters( state.tokens[tokenIdx].content = escapeHtmlCharacters(
state.tokens[tokenIdx].content, state.tokens[tokenIdx].content,
@@ -96,6 +96,10 @@ function sanitizeInline (html, options) {
function naughtyHRef (href, options) { function naughtyHRef (href, options) {
// href = href.replace(/[\x00-\x20]+/g, '') // href = href.replace(/[\x00-\x20]+/g, '')
if (!href) {
// No href
return false
}
href = href.replace(/<\!\-\-.*?\-\-\>/g, '') href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
const matches = href.match(/^([a-zA-Z]+)\:/) const matches = href.match(/^([a-zA-Z]+)\:/)

View File

@@ -4,6 +4,7 @@ import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math' import math from '@rokt33r/markdown-it-math'
import mdurl from 'mdurl' import mdurl from 'mdurl'
import smartArrows from 'markdown-it-smartarrows' import smartArrows from 'markdown-it-smartarrows'
import markdownItTocAndAnchor from '@hikerpig/markdown-it-toc-and-anchor'
import _ from 'lodash' import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex' import katex from 'katex'
@@ -127,6 +128,12 @@ class Markdown {
this.md.use(require('markdown-it-abbr')) this.md.use(require('markdown-it-abbr'))
this.md.use(require('markdown-it-sub')) this.md.use(require('markdown-it-sub'))
this.md.use(require('markdown-it-sup')) this.md.use(require('markdown-it-sup'))
this.md.use(markdownItTocAndAnchor, {
toc: true,
tocPattern: /\[TOC\]/i,
anchorLink: false,
appendIdToHeading: false
})
this.md.use(require('./markdown-it-deflist')) this.md.use(require('./markdown-it-deflist'))
this.md.use(require('./markdown-it-frontmatter')) this.md.use(require('./markdown-it-frontmatter'))
@@ -183,32 +190,47 @@ class Markdown {
}) })
const deflate = require('markdown-it-plantuml/lib/deflate') const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), { const plantuml = require('markdown-it-plantuml')
generateSource: function (umlCode) { const plantUmlStripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url const plantUmlServerAddress = plantUmlStripTrailingSlash(config.preview.plantUMLServerAddress)
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg' const parsePlantUml = function (umlCode, openMarker, closeMarker, type) {
const s = unescape(encodeURIComponent(umlCode)) const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64( const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9) deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
) )
return `${serverAddress}/${zippedCode}` return `${plantUmlServerAddress}/${type}/${zippedCode}`
} }
this.md.use(plantuml, {
generateSource: (umlCode) => parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
}) })
// Ditaa support // Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
this.md.use(require('markdown-it-plantuml'), { this.md.use(plantuml, {
openMarker: '@startditaa', openMarker: '@startditaa',
closeMarker: '@endditaa', closeMarker: '@endditaa',
generateSource: function (umlCode) { generateSource: (umlCode) => parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url })
// Currently PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/png' // Mindmap support
const s = unescape(encodeURIComponent(umlCode)) this.md.use(plantuml, {
const zippedCode = deflate.encode64( openMarker: '@startmindmap',
deflate.zip_deflate(`@startditaa\n${s}\n@endditaa`, 9) closeMarker: '@endmindmap',
) generateSource: (umlCode) => parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
return `${serverAddress}/${zippedCode}` })
}
// WBS support
this.md.use(plantuml, {
openMarker: '@startwbs',
closeMarker: '@endwbs',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
})
// Gantt support
this.md.use(plantuml, {
openMarker: '@startgantt',
closeMarker: '@endgantt',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
}) })
// Override task item // Override task item

9
browser/lib/turndown.js Normal file
View File

@@ -0,0 +1,9 @@
const TurndownService = require('turndown')
const { gfm } = require('turndown-plugin-gfm')
export const createTurndownService = function () {
const turndown = new TurndownService()
turndown.use(gfm)
turndown.remove('script')
return turndown
}

View File

@@ -136,9 +136,24 @@ export function isMarkdownTitleURL (str) {
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str) return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
} }
export function humanFileSize (bytes) {
const threshold = 1000
if (Math.abs(bytes) < threshold) {
return bytes + ' B'
}
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
var u = -1
do {
bytes /= threshold
++u
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
return bytes.toFixed(1) + ' ' + units[u]
}
export default { export default {
lastFindInArray, lastFindInArray,
escapeHtmlCharacters, escapeHtmlCharacters,
isObjectEqual, isObjectEqual,
isMarkdownTitleURL isMarkdownTitleURL,
humanFileSize
} }

View File

@@ -0,0 +1,69 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FromUrlButton.styl'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class FromUrlButton extends React.Component {
constructor (props) {
super(props)
this.state = {
isActive: false
}
}
handleMouseDown (e) {
this.setState({
isActive: true
})
}
handleMouseUp (e) {
this.setState({
isActive: false
})
}
handleMouseLeave (e) {
this.setState({
isActive: false
})
}
render () {
const { className } = this.props
return (
<button className={_.isString(className)
? 'FromUrlButton ' + className
: 'FromUrlButton'
}
styleName={this.state.isActive || this.props.isActive
? 'root--active'
: 'root'
}
onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick}>
<img styleName='icon'
src={this.state.isActive || this.props.isActive
? '../resources/icon/icon-external.svg'
: '../resources/icon/icon-external.svg'
}
/>
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
</button>
)
}
}
FromUrlButton.propTypes = {
isActive: PropTypes.bool,
onClick: PropTypes.func,
className: PropTypes.string
}
export default CSSModules(FromUrlButton, styles)

View File

@@ -0,0 +1,41 @@
.root
top 45px
topBarButtonRight()
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 125px
width 90px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.root--active
@extend .root
transition 0.15s
color $ui-favorite-star-button-color
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
.icon
transition transform 0.15s
height 13px
body[data-theme="dark"]
.root
topBarButtonDark()
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)

View File

@@ -152,7 +152,6 @@ class MarkdownNoteDetail extends React.Component {
} }
handleFolderChange (e) { handleFolderChange (e) {
const { dispatch } = this.props
const { note } = this.state const { note } = this.state
const value = this.refs.folder.value const value = this.refs.folder.value
const splitted = value.split('-') const splitted = value.split('-')
@@ -410,7 +409,7 @@ class MarkdownNoteDetail extends React.Component {
} }
render () { render () {
const { data, location, config } = this.props const { data, dispatch, location, config } = this.props
const { note, editorType } = this.state const { note, editorType } = this.state
const storageKey = note.storage const storageKey = note.storage
const folderKey = note.folder const folderKey = note.folder
@@ -465,6 +464,7 @@ class MarkdownNoteDetail extends React.Component {
saveTagsAlphabetically={config.ui.saveTagsAlphabetically} saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically} showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data} data={data}
dispatch={dispatch}
onChange={this.handleUpdateTag.bind(this)} onChange={this.handleUpdateTag.bind(this)}
coloredTags={config.coloredTags} coloredTags={config.coloredTags}
/> />
@@ -472,6 +472,7 @@ class MarkdownNoteDetail extends React.Component {
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} /> <ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<StarButton <StarButton
onClick={(e) => this.handleStarButtonClick(e)} onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred} isActive={note.isStarred}
@@ -511,7 +512,7 @@ class MarkdownNoteDetail extends React.Component {
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
exportAsPdf={this.exportAsPdf} exportAsPdf={this.exportAsPdf}
wordCount={note.content.split(' ').length} wordCount={note.content.trim().split(/\s+/g).length}
letterCount={note.content.replace(/\r?\n/g, '').length} letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type} type={note.type}
print={this.print} print={this.print}

View File

@@ -82,10 +82,3 @@ body[data-theme="dracula"]
border-left 1px solid $ui-dracula-borderColor border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor background-color $ui-dracula-noteDetail-backgroundColor
div
> button, div
-webkit-user-drag none
user-select none
> img, span
-webkit-user-drag none
user-select none

View File

@@ -108,3 +108,11 @@ body[data-theme="dracula"]
.info .info
border-color $ui-dracula-borderColor border-color $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor background-color $ui-dracula-noteDetail-backgroundColor
.info > div
> button
-webkit-user-drag none
user-select none
> img, span
-webkit-user-drag none
user-select none

View File

@@ -699,7 +699,7 @@ class SnippetNoteDetail extends React.Component {
} }
render () { render () {
const { data, config, location } = this.props const { data, dispatch, config, location } = this.props
const { note } = this.state const { note } = this.state
const storageKey = note.storage const storageKey = note.storage
@@ -823,6 +823,7 @@ class SnippetNoteDetail extends React.Component {
saveTagsAlphabetically={config.ui.saveTagsAlphabetically} saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically} showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data} data={data}
dispatch={dispatch}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
coloredTags={config.coloredTags} coloredTags={config.coloredTags}
/> />

View File

@@ -8,6 +8,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import Autosuggest from 'react-autosuggest' import Autosuggest from 'react-autosuggest'
import { push } from 'connected-react-router'
class TagSelect extends React.Component { class TagSelect extends React.Component {
constructor (props) { constructor (props) {
@@ -96,8 +97,11 @@ class TagSelect extends React.Component {
} }
handleTagLabelClick (tag) { handleTagLabelClick (tag) {
const { router } = this.context const { dispatch } = this.props
router.push(`/tags/${tag}`)
// Note: `tag` requires encoding later.
// E.g. % in tag is a problem (see issue #3170) - encodeURIComponent(tag) is not working.
dispatch(push(`/tags/${tag}`))
} }
handleTagRemoveButtonClick (tag) { handleTagRemoveButtonClick (tag) {
@@ -255,11 +259,8 @@ class TagSelect extends React.Component {
} }
} }
TagSelect.contextTypes = {
router: PropTypes.shape({})
}
TagSelect.propTypes = { TagSelect.propTypes = {
dispatch: PropTypes.func,
className: PropTypes.string, className: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string), value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func, onChange: PropTypes.func,

View File

@@ -75,3 +75,10 @@ body[data-theme="dracula"]
.active .active
background-color #bd93f9 background-color #bd93f9
box-shadow 2px 0px 7px #222222 box-shadow 2px 0px 7px #222222
.control-toggleModeButton
-webkit-user-drag none
user-select none
> div img
-webkit-user-drag none
user-select none

View File

@@ -50,16 +50,14 @@ class Detail extends React.Component {
const searchStr = params.searchword const searchStr = params.searchword
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
: searchFromNotes(allNotes, searchStr) : searchFromNotes(allNotes, searchStr)
} } else if (location.pathname.match(/^\/tags/)) {
if (location.pathname.match(/\/tags/)) {
const listOfTags = params.tagname.split(' ') const listOfTags = params.tagname.split(' ')
displayedNotes = data.noteMap.map(note => note).filter(note => displayedNotes = data.noteMap.map(note => note).filter(note =>
listOfTags.every(tag => note.tags.includes(tag)) listOfTags.every(tag => note.tags.includes(tag))
) )
} }
if (location.pathname.match(/\/trashed/)) { if (location.pathname.match(/^\/trashed/)) {
displayedNotes = trashedNotes displayedNotes = trashedNotes
} else { } else {
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key) displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)

View File

@@ -102,7 +102,7 @@ class Main extends React.Component {
{ {
name: 'example.js', name: 'example.js',
mode: 'javascript', mode: 'javascript',
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)", content: "var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
linesHighlighted: [] linesHighlighted: []
} }
] ]
@@ -169,6 +169,7 @@ class Main extends React.Component {
} }
}) })
// eslint-disable-next-line no-undef
delete CodeMirror.keyMap.emacs['Ctrl-V'] delete CodeMirror.keyMap.emacs['Ctrl-V']
eventEmitter.on('editor:fullscreen', this.toggleFullScreen) eventEmitter.on('editor:fullscreen', this.toggleFullScreen)

View File

@@ -88,6 +88,7 @@ class NoteList extends React.Component {
this.importFromFileHandler = this.importFromFile.bind(this) this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this) this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this) this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
this.handleNoteListBlur = this.handleNoteListBlur.bind(this)
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this) this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
this.cloneNote = this.cloneNote.bind(this) this.cloneNote = this.cloneNote.bind(this)
this.deleteNote = this.deleteNote.bind(this) this.deleteNote = this.deleteNote.bind(this)
@@ -348,6 +349,13 @@ class NoteList extends React.Component {
} }
} }
handleNoteListBlur () {
this.setState({
shiftKeyDown: false,
ctrlKeyDown: false
})
}
getNotes () { getNotes () {
const { data, match: { params }, location } = this.props const { data, match: { params }, location } = this.props
if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) { if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
@@ -1155,6 +1163,7 @@ class NoteList extends React.Component {
tabIndex='-1' tabIndex='-1'
onKeyDown={(e) => this.handleNoteListKeyDown(e)} onKeyDown={(e) => this.handleNoteListKeyDown(e)}
onKeyUp={this.handleNoteListKeyUp} onKeyUp={this.handleNoteListKeyUp}
onBlur={this.handleNoteListBlur}
> >
{noteList} {noteList}
</div> </div>

View File

@@ -22,9 +22,10 @@ import context from 'browser/lib/context'
import { remote } from 'electron' import { remote } from 'electron'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import ColorPicker from 'browser/components/ColorPicker' import ColorPicker from 'browser/components/ColorPicker'
import { every, sortBy } from 'lodash'
function matchActiveTags (tags, activeTags) { function matchActiveTags (tags, activeTags) {
return _.every(activeTags, v => tags.indexOf(v) >= 0) return every(activeTags, v => tags.indexOf(v) >= 0)
} }
class SideNav extends React.Component { class SideNav extends React.Component {
@@ -271,6 +272,7 @@ class SideNav extends React.Component {
<div styleName='tagList'> <div styleName='tagList'>
{this.tagListComponent(data)} {this.tagListComponent(data)}
</div> </div>
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
</div> </div>
) )
} }
@@ -283,7 +285,7 @@ class SideNav extends React.Component {
const { colorPicker } = this.state const { colorPicker } = this.state
const activeTags = this.getActiveTags(location.pathname) const activeTags = this.getActiveTags(location.pathname)
const relatedTags = this.getRelatedTags(activeTags, data.noteMap) const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
let tagList = _.sortBy(data.tagNoteMap.map( let tagList = sortBy(data.tagNoteMap.map(
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) }) (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
).filter( ).filter(
tag => tag.size > 0 tag => tag.size > 0
@@ -296,7 +298,7 @@ class SideNav extends React.Component {
}) })
} }
if (config.sortTagsBy === 'COUNTER') { if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size)) tagList = sortBy(tagList, item => (0 - item.size))
} }
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) { if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
tagList = tagList.filter( tagList = tagList.filter(

View File

@@ -31,6 +31,8 @@ export const DEFAULT_CONFIG = {
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M', toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace', deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V', pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
prettifyMarkdown: OSX ? 'Command + Shift + F' : 'Ctrl + Shift + F',
sortLines: OSX ? 'Command + Shift + S' : 'Ctrl + Shift + S',
insertDate: OSX ? 'Command + /' : 'Ctrl + /', insertDate: OSX ? 'Command + /' : 'Ctrl + /',
insertDateTime: OSX ? 'Command + Alt + /' : 'Ctrl + Shift + /', insertDateTime: OSX ? 'Command + Alt + /' : 'Ctrl + Shift + /',
toggleMenuBar: 'Alt' toggleMenuBar: 'Alt'
@@ -68,7 +70,14 @@ export const DEFAULT_CONFIG = {
spellcheck: false, spellcheck: false,
enableSmartPaste: false, enableSmartPaste: false,
enableMarkdownLint: false, enableMarkdownLint: false,
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG,
prettierConfig: ` {
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}`,
deleteUnusedAttachments: true
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',
@@ -86,8 +95,10 @@ export const DEFAULT_CONFIG = {
breaks: true, breaks: true,
smartArrows: false, smartArrows: false,
allowCustomCSS: false, allowCustomCSS: false,
customCSS: '/* Drop Your Custom CSS Code Here */', customCSS: '/* Drop Your Custom CSS Code Here */',
sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE' sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
mermaidHTMLLabel: false,
lineThroughCheckbox: true lineThroughCheckbox: true
}, },
blog: { blog: {

View File

@@ -8,6 +8,7 @@ const escapeStringRegexp = require('escape-string-regexp')
const sander = require('sander') const sander = require('sander')
const url = require('url') const url = require('url')
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { isString } from 'lodash'
const STORAGE_FOLDER_PLACEHOLDER = ':storage' const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments' const DESTINATION_FOLDER = 'attachments'
@@ -19,7 +20,7 @@ const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(
* @returns {Promise<Image>} Image element created * @returns {Promise<Image>} Image element created
*/ */
function getImage (file) { function getImage (file) {
if (_.isString(file)) { if (isString(file)) {
return new Promise(resolve => { return new Promise(resolve => {
const img = new Image() const img = new Image()
img.onload = () => resolve(img) img.onload = () => resolve(img)
@@ -623,6 +624,76 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
} }
} }
/**
* @description Get all existing attachments related to a specific note
including their status (in use or not) and their path. Return null if there're no attachment related to note or specified parametters are invalid
* @param markdownContent markdownContent of the current note
* @param storageKey StorageKey of the current note
* @param noteKey NoteKey of the currentNote
* @return {Promise<Array<{path: String, isInUse: bool}>>} Promise returning the
list of attachments with their properties */
function getAttachmentsPathAndStatus (markdownContent, storageKey, noteKey) {
if (storageKey == null || noteKey == null || markdownContent == null) {
return null
}
const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
const attachmentsInNoteOnlyFileNames = []
if (attachmentsInNote) {
for (let i = 0; i < attachmentsInNote.length; i++) {
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
}
}
if (fs.existsSync(attachmentFolder)) {
return new Promise((resolve, reject) => {
fs.readdir(attachmentFolder, (err, files) => {
if (err) {
console.error('Error reading directory "' + attachmentFolder + '". Error:')
console.error(err)
reject(err)
return
}
const attachments = []
for (const file of files) {
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
attachments.push({ path: absolutePathOfFile, isInUse: false })
} else {
attachments.push({ path: absolutePathOfFile, isInUse: true })
}
}
resolve(attachments)
})
})
} else {
return null
}
}
/**
* @description Remove all specified attachment paths
* @param attachments attachment paths
* @return {Promise} Promise after all attachments are removed */
function removeAttachmentsByPaths (attachments) {
const promises = []
for (const attachment of attachments) {
const promise = new Promise((resolve, reject) => {
fs.unlink(attachment, (err) => {
if (err) {
console.error('Could not delete "%s"', attachment)
console.error(err)
reject(err)
return
}
resolve()
})
})
promises.push(promise)
}
return Promise.all(promises)
}
/** /**
* Clones the attachments of a given note. * Clones the attachments of a given note.
* Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination. * Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
@@ -725,8 +796,10 @@ module.exports = {
getAbsolutePathsOfAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent,
importAttachments, importAttachments,
removeStorageAndNoteReferences, removeStorageAndNoteReferences,
removeAttachmentsByPaths,
deleteAttachmentFolder, deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote, deleteAttachmentsNotPresentInNote,
getAttachmentsPathAndStatus,
moveAttachments, moveAttachments,
cloneAttachments, cloneAttachments,
isAttachmentLink, isAttachmentLink,

View File

@@ -0,0 +1,79 @@
const http = require('http')
const https = require('https')
const { createTurndownService } = require('../../../lib/turndown')
const createNote = require('./createNote')
import { push } from 'connected-react-router'
import ee from 'browser/main/lib/eventEmitter'
function validateUrl (str) {
if (/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(str)) {
return true
} else {
return false
}
}
function createNoteFromUrl (url, storage, folder, dispatch = null, location = null) {
return new Promise((resolve, reject) => {
const td = createTurndownService()
if (!validateUrl(url)) {
reject({result: false, error: 'Please check your URL is in correct format. (Example, https://www.google.com)'})
}
const request = url.startsWith('https') ? https : http
const req = request.request(url, (res) => {
let data = ''
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
const markdownHTML = td.turndown(data)
if (dispatch !== null) {
createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: markdownHTML
})
.then((note) => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
pathname: location.pathname,
query: {key: noteHash}
}))
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
resolve({result: true, error: null})
})
} else {
createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: markdownHTML
}).then((note) => {
resolve({result: true, note, error: null})
})
}
})
})
req.on('error', (e) => {
console.error('error in parsing URL', e)
reject({result: false, error: e})
})
req.end()
})
}
module.exports = createNoteFromUrl

View File

@@ -3,7 +3,6 @@ const path = require('path')
const resolveStorageData = require('./resolveStorageData') const resolveStorageData = require('./resolveStorageData')
const resolveStorageNotes = require('./resolveStorageNotes') const resolveStorageNotes = require('./resolveStorageNotes')
const CSON = require('@rokt33r/season') const CSON = require('@rokt33r/season')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage') const { findStorage } = require('browser/lib/findStorage')
const deleteSingleNote = require('./deleteNote') const deleteSingleNote = require('./deleteNote')

View File

@@ -43,7 +43,7 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
) )
if (outputFormatter) { if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks, path.dirname(targetPath)) exportedData = outputFormatter(exportedData, exportTasks, targetPath)
} else { } else {
exportedData = Promise.resolve(exportedData) exportedData = Promise.resolve(exportedData)
} }

View File

@@ -11,6 +11,7 @@ const dataApi = {
exportFolder: require('./exportFolder'), exportFolder: require('./exportFolder'),
exportStorage: require('./exportStorage'), exportStorage: require('./exportStorage'),
createNote: require('./createNote'), createNote: require('./createNote'),
createNoteFromUrl: require('./createNoteFromUrl'),
updateNote: require('./updateNote'), updateNote: require('./updateNote'),
deleteNote: require('./deleteNote'), deleteNote: require('./deleteNote'),
moveNote: require('./moveNote'), moveNote: require('./moveNote'),

View File

@@ -1,7 +1,6 @@
const resolveStorageData = require('./resolveStorageData') const resolveStorageData = require('./resolveStorageData')
const _ = require('lodash') const _ = require('lodash')
const path = require('path') const path = require('path')
const fs = require('fs')
const CSON = require('@rokt33r/season') const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen') const keygen = require('browser/lib/keygen')
const sander = require('sander') const sander = require('sander')

View File

@@ -0,0 +1,118 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './CreateMarkdownFromURLModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'
class CreateMarkdownFromURLModal extends React.Component {
constructor (props) {
super(props)
this.state = {
name: '',
showerror: false,
errormessage: ''
}
}
componentDidMount () {
this.refs.name.focus()
this.refs.name.select()
}
handleCloseButtonClick (e) {
this.props.close()
}
handleChange (e) {
this.setState({
name: this.refs.name.value
})
}
handleKeyDown (e) {
if (e.keyCode === 27) {
this.props.close()
}
}
handleInputKeyDown (e) {
switch (e.keyCode) {
case 13:
this.confirm()
}
}
handleConfirmButtonClick (e) {
this.confirm()
}
showError (message) {
this.setState({
showerror: true,
errormessage: message
})
}
hideError () {
this.setState({
showerror: false,
errormessage: ''
})
}
confirm () {
this.hideError()
const { storage, folder, dispatch, location } = this.props
dataApi.createNoteFromUrl(this.state.name, storage, folder, dispatch, location).then((result) => {
this.props.close()
}).catch((result) => {
this.showError(result.error)
})
}
render () {
return (
<div styleName='root'
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>{i18n.__('Import Markdown From URL')}</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<div styleName='control-folder'>
<div styleName='control-folder-label'>{i18n.__('Insert URL Here')}</div>
<input styleName='control-folder-input'
ref='name'
value={this.state.name}
onChange={(e) => this.handleChange(e)}
onKeyDown={(e) => this.handleInputKeyDown(e)}
/>
</div>
<button styleName='control-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)}
>
{i18n.__('Import')}
</button>
<div className='error' styleName='error'>{this.state.errormessage}</div>
</div>
</div>
)
}
}
CreateMarkdownFromURLModal.propTypes = {
storage: PropTypes.string,
folder: PropTypes.string,
dispatch: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string
})
}
export default CSSModules(CreateMarkdownFromURLModal, styles)

View File

@@ -0,0 +1,160 @@
.root
modal()
width 500px
height 270px
overflow hidden
position relative
.header
height 80px
margin-bottom 10px
margin-top 20px
font-size 18px
line-height 50px
background-color $ui-backgroundColor
color $ui-text-color
.title
font-size 36px
font-weight 600
.control-folder-label
text-align left
font-size 14px
color $ui-text-color
.control-folder-input
display block
height 40px
width 490px
padding 0 5px
margin 10px 0
border 1px solid $ui-input--create-folder-modal
border-radius 2px
background-color transparent
outline none
vertical-align middle
font-size 16px
&:disabled
background-color $ui-input--disabled-backgroundColor
&:focus, &:active
border-color $ui-active-color
.control-confirmButton
display block
height 35px
width 140px
border none
border-radius 2px
padding 0 25px
margin 20px auto
font-size 14px
colorPrimaryButton()
body[data-theme="dark"]
.root
modalDark()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-dark-text-color
.control-folder-label
color $ui-dark-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorDarkPrimaryButton()
body[data-theme="solarized-dark"]
.root
modalSolarizedDark()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-solarized-dark-text-color
.control-folder-label
color $ui-solarized-dark-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorSolarizedDarkPrimaryButton()
.error
text-align center
color #F44336
body[data-theme="monokai"]
.root
modalMonokai()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-monokai-text-color
.control-folder-label
color $ui-monokai-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorMonokaiPrimaryButton()
body[data-theme="dracula"]
.root
modalDracula()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dracula-borderColor
color $ui-dracula-text-color
.control-folder-label
color $ui-dracula-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorDraculaPrimaryButton()

View File

@@ -3,6 +3,8 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteModal.styl' import styles from './NewNoteModal.styl'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { openModal } from 'browser/main/lib/modal'
import CreateMarkdownFromURLModal from '../modals/CreateMarkdownFromURLModal'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote' import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
import queryString from 'query-string' import queryString from 'query-string'
@@ -21,6 +23,18 @@ class NewNoteModal extends React.Component {
this.props.close() this.props.close()
} }
handleCreateMarkdownFromUrlClick (e) {
this.props.close()
const { storage, folder, dispatch, location } = this.props
openModal(CreateMarkdownFromURLModal, {
storage: storage,
folder: folder,
dispatch,
location
})
}
handleMarkdownNoteButtonClick (e) { handleMarkdownNoteButtonClick (e) {
const { storage, folder, dispatch, location, config } = this.props const { storage, folder, dispatch, location, config } = this.props
const params = location.search !== '' && queryString.parse(location.search) const params = location.search !== '' && queryString.parse(location.search)
@@ -115,10 +129,8 @@ class NewNoteModal extends React.Component {
</button> </button>
</div> </div>
<div styleName='description'> <div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
<i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')} <div styleName='from-url' onClick={(e) => this.handleCreateMarkdownFromUrlClick(e)}>Or, create a new markdown note from a URL</div>
</div>
</div> </div>
) )
} }

View File

@@ -48,6 +48,12 @@
text-align center text-align center
margin-bottom 25px margin-bottom 25px
.from-url
color $ui-inactive-text-color
text-align center
margin-bottom 25px
cursor pointer
body[data-theme="dark"] body[data-theme="dark"]
.root .root
modalDark() modalDark()
@@ -62,7 +68,7 @@ body[data-theme="dark"]
&:focus &:focus
colorDarkPrimaryButton() colorDarkPrimaryButton()
.description .description, .from-url
color $ui-inactive-text-color color $ui-inactive-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
@@ -79,7 +85,7 @@ body[data-theme="solarized-dark"]
&:focus &:focus
colorDarkPrimaryButton() colorDarkPrimaryButton()
.description .description, .from-url
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="monokai"] body[data-theme="monokai"]
@@ -96,7 +102,7 @@ body[data-theme="monokai"]
&:focus &:focus
colorDarkPrimaryButton() colorDarkPrimaryButton()
.description .description, .from-url
color $ui-monokai-text-color color $ui-monokai-text-color
body[data-theme="dracula"] body[data-theme="dracula"]

View File

@@ -76,13 +76,16 @@ class HotkeyTab extends React.Component {
handleHotkeyChange (e) { handleHotkeyChange (e) {
const { config } = this.state const { config } = this.state
config.hotkey = { config.hotkey = Object.assign({}, config.hotkey, {
toggleMain: this.refs.toggleMain.value, toggleMain: this.refs.toggleMain.value,
toggleMode: this.refs.toggleMode.value, toggleMode: this.refs.toggleMode.value,
deleteNote: this.refs.deleteNote.value, deleteNote: this.refs.deleteNote.value,
pasteSmartly: this.refs.pasteSmartly.value, pasteSmartly: this.refs.pasteSmartly.value,
toggleMenuBar: this.refs.toggleMenuBar.value prettifyMarkdown: this.refs.prettifyMarkdown.value,
} toggleMenuBar: this.refs.toggleMenuBar.value,
insertDate: this.refs.insertDate.value,
insertDateTime: this.refs.insertDateTime.value
})
this.setState({ this.setState({
config config
}) })
@@ -173,10 +176,21 @@ class HotkeyTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Prettify Markdown')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}
ref='prettifyMarkdown'
value={config.hotkey.prettifyMarkdown}
type='text' />
</div>
</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Insert Current Date')}</div> <div styleName='group-section-label'>{i18n.__('Insert Current Date')}</div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
ref='insertDate'
value={config.hotkey.insertDate} value={config.hotkey.insertDate}
type='text' type='text'
disabled='true' disabled='true'
@@ -187,6 +201,7 @@ class HotkeyTab extends React.Component {
<div styleName='group-section-label'>{i18n.__('Insert Current Date and Time')}</div> <div styleName='group-section-label'>{i18n.__('Insert Current Date and Time')}</div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
ref='insertDateTime'
value={config.hotkey.insertDateTime} value={config.hotkey.insertDateTime}
type='text' type='text'
disabled='true' disabled='true'

View File

@@ -102,3 +102,11 @@ body[data-theme="solarized-dark"]
border-color $ui-solarized-dark-button-backgroundColor border-color $ui-solarized-dark-button-backgroundColor
background-color $ui-solarized-dark-button-backgroundColor background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="dracula"]
.header
border-color $ui-dracula-borderColor
.header-control-button
colorDraculaDefaultButton()
border-color $ui-dracula-borderColor

View File

@@ -3,8 +3,11 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './StoragesTab.styl' import styles from './StoragesTab.styl'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import StorageItem from './StorageItem' import StorageItem from './StorageItem'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { humanFileSize } from 'browser/lib/utils'
import fs from 'fs'
const electron = require('electron') const electron = require('electron')
const { shell, remote } = electron const { shell, remote } = electron
@@ -35,8 +38,29 @@ class StoragesTab extends React.Component {
name: 'Unnamed', name: 'Unnamed',
type: 'FILESYSTEM', type: 'FILESYSTEM',
path: '' path: ''
},
attachments: []
} }
this.loadAttachmentStorage()
} }
loadAttachmentStorage () {
const promises = []
this.props.data.noteMap.map(note => {
const promise = attachmentManagement.getAttachmentsPathAndStatus(
note.content,
note.storage,
note.key
)
if (promise) promises.push(promise)
})
Promise.all(promises)
.then(data => {
const result = data.reduce((acc, curr) => acc.concat(curr), [])
this.setState({attachments: result})
})
.catch(console.error)
} }
handleAddStorageButton (e) { handleAddStorageButton (e) {
@@ -57,8 +81,39 @@ class StoragesTab extends React.Component {
e.preventDefault() e.preventDefault()
} }
handleRemoveUnusedAttachments (attachments) {
attachmentManagement.removeAttachmentsByPaths(attachments)
.then(() => this.loadAttachmentStorage())
.catch(console.error)
}
renderList () { renderList () {
const { data, boundingBox } = this.props const { data, boundingBox } = this.props
const { attachments } = this.state
const unusedAttachments = attachments.filter(attachment => !attachment.isInUse)
const inUseAttachments = attachments.filter(attachment => attachment.isInUse)
const totalUnusedAttachments = unusedAttachments.length
const totalInuseAttachments = inUseAttachments.length
const totalAttachments = totalUnusedAttachments + totalInuseAttachments
const totalUnusedAttachmentsSize = unusedAttachments
.reduce((acc, curr) => {
const stats = fs.statSync(curr.path)
const fileSizeInBytes = stats.size
return acc + fileSizeInBytes
}, 0)
const totalInuseAttachmentsSize = inUseAttachments
.reduce((acc, curr) => {
const stats = fs.statSync(curr.path)
const fileSizeInBytes = stats.size
return acc + fileSizeInBytes
}, 0)
const totalAttachmentsSize = totalUnusedAttachmentsSize + totalInuseAttachmentsSize
const unusedAttachmentPaths = unusedAttachments
.reduce((acc, curr) => acc.concat(curr.path), [])
if (!boundingBox) { return null } if (!boundingBox) { return null }
const storageList = data.storageMap.map((storage) => { const storageList = data.storageMap.map((storage) => {
@@ -82,6 +137,20 @@ class StoragesTab extends React.Component {
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')} <i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
</button> </button>
</div> </div>
<div styleName='header'>{i18n.__('Attachment storage')}</div>
<p styleName='list-attachment-label'>
Unused attachments size: {humanFileSize(totalUnusedAttachmentsSize)} ({totalUnusedAttachments} items)
</p>
<p styleName='list-attachment-label'>
In use attachments size: {humanFileSize(totalInuseAttachmentsSize)} ({totalInuseAttachments} items)
</p>
<p styleName='list-attachment-label'>
Total attachments size: {humanFileSize(totalAttachmentsSize)} ({totalAttachments} items)
</p>
<button styleName='list-attachement-clear-button'
onClick={() => this.handleRemoveUnusedAttachments(unusedAttachmentPaths)}>
{i18n.__('Clear unused attachments')}
</button>
</div> </div>
) )
} }

View File

@@ -33,6 +33,17 @@
colorDefaultButton() colorDefaultButton()
font-size $tab--button-font-size font-size $tab--button-font-size
border-radius 2px border-radius 2px
.list-attachment-label
margin-bottom 10px
color $ui-text-color
.list-attachement-clear-button
height 30px
border none
border-top-right-radius 2px
border-bottom-right-radius 2px
colorPrimaryButton()
vertical-align middle
padding 0 20px
.addStorage .addStorage
margin-bottom 15px margin-bottom 15px
@@ -154,8 +165,8 @@ body[data-theme="dark"]
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
.list-attachement-clear-button
colorDarkPrimaryButton()
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
@@ -194,6 +205,8 @@ body[data-theme="solarized-dark"]
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
.list-attachement-clear-button
colorSolarizedDarkPrimaryButton()
body[data-theme="monokai"] body[data-theme="monokai"]
.root .root
@@ -232,6 +245,8 @@ body[data-theme="monokai"]
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-monokai-borderColor border-color $ui-monokai-borderColor
.list-attachement-clear-button
colorMonokaiPrimaryButton()
body[data-theme="dracula"] body[data-theme="dracula"]
.root .root
@@ -270,3 +285,5 @@ body[data-theme="dracula"]
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-dracula-borderColor border-color $ui-dracula-borderColor
.list-attachement-clear-button
colorDraculaPrimaryButton()

View File

@@ -14,7 +14,6 @@ import { getLanguages } from 'browser/lib/Languages'
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
const WIN = global.process.platform === 'win32'
const electron = require('electron') const electron = require('electron')
const ipc = electron.ipcRenderer const ipc = electron.ipcRenderer
@@ -32,8 +31,12 @@ class UiTab extends React.Component {
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript') CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css') CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
CodeMirror.autoLoadMode(this.customMarkdownLintConfigCM.getCodeMirror(), 'javascript') CodeMirror.autoLoadMode(this.customMarkdownLintConfigCM.getCodeMirror(), 'javascript')
CodeMirror.autoLoadMode(this.prettierConfigCM.getCodeMirror(), 'javascript')
// Set CM editor Sizes
this.customCSSCM.getCodeMirror().setSize('400px', '400px') this.customCSSCM.getCodeMirror().setSize('400px', '400px')
this.prettierConfigCM.getCodeMirror().setSize('400px', '400px')
this.customMarkdownLintConfigCM.getCodeMirror().setSize('400px', '200px') this.customMarkdownLintConfigCM.getCodeMirror().setSize('400px', '200px')
this.handleSettingDone = () => { this.handleSettingDone = () => {
this.setState({UiAlert: { this.setState({UiAlert: {
type: 'success', type: 'success',
@@ -107,7 +110,9 @@ class UiTab extends React.Component {
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.getCodeMirror().getValue() customMarkdownLintConfig: this.customMarkdownLintConfigCM.getCodeMirror().getValue(),
prettierConfig: this.prettierConfigCM.getCodeMirror().getValue(),
deleteUnusedAttachments: this.refs.deleteUnusedAttachments.checked
}, },
preview: { preview: {
fontSize: this.refs.previewFontSize.value, fontSize: this.refs.previewFontSize.value,
@@ -125,6 +130,7 @@ class UiTab extends React.Component {
breaks: this.refs.previewBreaks.checked, breaks: this.refs.previewBreaks.checked,
smartArrows: this.refs.previewSmartArrows.checked, smartArrows: this.refs.previewSmartArrows.checked,
sanitize: this.refs.previewSanitize.value, sanitize: this.refs.previewSanitize.value,
mermaidHTMLLabel: this.refs.previewMermaidHTMLLabel.checked,
allowCustomCSS: this.refs.previewAllowCustomCSS.checked, allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
lineThroughCheckbox: this.refs.lineThroughCheckbox.checked, lineThroughCheckbox: this.refs.lineThroughCheckbox.checked,
customCSS: this.customCSSCM.getCodeMirror().getValue() customCSS: this.customCSSCM.getCodeMirror().getValue()
@@ -612,6 +618,16 @@ class UiTab extends React.Component {
{i18n.__('Enable spellcheck - Experimental feature!! :)')} {i18n.__('Enable spellcheck - Experimental feature!! :)')}
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.deleteUnusedAttachments}
ref='deleteUnusedAttachments'
type='checkbox'
/>&nbsp;
{i18n.__('Delete attachments, that are not referenced in the text anymore')}
</label>
</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
@@ -813,6 +829,16 @@ class UiTab extends React.Component {
</select> </select>
</div> </div>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.mermaidHTMLLabel}
ref='previewMermaidHTMLLabel'
type='checkbox'
/>&nbsp;
{i18n.__('Enable HTML label in mermaid flowcharts')}
</label>
</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('LaTeX Inline Open Delimiter')} {i18n.__('LaTeX Inline Open Delimiter')}
@@ -904,7 +930,27 @@ class UiTab extends React.Component {
</div> </div>
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Prettier Config')}
</div>
<div styleName='group-section-control'>
<div style={{fontFamily}}>
<ReactCodeMirror
width='400px'
height='400px'
onChange={e => this.handleUIChange(e)}
ref={e => (this.prettierConfigCM = e)}
value={config.editor.prettierConfig}
options={{
lineNumbers: true,
mode: 'application/json',
lint: true,
theme: codemirrorTheme
}} />
</div>
</div>
</div>
<div styleName='group-control'> <div styleName='group-control'>
<button styleName='group-control-rightButton' <button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveUIClick(e)}>{i18n.__('Save')} onClick={(e) => this.handleSaveUIClick(e)}>{i18n.__('Save')}

View File

@@ -410,6 +410,15 @@ $ui-dracula-button--active-color = #f8f8f2
$ui-dracula-button--active-backgroundColor = #bd93f9 $ui-dracula-button--active-backgroundColor = #bd93f9
$ui-dracula-button--hover-backgroundColor = lighten($ui-dracula-backgroundColor, 10%) $ui-dracula-button--hover-backgroundColor = lighten($ui-dracula-backgroundColor, 10%)
$ui-dracula-button--focus-borderColor = lighten(#44475a, 25%) $ui-dracula-button--focus-borderColor = lighten(#44475a, 25%)
colorDraculaDefaultButton()
border-color $ui-dracula-borderColor
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
&:hover
background-color $ui-dracula-button--hover-backgroundColor
&:active
&:active:hover
background-color $ui-dracula-button--active-backgroundColor
modalDracula() modalDracula()
position relative position relative

View File

@@ -5,24 +5,23 @@ const ipc = electron.ipcMain
const GhReleases = require('electron-gh-releases') const GhReleases = require('electron-gh-releases')
const { isPackaged } = app const { isPackaged } = app
// electron.crashReporter.start() // electron.crashReporter.start()
const singleInstance = app.requestSingleInstanceLock()
var ipcServer = null var ipcServer = null
var mainWindow = null var mainWindow = null
var shouldQuit = app.makeSingleInstance(function (commandLine, workingDirectory) { // Single Instance Lock
if (!singleInstance) {
app.quit()
} else {
app.on('second-instance', () => {
// Someone tried to run a second instance, it should focus the existing instance.
if (mainWindow) { if (mainWindow) {
if (process.platform === 'win32') { if (!mainWindow.isVisible()) mainWindow.show()
mainWindow.minimize()
mainWindow.restore()
}
mainWindow.focus() mainWindow.focus()
} }
return true
}) })
if (shouldQuit) {
app.quit()
} }
var isUpdateReady = false var isUpdateReady = false

View File

@@ -73,6 +73,11 @@
mix-blend-mode: difference; mix-blend-mode: difference;
} }
.CodeMirror-scroll {
margin-bottom: 0;
padding-bottom: 0;
}
.CodeMirror-lint-tooltip { .CodeMirror-lint-tooltip {
z-index: 1003; z-index: 1003;
} }

View File

@@ -71,6 +71,11 @@
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 {
margin-bottom: 0;
padding-bottom: 0;
}
</style> </style>
</head> </head>

View File

@@ -157,5 +157,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -213,5 +213,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -188,5 +188,6 @@
"New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags", "New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -159,5 +159,6 @@
"Show menu bar": "Mostrar barra del menú", "Show menu bar": "Mostrar barra del menú",
"Auto Detect": "Detección automática", "Auto Detect": "Detección automática",
"Snippet Default Language": "Lenguaje por defecto de los fragmentos de código", "Snippet Default Language": "Lenguaje por defecto de los fragmentos de código",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -161,5 +161,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -173,5 +173,6 @@
"Snippet prefix": "Préfixe du snippet", "Snippet prefix": "Préfixe du snippet",
"Delete Note": "Supprimer la note", "Delete Note": "Supprimer la note",
"New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage", "New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -181,5 +181,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -161,5 +161,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -220,5 +220,6 @@
"Spellcheck disabled": "スペルチェック無効", "Spellcheck disabled": "スペルチェック無効",
"Show menu bar": "メニューバーを表示", "Show menu bar": "メニューバーを表示",
"Auto Detect": "自動検出", "Auto Detect": "自動検出",
"Enable HTML label in mermaid flowcharts": "mermaid flowchartでHTMLラベルを有効にする ⚠ このオプションには潜在的なXSSの危険性があります。",
"Wrap line in Snippet Note": "行を右端で折り返すSnippet Note" "Wrap line in Snippet Note": "行を右端で折り返すSnippet Note"
} }

View File

@@ -164,5 +164,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -157,5 +157,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -166,5 +166,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -157,5 +157,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -156,5 +156,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -154,5 +154,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -156,5 +156,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -183,5 +183,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -156,5 +156,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -4,7 +4,7 @@
"Preferences": "首选项", "Preferences": "首选项",
"Make a note": "新建笔记", "Make a note": "新建笔记",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl", "Ctrl(^)": "Ctrl(^)",
"to create a new note": "新建笔记", "to create a new note": "新建笔记",
"Toggle Mode": "切换模式", "Toggle Mode": "切换模式",
"Trash": "废纸篓", "Trash": "废纸篓",
@@ -183,7 +183,7 @@
"Help": "帮助", "Help": "帮助",
"Hungarian": "匈牙利语", "Hungarian": "匈牙利语",
"Hide Help": "隐藏帮助", "Hide Help": "隐藏帮助",
"wordpress": "Wordpress", "wordpress": "wordpress",
"Add Storage": "添加存储", "Add Storage": "添加存储",
"Name": "名称", "Name": "名称",
"Type": "类型", "Type": "类型",
@@ -219,7 +219,29 @@
"Allow custom CSS for preview": "允许预览自定义 CSS", "Allow custom CSS for preview": "允许预览自定义 CSS",
"Render newlines in Markdown paragraphs as <br>": "在 Markdown 段落中使用 <br> 换行", "Render newlines in Markdown paragraphs as <br>": "在 Markdown 段落中使用 <br> 换行",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "显示菜单栏",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Enable HTML label in mermaid flowcharts": "在 mermaid flowcharts 中启用 HTML 标签 ⚠ 这个选项可能会产生 XSS",
"Wrap line in Snippet Note": "在 Snippet Note 里换行",
"Toggle Editor Mode": "切换编辑模式",
"Insert Current Date": "插入当前日期",
"Insert Current Date and Time": "插入当前日期和时间",
"Paste HTML": "粘贴 HTML",
"Show/Hide Menu Bar": "显示/隐藏 菜单栏",
"Save tags of a note in alphabetical order": "按字母顺序存储标签",
"Show tags of a note in alphabetical order": "按字母顺序显示标签",
"Enable live count of notes": "实时统计标签下笔记个数",
"New notes are tagged with the filtering tags": "新建的笔记带有在标签列表过滤的标签",
"Front matter title field": "从 front-matter 中抽取标题的字段名",
"Extract title from front matter": "启用从 front-matter 抽取标题",
"Enable HTML paste": "启用 HTML 粘贴(自动转换 html 到 md",
"Enable spellcheck - Experimental feature!! :)": "启用拼写检查 - 实验性功能!! :)",
"Matching character pairs": "Matching character pairs",
"Matching character triples": "Matching character triples",
"Exploding character pairs": "Exploding character pairs",
"Custom MarkdownLint Rules": "自定义 MarkdownLint 规则",
"Enable MarkdownLint": "启用 MarkdownLint",
"When scrolling, synchronize preview with editor": "滚动编辑页时同步滚动预览页",
"This will delete all notes in the folder and can not be undone.": "即将删除文件夹中所有笔记,并且不能撤销。",
"Always Ask": "每次询问"
} }

View File

@@ -165,5 +165,6 @@
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect", "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note" "Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -1,14 +1,15 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.12.1", "version": "0.13.0",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
"scripts": { "scripts": {
"start": "electron ./index.js", "start": "electron ./index.js",
"compile": "grunt compile", "compile": "grunt compile",
"test": "cross-env NODE_ENV=test ava --serial", "test": "npm run ava && npm run jest",
"ava": "cross-env NODE_ENV=test ava --serial",
"jest": "jest", "jest": "jest",
"fix": "eslint . --fix", "fix": "eslint . --fix",
"lint": "eslint .", "lint": "eslint .",
@@ -50,6 +51,7 @@
"homepage": "https://boostnote.io", "homepage": "https://boostnote.io",
"dependencies": { "dependencies": {
"@enyaxu/markdown-it-anchor": "^5.0.2", "@enyaxu/markdown-it-anchor": "^5.0.2",
"@hikerpig/markdown-it-toc-and-anchor": "^4.5.0",
"@rokt33r/js-sequence-diagrams": "^2.0.6-2", "@rokt33r/js-sequence-diagrams": "^2.0.6-2",
"@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/markdown-it-math": "^4.0.1",
"@rokt33r/season": "^5.3.0", "@rokt33r/season": "^5.3.0",
@@ -98,6 +100,7 @@
"mousetrap": "^1.6.2", "mousetrap": "^1.6.2",
"mousetrap-global-bind": "^1.1.0", "mousetrap-global-bind": "^1.1.0",
"node-ipc": "^8.1.0", "node-ipc": "^8.1.0",
"prettier": "^1.18.2",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"query-string": "^6.5.0", "query-string": "^6.5.0",
"raphael": "^2.2.7", "raphael": "^2.2.7",

6
prettier.config Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}

View File

@@ -1,4 +1,4 @@
:mega: The Boostnote team uses [IssueHunt](https://issuehunt.io/) for a sustainable open-source ecosystem. :mega: The renewal will be released end of Nov, 2019. [To keep updated, subscribe our mailing list!](https://boostnote.io/#subscribe)
![Boostnote app screenshot](./resources/repository/top.png) ![Boostnote app screenshot](./resources/repository/top.png)
@@ -14,7 +14,6 @@
## Authors & Maintainers ## Authors & Maintainers
- [Rokt33r](https://github.com/rokt33r) - [Rokt33r](https://github.com/rokt33r)
- [Sosuke](https://github.com/sosukesuzuki)
- [Kazz](https://github.com/kazup01) - [Kazz](https://github.com/kazup01)
- [ZeroX-DG](https://github.com/ZeroX-DG) - [ZeroX-DG](https://github.com/ZeroX-DG)
@@ -33,7 +32,7 @@ Issues on Boostnote can be funded by anyone and the money will be distributed to
## 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/enQtMzkxOTk4ODkyNzc0LThkNmMzY2VlZjVhYTNiYjE5YjQyZGVjNTJlYTY1OGMyZTFjNGU5YTUyYjUzOWZhYTU4OTVlNDYyNDFjYWMzNDM) - [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LWQxZTQwNjBlMDI4YjkyYjg2MTRiZGJhNzA1YjQ5ODA5M2M0M2NlMjI5YjhiYWQzNzgzYmU0MDMwOTlmZmZmMGE)
- [Blog](https://medium.com/boostnote) - [Blog](https://medium.com/boostnote)
- [Reddit](https://www.reddit.com/r/Boostnote/) - [Reddit](https://www.reddit.com/r/Boostnote/)

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<title>icon-external</title>
<defs>
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowOffsetOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="Artboard-4" stroke="none" stroke-width="1" fill="#c7c7c7" fill-rule="evenodd" transform="" stroke-linecap="round" stroke-linejoin="round">
<g>
<g>
<path d="M412.88,261.464c-11.423,0-20.682,9.259-20.682,20.682v156.879c0,17.43-14.181,31.611-31.612,31.611H72.975
c-17.43,0-31.611-14.181-31.611-31.611V151.414c0-17.43,14.181-31.611,31.611-31.611h156.879c11.422,0,20.682-9.26,20.682-20.682
c0-11.422-9.26-20.682-20.682-20.682H72.975C32.737,78.439,0,111.176,0,151.414v287.611C0,479.264,32.737,512,72.975,512h287.61
c40.239,0,72.976-32.736,72.977-72.975V282.146C433.562,270.723,424.303,261.464,412.88,261.464z"/>
</g>
</g>
<g>
<g>
<path d="M491.318,0H334.439c-11.423,0-20.682,9.26-20.682,20.682c0,11.422,9.259,20.682,20.682,20.682h136.197v136.197
c0,11.422,9.259,20.682,20.682,20.682c11.423,0,20.682-9.26,20.682-20.682V20.682C512,9.26,502.741,0,491.318,0z"/>
</g>
</g>
<g>
<g>
<path d="M505.942,6.058c-8.077-8.076-21.172-8.076-29.249,0L189.082,293.668c-8.077,8.077-8.077,21.172,0,29.249
c4.038,4.039,9.332,6.058,14.625,6.058c5.294,0,10.587-2.02,14.625-6.058L505.942,35.307
C514.019,27.23,514.019,14.135,505.942,6.058z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -578,6 +578,72 @@ it('should test that deleteAttachmentsNotPresentInNote does nothing if noteKey,
expect(fs.unlink).not.toHaveBeenCalled() expect(fs.unlink).not.toHaveBeenCalled()
}) })
it('should test that getAttachmentsPathAndStatus return null if noteKey, storageKey or noteContent was undefined', function () {
const noteKey = undefined
const storageKey = undefined
const markdownContent = ''
const result = systemUnderTest.getAttachmentsPathAndStatus(markdownContent, storageKey, noteKey)
expect(result).toBeNull()
})
it('should test that getAttachmentsPathAndStatus return null if noteKey, storageKey or noteContent was null', function () {
const noteKey = null
const storageKey = null
const markdownContent = ''
const result = systemUnderTest.getAttachmentsPathAndStatus(markdownContent, storageKey, noteKey)
expect(result).toBeNull()
})
it('should test that getAttachmentsPathAndStatus return the correct path and status for attachments', async function () {
const dummyStorage = {path: 'dummyStoragePath'}
const noteKey = 'noteKey'
const storageKey = 'storageKey'
const markdownContent =
'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + noteKey + path.win32.sep + 'file2.pdf](file2.pdf) \n'
const dummyFilesInFolder = ['file1.txt', 'file2.pdf', 'file3.jpg']
findStorage.findStorage = jest.fn(() => dummyStorage)
fs.existsSync = jest.fn(() => true)
fs.readdir = jest.fn((paht, callback) => callback(undefined, dummyFilesInFolder))
fs.unlink = jest.fn()
const targetStorage = findStorage.findStorage(storageKey)
const attachments = await systemUnderTest.getAttachmentsPathAndStatus(markdownContent, storageKey, noteKey)
expect(attachments.length).toBe(3)
expect(attachments[0].isInUse).toBe(false)
expect(attachments[1].isInUse).toBe(true)
expect(attachments[2].isInUse).toBe(false)
expect(attachments[0].path).toBe(
path.join(
targetStorage.path,
systemUnderTest.DESTINATION_FOLDER,
noteKey,
dummyFilesInFolder[0]
)
)
expect(attachments[1].path).toBe(
path.join(
targetStorage.path,
systemUnderTest.DESTINATION_FOLDER,
noteKey,
dummyFilesInFolder[1]
)
)
expect(attachments[2].path).toBe(
path.join(
targetStorage.path,
systemUnderTest.DESTINATION_FOLDER,
noteKey,
dummyFilesInFolder[2]
)
)
})
it('should test that moveAttachments moves attachments only if the source folder existed', function () { it('should test that moveAttachments moves attachments only if the source folder existed', function () {
fse.existsSync = jest.fn(() => false) fse.existsSync = jest.fn(() => false)
fse.moveSync = jest.fn() fse.moveSync = jest.fn()

View File

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

View File

@@ -109,6 +109,84 @@ const footnote = `
hello-world: https://github.com/BoostIO/Boostnote/ hello-world: https://github.com/BoostIO/Boostnote/
` `
const tocPlaceholder = `
[TOC]
# H1
## H2
### H3
###$ H4
`
const plantUmlMindMap = `
@startmindmap
* Debian
** Ubuntu
*** Linux Mint
*** Kubuntu
*** Lubuntu
*** KDE Neon
** LMDE
** SolydXK
** SteamOS
** Raspbian with a very long name
*** <s>Raspmbc</s> => OSMC
*** <s>Raspyfi</s> => Volumio
@endmindmap
`
const plantUmlGantt = `
@startgantt
[Prototype design] lasts 15 days
[Test prototype] lasts 10 days
[Test prototype] starts at [Prototype design]'s end
@endgantt
`
const plantUmlWbs = `
@startwbs
* Business Process Modelling WBS
** Launch the project
*** Complete Stakeholder Research
*** Initial Implementation Plan
** Design phase
*** Model of AsIs Processes Completed
**** Model of AsIs Processes Completed1
**** Model of AsIs Processes Completed2
*** Measure AsIs performance metrics
*** Identify Quick Wins
** Complete innovate phase
@endwbs
`
const plantUmlUml = `
@startuml
left to right direction
skinparam packageStyle rectangle
actor customer
actor clerk
rectangle checkout {
customer -- (checkout)
(checkout) .> (payment) : include
(help) .> (checkout) : extends
(checkout) -- clerk
}
@enduml
`
const plantUmlDitaa = `
@startditaa
+--------+ +-------+ +-------+
| +---+ ditaa +--> | |
| Text | +-------+ |Diagram|
|Dokument| |!Magie!| | |
| {d}| | | | |
+---+----+ +-------+ +-------+
: ^
| Ein Haufen Arbeit |
+-------------------------+
@endditaa
`
export default { export default {
basic, basic,
codeblock, codeblock,
@@ -121,5 +199,11 @@ export default {
supTexts, supTexts,
deflists, deflists,
shortcuts, shortcuts,
footnote footnote,
tocPlaceholder,
plantUmlMindMap,
plantUmlGantt,
plantUmlWbs,
plantUmlDitaa,
plantUmlUml
} }

View File

@@ -73,3 +73,33 @@ test('Markdown.render() should render footnote correctly', t => {
const rendered = md.render(markdownFixtures.footnote) const rendered = md.render(markdownFixtures.footnote)
t.snapshot(rendered) t.snapshot(rendered)
}) })
test('Markdown.render() should renders [TOC] placholder correctly', t => {
const rendered = md.render(markdownFixtures.tocPlaceholder)
t.snapshot(rendered)
})
test('Markdown.render() should render PlantUML MindMaps correctly', t => {
const rendered = md.render(markdownFixtures.plantUmlMindMap)
t.snapshot(rendered)
})
test('Markdown.render() should render PlantUML Gantt correctly', t => {
const rendered = md.render(markdownFixtures.plantUmlGantt)
t.snapshot(rendered)
})
test('Markdown.render() should render PlantUML WBS correctly', t => {
const rendered = md.render(markdownFixtures.plantUmlWbs)
t.snapshot(rendered)
})
test('Markdown.render() should render PlantUML Umls correctly', t => {
const rendered = md.render(markdownFixtures.plantUmlUml)
t.snapshot(rendered)
})
test('Markdown.render() should render PlantUML Ditaa correctly', t => {
const rendered = md.render(markdownFixtures.plantUmlDitaa)
t.snapshot(rendered)
})

View File

@@ -4,6 +4,41 @@ The actual snapshot is saved in `markdown-test.js.snap`.
Generated by [AVA](https://ava.li). Generated by [AVA](https://ava.li).
## Markdown.render() should render PlantUML Ditaa correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/png/SoWkIImgISaiIKpaqjQ50cq51GLj93Q2mrMZ00NQO3cmHX3RJW4cKmDI4v9QKQ805a8nfyObCp6zA34NgCObFxiqDpMl1AIcHj4tCJqpLH5i18evG52TKbk3B8og1kmC0cvMKB1Im0NYkA2ckMRcANWabgQbvYau5YMbPfP0p4UOWmcqkHnIyrB0GG00" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML Gantt correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/SoWkIImgIK_CAodXYWueoY_9BwaiI5L8IItEJC-BLSX9B2ufLZ0qLKX9h2pcYWv9BIvHA82fWaiRu906crsia5YYW6cqUh52QbuAbmEG0DiE0000" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML MindMaps correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/JOzD3e8m44Rtd6BMtNW192IM5I29HEDsAbKdeLD2MvNRIsjCMCsRlFd9LpgFipV4Wy4f4o2r8kHC23Yhm3wi9A0X3XzeYNrgwx1H6wvb1KTjqtRJoYhMtexBSAqJUescwoEUq4tn3xp9Fm7XfUS5HiiFO3Gw7SjT4QUCkkKxLy2-WAvl3rkrtEclBdOCXcnMwZN7ByiN" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML Umls correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/LOzD2eCm44RtESMtj0jx01V5E_G4Gvngo2_912gbTsz4LBfylCV7p5Y4ibJlbEENG2AocHV1P39hCJ6eOar8bCaZaROqyrDMnzWqXTcn8YqnGzSYqNC-q76sweoW5zOsLi57uMpHz-WESslY0jmVw1AjdaE30IPeLoVUceLTslrL3-2tS9ZA_qZRtm_vgh7PzkOF" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML WBS correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/ZP2_JiD03CRtFeNdRF04fR140gdGeREv-z8plVYYimFYxSabKbaxsR9-ylTdRyxLVpvjrz5XDb6OqR6MqEPRYSXPz4BdmsdNTVJAiuP4da1JBLy8lbmxUYxZbE6Wa_CLgUI8IXymS0rf9NeL5yxKDt24EhiKfMDcRNzVO79HcX8RLdvLfZBGa_KtFx2RKcpK7TZ3dTpZfWgskMAZ9jIXr94rW4PubM1RbBZOb-6NtcS9LpgBjlj_1w9QldbPjZHxQ5pg_GC0" alt="uml diagram" />␊
`
## Markdown.render() should render footnote correctly ## Markdown.render() should render footnote correctly
> Snapshot 1 > Snapshot 1
@@ -48,6 +83,28 @@ Generated by [AVA](https://ava.li).
`<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>␊ `<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>␊
` `
## Markdown.render() should renders [TOC] placholder correctly
> Snapshot 1
`<p data-line="1"><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>␊
</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>␊
`
## Markdown.render() should renders abbrevations correctly ## Markdown.render() should renders abbrevations correctly
> Snapshot 1 > Snapshot 1
@@ -169,4 +226,39 @@ Generated by [AVA](https://ava.li).
> Snapshot 2 > Snapshot 2
`<p data-line="0">This is a &quot;QUOTE&quot;.</p>␊ `<p data-line="0">This is a &quot;QUOTE&quot;.</p>␊
## Markdown.render() should render PlantUML Ditaa correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/png/SoWkIImgISaiIKpaqjQ50cq51GLj93Q2mrMZ00NQO3cmHX3RJW4cKmDI4v9QKQ805a8nfyObCp6zA34NgCObFxiqDpMl1AIcHj4tCJqpLH5i18evG52TKbk3B8og1kmC0cvMKB1Im0NYkA2ckMRcANWabgQbvYau5YMbPfP0p4UOWmcqkHnIyrB0GG00" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML Gantt correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/SoWkIImgIK_CAodXYWueoY_9BwaiI5L8IItEJC-BLSX9B2ufLZ0qLKX9h2pcYWv9BIvHA82fWaiRu906crsia5YYW6cqUh52QbuAbmEG0DiE0000" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML MindMaps correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/JOzD3e8m44Rtd6BMtNW192IM5I29HEDsAbKdeLD2MvNRIsjCMCsRlFd9LpgFipV4Wy4f4o2r8kHC23Yhm3wi9A0X3XzeYNrgwx1H6wvb1KTjqtRJoYhMtexBSAqJUescwoEUq4tn3xp9Fm7XfUS5HiiFO3Gw7SjT4QUCkkKxLy2-WAvl3rkrtEclBdOCXcnMwZN7ByiN" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML Umls correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/LOzD2eCm44RtESMtj0jx01V5E_G4Gvngo2_912gbTsz4LBfylCV7p5Y4ibJlbEENG2AocHV1P39hCJ6eOar8bCaZaROqyrDMnzWqXTcn8YqnGzSYqNC-q76sweoW5zOsLi57uMpHz-WESslY0jmVw1AjdaE30IPeLoVUceLTslrL3-2tS9ZA_qZRtm_vgh7PzkOF" alt="uml diagram" />␊
`
## Markdown.render() should render PlantUML WBS correctly
> Snapshot 1
`<img src="http://www.plantuml.com/plantuml/svg/ZP2_JiD03CRtFeNdRF04fR140gdGeREv-z8plVYYimFYxSabKbaxsR9-ylTdRyxLVpvjrz5XDb6OqR6MqEPRYSXPz4BdmsdNTVJAiuP4da1JBLy8lbmxUYxZbE6Wa_CLgUI8IXymS0rf9NeL5yxKDt24EhiKfMDcRNzVO79HcX8RLdvLfZBGa_KtFx2RKcpK7TZ3dTpZfWgskMAZ9jIXr94rW4PubM1RbBZOb-6NtcS9LpgBjlj_1w9QldbPjZHxQ5pg_GC0" alt="uml diagram" />␊
` `

View File

@@ -30,6 +30,7 @@ var config = {
] ]
}, },
externals: [ externals: [
'prettier',
'node-ipc', 'node-ipc',
'electron', 'electron',
'lodash', 'lodash',

View File

@@ -74,6 +74,14 @@
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@enyaxu/markdown-it-anchor/-/markdown-it-anchor-5.0.2.tgz#d173f7b60b492aabc17dfba864c4d071f5595f72" resolved "https://registry.yarnpkg.com/@enyaxu/markdown-it-anchor/-/markdown-it-anchor-5.0.2.tgz#d173f7b60b492aabc17dfba864c4d071f5595f72"
"@hikerpig/markdown-it-toc-and-anchor@^4.5.0":
version "4.5.0"
resolved "https://registry.npmjs.org/@hikerpig/markdown-it-toc-and-anchor/-/markdown-it-toc-and-anchor-4.5.0.tgz#c1652bdebfd07d41c9738254891515d759b054f0"
integrity sha512-PaYl/v9/ViceXm+fC+WoQOD/8lvYf76SnA3s8b/BQ6s3NpZdk/W/aW0dg5YHquQNcdaLfz3lmQTt6iafbe5tbg==
dependencies:
clone "^2.1.0"
uslug "^1.0.4"
"@ladjs/time-require@^0.1.4": "@ladjs/time-require@^0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@ladjs/time-require/-/time-require-0.1.4.tgz#5c615d75fd647ddd5de9cf6922649558856b21a1" resolved "https://registry.yarnpkg.com/@ladjs/time-require/-/time-require-0.1.4.tgz#5c615d75fd647ddd5de9cf6922649558856b21a1"
@@ -1814,6 +1822,11 @@ clone@^1.0.2:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
clone@^2.1.0:
version "2.1.2"
resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
co-with-promise@^4.6.0: co-with-promise@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/co-with-promise/-/co-with-promise-4.6.0.tgz#413e7db6f5893a60b942cf492c4bec93db415ab7" resolved "https://registry.yarnpkg.com/co-with-promise/-/co-with-promise-4.6.0.tgz#413e7db6f5893a60b942cf492c4bec93db415ab7"
@@ -6771,8 +6784,8 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e"
nwsapi@^2.0.0: nwsapi@^2.0.0:
version "2.0.0" version "2.0.1"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.0.tgz#7c8faf4ad501e1d17a651ebc5547f966b547c5c7" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.1.tgz#a50d59a2dcb14b6931401171713ced2d0eb3468f"
nwsapi@^2.0.7: nwsapi@^2.0.7:
version "2.0.8" version "2.0.8"
@@ -7482,6 +7495,11 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.18.2:
version "1.18.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
pretty-bytes@^1.0.2: pretty-bytes@^1.0.2:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
@@ -9552,6 +9570,11 @@ universalify@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
"unorm@>= 1.0.0":
version "1.6.0"
resolved "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz#029b289661fba714f1a9af439eb51d9b16c205af"
integrity sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==
unpipe@1.0.0, unpipe@~1.0.0: unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -9629,6 +9652,13 @@ user-home@^2.0.0:
dependencies: dependencies:
os-homedir "^1.0.0" os-homedir "^1.0.0"
uslug@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/uslug/-/uslug-1.0.4.tgz#b9a22f0914e0a86140633dacc302e5f4fa450677"
integrity sha1-uaIvCRTgqGFAYz2swwLl9PpFBnc=
dependencies:
unorm ">= 1.0.0"
utf8-byte-length@^1.0.1: utf8-byte-length@^1.0.1:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"