mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
102 Commits
v0.12.1-0
...
Flexo013-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b96661cd9 | ||
|
|
d010c5532d | ||
|
|
f2dc8b8020 | ||
|
|
1798353eac | ||
|
|
772a8b2383 | ||
|
|
5690c8361a | ||
|
|
6d09cf227c | ||
|
|
8736666e91 | ||
|
|
d1fd5cfb45 | ||
|
|
3eabf95fb3 | ||
|
|
8ea920ef91 | ||
|
|
3c0f20f364 | ||
|
|
59f8425c97 | ||
|
|
f181a7e459 | ||
|
|
6b1c595f87 | ||
|
|
0003de8f08 | ||
|
|
5357d8dc04 | ||
|
|
d069722bf9 | ||
|
|
3f4dd49a8f | ||
|
|
be06b3f7e8 | ||
|
|
5044bdda00 | ||
|
|
fbeffb0b5d | ||
|
|
ef0af39aa7 | ||
|
|
0697bc0a74 | ||
|
|
43d8ebb3c4 | ||
|
|
68175cd71b | ||
|
|
f4d87f64ae | ||
|
|
68b3077651 | ||
|
|
1332b337f3 | ||
|
|
e9975d1ea5 | ||
|
|
2c103aca3d | ||
|
|
c0a5eb0d2b | ||
|
|
ff7c4495f0 | ||
|
|
35fe639cd2 | ||
|
|
59add8982e | ||
|
|
8d9c514097 | ||
|
|
6f880d0f02 | ||
|
|
ec47ee8110 | ||
|
|
28b8141c6b | ||
|
|
5b0b309c49 | ||
|
|
0b84a372f6 | ||
|
|
8355e1e006 | ||
|
|
c7d33fbd83 | ||
|
|
cf324d93fe | ||
|
|
9a704a2bcb | ||
|
|
1e00651541 | ||
|
|
857e75594d | ||
|
|
2f1dadfc3e | ||
|
|
7d0404657e | ||
|
|
b9dd651fc1 | ||
|
|
25ef456af2 | ||
|
|
084decaa85 | ||
|
|
330a444fc5 | ||
|
|
a47dac2854 | ||
|
|
08070f3e2d | ||
|
|
2352c78cb6 | ||
|
|
6ef9c3865f | ||
|
|
ff9789b5a7 | ||
|
|
f09297f406 | ||
|
|
2d3c69d178 | ||
|
|
b837653cf1 | ||
|
|
eeca031c86 | ||
|
|
918a8627e9 | ||
|
|
86370edd1e | ||
|
|
1173631255 | ||
|
|
911fd9a004 | ||
|
|
0ad3da5bbc | ||
|
|
89ae2a9516 | ||
|
|
70892cae05 | ||
|
|
de0af153bc | ||
|
|
33161e46e6 | ||
|
|
7e3c662374 | ||
|
|
a39e9c2da6 | ||
|
|
72b8d56245 | ||
|
|
0d36f59036 | ||
|
|
a3f7d2287a | ||
|
|
8edfbd28ed | ||
|
|
606be4304d | ||
|
|
329066719e | ||
|
|
93b8ef35f7 | ||
|
|
484b003b34 | ||
|
|
c2a26a8547 | ||
|
|
addf9b920f | ||
|
|
aeb77e5a40 | ||
|
|
1d59d89588 | ||
|
|
bde357f952 | ||
|
|
558c091205 | ||
|
|
f67175e628 | ||
|
|
390f6d545f | ||
|
|
44efb0178c | ||
|
|
37eee26bdf | ||
|
|
ed4a670f0a | ||
|
|
fbb9afe34f | ||
|
|
020bc11bd7 | ||
|
|
ae0837e29b | ||
|
|
f0380ef733 | ||
|
|
25bdaf9f00 | ||
|
|
ef1809305c | ||
|
|
090b5c32f0 | ||
|
|
18aae8cf7b | ||
|
|
4a9bc69ac2 | ||
|
|
d97e62f864 |
@@ -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
1
.gitignore
vendored
@@ -10,3 +10,4 @@ node_modules/*
|
|||||||
*.log
|
*.log
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
package-lock.json
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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,13 +338,13 @@ 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,
|
||||||
this.props.storagePath
|
this.props.storagePath
|
||||||
)
|
)
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
file = file.replace('file:///', '')
|
file = file.replace('file:///', '')
|
||||||
@@ -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 () {
|
||||||
@@ -637,24 +668,26 @@ 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) {
|
||||||
const theme = consts.THEMES.find(theme => theme.name === name)
|
const theme = consts.THEMES.find(theme => theme.name === name)
|
||||||
|
|
||||||
return theme
|
return theme != null
|
||||||
? (win ? theme.path : `${appPath}/${theme.path}`)
|
? theme.path
|
||||||
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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] }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const themes = paths
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
path: path.join(directory.split(/\//g).slice(-3).join('/'), file),
|
path: path.join(directory, file),
|
||||||
className: `cm-s-${name}`
|
className: `cm-s-${name}`
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -28,17 +28,16 @@ const themes = paths
|
|||||||
|
|
||||||
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
|
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
|
||||||
name: 'solarized dark',
|
name: 'solarized dark',
|
||||||
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
className: `cm-s-solarized cm-s-dark`
|
className: `cm-s-solarized cm-s-dark`
|
||||||
}, {
|
}, {
|
||||||
name: 'solarized light',
|
name: 'solarized light',
|
||||||
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
className: `cm-s-solarized cm-s-light`
|
className: `cm-s-solarized cm-s-light`
|
||||||
})
|
})
|
||||||
|
|
||||||
themes.splice(0, 0, {
|
themes.splice(0, 0, {
|
||||||
name: 'default',
|
name: 'default',
|
||||||
path: `${CODEMIRROR_THEME_PATH}/elegant.css`,
|
path: path.join(paths[0], 'elegant.css'),
|
||||||
className: `cm-s-default`
|
className: `cm-s-default`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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]+)\:/)
|
||||||
|
|||||||
@@ -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
9
browser/lib/turndown.js
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
69
browser/main/Detail/FromUrlButton.js
Normal file
69
browser/main/Detail/FromUrlButton.js
Normal 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)
|
||||||
41
browser/main/Detail/FromUrlButton.styl
Normal file
41
browser/main/Detail/FromUrlButton.styl
Normal 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)
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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: {
|
||||||
@@ -143,7 +154,7 @@ function get () {
|
|||||||
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
|
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
|
||||||
|
|
||||||
if (theme) {
|
if (theme) {
|
||||||
editorTheme.setAttribute('href', win ? theme.path : `../${theme.path}`)
|
editorTheme.setAttribute('href', theme.path)
|
||||||
} else {
|
} else {
|
||||||
config.editor.theme = 'default'
|
config.editor.theme = 'default'
|
||||||
}
|
}
|
||||||
@@ -191,7 +202,7 @@ function set (updates) {
|
|||||||
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
|
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
|
||||||
|
|
||||||
if (newTheme) {
|
if (newTheme) {
|
||||||
editorTheme.setAttribute('href', win ? newTheme.path : `../${newTheme.path}`)
|
editorTheme.setAttribute('href', newTheme.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.send('config-renew', {
|
ipcRenderer.send('config-renew', {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
79
browser/main/lib/dataApi/createNoteFromUrl.js
Normal file
79
browser/main/lib/dataApi/createNoteFromUrl.js
Normal 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
|
||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
118
browser/main/modals/CreateMarkdownFromURLModal.js
Normal file
118
browser/main/modals/CreateMarkdownFromURLModal.js
Normal 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)
|
||||||
160
browser/main/modals/CreateMarkdownFromURLModal.styl
Normal file
160
browser/main/modals/CreateMarkdownFromURLModal.styl
Normal 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()
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -137,7 +143,7 @@ class UiTab extends React.Component {
|
|||||||
const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
|
const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
|
||||||
|
|
||||||
if (theme) {
|
if (theme) {
|
||||||
checkHighLight.setAttribute('href', win ? theme.path : `../${theme.path}`)
|
checkHighLight.setAttribute('href', theme.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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'
|
||||||
|
/>
|
||||||
|
{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'
|
||||||
|
/>
|
||||||
|
{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')}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": "每次询问"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.12.1-0",
|
"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
6
prettier.config
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -11,10 +11,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
[Find the latest release of Boostnote here!](https://github.com/BoostIO/boost-releases/releases/)
|
||||||
|
|
||||||
## 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 +36,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/)
|
||||||
|
|
||||||
|
|||||||
39
resources/icon/icon-external.svg
Normal file
39
resources/icon/icon-external.svg
Normal 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 |
@@ -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' +
|
||||||
|
' \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()
|
||||||
|
|||||||
43
tests/dataApi/createNoteFromUrl-test.js
Normal file
43
tests/dataApi/createNoteFromUrl-test.js
Normal 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)
|
||||||
|
})
|
||||||
86
tests/fixtures/markdowns.js
vendored
86
tests/fixtures/markdowns.js
vendored
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
|||||||
@@ -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 "QUOTE".</p>␊
|
`<p data-line="0">This is a "QUOTE".</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" />␊
|
||||||
`
|
`
|
||||||
Binary file not shown.
@@ -30,6 +30,7 @@ var config = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
'prettier',
|
||||||
'node-ipc',
|
'node-ipc',
|
||||||
'electron',
|
'electron',
|
||||||
'lodash',
|
'lodash',
|
||||||
|
|||||||
34
yarn.lock
34
yarn.lock
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user