mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
175 Commits
v0.11.17
...
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 | ||
|
|
ed427130a9 | ||
|
|
807feae540 | ||
|
|
4b62e93257 | ||
|
|
e425417d68 | ||
|
|
bc1e837466 | ||
|
|
95321e33a0 | ||
|
|
5720b313a3 | ||
|
|
f3e2205e69 | ||
|
|
410b611b14 | ||
|
|
1c8af47bac | ||
|
|
c8a2baca3c | ||
|
|
ac5a323115 | ||
|
|
fa65e7feef | ||
|
|
71d27d0e55 | ||
|
|
972d053c83 | ||
|
|
7797661489 | ||
|
|
9f9e036c68 | ||
|
|
e940253caf | ||
|
|
2b4d20b94e | ||
|
|
f88fc23e58 | ||
|
|
caf1f92fef | ||
|
|
f2a02a25a7 | ||
|
|
e85767b4a0 | ||
|
|
c83e5cc7d8 | ||
|
|
71f565f66b | ||
|
|
769407b3df | ||
|
|
e7615ed6d7 | ||
|
|
fe508307b2 | ||
|
|
c2a26a8547 | ||
|
|
addf9b920f | ||
|
|
4e30d4b8fb | ||
|
|
850561613b | ||
|
|
782d71ddb0 | ||
|
|
a0799d19f8 | ||
|
|
ba6eb4f26f | ||
|
|
38fcee35c2 | ||
|
|
4af7106e01 | ||
|
|
bd52226ae2 | ||
|
|
cb7ac77c61 | ||
|
|
55a7ee1f91 | ||
|
|
d37210a0d0 | ||
|
|
0c7a1e8f17 | ||
|
|
3d7ab40674 | ||
|
|
b8de51be57 | ||
|
|
1ce72b91ca | ||
|
|
3f96587a70 | ||
|
|
c012bbd54a | ||
|
|
a50852306e | ||
|
|
db396ec107 | ||
|
|
6b868658aa | ||
|
|
9fe9e1a1c4 | ||
|
|
aeb77e5a40 | ||
|
|
1d59d89588 | ||
|
|
bde357f952 | ||
|
|
558c091205 | ||
|
|
f67175e628 | ||
|
|
390f6d545f | ||
|
|
44efb0178c | ||
|
|
37eee26bdf | ||
|
|
b4251a793b | ||
|
|
6736a08240 | ||
|
|
ed4a670f0a | ||
|
|
fbb9afe34f | ||
|
|
020bc11bd7 | ||
|
|
ae0837e29b | ||
|
|
f0380ef733 | ||
|
|
25bdaf9f00 | ||
|
|
ef1809305c | ||
|
|
090b5c32f0 | ||
|
|
49c75e3599 | ||
|
|
05765642d9 | ||
|
|
f62eba9d7b | ||
|
|
bc27fd0acc | ||
|
|
244a28c7d2 | ||
|
|
edac4d3fed | ||
|
|
e402929cca | ||
|
|
639bfbe549 | ||
|
|
60e841e5a2 | ||
|
|
25d055e560 | ||
|
|
052fb3df5b | ||
|
|
929f475354 | ||
|
|
1afa02bbb3 | ||
|
|
3c39dc3cec | ||
|
|
7e8f46c4f3 | ||
|
|
333b0584a4 | ||
|
|
f7a648903e | ||
|
|
6ec687ef15 | ||
|
|
b6212f4bfe | ||
|
|
9794149fae | ||
|
|
edc9d8bd4d | ||
|
|
25728e51d2 | ||
|
|
18aae8cf7b | ||
|
|
4a9bc69ac2 | ||
|
|
d97e62f864 |
2
.babelrc
2
.babelrc
@@ -7,7 +7,7 @@
|
||||
"test": {
|
||||
"presets": ["env" ,"react", "es2015"],
|
||||
"plugins": [
|
||||
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
|
||||
[ "babel-plugin-webpack-alias", { "config": "<rootDir>/webpack.config.js" } ]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
"globals": {
|
||||
"FileReader": true,
|
||||
"localStorage": true,
|
||||
"fetch": true
|
||||
"fetch": true,
|
||||
"Image": true,
|
||||
"MutationObserver": true
|
||||
},
|
||||
"env": {
|
||||
"jest": true
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
||||
custom: https://issuehunt.io/r/BoostIo/Boostnote
|
||||
issuehunt: BoostIo/Boostnote
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ node_modules/*
|
||||
*.log
|
||||
.idea
|
||||
.vscode
|
||||
package-lock.json
|
||||
|
||||
@@ -3,7 +3,6 @@ node_js:
|
||||
- 8
|
||||
script:
|
||||
- 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'
|
||||
after_success:
|
||||
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||
|
||||
@@ -20,14 +20,15 @@ import styles from '../components/CodeEditor.styl'
|
||||
const { ipcRenderer, remote, clipboard } = require('electron')
|
||||
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||
const spellcheck = require('browser/lib/spellcheck')
|
||||
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
|
||||
import TurndownService from 'turndown'
|
||||
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
|
||||
import { createTurndownService } from '../lib/turndown'
|
||||
import {languageMaps} from '../lib/CMLanguageList'
|
||||
import snippetManager from '../lib/SnippetManager'
|
||||
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
|
||||
import markdownlint from 'markdownlint'
|
||||
import Jsonlint from 'jsonlint-mod'
|
||||
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
|
||||
import prettier from 'prettier'
|
||||
|
||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||
|
||||
@@ -53,6 +54,7 @@ export default class CodeEditor extends React.Component {
|
||||
this.focusHandler = () => {
|
||||
ipcRenderer.send('editor:focused', true)
|
||||
}
|
||||
const debouncedDeletionOfAttachments = _.debounce(attachmentManagement.deleteAttachmentsNotPresentInNote, 30000)
|
||||
this.blurHandler = (editor, e) => {
|
||||
ipcRenderer.send('editor:focused', false)
|
||||
if (e == null) return null
|
||||
@@ -64,16 +66,13 @@ export default class CodeEditor extends React.Component {
|
||||
el = el.parentNode
|
||||
}
|
||||
this.props.onBlur != null && this.props.onBlur(e)
|
||||
|
||||
const {
|
||||
storageKey,
|
||||
noteKey
|
||||
} = this.props
|
||||
attachmentManagement.deleteAttachmentsNotPresentInNote(
|
||||
this.editor.getValue(),
|
||||
storageKey,
|
||||
noteKey
|
||||
)
|
||||
if (this.props.deleteUnusedAttachments === true) {
|
||||
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
|
||||
}
|
||||
}
|
||||
this.pasteHandler = (editor, e) => {
|
||||
e.preventDefault()
|
||||
@@ -102,7 +101,7 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
this.editorActivityHandler = () => this.handleEditorActivity()
|
||||
|
||||
this.turndownService = new TurndownService()
|
||||
this.turndownService = createTurndownService()
|
||||
}
|
||||
|
||||
handleSearch (msg) {
|
||||
@@ -110,7 +109,7 @@ export default class CodeEditor extends React.Component {
|
||||
const component = this
|
||||
|
||||
if (component.searchState) cm.removeOverlay(component.searchState)
|
||||
if (msg.length < 3) return
|
||||
if (msg.length < 1) return
|
||||
|
||||
cm.operation(function () {
|
||||
component.searchState = makeOverlay(msg, 'searching')
|
||||
@@ -205,23 +204,11 @@ export default class CodeEditor extends React.Component {
|
||||
'Cmd-T': function (cm) {
|
||||
// Do nothing
|
||||
},
|
||||
'Ctrl-/': function (cm) {
|
||||
if (global.process.platform === 'darwin') { return }
|
||||
[translateHotkey(hotkey.insertDate)]: function (cm) {
|
||||
const dateNow = new Date()
|
||||
cm.replaceSelection(dateNow.toLocaleDateString())
|
||||
},
|
||||
'Cmd-/': function (cm) {
|
||||
if (global.process.platform !== 'darwin') { return }
|
||||
const dateNow = new Date()
|
||||
cm.replaceSelection(dateNow.toLocaleDateString())
|
||||
},
|
||||
'Shift-Ctrl-/': function (cm) {
|
||||
if (global.process.platform === 'darwin') { return }
|
||||
const dateNow = new Date()
|
||||
cm.replaceSelection(dateNow.toLocaleString())
|
||||
},
|
||||
'Shift-Cmd-/': function (cm) {
|
||||
if (global.process.platform !== 'darwin') { return }
|
||||
[translateHotkey(hotkey.insertDateTime)]: function (cm) {
|
||||
const dateNow = new Date()
|
||||
cm.replaceSelection(dateNow.toLocaleString())
|
||||
},
|
||||
@@ -232,6 +219,37 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
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 => {
|
||||
this.handlePaste(cm, true)
|
||||
}
|
||||
@@ -267,7 +285,7 @@ export default class CodeEditor extends React.Component {
|
||||
value: this.props.value,
|
||||
linesHighlighted: this.props.linesHighlighted,
|
||||
lineNumbers: this.props.displayLineNumbers,
|
||||
lineWrapping: true,
|
||||
lineWrapping: this.props.lineWrapping,
|
||||
theme: this.props.theme,
|
||||
indentUnit: this.props.indentSize,
|
||||
tabSize: this.props.indentSize,
|
||||
@@ -285,7 +303,8 @@ export default class CodeEditor extends React.Component {
|
||||
explode: this.props.explodingPairs,
|
||||
override: true
|
||||
},
|
||||
extraKeys: this.defaultKeyMap
|
||||
extraKeys: this.defaultKeyMap,
|
||||
prettierConfig: this.props.prettierConfig
|
||||
})
|
||||
|
||||
document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none'
|
||||
@@ -566,6 +585,10 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
|
||||
}
|
||||
|
||||
if (prevProps.lineWrapping !== this.props.lineWrapping) {
|
||||
this.editor.setOption('lineWrapping', this.props.lineWrapping)
|
||||
}
|
||||
|
||||
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
||||
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
||||
}
|
||||
@@ -620,6 +643,9 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
|
||||
}
|
||||
}
|
||||
if (prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments) {
|
||||
this.editor.setOption('deleteUnusedAttachments', this.props.deleteUnusedAttachments)
|
||||
}
|
||||
|
||||
if (needRefresh) {
|
||||
this.editor.refresh()
|
||||
@@ -848,6 +874,17 @@ export default class CodeEditor extends React.Component {
|
||||
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) {
|
||||
dropEvent.preventDefault()
|
||||
const {
|
||||
@@ -1181,7 +1218,8 @@ CodeEditor.propTypes = {
|
||||
autoDetect: PropTypes.bool,
|
||||
spellCheck: PropTypes.bool,
|
||||
enableMarkdownLint: PropTypes.bool,
|
||||
customMarkdownLintConfig: PropTypes.string
|
||||
customMarkdownLintConfig: PropTypes.string,
|
||||
deleteUnusedAttachments: PropTypes.bool
|
||||
}
|
||||
|
||||
CodeEditor.defaultProps = {
|
||||
@@ -1195,5 +1233,7 @@ CodeEditor.defaultProps = {
|
||||
autoDetect: false,
|
||||
spellCheck: false,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -159,24 +159,25 @@ class MarkdownEditor extends React.Component {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const idMatch = /checkbox-([0-9]+)/
|
||||
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
||||
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
||||
const checkReplace = /\[x\]/i
|
||||
const uncheckReplace = /\[ \]/
|
||||
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
|
||||
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
|
||||
const checkReplace = /\[x]/i
|
||||
const uncheckReplace = /\[ ]/
|
||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value
|
||||
.split('\n')
|
||||
|
||||
const targetLine = lines[lineIndex]
|
||||
let newLine = targetLine
|
||||
|
||||
if (targetLine.match(checkedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +305,7 @@ class MarkdownEditor extends React.Component {
|
||||
enableRulers={config.editor.enableRulers}
|
||||
rulers={config.editor.rulers}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
lineWrapping
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
@@ -321,6 +323,8 @@ class MarkdownEditor extends React.Component {
|
||||
switchPreview={config.editor.switchPreview}
|
||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||
prettierConfig={config.editor.prettierConfig}
|
||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||
/>
|
||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||
? 'preview'
|
||||
@@ -340,6 +344,7 @@ class MarkdownEditor extends React.Component {
|
||||
smartArrows={config.preview.smartArrows}
|
||||
breaks={config.preview.breaks}
|
||||
sanitize={config.preview.sanitize}
|
||||
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||
ref='preview'
|
||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||
onDoubleClick={(e) => this.handleDoubleClick(e)}
|
||||
|
||||
@@ -18,15 +18,13 @@ import mdurl from 'mdurl'
|
||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||
import yaml from 'js-yaml'
|
||||
import context from 'browser/lib/context'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import fs from 'fs'
|
||||
import { render } from 'react-dom'
|
||||
import Carousel from 'react-image-carousel'
|
||||
import ConfigManager from '../main/lib/ConfigManager'
|
||||
|
||||
const { remote, shell } = require('electron')
|
||||
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
||||
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
|
||||
|
||||
const { app } = remote
|
||||
const path = require('path')
|
||||
@@ -34,8 +32,6 @@ const fileUrl = require('file-url')
|
||||
|
||||
const dialog = remote.dialog
|
||||
|
||||
const uri2path = require('file-uri-to-path')
|
||||
|
||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||
const appPath = fileUrl(
|
||||
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
||||
@@ -46,16 +42,31 @@ const CSS_FILES = [
|
||||
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
||||
]
|
||||
|
||||
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,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
scrollPastEnd,
|
||||
optimizeOverflowScroll,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
) {
|
||||
} = opts
|
||||
return `
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
@@ -85,12 +96,14 @@ function buildStyle (
|
||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||
}
|
||||
|
||||
${markdownStyle}
|
||||
|
||||
body {
|
||||
font-family: '${fontFamily.join("','")}';
|
||||
font-size: ${fontSize}px;
|
||||
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
||||
${scrollPastEnd ? 'padding-bottom: 90vh;' : ''}
|
||||
${optimizeOverflowScroll ? 'height: 100%;' : ''}
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
@@ -249,30 +262,12 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
|
||||
handleContextMenu (event) {
|
||||
// If a contextMenu handler was passed to us, use it instead of the self-defined one -> return
|
||||
if (_.isFunction(this.props.onContextMenu)) {
|
||||
const menu = buildMarkdownPreviewContextMenu(this, event)
|
||||
const switchPreview = ConfigManager.get().editor.switchPreview
|
||||
if (menu != null && switchPreview !== 'RIGHTCLICK') {
|
||||
menu.popup(remote.getCurrentWindow())
|
||||
} else if (_.isFunction(this.props.onContextMenu)) {
|
||||
this.props.onContextMenu(event)
|
||||
return
|
||||
}
|
||||
// No contextMenu was passed to us -> execute our own link-opener
|
||||
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
|
||||
const href = event.target.href
|
||||
const isLocalFile = href.startsWith('file:')
|
||||
if (isLocalFile) {
|
||||
const absPath = uri2path(href)
|
||||
try {
|
||||
if (fs.lstatSync(absPath).isFile()) {
|
||||
context.popup([
|
||||
{
|
||||
label: i18n.__('Show in explorer'),
|
||||
click: (e) => shell.showItemInFolder(absPath)
|
||||
}
|
||||
])
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error while evaluating if the file is locally available', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +329,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
customCSS
|
||||
} = this.getStyleParams()
|
||||
|
||||
const inlineStyles = buildStyle(
|
||||
const inlineStyles = buildStyle({
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
@@ -343,9 +338,13 @@ export default class MarkdownPreview extends React.Component {
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
)
|
||||
})
|
||||
let body = this.markdown.render(noteContent)
|
||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||
body = attachmentManagement.fixLocalURLS(
|
||||
body,
|
||||
this.props.storagePath
|
||||
)
|
||||
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||
files.forEach(file => {
|
||||
if (global.process.platform === 'win32') {
|
||||
file = file.replace('file:///', '')
|
||||
@@ -381,7 +380,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
|
||||
handleSaveAsPdf () {
|
||||
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))
|
||||
return new Promise((resolve, reject) => {
|
||||
printout.webContents.on('did-finish-load', () => {
|
||||
@@ -576,16 +575,19 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
|
||||
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 (
|
||||
prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||
prevProps.sanitize !== this.props.sanitize ||
|
||||
prevProps.mermaidHTMLLabel !== this.props.mermaidHTMLLabel ||
|
||||
prevProps.smartArrows !== this.props.smartArrows ||
|
||||
prevProps.breaks !== this.props.breaks ||
|
||||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
|
||||
) {
|
||||
this.initMarkdown()
|
||||
this.rewriteIframe()
|
||||
needsRewriteIframe = true
|
||||
}
|
||||
if (
|
||||
prevProps.fontFamily !== this.props.fontFamily ||
|
||||
@@ -600,8 +602,17 @@ export default class MarkdownPreview extends React.Component {
|
||||
prevProps.customCSS !== this.props.customCSS
|
||||
) {
|
||||
this.applyStyle()
|
||||
needsRewriteIframe = true
|
||||
}
|
||||
|
||||
if (needsRewriteIframe) {
|
||||
this.rewriteIframe()
|
||||
}
|
||||
|
||||
// Should scroll to top after selecting another note
|
||||
if (prevProps.noteKey !== this.props.noteKey) {
|
||||
this.getWindow().scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
getStyleParams () {
|
||||
@@ -657,27 +668,27 @@ export default class MarkdownPreview extends React.Component {
|
||||
|
||||
this.getWindow().document.getElementById(
|
||||
'codeTheme'
|
||||
).href = this.GetCodeThemeLink(codeBlockTheme)
|
||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(
|
||||
).href = this.getCodeThemeLink(codeBlockTheme)
|
||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle({
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
scrollPastEnd,
|
||||
optimizeOverflowScroll: true,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
)
|
||||
})
|
||||
this.getWindow().document.documentElement.style.overflowY = 'hidden'
|
||||
}
|
||||
|
||||
GetCodeThemeLink (name) {
|
||||
getCodeThemeLink (name) {
|
||||
const theme = consts.THEMES.find(theme => theme.name === name)
|
||||
|
||||
if (theme) {
|
||||
return `${appPath}/${theme.path}`
|
||||
} else {
|
||||
return `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||
}
|
||||
return theme != null
|
||||
? theme.path
|
||||
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||
}
|
||||
|
||||
rewriteIframe () {
|
||||
@@ -703,7 +714,8 @@ export default class MarkdownPreview extends React.Component {
|
||||
showCopyNotification,
|
||||
storagePath,
|
||||
noteKey,
|
||||
sanitize
|
||||
sanitize,
|
||||
mermaidHTMLLabel
|
||||
} = this.props
|
||||
let { value, codeBlockTheme } = this.props
|
||||
|
||||
@@ -835,6 +847,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
canvas.height = height.value + 'vh'
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(canvas, chartConfig)
|
||||
} catch (e) {
|
||||
el.className = 'chart-error'
|
||||
@@ -845,7 +858,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
_.forEach(
|
||||
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
|
||||
el => {
|
||||
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme)
|
||||
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme, mermaidHTMLLabel)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -895,6 +908,12 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.setImgOnClickEventHelper(img, rect)
|
||||
imgObserver.observe(parentEl, config)
|
||||
}
|
||||
|
||||
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
|
||||
for (const a of aList) {
|
||||
a.removeEventListener('click', this.linkClickHandler)
|
||||
a.addEventListener('click', this.linkClickHandler)
|
||||
}
|
||||
}
|
||||
|
||||
setImgOnClickEventHelper (img, rect) {
|
||||
@@ -967,8 +986,6 @@ export default class MarkdownPreview extends React.Component {
|
||||
overlay.appendChild(zoomImg)
|
||||
document.body.appendChild(overlay)
|
||||
}
|
||||
|
||||
this.getWindow().scrollTo(0, 0)
|
||||
}
|
||||
|
||||
focus () {
|
||||
@@ -1015,14 +1032,23 @@ export default class MarkdownPreview extends React.Component {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const href = e.target.getAttribute('href')
|
||||
const linkHash = href.split('/').pop()
|
||||
const rawHref = e.target.getAttribute('href')
|
||||
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
|
||||
|
||||
if (!href) return
|
||||
const parser = document.createElement('a')
|
||||
parser.href = rawHref
|
||||
const isStartWithHash = rawHref[0] === '#'
|
||||
const { href, hash } = parser
|
||||
|
||||
const regexNoteInternalLink = /main.html#(.+)/
|
||||
if (regexNoteInternalLink.test(linkHash)) {
|
||||
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
||||
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(
|
||||
targetId
|
||||
)
|
||||
@@ -1032,6 +1058,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// this will match the new uuid v4 hash and the old hash
|
||||
// e.g.
|
||||
|
||||
@@ -78,24 +78,25 @@ class MarkdownSplitEditor extends React.Component {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const idMatch = /checkbox-([0-9]+)/
|
||||
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
||||
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
||||
const checkReplace = /\[x\]/i
|
||||
const uncheckReplace = /\[ \]/
|
||||
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
|
||||
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
|
||||
const checkReplace = /\[x]/i
|
||||
const uncheckReplace = /\[ ]/
|
||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value
|
||||
.split('\n')
|
||||
|
||||
const targetLine = lines[lineIndex]
|
||||
let newLine = targetLine
|
||||
|
||||
if (targetLine.match(checkedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +151,6 @@ class MarkdownSplitEditor extends React.Component {
|
||||
onMouseMove={e => this.handleMouseMove(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}>
|
||||
<CodeEditor
|
||||
styleName='codeEditor'
|
||||
ref='code'
|
||||
width={this.state.codeEditorWidthInPercent + '%'}
|
||||
mode='Boost Flavored Markdown'
|
||||
@@ -160,6 +160,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
fontFamily={config.editor.fontFamily}
|
||||
fontSize={editorFontSize}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
lineWrapping
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
@@ -181,13 +182,13 @@ class MarkdownSplitEditor extends React.Component {
|
||||
switchPreview={config.editor.switchPreview}
|
||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||
/>
|
||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||
<div styleName='slider-hitbox' />
|
||||
</div>
|
||||
<MarkdownPreview
|
||||
style={previewStyle}
|
||||
styleName='preview'
|
||||
theme={config.ui.theme}
|
||||
keyMap={config.editor.keyMap}
|
||||
fontSize={config.preview.fontSize}
|
||||
@@ -200,6 +201,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
smartArrows={config.preview.smartArrows}
|
||||
breaks={config.preview.breaks}
|
||||
sanitize={config.preview.sanitize}
|
||||
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||
ref='preview'
|
||||
tabInde='0'
|
||||
value={value}
|
||||
|
||||
@@ -8,9 +8,30 @@
|
||||
top -2px
|
||||
width 0
|
||||
z-index 0
|
||||
border-left 1px solid $ui-borderColor
|
||||
.slider-hitbox
|
||||
absolute top bottom left right
|
||||
width 7px
|
||||
left -3px
|
||||
z-index 10
|
||||
cursor col-resize
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-dark-borderColor
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
|
||||
@@ -8,7 +8,7 @@ const ModalEscButton = ({
|
||||
}) => (
|
||||
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||
<div styleName='esc-mark'>×</div>
|
||||
<div styleName='esc-text'>esc</div>
|
||||
<div>esc</div>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { isArray } from 'lodash'
|
||||
import { isArray, sortBy } from 'lodash'
|
||||
import invertColor from 'invert-color'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
||||
@@ -43,7 +43,7 @@ const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
|
||||
}
|
||||
|
||||
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 {
|
||||
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
|
||||
}
|
||||
@@ -148,15 +148,14 @@ NoteItem.propTypes = {
|
||||
tags: PropTypes.array,
|
||||
isStarred: PropTypes.bool.isRequired,
|
||||
isTrashed: PropTypes.bool.isRequired,
|
||||
blog: {
|
||||
blog: PropTypes.shape({
|
||||
blogLink: PropTypes.string,
|
||||
blogId: PropTypes.number
|
||||
}
|
||||
})
|
||||
}),
|
||||
handleNoteClick: PropTypes.func.isRequired,
|
||||
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||
handleDragStart: PropTypes.func.isRequired,
|
||||
handleDragEnd: PropTypes.func.isRequired
|
||||
handleDragStart: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(NoteItem, styles)
|
||||
|
||||
@@ -74,7 +74,7 @@ SideNavFilter.propTypes = {
|
||||
isStarredActive: PropTypes.bool.isRequired,
|
||||
isTrashedActive: PropTypes.bool.isRequired,
|
||||
handleStarredButtonClick: PropTypes.func.isRequired,
|
||||
handleTrashdButtonClick: PropTypes.func.isRequired
|
||||
handleTrashedButtonClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(SideNavFilter, styles)
|
||||
|
||||
@@ -114,7 +114,7 @@ class SnippetTab extends React.Component {
|
||||
>
|
||||
{snippet.name.trim().length > 0
|
||||
? snippet.name
|
||||
: <span styleName='button-unnamed'>
|
||||
: <span>
|
||||
{i18n.__('Unnamed')}
|
||||
</span>
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ const TodoProcess = ({
|
||||
)
|
||||
|
||||
TodoProcess.propTypes = {
|
||||
todoStatus: {
|
||||
todoStatus: PropTypes.exact({
|
||||
total: PropTypes.number.isRequired,
|
||||
completed: PropTypes.number.isRequired
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default CSSModules(TodoProcess, styles)
|
||||
|
||||
@@ -19,7 +19,7 @@ function getId () {
|
||||
return id
|
||||
}
|
||||
|
||||
function render (element, content, theme) {
|
||||
function render (element, content, theme, enableHTMLLabel) {
|
||||
try {
|
||||
const height = element.attributes.getNamedItem('data-height')
|
||||
if (height && height.value !== 'undefined') {
|
||||
@@ -29,7 +29,8 @@ function render (element, content, theme) {
|
||||
mermaidAPI.initialize({
|
||||
theme: isDarkTheme ? 'dark' : 'default',
|
||||
themeCSS: isDarkTheme ? darkThemeStyling : '',
|
||||
useMaxWidth: false
|
||||
useMaxWidth: false,
|
||||
flowchart: { htmlLabels: enableHTMLLabel }
|
||||
})
|
||||
mermaidAPI.render(getId(), content, (svgGraph) => {
|
||||
element.innerHTML = svgGraph
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import CSSModules from 'react-css-modules'
|
||||
|
||||
export default function (component, styles) {
|
||||
return CSSModules(component, styles, {errorWhenNotFound: false})
|
||||
return CSSModules(component, styles, {handleNotFoundStyleName: 'log'})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
|
||||
const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
const paths = [
|
||||
isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
|
||||
isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
|
||||
@@ -18,7 +19,7 @@ const themes = paths
|
||||
|
||||
return {
|
||||
name,
|
||||
path: path.join(directory.split(/\//g).slice(-3).join('/'), file),
|
||||
path: path.join(directory, file),
|
||||
className: `cm-s-${name}`
|
||||
}
|
||||
}))
|
||||
@@ -27,17 +28,16 @@ const themes = paths
|
||||
|
||||
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
|
||||
name: 'solarized dark',
|
||||
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
|
||||
path: path.join(paths[0], 'solarized.css'),
|
||||
className: `cm-s-solarized cm-s-dark`
|
||||
}, {
|
||||
name: 'solarized light',
|
||||
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
|
||||
path: path.join(paths[0], 'solarized.css'),
|
||||
className: `cm-s-solarized cm-s-light`
|
||||
})
|
||||
|
||||
themes.splice(0, 0, {
|
||||
name: 'default',
|
||||
path: `${CODEMIRROR_THEME_PATH}/elegant.css`,
|
||||
path: path.join(paths[0], 'elegant.css'),
|
||||
className: `cm-s-default`
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import fs from 'fs'
|
||||
|
||||
const {remote} = require('electron')
|
||||
const {Menu} = remote.require('electron')
|
||||
const {clipboard} = remote.require('electron')
|
||||
const {shell} = remote.require('electron')
|
||||
const spellcheck = require('./spellcheck')
|
||||
const uri2path = require('file-uri-to-path')
|
||||
|
||||
/**
|
||||
* Creates the context menu that is shown when there is a right click in the editor of a (not-snippet) note.
|
||||
@@ -62,4 +68,57 @@ const buildEditorContextMenu = function (editor, event) {
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
module.exports = buildEditorContextMenu
|
||||
/**
|
||||
* Creates the context menu that is shown when there is a right click Markdown preview of a (not-snippet) note.
|
||||
* @param {MarkdownPreview} markdownPreview
|
||||
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||
* @returns {Electron.Menu} The created electron context menu
|
||||
*/
|
||||
const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
|
||||
if (markdownPreview == null || event == null || event.pageX == null || event.pageY == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Default context menu inclusions
|
||||
const template = [{
|
||||
role: 'copy'
|
||||
}, {
|
||||
role: 'selectall'
|
||||
}]
|
||||
|
||||
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
|
||||
// Link opener for files on the local system pointed to by href
|
||||
const href = event.target.href
|
||||
const isLocalFile = href.startsWith('file:')
|
||||
if (isLocalFile) {
|
||||
const absPath = uri2path(href)
|
||||
try {
|
||||
if (fs.lstatSync(absPath).isFile()) {
|
||||
template.push(
|
||||
{
|
||||
label: i18n.__('Show in explorer'),
|
||||
click: (e) => shell.showItemInFolder(absPath)
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error while evaluating if the file is locally available', e)
|
||||
}
|
||||
}
|
||||
|
||||
// Add option to context menu to copy url
|
||||
template.push(
|
||||
{
|
||||
label: i18n.__('Copy Url'),
|
||||
click: (e) => clipboard.writeText(href)
|
||||
}
|
||||
)
|
||||
}
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
module.exports =
|
||||
{
|
||||
buildEditorContextMenu: buildEditorContextMenu,
|
||||
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
|
||||
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
|
||||
if (stylusCodeInfo == null) {
|
||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
||||
} else {
|
||||
stylusCodeInfo.alias = ['styl']
|
||||
}
|
||||
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
||||
|
||||
@@ -4,11 +4,11 @@ export function getTodoStatus (content) {
|
||||
let numberOfCompletedTodo = 0
|
||||
|
||||
splitted.forEach((line) => {
|
||||
const trimmedLine = line.trim()
|
||||
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./i)) {
|
||||
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
|
||||
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
|
||||
numberOfTodo++
|
||||
}
|
||||
if (trimmedLine.match(/^[\+\-\*] \[x\] ./i)) {
|
||||
if (trimmedLine.match(/^[+\-*] \[x] ./i)) {
|
||||
numberOfCompletedTodo++
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const crypto = require('crypto')
|
||||
const _ = require('lodash')
|
||||
const uuidv4 = require('uuid/v4')
|
||||
|
||||
module.exports = function (uuid) {
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
|
||||
options
|
||||
)
|
||||
}
|
||||
if (state.tokens[tokenIdx].type === '_fence') {
|
||||
if (state.tokens[tokenIdx].type.match(/.*_fence$/)) {
|
||||
// escapeHtmlCharacters has better performance
|
||||
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
||||
state.tokens[tokenIdx].content,
|
||||
@@ -96,6 +96,10 @@ function sanitizeInline (html, options) {
|
||||
|
||||
function naughtyHRef (href, options) {
|
||||
// href = href.replace(/[\x00-\x20]+/g, '')
|
||||
if (!href) {
|
||||
// No href
|
||||
return false
|
||||
}
|
||||
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
|
||||
|
||||
const matches = href.match(/^([a-zA-Z]+)\:/)
|
||||
|
||||
@@ -21,7 +21,7 @@ function uniqueSlug (slug, slugs, opts) {
|
||||
}
|
||||
|
||||
function linkify (token) {
|
||||
token.content = mdlink(token.content, '#' + token.slug)
|
||||
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
|
||||
return token
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ import markdownit from 'markdown-it'
|
||||
import sanitize from './markdown-it-sanitize-html'
|
||||
import emoji from 'markdown-it-emoji'
|
||||
import math from '@rokt33r/markdown-it-math'
|
||||
import mdurl from 'mdurl'
|
||||
import smartArrows from 'markdown-it-smartarrows'
|
||||
import markdownItTocAndAnchor from '@hikerpig/markdown-it-toc-and-anchor'
|
||||
import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import katex from 'katex'
|
||||
@@ -32,6 +34,7 @@ class Markdown {
|
||||
|
||||
const updatedOptions = Object.assign(defaultOptions, options)
|
||||
this.md = markdownit(updatedOptions)
|
||||
this.md.linkify.set({ fuzzyLink: false })
|
||||
|
||||
if (updatedOptions.sanitize !== 'NONE') {
|
||||
const allowedTags = ['iframe', 'input', 'b',
|
||||
@@ -125,6 +128,12 @@ class Markdown {
|
||||
this.md.use(require('markdown-it-abbr'))
|
||||
this.md.use(require('markdown-it-sub'))
|
||||
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-frontmatter'))
|
||||
|
||||
@@ -149,9 +158,9 @@ class Markdown {
|
||||
const content = token.content.split('\n').slice(0, -1).map(line => {
|
||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||
if (match) {
|
||||
return match[1]
|
||||
return mdurl.encode(match[1])
|
||||
} else {
|
||||
return line
|
||||
return mdurl.encode(line)
|
||||
}
|
||||
}).join('\n')
|
||||
|
||||
@@ -181,32 +190,47 @@ class Markdown {
|
||||
})
|
||||
|
||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||
this.md.use(require('markdown-it-plantuml'), {
|
||||
generateSource: function (umlCode) {
|
||||
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
||||
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
|
||||
const plantuml = require('markdown-it-plantuml')
|
||||
const plantUmlStripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
||||
const plantUmlServerAddress = plantUmlStripTrailingSlash(config.preview.plantUMLServerAddress)
|
||||
const parsePlantUml = function (umlCode, openMarker, closeMarker, type) {
|
||||
const s = unescape(encodeURIComponent(umlCode))
|
||||
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
|
||||
this.md.use(require('markdown-it-plantuml'), {
|
||||
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startditaa',
|
||||
closeMarker: '@endditaa',
|
||||
generateSource: function (umlCode) {
|
||||
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'
|
||||
const s = unescape(encodeURIComponent(umlCode))
|
||||
const zippedCode = deflate.encode64(
|
||||
deflate.zip_deflate(`@startditaa\n${s}\n@endditaa`, 9)
|
||||
)
|
||||
return `${serverAddress}/${zippedCode}`
|
||||
}
|
||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
||||
})
|
||||
|
||||
// Mindmap support
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startmindmap',
|
||||
closeMarker: '@endmindmap',
|
||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
|
||||
})
|
||||
|
||||
// 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
|
||||
@@ -287,8 +311,10 @@ class Markdown {
|
||||
case 'list_item_open':
|
||||
case 'paragraph_open':
|
||||
case 'table_open':
|
||||
if (token.map) {
|
||||
token.attrPush(['data-line', token.map[0]])
|
||||
}
|
||||
}
|
||||
})
|
||||
const result = originalRender.call(this.md.renderer, tokens, options, env)
|
||||
return result
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { hashHistory } from 'react-router'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import queryString from 'query-string'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||
@@ -28,10 +29,10 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
|
||||
note: note
|
||||
})
|
||||
|
||||
hashHistory.push({
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: { key: noteHash }
|
||||
})
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
}))
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
@@ -70,10 +71,10 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: { key: noteHash }
|
||||
})
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
}))
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import diacritics from 'diacritics-map'
|
||||
|
||||
function replaceDiacritics (str) {
|
||||
return str.replace(/[À-ž]/g, function (ch) {
|
||||
return diacritics[ch] || ch
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = function slugify (title) {
|
||||
let slug = title.trim()
|
||||
const slug = encodeURI(
|
||||
title.trim()
|
||||
.replace(/^\s+/, '')
|
||||
.replace(/\s+$/, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
|
||||
)
|
||||
|
||||
slug = replaceDiacritics(slug)
|
||||
|
||||
slug = slug.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
|
||||
|
||||
return encodeURI(slug).replace(/\-+$/, '')
|
||||
return slug
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
lastFindInArray,
|
||||
escapeHtmlCharacters,
|
||||
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)
|
||||
@@ -11,7 +11,7 @@ const FullscreenButton = ({
|
||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||
return (
|
||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||
<img src='../resources/icon/icon-full.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -60,7 +60,7 @@ class InfoPanel extends React.Component {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
<input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
||||
<i className='fa fa-clipboard' />
|
||||
</button>
|
||||
|
||||
@@ -9,7 +9,6 @@ import StarButton from './StarButton'
|
||||
import TagSelect from './TagSelect'
|
||||
import FolderSelect from './FolderSelect'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import markdown from 'browser/lib/markdownTextHelper'
|
||||
import StatusBar from '../StatusBar'
|
||||
@@ -30,6 +29,8 @@ import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||
import queryString from 'query-string'
|
||||
import { replace } from 'connected-react-router'
|
||||
|
||||
class MarkdownNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -66,9 +67,6 @@ class MarkdownNoteDetail extends React.Component {
|
||||
})
|
||||
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||
ee.on('code:generate-toc', this.generateToc)
|
||||
|
||||
// Focus content if using blur or double click
|
||||
if (this.state.switchPreview === 'BLUR' || this.state.switchPreview === 'DBL_CLICK') this.focus()
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@@ -83,6 +81,20 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (this.refs.tags) this.refs.tags.reset()
|
||||
})
|
||||
}
|
||||
|
||||
// Focus content if using blur or double click
|
||||
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
|
||||
const {switchPreview} = nextProps.config.editor
|
||||
|
||||
if (this.state.switchPreview !== switchPreview) {
|
||||
this.setState({
|
||||
switchPreview
|
||||
})
|
||||
if (switchPreview === 'BLUR' || switchPreview === 'DBL_CLICK') {
|
||||
console.log('setting focus', switchPreview)
|
||||
this.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
@@ -159,12 +171,12 @@ class MarkdownNoteDetail extends React.Component {
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
hashHistory.replace({
|
||||
dispatch(replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
}
|
||||
})
|
||||
}))
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
@@ -298,7 +310,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
getToggleLockButton () {
|
||||
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
|
||||
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
|
||||
}
|
||||
|
||||
handleDeleteKeyDown (e) {
|
||||
@@ -397,7 +409,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, location, config } = this.props
|
||||
const { data, dispatch, location, config } = this.props
|
||||
const { note, editorType } = this.state
|
||||
const storageKey = note.storage
|
||||
const folderKey = note.folder
|
||||
@@ -437,7 +449,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div styleName='info-left-top'>
|
||||
<div>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
@@ -452,6 +464,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
dispatch={dispatch}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
coloredTags={config.coloredTags}
|
||||
/>
|
||||
@@ -459,6 +472,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
@@ -471,7 +485,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src={imgSrc} />
|
||||
<img src={imgSrc} />
|
||||
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
||||
</button>
|
||||
|
||||
@@ -491,14 +505,14 @@ class MarkdownNoteDetail extends React.Component {
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.split(' ').length}
|
||||
wordCount={note.content.trim().split(/\s+/g).length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
|
||||
@@ -81,3 +81,4 @@ body[data-theme="dracula"]
|
||||
.root
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
|
||||
@@ -108,3 +108,11 @@ body[data-theme="dracula"]
|
||||
.info
|
||||
border-color $ui-dracula-borderColor
|
||||
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
|
||||
@@ -10,7 +10,7 @@ const PermanentDeleteButton = ({
|
||||
<button styleName='control-trashButton--in-trash'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ import StarButton from './StarButton'
|
||||
import TagSelect from './TagSelect'
|
||||
import FolderSelect from './FolderSelect'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import {hashHistory} from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
@@ -18,7 +17,6 @@ import context from 'browser/lib/context'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import _ from 'lodash'
|
||||
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
||||
import convertModeName from 'browser/lib/convertModeName'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import FullscreenButton from './FullscreenButton'
|
||||
import TrashButton from './TrashButton'
|
||||
@@ -31,6 +29,8 @@ import { formatDate } from 'browser/lib/date-formatter'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||
import queryString from 'query-string'
|
||||
import { replace } from 'connected-react-router'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
@@ -166,12 +166,12 @@ class SnippetNoteDetail extends React.Component {
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
hashHistory.replace({
|
||||
dispatch(replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
}
|
||||
})
|
||||
}))
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
@@ -518,6 +518,19 @@ class SnippetNoteDetail extends React.Component {
|
||||
])
|
||||
}
|
||||
|
||||
handleWrapLineButtonClick (e) {
|
||||
context.popup([
|
||||
{
|
||||
label: 'on',
|
||||
click: (e) => this.handleWrapLineItemClick(e, true)
|
||||
},
|
||||
{
|
||||
label: 'off',
|
||||
click: (e) => this.handleWrapLineItemClick(e, false)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
handleIndentSizeItemClick (e, indentSize) {
|
||||
const { config, dispatch } = this.props
|
||||
const editor = Object.assign({}, config.editor, {
|
||||
@@ -550,6 +563,22 @@ class SnippetNoteDetail extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleWrapLineItemClick (e, lineWrapping) {
|
||||
const { config, dispatch } = this.props
|
||||
const editor = Object.assign({}, config.editor, {
|
||||
lineWrapping
|
||||
})
|
||||
ConfigManager.set({
|
||||
editor
|
||||
})
|
||||
dispatch({
|
||||
type: 'SET_CONFIG',
|
||||
config: {
|
||||
editor
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
focus () {
|
||||
this.refs.description.focus()
|
||||
}
|
||||
@@ -670,7 +699,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, config, location } = this.props
|
||||
const { data, dispatch, config, location } = this.props
|
||||
const { note } = this.state
|
||||
|
||||
const storageKey = note.storage
|
||||
@@ -720,6 +749,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)}
|
||||
value={snippet.content}
|
||||
linesHighlighted={snippet.linesHighlighted}
|
||||
lineWrapping={config.editor.lineWrapping}
|
||||
theme={config.editor.theme}
|
||||
fontFamily={config.editor.fontFamily}
|
||||
fontSize={editorFontSize}
|
||||
@@ -778,7 +808,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div styleName='info-left-top'>
|
||||
<div>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
@@ -793,6 +823,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
dispatch={dispatch}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
coloredTags={config.coloredTags}
|
||||
/>
|
||||
@@ -814,7 +845,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.showWarning}
|
||||
@@ -899,6 +930,12 @@ class SnippetNoteDetail extends React.Component {
|
||||
size: {config.editor.indentSize}
|
||||
<i className='fa fa-caret-down' />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => this.handleWrapLineButtonClick(e)}
|
||||
>
|
||||
Wrap Line: {config.editor.lineWrapping ? 'on' : 'off'}
|
||||
<i className='fa fa-caret-down' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<StatusBar
|
||||
|
||||
@@ -8,6 +8,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import Autosuggest from 'react-autosuggest'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
class TagSelect extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -96,8 +97,11 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
handleTagLabelClick (tag) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${tag}`)
|
||||
const { dispatch } = this.props
|
||||
|
||||
// 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) {
|
||||
@@ -255,11 +259,8 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
TagSelect.contextTypes = {
|
||||
router: PropTypes.shape({})
|
||||
}
|
||||
|
||||
TagSelect.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.string),
|
||||
onChange: PropTypes.func,
|
||||
|
||||
@@ -8,11 +8,11 @@ const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : undefined} onClick={() => onClick('SPLIT')}>
|
||||
<img src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
||||
</div>
|
||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||
<img src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||
</div>
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||
</div>
|
||||
@@ -20,7 +20,7 @@ const ToggleModeButton = ({
|
||||
|
||||
ToggleModeButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
editorType: PropTypes.string.Required
|
||||
editorType: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(ToggleModeButton, styles)
|
||||
|
||||
@@ -75,3 +75,10 @@ body[data-theme="dracula"]
|
||||
.active
|
||||
background-color #bd93f9
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
.control-toggleModeButton
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> div img
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
|
||||
@@ -10,7 +10,7 @@ const TrashButton = ({
|
||||
<button styleName='control-trashButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import StatusBar from '../StatusBar'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import debounceRender from 'react-debounce-render'
|
||||
import searchFromNotes from 'browser/lib/search'
|
||||
import queryString from 'query-string'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
@@ -36,11 +37,11 @@ class Detail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, data, params, config } = this.props
|
||||
const { location, data, match: { params }, config } = this.props
|
||||
const noteKey = location.search !== '' && queryString.parse(location.search).key
|
||||
let note = null
|
||||
|
||||
if (location.query.key != null) {
|
||||
const noteKey = location.query.key
|
||||
if (location.search !== '') {
|
||||
const allNotes = data.noteMap.map(note => note)
|
||||
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
let displayedNotes = allNotes
|
||||
@@ -49,16 +50,14 @@ class Detail extends React.Component {
|
||||
const searchStr = params.searchword
|
||||
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/tags/)) {
|
||||
} else if (location.pathname.match(/^\/tags/)) {
|
||||
const listOfTags = params.tagname.split(' ')
|
||||
displayedNotes = data.noteMap.map(note => note).filter(note =>
|
||||
listOfTags.every(tag => note.tags.includes(tag))
|
||||
)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/trashed/)) {
|
||||
if (location.pathname.match(/^\/trashed/)) {
|
||||
displayedNotes = trashedNotes
|
||||
} else {
|
||||
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
||||
|
||||
16
browser/main/DevTools/index.dev.js
Normal file
16
browser/main/DevTools/index.dev.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
import { createDevTools } from 'redux-devtools'
|
||||
import LogMonitor from 'redux-devtools-log-monitor'
|
||||
import DockMonitor from 'redux-devtools-dock-monitor'
|
||||
|
||||
const DevTools = createDevTools(
|
||||
<DockMonitor
|
||||
toggleVisibilityKey='ctrl-h'
|
||||
changePositionKey='ctrl-q'
|
||||
defaultIsVisible={false}
|
||||
>
|
||||
<LogMonitor theme='tomorrow' />
|
||||
</DockMonitor>
|
||||
)
|
||||
|
||||
export default DevTools
|
||||
8
browser/main/DevTools/index.js
Normal file
8
browser/main/DevTools/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable no-undef */
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// eslint-disable-next-line global-require
|
||||
module.exports = require('./index.dev').default
|
||||
} else {
|
||||
// eslint-disable-next-line global-require
|
||||
module.exports = require('./index.prod').default
|
||||
}
|
||||
6
browser/main/DevTools/index.prod.js
Normal file
6
browser/main/DevTools/index.prod.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
const DevTools = () => <div />
|
||||
DevTools.instrument = () => {}
|
||||
|
||||
export default DevTools
|
||||
@@ -12,11 +12,11 @@ import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import { hashHistory } from 'react-router'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { getLocales } from 'browser/lib/Languages'
|
||||
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
||||
import { push } from 'connected-react-router'
|
||||
const path = require('path')
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
@@ -102,7 +102,7 @@ class Main extends React.Component {
|
||||
{
|
||||
name: 'example.js',
|
||||
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: []
|
||||
}
|
||||
]
|
||||
@@ -132,7 +132,7 @@ class Main extends React.Component {
|
||||
.then(() => data.storage)
|
||||
})
|
||||
.then(storage => {
|
||||
hashHistory.push('/storages/' + storage.key)
|
||||
store.dispatch(push('/storages/' + storage.key))
|
||||
})
|
||||
.catch(err => {
|
||||
throw err
|
||||
@@ -169,6 +169,7 @@ class Main extends React.Component {
|
||||
}
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
delete CodeMirror.keyMap.emacs['Ctrl-V']
|
||||
|
||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||
@@ -311,7 +312,7 @@ class Main extends React.Component {
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
>
|
||||
<SideNav
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
|
||||
width={this.state.navWidth}
|
||||
/>
|
||||
{!config.isSideNavFolded &&
|
||||
@@ -341,7 +342,7 @@ class Main extends React.Component {
|
||||
'dispatch',
|
||||
'config',
|
||||
'data',
|
||||
'params',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
/>
|
||||
@@ -351,7 +352,7 @@ class Main extends React.Component {
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
/>
|
||||
@@ -373,7 +374,7 @@ class Main extends React.Component {
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
|
||||
|
||||
@@ -21,23 +21,20 @@ class NewNoteButton extends React.Component {
|
||||
this.state = {
|
||||
}
|
||||
|
||||
this.newNoteHandler = () => {
|
||||
this.handleNewNoteButtonClick()
|
||||
}
|
||||
this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
eventEmitter.on('top:new-note', this.newNoteHandler)
|
||||
eventEmitter.on('top:new-note', this.handleNewNoteButtonClick)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
eventEmitter.off('top:new-note', this.newNoteHandler)
|
||||
eventEmitter.off('top:new-note', this.handleNewNoteButtonClick)
|
||||
}
|
||||
|
||||
handleNewNoteButtonClick (e) {
|
||||
const { location, params, dispatch, config } = this.props
|
||||
const { location, dispatch, match: { params }, config } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
||||
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
|
||||
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
||||
@@ -55,9 +52,8 @@ class NewNoteButton extends React.Component {
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
const { data, match: { params } } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (const kv of data.storageMap) {
|
||||
@@ -93,8 +89,8 @@ class NewNoteButton extends React.Component {
|
||||
>
|
||||
<div styleName='control'>
|
||||
<button styleName='control-newNoteButton'
|
||||
onClick={(e) => this.handleNewNoteButtonClick(e)}>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
|
||||
onClick={this.handleNewNoteButtonClick}>
|
||||
<img src='../resources/icon/icon-newnote.svg' />
|
||||
<span styleName='control-newNoteButton-tooltip'>
|
||||
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
|
||||
</span>
|
||||
|
||||
@@ -14,13 +14,14 @@ import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||
import searchFromNotes from 'browser/lib/search'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { hashHistory } from 'react-router'
|
||||
import { push, replace } from 'connected-react-router'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import Markdown from '../../lib/markdown'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import context from 'browser/lib/context'
|
||||
import queryString from 'query-string'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
@@ -87,6 +88,7 @@ class NoteList extends React.Component {
|
||||
this.importFromFileHandler = this.importFromFile.bind(this)
|
||||
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
|
||||
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
|
||||
this.handleNoteListBlur = this.handleNoteListBlur.bind(this)
|
||||
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
|
||||
this.cloneNote = this.cloneNote.bind(this)
|
||||
this.deleteNote = this.deleteNote.bind(this)
|
||||
@@ -145,15 +147,15 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
const { selectedNoteKeys } = this.state
|
||||
const visibleNoteKeys = this.notes.map(note => note.key)
|
||||
const note = this.notes[0]
|
||||
const prevKey = prevProps.location.query.key
|
||||
const visibleNoteKeys = this.notes && this.notes.map(note => note.key)
|
||||
const note = this.notes && this.notes[0]
|
||||
const key = location.search && queryString.parse(location.search).key
|
||||
const prevKey = prevProps.location.search && queryString.parse(prevProps.location.search).key
|
||||
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
|
||||
|
||||
if (note && location.query.key == null) {
|
||||
const { router } = this.context
|
||||
if (note && location.search === '') {
|
||||
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
|
||||
|
||||
// A visible note is an active note
|
||||
@@ -163,17 +165,17 @@ class NoteList extends React.Component {
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
|
||||
router.replace({
|
||||
dispatch(replace({ // was passed with context - we can use connected router here
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: noteKey
|
||||
}
|
||||
})
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
// Auto scroll
|
||||
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
||||
if (_.isString(key) && prevKey === key) {
|
||||
const targetIndex = this.getTargetIndex()
|
||||
if (targetIndex > -1) {
|
||||
const list = this.refs.list
|
||||
@@ -194,18 +196,18 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
focusNote (selectedNoteKeys, noteKey, pathname) {
|
||||
const { router } = this.context
|
||||
const { dispatch } = this.props
|
||||
|
||||
this.setState({
|
||||
selectedNoteKeys
|
||||
})
|
||||
|
||||
router.push({
|
||||
dispatch(push({
|
||||
pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: noteKey
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
getNoteKeyFromTargetIndex (targetIndex) {
|
||||
@@ -347,9 +349,15 @@ class NoteList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getNotes () {
|
||||
const { data, params, location } = this.props
|
||||
handleNoteListBlur () {
|
||||
this.setState({
|
||||
shiftKeyDown: false,
|
||||
ctrlKeyDown: false
|
||||
})
|
||||
}
|
||||
|
||||
getNotes () {
|
||||
const { data, match: { params }, location } = this.props
|
||||
if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
|
||||
const allNotes = data.noteMap.map((note) => note)
|
||||
this.contextNotes = allNotes
|
||||
@@ -390,7 +398,7 @@ class NoteList extends React.Component {
|
||||
|
||||
// get notes in the current folder
|
||||
getContextNotes () {
|
||||
const { data, params } = this.props
|
||||
const { data, match: { params } } = this.props
|
||||
const storageKey = params.storageKey
|
||||
const folderKey = params.folderKey
|
||||
const storage = data.storageMap.get(storageKey)
|
||||
@@ -430,8 +438,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
handleNoteClick (e, uniqueKey) {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
let { selectedNoteKeys, prevShiftNoteIndex } = this.state
|
||||
const { ctrlKeyDown, shiftKeyDown } = this.state
|
||||
const hasSelectedNoteKey = selectedNoteKeys.length > 0
|
||||
@@ -482,16 +489,16 @@ class NoteList extends React.Component {
|
||||
prevShiftNoteIndex
|
||||
})
|
||||
|
||||
router.push({
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: uniqueKey
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
handleSortByChange (e) {
|
||||
const { dispatch, params: { folderKey } } = this.props
|
||||
const { dispatch, match: { params: { folderKey } } } = this.props
|
||||
|
||||
const config = {
|
||||
[folderKey]: { sortBy: e.target.value }
|
||||
@@ -764,10 +771,10 @@ class NoteList extends React.Component {
|
||||
selectedNoteKeys: [note.key]
|
||||
})
|
||||
|
||||
hashHistory.push({
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: {key: note.key}
|
||||
})
|
||||
search: queryString.stringify({key: note.key})
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -777,13 +784,13 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
navigate (sender, pathname) {
|
||||
const { router } = this.context
|
||||
router.push({
|
||||
const { dispatch } = this.props
|
||||
dispatch(push({
|
||||
pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
// key: noteKey
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
save (note) {
|
||||
@@ -947,10 +954,10 @@ class NoteList extends React.Component {
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: {key: getNoteKey(note)}
|
||||
})
|
||||
search: queryString.stringify({key: getNoteKey(note)})
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -960,14 +967,15 @@ class NoteList extends React.Component {
|
||||
|
||||
getTargetIndex () {
|
||||
const { location } = this.props
|
||||
const key = queryString.parse(location.search).key
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return getNoteKey(note) === location.query.key
|
||||
return getNoteKey(note) === key
|
||||
})
|
||||
return targetIndex
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
const { data, match: { params } } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
@@ -1015,7 +1023,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, config, params: { folderKey } } = this.props
|
||||
const { location, config, match: { params: { folderKey } } } = this.props
|
||||
let { notes } = this.props
|
||||
const { selectedNoteKeys } = this.state
|
||||
const sortBy = _.get(config, [folderKey, 'sortBy'], config.sortBy.default)
|
||||
@@ -1138,7 +1146,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
|
||||
>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
|
||||
<img src='../resources/icon/icon-column.svg' />
|
||||
</button>
|
||||
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL'
|
||||
? 'control-button--active'
|
||||
@@ -1146,7 +1154,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
|
||||
>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-column-list.svg' />
|
||||
<img src='../resources/icon/icon-column-list.svg' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1155,6 +1163,7 @@ class NoteList extends React.Component {
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
|
||||
onKeyUp={this.handleNoteListKeyUp}
|
||||
onBlur={this.handleNoteListBlur}
|
||||
>
|
||||
{noteList}
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ const PreferenceButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
|
||||
<img src='../resources/icon/icon-setting.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StorageItem.styl'
|
||||
import { hashHistory } from 'react-router'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
|
||||
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
||||
@@ -12,6 +11,7 @@ import _ from 'lodash'
|
||||
import { SortableElement } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import context from 'browser/lib/context'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
@@ -134,14 +134,14 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleHeaderInfoClick (e) {
|
||||
const { storage } = this.props
|
||||
hashHistory.push('/storages/' + storage.key)
|
||||
const { storage, dispatch } = this.props
|
||||
dispatch(push('/storages/' + storage.key))
|
||||
}
|
||||
|
||||
handleFolderButtonClick (folderKey) {
|
||||
return (e) => {
|
||||
const { storage } = this.props
|
||||
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
|
||||
const { storage, dispatch } = this.props
|
||||
dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,14 +362,14 @@ class StorageItem extends React.Component {
|
||||
<button styleName='header-addFolderButton'
|
||||
onClick={(e) => this.handleAddFolderButtonClick(e)}
|
||||
>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-plus.svg' />
|
||||
<img src='../resources/icon/icon-plus.svg' />
|
||||
</button>
|
||||
}
|
||||
|
||||
<button styleName='header-info'
|
||||
onClick={(e) => this.handleHeaderInfoClick(e)}
|
||||
>
|
||||
<span styleName='header-info-name'>
|
||||
<span>
|
||||
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
|
||||
</span>
|
||||
{isFolded &&
|
||||
@@ -380,7 +380,7 @@ class StorageItem extends React.Component {
|
||||
</button>
|
||||
</div>
|
||||
{this.state.isOpen &&
|
||||
<div styleName='folderList' >
|
||||
<div>
|
||||
{folderList}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { push } from 'connected-react-router'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './SideNav.styl'
|
||||
@@ -21,9 +22,10 @@ import context from 'browser/lib/context'
|
||||
import { remote } from 'electron'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import ColorPicker from 'browser/components/ColorPicker'
|
||||
import { every, sortBy } from 'lodash'
|
||||
|
||||
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 {
|
||||
@@ -62,7 +64,7 @@ class SideNav extends React.Component {
|
||||
})
|
||||
|
||||
if (selectedButton === 0) {
|
||||
const { data, dispatch, location, params } = this.props
|
||||
const { data, dispatch, location, match: { params } } = this.props
|
||||
|
||||
const notes = data.noteMap
|
||||
.map(note => note)
|
||||
@@ -92,7 +94,7 @@ class SideNav extends React.Component {
|
||||
if (index !== -1) {
|
||||
tags.splice(index, 1)
|
||||
|
||||
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`)
|
||||
dispatch(push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -104,13 +106,13 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleHomeButtonClick (e) {
|
||||
const { router } = this.context
|
||||
router.push('/home')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/home'))
|
||||
}
|
||||
|
||||
handleStarredButtonClick (e) {
|
||||
const { router } = this.context
|
||||
router.push('/starred')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/starred'))
|
||||
}
|
||||
|
||||
handleTagContextMenu (e, tag) {
|
||||
@@ -190,18 +192,18 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleTrashedButtonClick (e) {
|
||||
const { router } = this.context
|
||||
router.push('/trashed')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/trashed'))
|
||||
}
|
||||
|
||||
handleSwitchFoldersButtonClick () {
|
||||
const { router } = this.context
|
||||
router.push('/home')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/home'))
|
||||
}
|
||||
|
||||
handleSwitchTagsButtonClick () {
|
||||
const { router } = this.context
|
||||
router.push('/alltags')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/alltags'))
|
||||
}
|
||||
|
||||
onSortEnd (storage) {
|
||||
@@ -270,6 +272,7 @@ class SideNav extends React.Component {
|
||||
<div styleName='tagList'>
|
||||
{this.tagListComponent(data)}
|
||||
</div>
|
||||
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -282,7 +285,7 @@ class SideNav extends React.Component {
|
||||
const { colorPicker } = this.state
|
||||
const activeTags = this.getActiveTags(location.pathname)
|
||||
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) })
|
||||
).filter(
|
||||
tag => tag.size > 0
|
||||
@@ -295,7 +298,7 @@ class SideNav extends React.Component {
|
||||
})
|
||||
}
|
||||
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)) {
|
||||
tagList = tagList.filter(
|
||||
@@ -348,8 +351,8 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleClickTagListItem (name) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${encodeURIComponent(name)}`)
|
||||
const { dispatch } = this.props
|
||||
dispatch(push(`/tags/${encodeURIComponent(name)}`))
|
||||
}
|
||||
|
||||
handleSortTagsByChange (e) {
|
||||
@@ -367,8 +370,7 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleClickNarrowToTag (tag) {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
const listOfTags = this.getActiveTags(location.pathname)
|
||||
const indexOfTag = listOfTags.indexOf(tag)
|
||||
if (indexOfTag > -1) {
|
||||
@@ -376,7 +378,7 @@ class SideNav extends React.Component {
|
||||
} else {
|
||||
listOfTags.push(tag)
|
||||
}
|
||||
router.push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`)
|
||||
dispatch(push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`))
|
||||
}
|
||||
|
||||
emptyTrash (entries) {
|
||||
@@ -440,7 +442,7 @@ class SideNav extends React.Component {
|
||||
|
||||
const style = {}
|
||||
if (!isFolded) style.width = this.props.width
|
||||
const isTagActive = location.pathname.match(/tag/)
|
||||
const isTagActive = /tag/.test(location.pathname)
|
||||
return (
|
||||
<div className='SideNav'
|
||||
styleName={isFolded ? 'root--folded' : 'root'}
|
||||
|
||||
@@ -7,6 +7,8 @@ import ee from 'browser/main/lib/eventEmitter'
|
||||
import NewNoteButton from 'browser/main/NewNoteButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import debounce from 'lodash/debounce'
|
||||
import CInput from 'react-composition-input'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
class TopBar extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -15,26 +17,36 @@ class TopBar extends React.Component {
|
||||
this.state = {
|
||||
search: '',
|
||||
searchOptions: [],
|
||||
isSearching: false,
|
||||
isAlphabet: false,
|
||||
isIME: false,
|
||||
isConfirmTranslation: false
|
||||
isSearching: false
|
||||
}
|
||||
|
||||
const { dispatch } = this.props
|
||||
|
||||
this.focusSearchHandler = () => {
|
||||
this.handleOnSearchFocus()
|
||||
}
|
||||
|
||||
this.codeInitHandler = this.handleCodeInit.bind(this)
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this)
|
||||
this.handleSearchFocus = this.handleSearchFocus.bind(this)
|
||||
this.handleSearchBlur = this.handleSearchBlur.bind(this)
|
||||
this.handleSearchChange = this.handleSearchChange.bind(this)
|
||||
this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
|
||||
|
||||
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
|
||||
this.debouncedUpdateKeyword = debounce((keyword) => {
|
||||
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
|
||||
this.setState({
|
||||
search: keyword
|
||||
})
|
||||
ee.emit('top:search', keyword)
|
||||
}, 1000 / 60, {
|
||||
maxWait: 1000 / 8
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { params } = this.props
|
||||
const searchWord = params.searchword
|
||||
const { match: { params } } = this.props
|
||||
const searchWord = params && params.searchword
|
||||
if (searchWord !== undefined) {
|
||||
this.setState({
|
||||
search: searchWord,
|
||||
@@ -51,22 +63,22 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
handleSearchClearButton (e) {
|
||||
const { router } = this.context
|
||||
const { dispatch } = this.props
|
||||
this.setState({
|
||||
search: '',
|
||||
isSearching: false
|
||||
})
|
||||
this.refs.search.childNodes[0].blur
|
||||
router.push('/searched')
|
||||
dispatch(push('/searched'))
|
||||
e.preventDefault()
|
||||
this.debouncedUpdateKeyword('')
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
// reset states
|
||||
this.setState({
|
||||
isAlphabet: false,
|
||||
isIME: false
|
||||
})
|
||||
// Re-apply search field on ENTER key
|
||||
if (e.keyCode === 13) {
|
||||
this.debouncedUpdateKeyword(e.target.value)
|
||||
}
|
||||
|
||||
// Clear search on ESC
|
||||
if (e.keyCode === 27) {
|
||||
@@ -84,51 +96,11 @@ class TopBar extends React.Component {
|
||||
ee.emit('list:prior')
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// When the key is an alphabet, del, enter or ctr
|
||||
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
|
||||
this.setState({
|
||||
isAlphabet: true
|
||||
})
|
||||
// When the key is an IME input (Japanese, Chinese)
|
||||
} else if (e.keyCode === 229) {
|
||||
this.setState({
|
||||
isIME: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyUp (e) {
|
||||
// reset states
|
||||
this.setState({
|
||||
isConfirmTranslation: false
|
||||
})
|
||||
|
||||
// When the key is translation confirmation (Enter, Space)
|
||||
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
|
||||
this.setState({
|
||||
isConfirmTranslation: true
|
||||
})
|
||||
const keyword = this.refs.searchInput.value
|
||||
this.updateKeyword(keyword)
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
|
||||
const keyword = this.refs.searchInput.value
|
||||
this.updateKeyword(keyword)
|
||||
} else {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
updateKeyword (keyword) {
|
||||
this.context.router.push(`/searched/${encodeURIComponent(keyword)}`)
|
||||
this.setState({
|
||||
search: keyword
|
||||
})
|
||||
ee.emit('top:search', keyword)
|
||||
const keyword = e.target.value
|
||||
this.debouncedUpdateKeyword(keyword)
|
||||
}
|
||||
|
||||
handleSearchFocus (e) {
|
||||
@@ -136,6 +108,7 @@ class TopBar extends React.Component {
|
||||
isSearching: true
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchBlur (e) {
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -165,7 +138,7 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
handleCodeInit () {
|
||||
ee.emit('top:search', this.refs.searchInput.value)
|
||||
ee.emit('top:search', this.refs.searchInput.value || '')
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -178,24 +151,23 @@ class TopBar extends React.Component {
|
||||
<div styleName='control'>
|
||||
<div styleName='control-search'>
|
||||
<div styleName='control-search-input'
|
||||
onFocus={(e) => this.handleSearchFocus(e)}
|
||||
onBlur={(e) => this.handleSearchBlur(e)}
|
||||
onFocus={this.handleSearchFocus}
|
||||
onBlur={this.handleSearchBlur}
|
||||
tabIndex='-1'
|
||||
ref='search'
|
||||
>
|
||||
<input
|
||||
<CInput
|
||||
ref='searchInput'
|
||||
value={this.state.search}
|
||||
onChange={(e) => this.handleSearchChange(e)}
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||
onInputChange={this.handleSearchChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
placeholder={i18n.__('Search')}
|
||||
type='text'
|
||||
className='searchInput'
|
||||
/>
|
||||
{this.state.search !== '' &&
|
||||
<button styleName='control-search-input-clear'
|
||||
onClick={(e) => this.handleSearchClearButton(e)}
|
||||
onClick={this.handleSearchClearButton}
|
||||
>
|
||||
<i className='fa fa-fw fa-times' />
|
||||
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>
|
||||
@@ -210,8 +182,8 @@ class TopBar extends React.Component {
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'location'
|
||||
'location',
|
||||
'match'
|
||||
])}
|
||||
/>}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Provider } from 'react-redux'
|
||||
import Main from './Main'
|
||||
import store from './store'
|
||||
import React from 'react'
|
||||
import { store, history } from './store'
|
||||
import React, { Fragment } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
require('!!style!css!stylus?sourceMap!./global.styl')
|
||||
import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-router'
|
||||
import { syncHistoryWithStore } from 'react-router-redux'
|
||||
import { Route, Switch, Redirect } from 'react-router-dom'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import DevTools from './DevTools'
|
||||
|
||||
require('./lib/ipcClient')
|
||||
require('../lib/customMeta')
|
||||
import i18n from 'browser/lib/i18n'
|
||||
@@ -77,7 +79,6 @@ document.addEventListener('click', function (e) {
|
||||
})
|
||||
|
||||
const el = document.getElementById('content')
|
||||
const history = syncHistoryWithStore(hashHistory, store)
|
||||
|
||||
function notify (...args) {
|
||||
return new window.Notification(...args)
|
||||
@@ -98,29 +99,24 @@ function updateApp () {
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<Route path='/' component={Main}>
|
||||
<IndexRedirect to='/home' />
|
||||
<Route path='home' />
|
||||
<Route path='starred' />
|
||||
<Route path='searched'>
|
||||
<Route path=':searchword' />
|
||||
</Route>
|
||||
<Route path='trashed' />
|
||||
<Route path='alltags' />
|
||||
<Route path='tags'>
|
||||
<IndexRedirect to='/alltags' />
|
||||
<Route path=':tagname' />
|
||||
</Route>
|
||||
<Route path='storages'>
|
||||
<IndexRedirect to='/home' />
|
||||
<Route path=':storageKey'>
|
||||
<IndexRoute />
|
||||
<Route path='folders/:folderKey' />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
<ConnectedRouter history={history}>
|
||||
<Fragment>
|
||||
<Switch>
|
||||
<Redirect path='/' to='/home' exact />
|
||||
<Route path='/(home|alltags|starred|trashed)' component={Main} />
|
||||
<Route path='/searched' component={Main} exact />
|
||||
<Route path='/searched/:searchword' component={Main} />
|
||||
<Redirect path='/tags' to='/alltags' exact />
|
||||
<Route path='/tags/:tagname' component={Main} />
|
||||
|
||||
{/* storages */}
|
||||
<Redirect path='/storages' to='/home' exact />
|
||||
<Route path='/storages/:storageKey' component={Main} exact />
|
||||
<Route path='/storages/:storageKey/folders/:folderKey' component={Main} />
|
||||
</Switch>
|
||||
<DevTools />
|
||||
</Fragment>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
), el, function () {
|
||||
const loadingCover = document.getElementById('loadingCover')
|
||||
|
||||
@@ -31,6 +31,10 @@ export const DEFAULT_CONFIG = {
|
||||
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
|
||||
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
|
||||
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 + /',
|
||||
insertDateTime: OSX ? 'Command + Alt + /' : 'Ctrl + Shift + /',
|
||||
toggleMenuBar: 'Alt'
|
||||
},
|
||||
ui: {
|
||||
@@ -48,6 +52,7 @@ export const DEFAULT_CONFIG = {
|
||||
fontFamily: win ? 'Consolas' : 'Monaco',
|
||||
indentType: 'space',
|
||||
indentSize: '2',
|
||||
lineWrapping: true,
|
||||
enableRulers: false,
|
||||
rulers: [80, 120],
|
||||
displayLineNumbers: true,
|
||||
@@ -65,7 +70,14 @@ export const DEFAULT_CONFIG = {
|
||||
spellcheck: false,
|
||||
enableSmartPaste: 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: {
|
||||
fontSize: '14',
|
||||
@@ -83,8 +95,10 @@ export const DEFAULT_CONFIG = {
|
||||
breaks: true,
|
||||
smartArrows: false,
|
||||
allowCustomCSS: false,
|
||||
customCSS: '',
|
||||
|
||||
customCSS: '/* Drop Your Custom CSS Code Here */',
|
||||
sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
||||
mermaidHTMLLabel: false,
|
||||
lineThroughCheckbox: true
|
||||
},
|
||||
blog: {
|
||||
@@ -108,7 +122,6 @@ function validate (config) {
|
||||
}
|
||||
|
||||
function _save (config) {
|
||||
console.log(config)
|
||||
window.localStorage.setItem('config', JSON.stringify(config))
|
||||
}
|
||||
|
||||
@@ -141,7 +154,7 @@ function get () {
|
||||
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
|
||||
|
||||
if (theme) {
|
||||
editorTheme.setAttribute('href', `../${theme.path}`)
|
||||
editorTheme.setAttribute('href', theme.path)
|
||||
} else {
|
||||
config.editor.theme = 'default'
|
||||
}
|
||||
@@ -152,7 +165,13 @@ function get () {
|
||||
|
||||
function set (updates) {
|
||||
const currentConfig = get()
|
||||
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
|
||||
|
||||
const arrangedUpdates = updates
|
||||
if (updates.preview !== undefined && updates.preview.customCSS === '') {
|
||||
arrangedUpdates.preview.customCSS = DEFAULT_CONFIG.preview.customCSS
|
||||
}
|
||||
|
||||
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, arrangedUpdates)
|
||||
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
||||
_save(newConfig)
|
||||
|
||||
@@ -183,7 +202,7 @@ function set (updates) {
|
||||
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
|
||||
|
||||
if (newTheme) {
|
||||
editorTheme.setAttribute('href', `../${newTheme.path}`)
|
||||
editorTheme.setAttribute('href', newTheme.path)
|
||||
}
|
||||
|
||||
ipcRenderer.send('config-renew', {
|
||||
|
||||
@@ -8,6 +8,7 @@ const escapeStringRegexp = require('escape-string-regexp')
|
||||
const sander = require('sander')
|
||||
const url = require('url')
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { isString } from 'lodash'
|
||||
|
||||
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
||||
const DESTINATION_FOLDER = 'attachments'
|
||||
@@ -19,7 +20,7 @@ const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(
|
||||
* @returns {Promise<Image>} Image element created
|
||||
*/
|
||||
function getImage (file) {
|
||||
if (_.isString(file)) {
|
||||
if (isString(file)) {
|
||||
return new Promise(resolve => {
|
||||
const img = new Image()
|
||||
img.onload = () => resolve(img)
|
||||
@@ -241,6 +242,10 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
|
||||
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
||||
*/
|
||||
function fixLocalURLS (renderedHTML, storagePath) {
|
||||
const encodedWin32SeparatorRegex = /%5C/g
|
||||
const storageRegex = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g')
|
||||
const storageUrl = 'file:///' + path.join(storagePath, DESTINATION_FOLDER).replace(/\\/g, '/')
|
||||
|
||||
/*
|
||||
A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`.
|
||||
|
||||
@@ -250,8 +255,7 @@ function fixLocalURLS (renderedHTML, storagePath) {
|
||||
- `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows.
|
||||
*/
|
||||
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[-.\\w]+)+', 'g'), function (match) {
|
||||
var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
|
||||
return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
||||
return match.replace(encodedWin32SeparatorRegex, '/').replace(storageRegex, storageUrl)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -617,11 +621,79 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.info('Attachment folder ("' + attachmentFolder + '") did not exist..')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* 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.
|
||||
@@ -724,8 +796,10 @@ module.exports = {
|
||||
getAbsolutePathsOfAttachmentsInContent,
|
||||
importAttachments,
|
||||
removeStorageAndNoteReferences,
|
||||
removeAttachmentsByPaths,
|
||||
deleteAttachmentFolder,
|
||||
deleteAttachmentsNotPresentInNote,
|
||||
getAttachmentsPathAndStatus,
|
||||
moveAttachments,
|
||||
cloneAttachments,
|
||||
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 resolveStorageNotes = require('./resolveStorageNotes')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
const deleteSingleNote = require('./deleteNote')
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
|
||||
)
|
||||
|
||||
if (outputFormatter) {
|
||||
exportedData = outputFormatter(exportedData, exportTasks, path.dirname(targetPath))
|
||||
exportedData = outputFormatter(exportedData, exportTasks, targetPath)
|
||||
} else {
|
||||
exportedData = Promise.resolve(exportedData)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const dataApi = {
|
||||
exportFolder: require('./exportFolder'),
|
||||
exportStorage: require('./exportStorage'),
|
||||
createNote: require('./createNote'),
|
||||
createNoteFromUrl: require('./createNoteFromUrl'),
|
||||
updateNote: require('./updateNote'),
|
||||
deleteNote: require('./deleteNote'),
|
||||
moveNote: require('./moveNote'),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const keygen = require('browser/lib/keygen')
|
||||
const sander = require('sander')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import ReactDOM from 'react-dom'
|
||||
import store from '../store'
|
||||
import { store } from '../store'
|
||||
|
||||
class ModalBase extends React.Component {
|
||||
constructor (props) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './CreateFolderModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import consts from 'browser/lib/consts'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
|
||||
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,7 +3,10 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './NewNoteModal.styl'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
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 queryString from 'query-string'
|
||||
|
||||
class NewNoteModal extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -20,8 +23,21 @@ class NewNoteModal extends React.Component {
|
||||
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) {
|
||||
const { storage, folder, dispatch, location, params, config } = this.props
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
const params = location.search !== '' && queryString.parse(location.search)
|
||||
if (!this.lock) {
|
||||
this.lock = true
|
||||
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
@@ -38,7 +54,8 @@ class NewNoteModal extends React.Component {
|
||||
}
|
||||
|
||||
handleSnippetNoteButtonClick (e) {
|
||||
const { storage, folder, dispatch, location, params, config } = this.props
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
const params = location.search !== '' && queryString.parse(location.search)
|
||||
if (!this.lock) {
|
||||
this.lock = true
|
||||
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
@@ -112,10 +129,8 @@ class NewNoteModal extends React.Component {
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div styleName='description'>
|
||||
<i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}
|
||||
</div>
|
||||
|
||||
<div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
|
||||
<div styleName='from-url' onClick={(e) => this.handleCreateMarkdownFromUrlClick(e)}>Or, create a new markdown note from a URL</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
.control
|
||||
padding 25px 0px
|
||||
text-align center
|
||||
display: flex
|
||||
|
||||
.control-button
|
||||
width 240px
|
||||
@@ -47,6 +48,12 @@
|
||||
text-align center
|
||||
margin-bottom 25px
|
||||
|
||||
.from-url
|
||||
color $ui-inactive-text-color
|
||||
text-align center
|
||||
margin-bottom 25px
|
||||
cursor pointer
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
modalDark()
|
||||
@@ -61,7 +68,7 @@ body[data-theme="dark"]
|
||||
&:focus
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
.description
|
||||
.description, .from-url
|
||||
color $ui-inactive-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
@@ -78,7 +85,7 @@ body[data-theme="solarized-dark"]
|
||||
&:focus
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
.description
|
||||
.description, .from-url
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
body[data-theme="monokai"]
|
||||
@@ -95,7 +102,7 @@ body[data-theme="monokai"]
|
||||
&:focus
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
.description
|
||||
.description, .from-url
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import PropTypes from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import ReactDOM from 'react-dom'
|
||||
import styles from './FolderItem.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import { SketchPicker } from 'react-color'
|
||||
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
@@ -225,7 +225,7 @@ class FolderItem extends React.Component {
|
||||
<div styleName='folderItem-left'
|
||||
style={{borderColor: folder.color}}
|
||||
>
|
||||
<span styleName='folderItem-left-name'>{folder.name}</span>
|
||||
<span>{folder.name}</span>
|
||||
<span styleName='folderItem-left-key'>({folder.key})</span>
|
||||
</div>
|
||||
<div styleName='folderItem-right'>
|
||||
@@ -288,10 +288,10 @@ class Handle extends React.Component {
|
||||
|
||||
class SortableFolderItemComponent extends React.Component {
|
||||
render () {
|
||||
const StyledHandle = CSSModules(Handle, this.props.styles)
|
||||
const StyledHandle = CSSModules(Handle, styles)
|
||||
const DragHandle = SortableHandle(StyledHandle)
|
||||
|
||||
const StyledFolderItem = CSSModules(FolderItem, this.props.styles)
|
||||
const StyledFolderItem = CSSModules(FolderItem, styles)
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './FolderList.styl'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import FolderItem from './FolderItem'
|
||||
import { SortableContainer } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
@@ -22,7 +22,7 @@ class FolderList extends React.Component {
|
||||
})
|
||||
|
||||
return (
|
||||
<div styleName='folderList'>
|
||||
<div>
|
||||
{folderList.length > 0
|
||||
? folderList
|
||||
: <div styleName='folderList-empty'>{i18n.__('No Folders')}</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
@@ -76,13 +76,16 @@ class HotkeyTab extends React.Component {
|
||||
|
||||
handleHotkeyChange (e) {
|
||||
const { config } = this.state
|
||||
config.hotkey = {
|
||||
config.hotkey = Object.assign({}, config.hotkey, {
|
||||
toggleMain: this.refs.toggleMain.value,
|
||||
toggleMode: this.refs.toggleMode.value,
|
||||
deleteNote: this.refs.deleteNote.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({
|
||||
config
|
||||
})
|
||||
@@ -173,6 +176,38 @@ class HotkeyTab extends React.Component {
|
||||
/>
|
||||
</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-label'>{i18n.__('Insert Current Date')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
ref='insertDate'
|
||||
value={config.hotkey.insertDate}
|
||||
type='text'
|
||||
disabled='true'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Insert Current Date and Time')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
ref='insertDateTime'
|
||||
value={config.hotkey.insertDateTime}
|
||||
type='text'
|
||||
disabled='true'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-control'>
|
||||
<button styleName='group-control-leftButton'
|
||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StorageItem.styl'
|
||||
import consts from 'browser/lib/consts'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import FolderList from './FolderList'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
|
||||
@@ -102,3 +102,11 @@ body[data-theme="solarized-dark"]
|
||||
border-color $ui-solarized-dark-button-backgroundColor
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
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 styles from './StoragesTab.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||
import StorageItem from './StorageItem'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { humanFileSize } from 'browser/lib/utils'
|
||||
import fs from 'fs'
|
||||
|
||||
const electron = require('electron')
|
||||
const { shell, remote } = electron
|
||||
@@ -35,8 +38,29 @@ class StoragesTab extends React.Component {
|
||||
name: 'Unnamed',
|
||||
type: 'FILESYSTEM',
|
||||
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) {
|
||||
@@ -57,8 +81,39 @@ class StoragesTab extends React.Component {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
handleRemoveUnusedAttachments (attachments) {
|
||||
attachmentManagement.removeAttachmentsByPaths(attachments)
|
||||
.then(() => this.loadAttachmentStorage())
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
renderList () {
|
||||
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 }
|
||||
const storageList = data.storageMap.map((storage) => {
|
||||
@@ -82,6 +137,20 @@ class StoragesTab extends React.Component {
|
||||
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
|
||||
</button>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,17 @@
|
||||
colorDefaultButton()
|
||||
font-size $tab--button-font-size
|
||||
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
|
||||
margin-bottom 15px
|
||||
@@ -154,8 +165,8 @@ body[data-theme="dark"]
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
|
||||
.list-attachement-clear-button
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
@@ -194,6 +205,8 @@ body[data-theme="solarized-dark"]
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.list-attachement-clear-button
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
@@ -232,6 +245,8 @@ body[data-theme="monokai"]
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-monokai-borderColor
|
||||
.list-attachement-clear-button
|
||||
colorMonokaiPrimaryButton()
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
@@ -270,3 +285,5 @@ body[data-theme="dracula"]
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dracula-borderColor
|
||||
.list-attachement-clear-button
|
||||
colorDraculaPrimaryButton()
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import consts from 'browser/lib/consts'
|
||||
import ReactCodeMirror from 'react-codemirror'
|
||||
import CodeMirror from 'codemirror'
|
||||
@@ -31,8 +31,12 @@ class UiTab extends React.Component {
|
||||
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
|
||||
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
|
||||
CodeMirror.autoLoadMode(this.customMarkdownLintConfigCM.getCodeMirror(), 'javascript')
|
||||
CodeMirror.autoLoadMode(this.prettierConfigCM.getCodeMirror(), 'javascript')
|
||||
// Set CM editor Sizes
|
||||
this.customCSSCM.getCodeMirror().setSize('400px', '400px')
|
||||
this.prettierConfigCM.getCodeMirror().setSize('400px', '400px')
|
||||
this.customMarkdownLintConfigCM.getCodeMirror().setSize('400px', '200px')
|
||||
|
||||
this.handleSettingDone = () => {
|
||||
this.setState({UiAlert: {
|
||||
type: 'success',
|
||||
@@ -91,6 +95,7 @@ class UiTab extends React.Component {
|
||||
enableRulers: this.refs.enableEditorRulers.value === 'true',
|
||||
rulers: this.refs.editorRulers.value.replace(/[^0-9,]/g, '').split(','),
|
||||
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
|
||||
lineWrapping: this.refs.editorLineWrapping.checked,
|
||||
switchPreview: this.refs.editorSwitchPreview.value,
|
||||
keyMap: this.refs.editorKeyMap.value,
|
||||
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
|
||||
@@ -105,7 +110,9 @@ class UiTab extends React.Component {
|
||||
spellcheck: this.refs.spellcheck.checked,
|
||||
enableSmartPaste: this.refs.enableSmartPaste.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: {
|
||||
fontSize: this.refs.previewFontSize.value,
|
||||
@@ -123,6 +130,7 @@ class UiTab extends React.Component {
|
||||
breaks: this.refs.previewBreaks.checked,
|
||||
smartArrows: this.refs.previewSmartArrows.checked,
|
||||
sanitize: this.refs.previewSanitize.value,
|
||||
mermaidHTMLLabel: this.refs.previewMermaidHTMLLabel.checked,
|
||||
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
|
||||
lineThroughCheckbox: this.refs.lineThroughCheckbox.checked,
|
||||
customCSS: this.customCSSCM.getCodeMirror().getValue()
|
||||
@@ -135,7 +143,7 @@ class UiTab extends React.Component {
|
||||
const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
|
||||
|
||||
if (theme) {
|
||||
checkHighLight.setAttribute('href', `../${theme.path}`)
|
||||
checkHighLight.setAttribute('href', theme.path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +553,17 @@ class UiTab extends React.Component {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.editor.lineWrapping}
|
||||
ref='editorLineWrapping'
|
||||
type='checkbox'
|
||||
/>
|
||||
{i18n.__('Wrap line in Snippet Note')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
@@ -599,6 +618,16 @@ class UiTab extends React.Component {
|
||||
{i18n.__('Enable spellcheck - Experimental feature!! :)')}
|
||||
</label>
|
||||
</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-label'>
|
||||
@@ -800,6 +829,16 @@ class UiTab extends React.Component {
|
||||
</select>
|
||||
</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-label'>
|
||||
{i18n.__('LaTeX Inline Open Delimiter')}
|
||||
@@ -883,7 +922,6 @@ class UiTab extends React.Component {
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
ref={e => (this.customCSSCM = e)}
|
||||
value={config.preview.customCSS}
|
||||
defaultValue={'/* Drop Your Custom CSS Code Here */\n'}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'css',
|
||||
@@ -892,7 +930,27 @@ class UiTab extends React.Component {
|
||||
</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'>
|
||||
<button styleName='group-control-rightButton'
|
||||
onClick={(e) => this.handleSaveUIClick(e)}>{i18n.__('Save')}
|
||||
|
||||
@@ -147,7 +147,7 @@ class Preferences extends React.Component {
|
||||
key={tab.target}
|
||||
onClick={(e) => this.handleNavButtonClick(tab.target)(e)}
|
||||
>
|
||||
<span styleName='nav-button-label'>
|
||||
<span>
|
||||
{tab.label}
|
||||
</span>
|
||||
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RenameFolderModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { combineReducers, createStore } from 'redux'
|
||||
import { routerReducer } from 'react-router-redux'
|
||||
import { combineReducers, createStore, compose, applyMiddleware } from 'redux'
|
||||
import { connectRouter, routerMiddleware } from 'connected-react-router'
|
||||
import { createHashHistory as createHistory } from 'history'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import { Map, Set } from 'browser/lib/Mutable'
|
||||
import _ from 'lodash'
|
||||
import DevTools from './DevTools'
|
||||
|
||||
function defaultDataMap () {
|
||||
return {
|
||||
@@ -465,13 +467,17 @@ function getOrInitItem (target, key) {
|
||||
return results
|
||||
}
|
||||
|
||||
const history = createHistory()
|
||||
|
||||
const reducer = combineReducers({
|
||||
data,
|
||||
config,
|
||||
status,
|
||||
routing: routerReducer
|
||||
router: connectRouter(history)
|
||||
})
|
||||
|
||||
const store = createStore(reducer)
|
||||
const store = createStore(reducer, undefined, process.env.NODE_ENV === 'development'
|
||||
? compose(applyMiddleware(routerMiddleware(history)), DevTools.instrument())
|
||||
: applyMiddleware(routerMiddleware(history)))
|
||||
|
||||
export default store
|
||||
export { store, history }
|
||||
|
||||
@@ -410,6 +410,15 @@ $ui-dracula-button--active-color = #f8f8f2
|
||||
$ui-dracula-button--active-backgroundColor = #bd93f9
|
||||
$ui-dracula-button--hover-backgroundColor = lighten($ui-dracula-backgroundColor, 10%)
|
||||
$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()
|
||||
position relative
|
||||
|
||||
@@ -5,24 +5,23 @@ const ipc = electron.ipcMain
|
||||
const GhReleases = require('electron-gh-releases')
|
||||
const { isPackaged } = app
|
||||
// electron.crashReporter.start()
|
||||
const singleInstance = app.requestSingleInstanceLock()
|
||||
|
||||
var ipcServer = 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 (process.platform === 'win32') {
|
||||
mainWindow.minimize()
|
||||
mainWindow.restore()
|
||||
}
|
||||
if (!mainWindow.isVisible()) mainWindow.show()
|
||||
mainWindow.focus()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if (shouldQuit) {
|
||||
app.quit()
|
||||
}
|
||||
|
||||
var isUpdateReady = false
|
||||
|
||||
@@ -3,6 +3,7 @@ const BrowserWindow = electron.BrowserWindow
|
||||
const shell = electron.shell
|
||||
const ipc = electron.ipcMain
|
||||
const mainWindow = require('./main-window')
|
||||
const os = require('os')
|
||||
|
||||
const macOS = process.platform === 'darwin'
|
||||
// const WIN = process.platform === 'win32'
|
||||
@@ -411,6 +412,28 @@ const help = {
|
||||
click () { shell.openExternal('https://github.com/TobseF/boostnote-markdown-cheatsheet/blob/master/BOOSTNOTE_MARKDOWN_CHEAT_SHEET.md') }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'About',
|
||||
click () {
|
||||
const version = electron.app.getVersion()
|
||||
const electronVersion = process.versions.electron
|
||||
const chromeVersion = process.versions.chrome
|
||||
const nodeVersion = process.versions.node
|
||||
const v8Version = process.versions.v8
|
||||
const OSInfo = `${os.type()} ${os.arch()} ${os.release()}`
|
||||
const detail = `Version: ${version}\nElectron: ${electronVersion}\nChrome: ${chromeVersion}\nNode.js: ${nodeVersion}\nV8: ${v8Version}\nOS: ${OSInfo}`
|
||||
electron.dialog.showMessageBox(BrowserWindow.getFocusedWindow(),
|
||||
{
|
||||
title: 'BoostNote',
|
||||
message: 'BoostNote',
|
||||
type: 'info',
|
||||
detail: `\n${detail}`
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,33 @@ const Config = require('electron-config')
|
||||
const config = new Config()
|
||||
const _ = require('lodash')
|
||||
|
||||
// set up some chrome extensions
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const {
|
||||
default: installExtension,
|
||||
REACT_DEVELOPER_TOOLS,
|
||||
REACT_PERF
|
||||
} = require('electron-devtools-installer')
|
||||
|
||||
require('electron-debug')({ showDevTools: false })
|
||||
|
||||
const ChromeLens = {
|
||||
// ID of the extension (https://chrome.google.com/webstore/detail/chromelens/idikgljglpfilbhaboonnpnnincjhjkd)
|
||||
id: 'idikgljglpfilbhaboonnpnnincjhjkd',
|
||||
electron: '>=1.2.1'
|
||||
}
|
||||
|
||||
const extensions = [REACT_DEVELOPER_TOOLS, REACT_PERF, ChromeLens]
|
||||
|
||||
for (const extension of extensions) {
|
||||
try {
|
||||
installExtension(extension)
|
||||
} catch (e) {
|
||||
console.error(`[ELECTRON] Extension installation failed`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const windowSize = config.get('windowsize') || {
|
||||
x: null,
|
||||
y: null,
|
||||
@@ -27,8 +54,7 @@ const mainWindow = new BrowserWindow({
|
||||
},
|
||||
icon: path.resolve(__dirname, '../resources/app.png')
|
||||
})
|
||||
|
||||
const url = path.resolve(__dirname, './main.html')
|
||||
const url = path.resolve(__dirname, process.env.NODE_ENV === 'development' ? './main.development.html' : './main.production.html')
|
||||
|
||||
mainWindow.loadURL('file://' + url)
|
||||
mainWindow.setMenuBarVisibility(false)
|
||||
|
||||
@@ -73,6 +73,11 @@
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-tooltip {
|
||||
z-index: 1003;
|
||||
}
|
||||
@@ -110,7 +115,6 @@
|
||||
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
|
||||
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
|
||||
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
|
||||
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
@@ -141,8 +145,8 @@
|
||||
</script>
|
||||
|
||||
<script src="../node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
|
||||
<script src="../node_modules/react/dist/react.min.js"></script>
|
||||
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
|
||||
<script src="../node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="../node_modules/redux/dist/redux.min.js"></script>
|
||||
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
|
||||
<script type='text/javascript'>
|
||||
161
lib/main.production.html
Normal file
161
lib/main.production.html
Normal file
@@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
|
||||
<link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
||||
<link rel="shortcut icon" href="../resources/favicon.ico">
|
||||
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="../node_modules/katex/dist/katex.min.css">
|
||||
<link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css">
|
||||
<link rel="stylesheet" href="../extra_scripts/codemirror/mode/bfm/bfm.css">
|
||||
|
||||
<title>Boostnote</title>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'OpenSans';
|
||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
#loadingCover {
|
||||
background-color: #f4f4f4;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 65px 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#loadingCover img {
|
||||
display: block;
|
||||
margin: 75px auto 5px;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
#loadingCover .message {
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
font-weight: 100;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.CodeEditor {
|
||||
opacity: 1 !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
.CodeMirror-ruler {
|
||||
border-left-color: rgba(142, 142, 142, 0.5);
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="loadingCover">
|
||||
<img src="../resources/app.png">
|
||||
<div class='message'>
|
||||
<i class="fa fa-spinner fa-spin" spin></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
|
||||
<script src="../node_modules/codemirror/mode/meta.js"></script>
|
||||
<script src="../node_modules/codemirror-mode-elixir/dist/elixir.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/mode/multiplex.js"></script>
|
||||
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
||||
<script src="../node_modules/codemirror/keymap/vim.js"></script>
|
||||
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/display/panel.js"></script>
|
||||
<script src="../node_modules/codemirror/mode/xml/xml.js"></script>
|
||||
<script src="../node_modules/codemirror/mode/markdown/markdown.js"></script>
|
||||
<script src="../node_modules/codemirror/mode/gfm/gfm.js"></script>
|
||||
<script src="../node_modules/codemirror/mode/yaml/yaml.js"></script>
|
||||
<script src="../node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js"></script>
|
||||
|
||||
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
|
||||
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
|
||||
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/search/search.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/search/searchcursor.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/scroll/scrollpastend.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/search/jump-to-line.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/fold/brace-fold.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/fold/markdown-fold.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/fold/foldgutter.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/fold/foldcode.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/dialog/dialog.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/display/rulers.js"></script>
|
||||
|
||||
<script src="../node_modules/raphael/raphael.min.js"></script>
|
||||
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
|
||||
<script>
|
||||
window._ = require('lodash')
|
||||
</script>
|
||||
|
||||
<script src="../node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
|
||||
<script src="../node_modules/react/umd/react.production.min.js"></script>
|
||||
<script src="../node_modules/react-dom/umd/react-dom.production.min.js"></script>
|
||||
<script src="../node_modules/redux/dist/redux.min.js"></script>
|
||||
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
|
||||
<script type='text/javascript'>
|
||||
const electron = require('electron')
|
||||
electron.webFrame.setVisualZoomLevelLimits(1, 1)
|
||||
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
|
||||
? 'http://localhost:8080/assets/main.js'
|
||||
: '../compiled/main.js'
|
||||
var scriptEl = document.createElement('script')
|
||||
scriptEl.setAttribute('type', 'text/javascript')
|
||||
scriptEl.setAttribute('src', scriptUrl)
|
||||
document.body.appendChild(scriptEl)
|
||||
</script>
|
||||
<style>
|
||||
.ace_search {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -156,5 +156,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -212,5 +212,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -187,5 +187,7 @@
|
||||
"Snippet Default Language": "Snippet Default Language",
|
||||
"New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -158,5 +158,7 @@
|
||||
"Spellcheck disabled": "Deshabilitar corrector ortográfico",
|
||||
"Show menu bar": "Mostrar barra del menú",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -160,5 +160,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -172,5 +172,7 @@
|
||||
"Snippet name": "Nom du snippet",
|
||||
"Snippet prefix": "Préfixe du snippet",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -180,5 +180,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -160,5 +160,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -219,5 +219,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ このノートのストレージに存在しない添付ファイルへのリンクを貼り付けました。添付ファイルへのリンクの貼り付けは同一ストレージ内でのみサポートされています。代わりに添付ファイルをドラッグアンドドロップしてください! ⚠",
|
||||
"Spellcheck disabled": "スペルチェック無効",
|
||||
"Show menu bar": "メニューバーを表示",
|
||||
"Auto Detect": "自動検出"
|
||||
"Auto Detect": "自動検出",
|
||||
"Enable HTML label in mermaid flowcharts": "mermaid flowchartでHTMLラベルを有効にする ⚠ このオプションには潜在的なXSSの危険性があります。",
|
||||
"Wrap line in Snippet Note": "行を右端で折り返す(Snippet Note)"
|
||||
}
|
||||
|
||||
@@ -163,5 +163,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -156,5 +156,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -165,5 +165,7 @@
|
||||
"Add tag...": "Dodaj tag...",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -156,5 +156,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -155,5 +155,7 @@
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Você colou um link referente a um anexo que não pôde ser encontrado no local de armazenamento desta nota. A vinculação de anexos de referência de links só é suportada se o local de origem e de destino for o mesmo de armazenamento. Por favor, arraste e solte o anexo na nota! ⚠",
|
||||
"Spellcheck disabled": "Spellcheck disabled",
|
||||
"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"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user