mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
140 Commits
v0.11.7
...
revert-fli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d015b18c66 | ||
|
|
039f73711a | ||
|
|
6e885acf8c | ||
|
|
9ef07cea7a | ||
|
|
9e3b321aaf | ||
|
|
21560701ea | ||
|
|
4556375174 | ||
|
|
91b5398b5a | ||
|
|
eeb8016992 | ||
|
|
736106be3a | ||
|
|
f400568dc0 | ||
|
|
0ca96cba6e | ||
|
|
df4d837026 | ||
|
|
760f84d7fa | ||
|
|
174a315e3f | ||
|
|
0834313456 | ||
|
|
df931e10c0 | ||
|
|
9572cb2d33 | ||
|
|
51e836f32a | ||
|
|
7fefbd88d0 | ||
|
|
cb956c5508 | ||
|
|
47b0086bf8 | ||
|
|
b8d66e4a95 | ||
|
|
bfc1c93153 | ||
|
|
404faf8a0b | ||
|
|
4a7b0f4711 | ||
|
|
dd62fca45d | ||
|
|
79fb04126c | ||
|
|
39c9574ae3 | ||
|
|
38af257adf | ||
|
|
5aae9a4722 | ||
|
|
cfe3cae88d | ||
|
|
612de84ac6 | ||
|
|
33be597ef0 | ||
|
|
cc26fd80d7 | ||
|
|
c227a1ffec | ||
|
|
f0df787bbe | ||
|
|
09188bed48 | ||
|
|
4a3bcaba06 | ||
|
|
1d1ab65edd | ||
|
|
7330cdaf1c | ||
|
|
050a1fb6cf | ||
|
|
1e8397cf17 | ||
|
|
59b53ece2b | ||
|
|
16c62cd46f | ||
|
|
eff56c2514 | ||
|
|
ee6b9a223f | ||
|
|
acc6ea434a | ||
|
|
1e5a7356f4 | ||
|
|
4c8342c19d | ||
|
|
dad5232ecb | ||
|
|
6cad2ab4df | ||
|
|
be972781ee | ||
|
|
58fbc298b1 | ||
|
|
7de7772339 | ||
|
|
ad847a2f5d | ||
|
|
856d52891c | ||
|
|
8de3b3bd8d | ||
|
|
0414483be2 | ||
|
|
22939aa472 | ||
|
|
0cb7c44985 | ||
|
|
b18a09e5eb | ||
|
|
ef3649b1d6 | ||
|
|
ac70a0d94d | ||
|
|
3b91f9b88b | ||
|
|
c37b780ca4 | ||
|
|
20061d2c65 | ||
|
|
f18fa77c1c | ||
|
|
a4c6869d4d | ||
|
|
f9a0070c82 | ||
|
|
5cc52f91cb | ||
|
|
a46b9fb2be | ||
|
|
933e38eca9 | ||
|
|
e182390480 | ||
|
|
563fdcba94 | ||
|
|
bc640834cd | ||
|
|
0e9e7d644a | ||
|
|
1d9b3ac2b5 | ||
|
|
aebed4a644 | ||
|
|
7bfb094a40 | ||
|
|
f90a44c1d0 | ||
|
|
dfcf6d2729 | ||
|
|
806a5daa86 | ||
|
|
4a3602099a | ||
|
|
c69be54655 | ||
|
|
680eaa1d4a | ||
|
|
9cc7b8bcc6 | ||
|
|
55d86d853a | ||
|
|
7d9f309e04 | ||
|
|
c2f0147cff | ||
|
|
05488e66ae | ||
|
|
09eac89086 | ||
|
|
866a0e7534 | ||
|
|
3c8337cf54 | ||
|
|
883b4c4c26 | ||
|
|
6bc42c564d | ||
|
|
4f79f52524 | ||
|
|
d2b2e76a6a | ||
|
|
9d9109e9e5 | ||
|
|
18efb89b9a | ||
|
|
cefe883025 | ||
|
|
0ffa0b96d3 | ||
|
|
0429acfa1b | ||
|
|
827e3c1829 | ||
|
|
aa756ef194 | ||
|
|
d8aad65b24 | ||
|
|
1038e86196 | ||
|
|
47845fd4e3 | ||
|
|
294c3f10ab | ||
|
|
f6afc756dc | ||
|
|
64407e5ca6 | ||
|
|
0a42b0f61f | ||
|
|
ddd339851b | ||
|
|
82db986bd7 | ||
|
|
7bacd6f8f0 | ||
|
|
f0941f47dd | ||
|
|
c9d05b1117 | ||
|
|
7ee12752ec | ||
|
|
b44772441d | ||
|
|
72e3784fa5 | ||
|
|
2c7f24cb8c | ||
|
|
8a6c86bf65 | ||
|
|
cc52cf60dc | ||
|
|
398ebae2ba | ||
|
|
0095735841 | ||
|
|
95c10a1de7 | ||
|
|
8f4c92e251 | ||
|
|
58354061d8 | ||
|
|
5de176757d | ||
|
|
7414d52dc2 | ||
|
|
c42b5c8806 | ||
|
|
5c60da0f8f | ||
|
|
0ae1263d9d | ||
|
|
31f1ebe801 | ||
|
|
c6a9c9c57d | ||
|
|
707356bffe | ||
|
|
1516807ed5 | ||
|
|
9893fd9ae5 | ||
|
|
d6c28da3a8 | ||
|
|
52f694a714 |
@@ -22,7 +22,9 @@
|
|||||||
"fontSize": "14",
|
"fontSize": "14",
|
||||||
"lineNumber": true
|
"lineNumber": true
|
||||||
},
|
},
|
||||||
"sortBy": "UPDATED_AT",
|
"sortBy": {
|
||||||
|
"default": "UPDATED_AT"
|
||||||
|
},
|
||||||
"sortTagsBy": "ALPHABETICAL",
|
"sortTagsBy": "ALPHABETICAL",
|
||||||
"ui": {
|
"ui": {
|
||||||
"defaultNote": "ALWAYS_ASK",
|
"defaultNote": "ALWAYS_ASK",
|
||||||
|
|||||||
@@ -5,25 +5,30 @@ import CodeMirror from 'codemirror'
|
|||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
import convertModeName from 'browser/lib/convertModeName'
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
|
import { options, TableEditor } from '@susisu/mte-kernel'
|
||||||
|
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import iconv from 'iconv-lite'
|
import iconv from 'iconv-lite'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
const { ipcRenderer } = require('electron')
|
const { ipcRenderer } = require('electron')
|
||||||
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||||
|
|
||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
|
||||||
const buildCMRulers = (rulers, enableRulers) =>
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
enableRulers ? rulers.map(ruler => ({column: ruler})) : []
|
(enableRulers ? rulers.map(ruler => ({ column: ruler })) : [])
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
|
||||||
this.changeHandler = (e) => this.handleChange(e)
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
})
|
||||||
|
this.changeHandler = e => this.handleChange(e)
|
||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
ipcRenderer.send('editor:focused', true)
|
ipcRenderer.send('editor:focused', true)
|
||||||
}
|
}
|
||||||
@@ -39,15 +44,21 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
|
|
||||||
const {storageKey, noteKey} = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
|
attachmentManagement.deleteAttachmentsNotPresentInNote(
|
||||||
|
this.editor.getValue(),
|
||||||
|
storageKey,
|
||||||
|
noteKey
|
||||||
|
)
|
||||||
}
|
}
|
||||||
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
this.loadStyleHandler = (e) => {
|
this.loadStyleHandler = e => {
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
||||||
this.searchState = null
|
this.searchState = null
|
||||||
|
|
||||||
|
this.formatTable = () => this.handleFormatTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch (msg) {
|
handleSearch (msg) {
|
||||||
@@ -62,7 +73,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
cm.addOverlay(component.searchState)
|
cm.addOverlay(component.searchState)
|
||||||
|
|
||||||
function makeOverlay (query, style) {
|
function makeOverlay (query, style) {
|
||||||
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
|
query = new RegExp(
|
||||||
|
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
||||||
|
'gi'
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
token: function (stream) {
|
token: function (stream) {
|
||||||
query.lastIndex = stream.pos
|
query.lastIndex = stream.pos
|
||||||
@@ -81,6 +95,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFormatTable () {
|
||||||
|
this.tableEditor.formatAll(options({textWidthOptions: {}}))
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { rulers, enableRulers } = this.props
|
const { rulers, enableRulers } = this.props
|
||||||
const expandSnippet = this.expandSnippet.bind(this)
|
const expandSnippet = this.expandSnippet.bind(this)
|
||||||
@@ -94,7 +112,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
|
fs.writeFileSync(
|
||||||
|
consts.SNIPPET_FILE,
|
||||||
|
JSON.stringify(defaultSnippet, null, 4),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
@@ -113,7 +135,12 @@ export default class CodeEditor extends React.Component {
|
|||||||
dragDrop: false,
|
dragDrop: false,
|
||||||
foldGutter: true,
|
foldGutter: true,
|
||||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: {
|
||||||
|
pairs: '()[]{}\'\'""$$**``',
|
||||||
|
triples: '```"""\'\'\'',
|
||||||
|
explode: '[]{}``$$',
|
||||||
|
override: true
|
||||||
|
},
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
const cursor = cm.getCursor()
|
const cursor = cm.getCursor()
|
||||||
@@ -131,9 +158,14 @@ export default class CodeEditor extends React.Component {
|
|||||||
cm.execCommand('insertSoftTab')
|
cm.execCommand('insertSoftTab')
|
||||||
}
|
}
|
||||||
cm.execCommand('goLineEnd')
|
cm.execCommand('goLineEnd')
|
||||||
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
|
} else if (
|
||||||
|
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
|
||||||
|
cursor.ch > 1
|
||||||
|
) {
|
||||||
// text expansion on tab key if the char before is alphabet
|
// text expansion on tab key if the char before is alphabet
|
||||||
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
|
const snippets = JSON.parse(
|
||||||
|
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
|
||||||
|
)
|
||||||
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
@@ -154,7 +186,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
},
|
},
|
||||||
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
||||||
'Ctrl-C': (cm) => {
|
'Ctrl-C': cm => {
|
||||||
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
||||||
document.execCommand('copy')
|
document.execCommand('copy')
|
||||||
}
|
}
|
||||||
@@ -182,10 +214,17 @@ export default class CodeEditor extends React.Component {
|
|||||||
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
||||||
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||||
|
|
||||||
|
this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
|
||||||
|
eventEmitter.on('code:format-table', this.formatTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSnippet (line, cursor, cm, snippets) {
|
expandSnippet (line, cursor, cm, snippets) {
|
||||||
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
|
const wordBeforeCursor = this.getWordBeforeCursor(
|
||||||
|
line,
|
||||||
|
cursor.line,
|
||||||
|
cursor.ch
|
||||||
|
)
|
||||||
const templateCursorString = ':{}'
|
const templateCursorString = ':{}'
|
||||||
for (let i = 0; i < snippets.length; i++) {
|
for (let i = 0; i < snippets.length; i++) {
|
||||||
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
||||||
@@ -203,7 +242,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
wordBeforeCursor.range.from,
|
wordBeforeCursor.range.from,
|
||||||
wordBeforeCursor.range.to
|
wordBeforeCursor.range.to
|
||||||
)
|
)
|
||||||
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
|
cm.setCursor({
|
||||||
|
line: cursor.line + cursorLineNumber,
|
||||||
|
ch: cursorLinePosition
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -245,8 +287,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
return {
|
return {
|
||||||
text: wordBeforeCursor,
|
text: wordBeforeCursor,
|
||||||
range: {
|
range: {
|
||||||
from: {line: lineNumber, ch: originCursorPosition},
|
from: { line: lineNumber, ch: originCursorPosition },
|
||||||
to: {line: lineNumber, ch: cursorPosition}
|
to: { line: lineNumber, ch: cursorPosition }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,11 +306,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.off('scroll', this.scrollHandler)
|
this.editor.off('scroll', this.scrollHandler)
|
||||||
const editorTheme = document.getElementById('editorTheme')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
|
eventEmitter.off('code:format-table', this.formatTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
let needRefresh = false
|
let needRefresh = false
|
||||||
const {rulers, enableRulers} = this.props
|
const { rulers, enableRulers } = this.props
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
}
|
}
|
||||||
@@ -286,7 +330,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
needRefresh = true
|
needRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) {
|
if (
|
||||||
|
prevProps.enableRulers !== enableRulers ||
|
||||||
|
prevProps.rulers !== rulers
|
||||||
|
) {
|
||||||
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
|
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,11 +373,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveCursorTo (row, col) {
|
moveCursorTo (row, col) {}
|
||||||
}
|
|
||||||
|
|
||||||
scrollToLine (num) {
|
scrollToLine (num) {}
|
||||||
}
|
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
this.editor.focus()
|
this.editor.focus()
|
||||||
@@ -358,8 +403,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
handleDropImage (dropEvent) {
|
handleDropImage (dropEvent) {
|
||||||
dropEvent.preventDefault()
|
dropEvent.preventDefault()
|
||||||
const {storageKey, noteKey} = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent)
|
attachmentManagement.handleAttachmentDrop(
|
||||||
|
this,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
dropEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
insertAttachmentMd (imageMd) {
|
insertAttachmentMd (imageMd) {
|
||||||
@@ -368,34 +418,44 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
handlePaste (editor, e) {
|
handlePaste (editor, e) {
|
||||||
const clipboardData = e.clipboardData
|
const clipboardData = e.clipboardData
|
||||||
const {storageKey, noteKey} = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
const dataTransferItem = clipboardData.items[0]
|
const dataTransferItem = clipboardData.items[0]
|
||||||
const pastedTxt = clipboardData.getData('text')
|
const pastedTxt = clipboardData.getData('text')
|
||||||
const isURL = (str) => {
|
const isURL = str => {
|
||||||
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
||||||
return matcher.test(str)
|
return matcher.test(str)
|
||||||
}
|
}
|
||||||
const isInLinkTag = (editor) => {
|
const isInLinkTag = editor => {
|
||||||
const startCursor = editor.getCursor('start')
|
const startCursor = editor.getCursor('start')
|
||||||
const prevChar = editor.getRange(
|
const prevChar = editor.getRange(
|
||||||
{line: startCursor.line, ch: startCursor.ch - 2},
|
{ line: startCursor.line, ch: startCursor.ch - 2 },
|
||||||
{line: startCursor.line, ch: startCursor.ch}
|
{ line: startCursor.line, ch: startCursor.ch }
|
||||||
)
|
)
|
||||||
const endCursor = editor.getCursor('end')
|
const endCursor = editor.getCursor('end')
|
||||||
const nextChar = editor.getRange(
|
const nextChar = editor.getRange(
|
||||||
{line: endCursor.line, ch: endCursor.ch},
|
{ line: endCursor.line, ch: endCursor.ch },
|
||||||
{line: endCursor.line, ch: endCursor.ch + 1}
|
{ line: endCursor.line, ch: endCursor.ch + 1 }
|
||||||
)
|
)
|
||||||
return prevChar === '](' && nextChar === ')'
|
return prevChar === '](' && nextChar === ')'
|
||||||
}
|
}
|
||||||
if (dataTransferItem.type.match('image')) {
|
if (dataTransferItem.type.match('image')) {
|
||||||
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
|
attachmentManagement.handlePastImageEvent(
|
||||||
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
this,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
dataTransferItem
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
this.props.fetchUrlTitle &&
|
||||||
|
isURL(pastedTxt) &&
|
||||||
|
!isInLinkTag(editor)
|
||||||
|
) {
|
||||||
this.handlePasteUrl(e, editor, pastedTxt)
|
this.handlePasteUrl(e, editor, pastedTxt)
|
||||||
}
|
}
|
||||||
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||||
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
attachmentManagement
|
||||||
.then((modifiedText) => {
|
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
||||||
|
.then(modifiedText => {
|
||||||
this.editor.replaceSelection(modifiedText)
|
this.editor.replaceSelection(modifiedText)
|
||||||
})
|
})
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -413,39 +473,49 @@ export default class CodeEditor extends React.Component {
|
|||||||
const taggedUrl = `<${pastedTxt}>`
|
const taggedUrl = `<${pastedTxt}>`
|
||||||
editor.replaceSelection(taggedUrl)
|
editor.replaceSelection(taggedUrl)
|
||||||
|
|
||||||
const isImageReponse = (response) => {
|
const isImageReponse = response => {
|
||||||
return response.headers.has('content-type') &&
|
return (
|
||||||
|
response.headers.has('content-type') &&
|
||||||
response.headers.get('content-type').match(/^image\/.+$/)
|
response.headers.get('content-type').match(/^image\/.+$/)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const replaceTaggedUrl = (replacement) => {
|
const replaceTaggedUrl = replacement => {
|
||||||
const value = editor.getValue()
|
const value = editor.getValue()
|
||||||
const cursor = editor.getCursor()
|
const cursor = editor.getCursor()
|
||||||
const newValue = value.replace(taggedUrl, replacement)
|
const newValue = value.replace(taggedUrl, replacement)
|
||||||
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
|
const newCursor = Object.assign({}, cursor, {
|
||||||
|
ch: cursor.ch + newValue.length - value.length
|
||||||
|
})
|
||||||
editor.setValue(newValue)
|
editor.setValue(newValue)
|
||||||
editor.setCursor(newCursor)
|
editor.setCursor(newCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(pastedTxt, {
|
fetch(pastedTxt, {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
}).then((response) => {
|
|
||||||
if (isImageReponse(response)) {
|
|
||||||
return this.mapImageResponse(response, pastedTxt)
|
|
||||||
} else {
|
|
||||||
return this.mapNormalResponse(response, pastedTxt)
|
|
||||||
}
|
|
||||||
}).then((replacement) => {
|
|
||||||
replaceTaggedUrl(replacement)
|
|
||||||
}).catch((e) => {
|
|
||||||
replaceTaggedUrl(pastedTxt)
|
|
||||||
})
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (isImageReponse(response)) {
|
||||||
|
return this.mapImageResponse(response, pastedTxt)
|
||||||
|
} else {
|
||||||
|
return this.mapNormalResponse(response, pastedTxt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(replacement => {
|
||||||
|
replaceTaggedUrl(replacement)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
replaceTaggedUrl(pastedTxt)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mapNormalResponse (response, pastedTxt) {
|
mapNormalResponse (response, pastedTxt) {
|
||||||
return this.decodeResponse(response).then((body) => {
|
return this.decodeResponse(response).then(body => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html')
|
const parsedBody = new window.DOMParser().parseFromString(
|
||||||
|
body,
|
||||||
|
'text/html'
|
||||||
|
)
|
||||||
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
||||||
resolve(linkWithTitle)
|
resolve(linkWithTitle)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -473,10 +543,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
const _charset = headers.has('content-type')
|
const _charset = headers.has('content-type')
|
||||||
? this.extractContentTypeCharset(headers.get('content-type'))
|
? this.extractContentTypeCharset(headers.get('content-type'))
|
||||||
: undefined
|
: undefined
|
||||||
return response.arrayBuffer().then((buff) => {
|
return response.arrayBuffer().then(buff => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const charset = _charset !== undefined && iconv.encodingExists(_charset) ? _charset : 'utf-8'
|
const charset = _charset !== undefined &&
|
||||||
|
iconv.encodingExists(_charset)
|
||||||
|
? _charset
|
||||||
|
: 'utf-8'
|
||||||
resolve(iconv.decode(new Buffer(buff), charset).toString())
|
resolve(iconv.decode(new Buffer(buff), charset).toString())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e)
|
reject(e)
|
||||||
@@ -486,34 +559,31 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extractContentTypeCharset (contentType) {
|
extractContentTypeCharset (contentType) {
|
||||||
return contentType.split(';').filter((str) => {
|
return contentType
|
||||||
return str.trim().toLowerCase().startsWith('charset')
|
.split(';')
|
||||||
}).map((str) => {
|
.filter(str => {
|
||||||
return str.replace(/['"]/g, '').split('=')[1]
|
return str.trim().toLowerCase().startsWith('charset')
|
||||||
})[0]
|
})
|
||||||
|
.map(str => {
|
||||||
|
return str.replace(/['"]/g, '').split('=')[1]
|
||||||
|
})[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {className, fontSize} = this.props
|
const {className, fontSize} = this.props
|
||||||
let fontFamily = this.props.fontFamily
|
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
|
||||||
? [fontFamily].concat(defaultEditorFontFamily)
|
|
||||||
: defaultEditorFontFamily
|
|
||||||
const width = this.props.width
|
const width = this.props.width
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className == null
|
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
|
||||||
? 'CodeEditor'
|
|
||||||
: `CodeEditor ${className}`
|
|
||||||
}
|
|
||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={{
|
style={{
|
||||||
fontFamily: fontFamily.join(', '),
|
fontFamily,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
width: width
|
width: width
|
||||||
}}
|
}}
|
||||||
onDrop={(e) => this.handleDropImage(e)}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import 'codemirror-mode-elixir'
|
|||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import Raphael from 'raphael'
|
import Raphael from 'raphael'
|
||||||
import flowchart from 'flowchart'
|
import flowchart from 'flowchart'
|
||||||
|
import mermaidRender from './render/MermaidRender'
|
||||||
import SequenceDiagram from 'js-sequence-diagrams'
|
import SequenceDiagram from 'js-sequence-diagrams'
|
||||||
|
import Chart from 'chart.js'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
import convertModeName from 'browser/lib/convertModeName'
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
@@ -26,15 +28,24 @@ const fileUrl = require('file-url')
|
|||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const appPath = fileUrl(process.env.NODE_ENV === 'production'
|
const appPath = fileUrl(
|
||||||
? app.getAppPath()
|
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
||||||
: path.resolve())
|
)
|
||||||
const CSS_FILES = [
|
const CSS_FILES = [
|
||||||
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
||||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
|
function buildStyle (
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
) {
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -131,6 +142,25 @@ body p {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollBarStyle = `
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const scrollBarDarkStyle = `
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const { shell } = require('electron')
|
const { shell } = require('electron')
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -139,17 +169,27 @@ if (!OSX) {
|
|||||||
defaultFontFamily.unshift('Microsoft YaHei')
|
defaultFontFamily.unshift('Microsoft YaHei')
|
||||||
defaultFontFamily.unshift('meiryo')
|
defaultFontFamily.unshift('meiryo')
|
||||||
}
|
}
|
||||||
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultCodeBlockFontFamily = [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
export default class MarkdownPreview extends React.Component {
|
export default class MarkdownPreview extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
this.contextMenuHandler = e => this.handleContextMenu(e)
|
||||||
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
this.mouseDownHandler = e => this.handleMouseDown(e)
|
||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.mouseUpHandler = e => this.handleMouseUp(e)
|
||||||
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
this.DoubleClickHandler = e => this.handleDoubleClick(e)
|
||||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
|
||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
})
|
||||||
|
this.checkboxClickHandler = e => this.handleCheckboxClick(e)
|
||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||||
@@ -211,36 +251,85 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsMd () {
|
handleSaveAsMd () {
|
||||||
this.exportAsDocument('md')
|
this.exportAsDocument('md', (noteContent, exportTasks) => {
|
||||||
|
let result = noteContent
|
||||||
|
if (this.props && this.props.storagePath && this.props.noteKey) {
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
|
noteContent,
|
||||||
|
this.props.storagePath
|
||||||
|
)
|
||||||
|
attachmentsAbsolutePaths.forEach(attachment => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
})
|
||||||
|
})
|
||||||
|
result = attachmentManagement.removeStorageAndNoteReferences(
|
||||||
|
noteContent,
|
||||||
|
this.props.noteKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsHtml () {
|
handleSaveAsHtml () {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
const {
|
||||||
|
fontFamily,
|
||||||
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
fontSize,
|
||||||
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
} = this.getStyleParams()
|
||||||
|
|
||||||
|
const inlineStyles = buildStyle(
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
)
|
||||||
|
let body = this.markdown.render(
|
||||||
|
escapeHtmlCharacters(noteContent, { detectCodeBlock: true })
|
||||||
|
)
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
|
noteContent,
|
||||||
|
this.props.storagePath
|
||||||
|
)
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach(file => {
|
||||||
file = file.replace('file://', '')
|
if (global.process.platform === 'win32') {
|
||||||
|
file = file.replace('file:///', '')
|
||||||
|
} else {
|
||||||
|
file = file.replace('file://', '')
|
||||||
|
}
|
||||||
exportTasks.push({
|
exportTasks.push({
|
||||||
src: file,
|
src: file,
|
||||||
dst: 'css'
|
dst: 'css'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
attachmentsAbsolutePaths.forEach((attachment) => {
|
attachmentsAbsolutePaths.forEach(attachment => {
|
||||||
exportTasks.push({
|
exportTasks.push({
|
||||||
src: attachment,
|
src: attachment,
|
||||||
dst: attachmentManagement.DESTINATION_FOLDER
|
dst: attachmentManagement.DESTINATION_FOLDER
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
|
body = attachmentManagement.removeStorageAndNoteReferences(
|
||||||
|
body,
|
||||||
|
this.props.noteKey
|
||||||
|
)
|
||||||
|
|
||||||
let styles = ''
|
let styles = ''
|
||||||
files.forEach((file) => {
|
files.forEach(file => {
|
||||||
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -262,50 +351,75 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
exportAsDocument (fileType, contentFormatter) {
|
exportAsDocument (fileType, contentFormatter) {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [
|
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||||
{name: 'Documents', extensions: [fileType]}
|
|
||||||
],
|
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.showSaveDialog(remote.getCurrentWindow(), options,
|
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
||||||
(filename) => {
|
if (filename) {
|
||||||
if (filename) {
|
const content = this.props.value
|
||||||
const content = this.props.value
|
const storage = this.props.storagePath
|
||||||
const storage = this.props.storagePath
|
|
||||||
|
|
||||||
exportNote(storage, content, filename, contentFormatter)
|
exportNote(storage, content, filename, contentFormatter)
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`})
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
}).catch((err) => {
|
type: 'info',
|
||||||
dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export')
|
message: `Exported to ${filename}`
|
||||||
throw err
|
})
|
||||||
})
|
})
|
||||||
}
|
.catch(err => {
|
||||||
})
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
err ? err.message || err : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fixDecodedURI (node) {
|
fixDecodedURI (node) {
|
||||||
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
|
if (
|
||||||
|
node &&
|
||||||
|
node.children.length === 1 &&
|
||||||
|
typeof node.children[0] === 'string'
|
||||||
|
) {
|
||||||
const { innerText, href } = node
|
const { innerText, href } = node
|
||||||
|
|
||||||
node.innerText = mdurl.decode(href) === innerText
|
node.innerText = mdurl.decode(href) === innerText ? href : innerText
|
||||||
? href
|
}
|
||||||
: innerText
|
}
|
||||||
|
|
||||||
|
getScrollBarStyle () {
|
||||||
|
const { theme } = this.props
|
||||||
|
|
||||||
|
switch (theme) {
|
||||||
|
case 'dark':
|
||||||
|
case 'solarized-dark':
|
||||||
|
case 'monokai':
|
||||||
|
return scrollBarDarkStyle
|
||||||
|
default:
|
||||||
|
return scrollBarStyle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||||
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.addEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
this.contextMenuHandler
|
||||||
|
)
|
||||||
|
|
||||||
let styles = `
|
let styles = `
|
||||||
<style id='style'></style>
|
<style id='style'></style>
|
||||||
<link rel="stylesheet" id="codeTheme">
|
<link rel="stylesheet" id="codeTheme">
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<style>
|
||||||
|
${this.getScrollBarStyle()}
|
||||||
|
</style>
|
||||||
`
|
`
|
||||||
|
|
||||||
CSS_FILES.forEach((file) => {
|
CSS_FILES.forEach(file => {
|
||||||
styles += `<link rel="stylesheet" href="${file}">`
|
styles += `<link rel="stylesheet" href="${file}">`
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -313,12 +427,30 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
|
'mousedown',
|
||||||
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
|
this.mouseDownHandler
|
||||||
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
)
|
||||||
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
this.refs.root.contentWindow.document.addEventListener('scroll', this.scrollHandler)
|
'mouseup',
|
||||||
|
this.mouseUpHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'dblclick',
|
||||||
|
this.DoubleClickHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'drop',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'dragover',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'scroll',
|
||||||
|
this.scrollHandler
|
||||||
|
)
|
||||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||||
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||||
@@ -326,13 +458,34 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.removeEventListener(
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
|
'contextmenu',
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
|
this.contextMenuHandler
|
||||||
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
|
)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
'mousedown',
|
||||||
this.refs.root.contentWindow.document.removeEventListener('scroll', this.scrollHandler)
|
this.mouseDownHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'mouseup',
|
||||||
|
this.mouseUpHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'dblclick',
|
||||||
|
this.DoubleClickHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'drop',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'dragover',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'scroll',
|
||||||
|
this.scrollHandler
|
||||||
|
)
|
||||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||||
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||||
@@ -341,14 +494,17 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
if (prevProps.smartQuotes !== this.props.smartQuotes ||
|
if (
|
||||||
prevProps.sanitize !== this.props.sanitize ||
|
prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
prevProps.smartArrows !== this.props.smartArrows ||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
prevProps.breaks !== this.props.breaks) {
|
prevProps.smartArrows !== this.props.smartArrows ||
|
||||||
|
prevProps.breaks !== this.props.breaks
|
||||||
|
) {
|
||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
if (prevProps.fontFamily !== this.props.fontFamily ||
|
if (
|
||||||
|
prevProps.fontFamily !== this.props.fontFamily ||
|
||||||
prevProps.fontSize !== this.props.fontSize ||
|
prevProps.fontSize !== this.props.fontSize ||
|
||||||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
||||||
@@ -357,34 +513,82 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.theme !== this.props.theme ||
|
prevProps.theme !== this.props.theme ||
|
||||||
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
||||||
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
||||||
prevProps.customCSS !== this.props.customCSS) {
|
prevProps.customCSS !== this.props.customCSS
|
||||||
|
) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams () {
|
getStyleParams () {
|
||||||
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
|
const {
|
||||||
|
fontSize,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
} = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
? fontFamily
|
||||||
: defaultFontFamily
|
.split(',')
|
||||||
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
.map(fontName => fontName.trim())
|
||||||
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
.concat(defaultFontFamily)
|
||||||
: defaultCodeBlockFontFamily
|
: defaultFontFamily
|
||||||
|
codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
|
||||||
|
codeBlockFontFamily.trim().length > 0
|
||||||
|
? codeBlockFontFamily
|
||||||
|
.split(',')
|
||||||
|
.map(fontName => fontName.trim())
|
||||||
|
.concat(defaultCodeBlockFontFamily)
|
||||||
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
|
return {
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
applyStyle () {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
const {
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
} = this.getStyleParams()
|
||||||
|
|
||||||
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
|
this.getWindow().document.getElementById(
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
'codeTheme'
|
||||||
|
).href = this.GetCodeThemeLink(codeBlockTheme)
|
||||||
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
GetCodeThemeLink (theme) {
|
GetCodeThemeLink (theme) {
|
||||||
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
theme = consts.THEMES.some(_theme => _theme === theme) &&
|
||||||
|
theme !== 'default'
|
||||||
? theme
|
? theme
|
||||||
: 'elegant'
|
: 'elegant'
|
||||||
return theme.startsWith('solarized')
|
return theme.startsWith('solarized')
|
||||||
@@ -393,71 +597,92 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe () {
|
rewriteIframe () {
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(
|
||||||
el.removeEventListener('click', this.checkboxClickHandler)
|
this.refs.root.contentWindow.document.querySelectorAll(
|
||||||
})
|
'input[type="checkbox"]'
|
||||||
|
),
|
||||||
|
el => {
|
||||||
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(
|
||||||
el.removeEventListener('click', this.linkClickHandler)
|
this.refs.root.contentWindow.document.querySelectorAll('a'),
|
||||||
})
|
el => {
|
||||||
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const { theme, indentSize, showCopyNotification, storagePath, noteKey } = this.props
|
const {
|
||||||
|
theme,
|
||||||
|
indentSize,
|
||||||
|
showCopyNotification,
|
||||||
|
storagePath,
|
||||||
|
noteKey
|
||||||
|
} = this.props
|
||||||
let { value, codeBlockTheme } = this.props
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
|
const renderedHTML = this.markdown.render(value)
|
||||||
|
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
||||||
|
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
|
||||||
|
renderedHTML,
|
||||||
|
storagePath
|
||||||
|
)
|
||||||
|
_.forEach(
|
||||||
|
this.refs.root.contentWindow.document.querySelectorAll(
|
||||||
|
'input[type="checkbox"]'
|
||||||
|
),
|
||||||
|
el => {
|
||||||
|
el.addEventListener('click', this.checkboxClickHandler)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g)
|
_.forEach(
|
||||||
if (codeBlocks !== null) {
|
this.refs.root.contentWindow.document.querySelectorAll('a'),
|
||||||
codeBlocks.forEach((codeBlock) => {
|
el => {
|
||||||
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
this.fixDecodedURI(el)
|
||||||
})
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
}
|
}
|
||||||
let renderedHTML = this.markdown.render(value)
|
)
|
||||||
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
|
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
|
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
codeBlockTheme = consts.THEMES.some(_theme => _theme === codeBlockTheme)
|
||||||
el.addEventListener('click', this.checkboxClickHandler)
|
|
||||||
})
|
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
|
||||||
this.fixDecodedURI(el)
|
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
|
||||||
})
|
|
||||||
|
|
||||||
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
|
||||||
? codeBlockTheme
|
? codeBlockTheme
|
||||||
: 'default'
|
: 'default'
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
_.forEach(
|
||||||
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
|
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
el => {
|
||||||
CodeMirror.requireMode(syntax.mode, () => {
|
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
|
||||||
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
const copyIcon = document.createElement('i')
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
copyIcon.onclick = (e) => {
|
const copyIcon = document.createElement('i')
|
||||||
copy(content)
|
copyIcon.innerHTML =
|
||||||
if (showCopyNotification) {
|
'<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
||||||
this.notify('Saved to Clipboard!', {
|
copyIcon.onclick = e => {
|
||||||
body: 'Paste it wherever you want!',
|
copy(content)
|
||||||
silent: true
|
if (showCopyNotification) {
|
||||||
})
|
this.notify('Saved to Clipboard!', {
|
||||||
|
body: 'Paste it wherever you want!',
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
el.parentNode.appendChild(copyIcon)
|
||||||
el.parentNode.appendChild(copyIcon)
|
el.innerHTML = ''
|
||||||
el.innerHTML = ''
|
if (codeBlockTheme.indexOf('solarized') === 0) {
|
||||||
if (codeBlockTheme.indexOf('solarized') === 0) {
|
const [refThema, color] = codeBlockTheme.split(' ')
|
||||||
const [refThema, color] = codeBlockTheme.split(' ')
|
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
|
||||||
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
|
} else {
|
||||||
} else {
|
el.parentNode.className += ` cm-s-${codeBlockTheme}`
|
||||||
el.parentNode.className += ` cm-s-${codeBlockTheme}`
|
}
|
||||||
}
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
CodeMirror.runMode(content, syntax.mime, el, {
|
tabSize: indentSize
|
||||||
tabSize: indentSize
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
const opts = {}
|
const opts = {}
|
||||||
// if (this.props.theme === 'dark') {
|
// if (this.props.theme === 'dark') {
|
||||||
// opts['font-color'] = '#DDD'
|
// opts['font-color'] = '#DDD'
|
||||||
@@ -465,37 +690,71 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
// opts['element-color'] = '#DDD'
|
// opts['element-color'] = '#DDD'
|
||||||
// opts['fill'] = '#3A404C'
|
// opts['fill'] = '#3A404C'
|
||||||
// }
|
// }
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
|
_.forEach(
|
||||||
Raphael.setWindow(this.getWindow())
|
this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
|
||||||
try {
|
el => {
|
||||||
const diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
Raphael.setWindow(this.getWindow())
|
||||||
el.innerHTML = ''
|
try {
|
||||||
diagram.drawSVG(el, opts)
|
const diagram = flowchart.parse(
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
)
|
||||||
})
|
el.innerHTML = ''
|
||||||
} catch (e) {
|
diagram.drawSVG(el, opts)
|
||||||
console.error(e)
|
_.forEach(el.querySelectorAll('a'), el => {
|
||||||
el.className = 'flowchart-error'
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
el.innerHTML = 'Flowchart parse error: ' + e.message
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'flowchart-error'
|
||||||
|
el.innerHTML = 'Flowchart parse error: ' + e.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
|
_.forEach(
|
||||||
Raphael.setWindow(this.getWindow())
|
this.refs.root.contentWindow.document.querySelectorAll('.sequence'),
|
||||||
try {
|
el => {
|
||||||
const diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
Raphael.setWindow(this.getWindow())
|
||||||
el.innerHTML = ''
|
try {
|
||||||
diagram.drawSVG(el, {theme: 'simple'})
|
const diagram = SequenceDiagram.parse(
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
)
|
||||||
})
|
el.innerHTML = ''
|
||||||
} catch (e) {
|
diagram.drawSVG(el, { theme: 'simple' })
|
||||||
console.error(e)
|
_.forEach(el.querySelectorAll('a'), el => {
|
||||||
el.className = 'sequence-error'
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'sequence-error'
|
||||||
|
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
|
_.forEach(
|
||||||
|
this.refs.root.contentWindow.document.querySelectorAll('.chart'),
|
||||||
|
el => {
|
||||||
|
try {
|
||||||
|
const chartConfig = JSON.parse(el.innerHTML)
|
||||||
|
el.innerHTML = ''
|
||||||
|
var canvas = document.createElement('canvas')
|
||||||
|
el.appendChild(canvas)
|
||||||
|
/* eslint-disable no-new */
|
||||||
|
new Chart(canvas, chartConfig)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'chart-error'
|
||||||
|
el.innerHTML = 'chartjs diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
_.forEach(
|
||||||
|
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
|
||||||
|
el => {
|
||||||
|
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
@@ -507,7 +766,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollTo (targetRow) {
|
scrollTo (targetRow) {
|
||||||
const blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
const blocks = this.getWindow().document.querySelectorAll(
|
||||||
|
'body>[data-line]'
|
||||||
|
)
|
||||||
|
|
||||||
for (let index = 0; index < blocks.length; index++) {
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
let block = blocks[index]
|
let block = blocks[index]
|
||||||
@@ -527,7 +788,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
notify (title, options) {
|
notify (title, options) {
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
options.icon = path.join(
|
||||||
|
'file://',
|
||||||
|
global.__dirname,
|
||||||
|
'../../resources/app.png'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return new window.Notification(title, options)
|
return new window.Notification(title, options)
|
||||||
}
|
}
|
||||||
@@ -542,7 +807,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
const regexNoteInternalLink = /main.html#(.+)/
|
const regexNoteInternalLink = /main.html#(.+)/
|
||||||
if (regexNoteInternalLink.test(linkHash)) {
|
if (regexNoteInternalLink.test(linkHash)) {
|
||||||
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
||||||
const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
|
const targetElement = this.refs.root.contentWindow.document.getElementById(
|
||||||
|
targetId
|
||||||
|
)
|
||||||
|
|
||||||
if (targetElement != null) {
|
if (targetElement != null) {
|
||||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||||
@@ -576,9 +843,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
render () {
|
render () {
|
||||||
const { className, style, tabIndex } = this.props
|
const { className, style, tabIndex } = this.props
|
||||||
return (
|
return (
|
||||||
<iframe className={className != null
|
<iframe
|
||||||
? 'MarkdownPreview ' + className
|
className={
|
||||||
: 'MarkdownPreview'
|
className != null ? 'MarkdownPreview ' + className : 'MarkdownPreview'
|
||||||
}
|
}
|
||||||
style={style}
|
style={style}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ const TagElement = ({ tagName }) => (
|
|||||||
* @param {Array|null} tags
|
* @param {Array|null} tags
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const TagElementList = (tags) => {
|
const TagElementList = tags => {
|
||||||
if (!isArray(tags)) {
|
if (!isArray(tags)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagElements = tags.map(tag => (
|
const tagElements = tags.map(tag => TagElement({ tagName: tag }))
|
||||||
TagElement({tagName: tag})
|
|
||||||
))
|
|
||||||
|
|
||||||
return tagElements
|
return tagElements
|
||||||
}
|
}
|
||||||
@@ -59,10 +57,8 @@ const NoteItem = ({
|
|||||||
folderName,
|
folderName,
|
||||||
viewType
|
viewType
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isActive
|
<div
|
||||||
? 'item--active'
|
styleName={isActive ? 'item--active' : 'item'}
|
||||||
: 'item'
|
|
||||||
}
|
|
||||||
key={note.key}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, note.key)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
@@ -72,42 +68,54 @@ const NoteItem = ({
|
|||||||
<div styleName='item-wrapper'>
|
<div styleName='item-wrapper'>
|
||||||
{note.type === 'SNIPPET_NOTE'
|
{note.type === 'SNIPPET_NOTE'
|
||||||
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
||||||
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
|
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />}
|
||||||
}
|
|
||||||
<div styleName='item-title'>
|
<div styleName='item-title'>
|
||||||
{note.title.trim().length > 0
|
{note.title.trim().length > 0
|
||||||
? note.title
|
? note.title
|
||||||
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
|
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
|
{['ALL', 'STORAGE'].includes(viewType) &&
|
||||||
<div styleName='item-middle-time'>{dateDisplay}</div>
|
<div styleName='item-middle'>
|
||||||
<div styleName='item-middle-app-meta'>
|
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||||
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
|
<div styleName='item-middle-app-meta'>
|
||||||
{viewType === 'ALL' && storageName}
|
<div
|
||||||
{viewType === 'STORAGE' && folderName}
|
title={
|
||||||
|
viewType === 'ALL'
|
||||||
|
? storageName
|
||||||
|
: viewType === 'STORAGE' ? folderName : null
|
||||||
|
}
|
||||||
|
styleName='item-middle-app-meta-label'
|
||||||
|
>
|
||||||
|
{viewType === 'ALL' && storageName}
|
||||||
|
{viewType === 'STORAGE' && folderName}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>}
|
||||||
</div>}
|
|
||||||
|
|
||||||
<div styleName='item-bottom'>
|
<div styleName='item-bottom'>
|
||||||
<div styleName='item-bottom-tagList'>
|
<div styleName='item-bottom-tagList'>
|
||||||
{note.tags.length > 0
|
{note.tags.length > 0
|
||||||
? TagElementList(note.tags)
|
? TagElementList(note.tags)
|
||||||
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>{i18n.__('No tags')}</span>
|
: <span
|
||||||
}
|
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
||||||
|
styleName='item-bottom-tagList-empty'
|
||||||
|
>
|
||||||
|
{i18n.__('No tags')}
|
||||||
|
</span>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{note.isStarred
|
{note.isStarred
|
||||||
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
|
? <img
|
||||||
}
|
styleName='item-star'
|
||||||
|
src='../resources/icon/icon-starred.svg'
|
||||||
|
/>
|
||||||
|
: ''}
|
||||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
||||||
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
|
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
}
|
: ''}
|
||||||
{note.type === 'MARKDOWN_NOTE'
|
{note.type === 'MARKDOWN_NOTE'
|
||||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||||
: ''
|
: ''}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,9 +54,8 @@ const StorageItem = ({
|
|||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
>
|
>
|
||||||
{!isFolded && (
|
{!isFolded &&
|
||||||
<DraggableIcon className={styles['folderList-item-reorder']} />
|
<DraggableIcon className={styles['folderList-item-reorder']} />}
|
||||||
)}
|
|
||||||
<span
|
<span
|
||||||
styleName={
|
styleName={
|
||||||
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||||
@@ -72,12 +71,10 @@ const StorageItem = ({
|
|||||||
: folderName}
|
: folderName}
|
||||||
</span>
|
</span>
|
||||||
{!isFolded &&
|
{!isFolded &&
|
||||||
_.isNumber(noteCount) && (
|
_.isNumber(noteCount) &&
|
||||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>}
|
||||||
)}
|
{isFolded &&
|
||||||
{isFolded && (
|
<span styleName='folderList-item-tooltip'>{folderName}</span>}
|
||||||
<span styleName='folderList-item-tooltip'>{folderName}</span>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ body
|
|||||||
padding 5px
|
padding 5px
|
||||||
margin -5px
|
margin -5px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
.flowchart-error, .sequence-error
|
.flowchart-error, .sequence-error .chart-error
|
||||||
background-color errorBackgroundColor
|
background-color errorBackgroundColor
|
||||||
color errorTextColor
|
color errorTextColor
|
||||||
padding 5px
|
padding 5px
|
||||||
@@ -213,7 +213,7 @@ pre
|
|||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
display flex
|
display flex
|
||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
&.flowchart, &.sequence
|
&.flowchart, &.sequence, &.chart
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content center
|
||||||
background-color white
|
background-color white
|
||||||
|
|||||||
39
browser/components/render/MermaidRender.js
Normal file
39
browser/components/render/MermaidRender.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import mermaidAPI from 'mermaid'
|
||||||
|
|
||||||
|
// fixes bad styling in the mermaid dark theme
|
||||||
|
const darkThemeStyling = `
|
||||||
|
.loopText tspan {
|
||||||
|
fill: white;
|
||||||
|
}`
|
||||||
|
|
||||||
|
function getRandomInt (min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
function getId () {
|
||||||
|
var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
var id = 'm-'
|
||||||
|
for (var i = 0; i < 7; i++) {
|
||||||
|
id += pool[getRandomInt(0, 16)]
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
function render (element, content, theme) {
|
||||||
|
try {
|
||||||
|
let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai'
|
||||||
|
mermaidAPI.initialize({
|
||||||
|
theme: isDarkTheme ? 'dark' : 'default',
|
||||||
|
themeCSS: isDarkTheme ? darkThemeStyling : ''
|
||||||
|
})
|
||||||
|
mermaidAPI.render(getId(), content, (svgGraph) => {
|
||||||
|
element.innerHTML = svgGraph
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
element.className = 'mermaid-error'
|
||||||
|
element.innerHTML = 'mermaid diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default render
|
||||||
53
browser/lib/TextEditorInterface.js
Normal file
53
browser/lib/TextEditorInterface.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Point } from '@susisu/mte-kernel'
|
||||||
|
|
||||||
|
export default class TextEditorInterface {
|
||||||
|
constructor (editor) {
|
||||||
|
this.editor = editor
|
||||||
|
}
|
||||||
|
|
||||||
|
getCursorPosition () {
|
||||||
|
const pos = this.editor.getCursor()
|
||||||
|
return new Point(pos.line, pos.ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursorPosition (pos) {
|
||||||
|
this.editor.setCursor({line: pos.row, ch: pos.column})
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectionRange (range) {
|
||||||
|
this.editor.setSelection({
|
||||||
|
anchor: {line: range.start.row, ch: range.start.column},
|
||||||
|
head: {line: range.end.row, ch: range.end.column}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastRow () {
|
||||||
|
return this.editor.lastLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptsTableEdit (row) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
getLine (row) {
|
||||||
|
return this.editor.getLine(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertLine (row, line) {
|
||||||
|
this.editor.replaceRange(line, {line: row, ch: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteLine (row) {
|
||||||
|
this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length})
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceLines (startRow, endRow, lines) {
|
||||||
|
endRow-- // because endRow is a first line after a table.
|
||||||
|
const endRowCh = this.editor.getLine(endRow).length
|
||||||
|
this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh})
|
||||||
|
}
|
||||||
|
|
||||||
|
transact (func) {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,15 @@ const consts = {
|
|||||||
'Violet Eggplant'
|
'Violet Eggplant'
|
||||||
],
|
],
|
||||||
THEMES: ['default'].concat(themes),
|
THEMES: ['default'].concat(themes),
|
||||||
SNIPPET_FILE: snippetFile
|
SNIPPET_FILE: snippetFile,
|
||||||
|
DEFAULT_EDITOR_FONT_FAMILY: [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = consts
|
module.exports = consts
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ export function getTodoStatus (content) {
|
|||||||
|
|
||||||
splitted.forEach((line) => {
|
splitted.forEach((line) => {
|
||||||
const trimmedLine = line.trim()
|
const trimmedLine = line.trim()
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./)) {
|
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./i)) {
|
||||||
numberOfTodo++
|
numberOfTodo++
|
||||||
}
|
}
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
|
if (trimmedLine.match(/^[\+\-\*] \[x\] ./i)) {
|
||||||
numberOfCompletedTodo++
|
numberOfCompletedTodo++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import sanitizeHtml from 'sanitize-html'
|
import sanitizeHtml from 'sanitize-html'
|
||||||
|
import { escapeHtmlCharacters } from './utils'
|
||||||
|
|
||||||
module.exports = function sanitizePlugin (md, options) {
|
module.exports = function sanitizePlugin (md, options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
@@ -8,13 +9,26 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
||||||
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
|
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
|
||||||
if (state.tokens[tokenIdx].type === 'html_block') {
|
if (state.tokens[tokenIdx].type === 'html_block') {
|
||||||
state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
|
state.tokens[tokenIdx].content = sanitizeHtml(
|
||||||
|
state.tokens[tokenIdx].content,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (state.tokens[tokenIdx].type === 'fence') {
|
||||||
|
// escapeHtmlCharacters has better performance
|
||||||
|
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
||||||
|
state.tokens[tokenIdx].content,
|
||||||
|
{ skipSingleQuote: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (state.tokens[tokenIdx].type === 'inline') {
|
if (state.tokens[tokenIdx].type === 'inline') {
|
||||||
const inlineTokens = state.tokens[tokenIdx].children
|
const inlineTokens = state.tokens[tokenIdx].children
|
||||||
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
||||||
if (inlineTokens[childIdx].type === 'html_inline') {
|
if (inlineTokens[childIdx].type === 'html_inline') {
|
||||||
inlineTokens[childIdx].content = sanitizeHtml(inlineTokens[childIdx].content, options)
|
inlineTokens[childIdx].content = sanitizeHtml(
|
||||||
|
inlineTokens[childIdx].content,
|
||||||
|
options
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ class Markdown {
|
|||||||
if (langType === 'sequence') {
|
if (langType === 'sequence') {
|
||||||
return `<pre class="sequence">${str}</pre>`
|
return `<pre class="sequence">${str}</pre>`
|
||||||
}
|
}
|
||||||
|
if (langType === 'chart') {
|
||||||
|
return `<pre class="chart">${str}</pre>`
|
||||||
|
}
|
||||||
|
if (langType === 'mermaid') {
|
||||||
|
return `<pre class="mermaid">${str}</pre>`
|
||||||
|
}
|
||||||
return '<pre class="code CodeMirror">' +
|
return '<pre class="code CodeMirror">' +
|
||||||
'<span class="filename">' + fileName + '</span>' +
|
'<span class="filename">' + fileName + '</span>' +
|
||||||
createGutter(str, firstLineNumber) +
|
createGutter(str, firstLineNumber) +
|
||||||
@@ -157,6 +163,22 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Ditaa support
|
||||||
|
this.md.use(require('markdown-it-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}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Override task item
|
// Override task item
|
||||||
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
let content, terminate, i, l, token
|
let content, terminate, i, l, token
|
||||||
@@ -245,4 +267,3 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Markdown
|
export default Markdown
|
||||||
|
|
||||||
|
|||||||
9
browser/lib/normalizeEditorFontFamily.js
Normal file
9
browser/lib/normalizeEditorFontFamily.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import isString from 'lodash/isString'
|
||||||
|
|
||||||
|
export default function normalizeEditorFontFamily (fontFamily) {
|
||||||
|
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||||
|
return isString(fontFamily) && fontFamily.length > 0
|
||||||
|
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
|
||||||
|
: defaultEditorFontFamily.join(', ')
|
||||||
|
}
|
||||||
@@ -23,7 +23,9 @@ function findByWordOrTag (notes, block) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
return note.description.match(wordRegExp)
|
return note.description.match(wordRegExp) || note.snippets.some((snippet) => {
|
||||||
|
return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
||||||
|
})
|
||||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
} else if (note.type === 'MARKDOWN_NOTE') {
|
||||||
return note.content.match(wordRegExp)
|
return note.content.match(wordRegExp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,52 +6,113 @@ export function lastFindInArray (array, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function escapeHtmlCharacters (text) {
|
export function escapeHtmlCharacters (
|
||||||
const matchHtmlRegExp = /["'&<>]/
|
html,
|
||||||
const str = '' + text
|
opt = { detectCodeBlock: false, skipSingleQuote: false }
|
||||||
const match = matchHtmlRegExp.exec(str)
|
) {
|
||||||
|
const matchHtmlRegExp = /["'&<>]/g
|
||||||
|
const matchCodeBlockRegExp = /```/g
|
||||||
|
const escapes = ['"', '&', ''', '<', '>']
|
||||||
|
let match = null
|
||||||
|
const replaceAt = (str, index, replace) =>
|
||||||
|
str.substr(0, index) +
|
||||||
|
replace +
|
||||||
|
str.substr(index + replace.length - (replace.length - 1))
|
||||||
|
|
||||||
if (!match) {
|
while ((match = matchHtmlRegExp.exec(html)) !== null) {
|
||||||
return str
|
const current = { char: match[0], index: match.index }
|
||||||
}
|
const codeBlockIndexs = []
|
||||||
|
let openCodeBlock = null
|
||||||
let escape
|
// if the detectCodeBlock option is activated then this function should skip
|
||||||
let html = ''
|
// characters that needed to be escape but located in code block
|
||||||
let index = 0
|
if (opt.detectCodeBlock) {
|
||||||
let lastIndex = 0
|
// The first type of code block is lines that start with 4 spaces
|
||||||
|
// Here we check for the \n character located before the character that
|
||||||
for (index = match.index; index < str.length; index++) {
|
// needed to be escape. It means we check for the begining of the line that
|
||||||
switch (str.charCodeAt(index)) {
|
// contain that character, then we check if there are 4 spaces next to the
|
||||||
case 34: // "
|
// \n character (the line start with 4 spaces)
|
||||||
escape = '"'
|
let previousLineEnd = current.index - 1
|
||||||
break
|
while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
|
||||||
case 38: // &
|
previousLineEnd--
|
||||||
escape = '&'
|
}
|
||||||
break
|
// 4 spaces means this character is in a code block
|
||||||
case 39: // '
|
if (
|
||||||
escape = '''
|
html[previousLineEnd + 1] === ' ' &&
|
||||||
break
|
html[previousLineEnd + 2] === ' ' &&
|
||||||
case 60: // <
|
html[previousLineEnd + 3] === ' ' &&
|
||||||
escape = '<'
|
html[previousLineEnd + 4] === ' '
|
||||||
break
|
) {
|
||||||
case 62: // >
|
// skip the current character
|
||||||
escape = '>'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
// The second type of code block is lines that wrapped in ```
|
||||||
|
// We will get the position of each ```
|
||||||
|
// then push it into an array
|
||||||
|
// then the array returned will be like this:
|
||||||
|
// [startCodeblock, endCodeBlock, startCodeBlock, endCodeBlock]
|
||||||
|
while ((openCodeBlock = matchCodeBlockRegExp.exec(html)) !== null) {
|
||||||
|
codeBlockIndexs.push(openCodeBlock.index)
|
||||||
|
}
|
||||||
|
let shouldSkipChar = false
|
||||||
|
// we loop through the array of positions
|
||||||
|
// we skip 2 element as the i index position is the position of ``` that
|
||||||
|
// open the codeblock and the i + 1 is the position of the ``` that close
|
||||||
|
// the code block
|
||||||
|
for (let i = 0; i < codeBlockIndexs.length; i += 2) {
|
||||||
|
// the i index position is the position of the ``` that open code block
|
||||||
|
// so we have to + 2 as that position is the position of the first ` in the ````
|
||||||
|
// but we need to make sure that the position current character is larger
|
||||||
|
// that the last ` in the ``` that open the code block so we have to take
|
||||||
|
// the position of the first ` and + 2
|
||||||
|
// the i + 1 index position is the closing ``` so the char must less than it
|
||||||
|
if (
|
||||||
|
current.index > codeBlockIndexs[i] + 2 &&
|
||||||
|
current.index < codeBlockIndexs[i + 1]
|
||||||
|
) {
|
||||||
|
// skip it
|
||||||
|
shouldSkipChar = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSkipChar) {
|
||||||
|
// skip the current character
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// otherwise, escape it !!!
|
||||||
if (lastIndex !== index) {
|
if (current.char === '&') {
|
||||||
html += str.substring(lastIndex, index)
|
// when escaping character & we have to be becareful as the & could be a part
|
||||||
|
// of an escaped character like " will be came &quot;
|
||||||
|
let nextStr = ''
|
||||||
|
let nextIndex = current.index
|
||||||
|
let escapedStr = false
|
||||||
|
// maximum length of an escaped string is 5. For example ('"')
|
||||||
|
// we take the next 5 character of the next string if it is one of the string:
|
||||||
|
// ['"', '&', ''', '<', '>'] then we will not escape the & character
|
||||||
|
// as it is a part of the escaped string and should not be escaped
|
||||||
|
while (nextStr.length <= 5) {
|
||||||
|
nextStr += html[nextIndex]
|
||||||
|
nextIndex++
|
||||||
|
if (escapes.indexOf(nextStr) !== -1) {
|
||||||
|
escapedStr = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!escapedStr) {
|
||||||
|
// this & char is not a part of an escaped string
|
||||||
|
html = replaceAt(html, current.index, '&')
|
||||||
|
}
|
||||||
|
} else if (current.char === '"') {
|
||||||
|
html = replaceAt(html, current.index, '"')
|
||||||
|
} else if (current.char === "'" && !opt.skipSingleQuote) {
|
||||||
|
html = replaceAt(html, current.index, ''')
|
||||||
|
} else if (current.char === '<') {
|
||||||
|
html = replaceAt(html, current.index, '<')
|
||||||
|
} else if (current.char === '>') {
|
||||||
|
html = replaceAt(html, current.index, '>')
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIndex = index + 1
|
|
||||||
html += escape
|
|
||||||
}
|
}
|
||||||
|
return html
|
||||||
return lastIndex !== index
|
|
||||||
? html + str.substring(lastIndex, index)
|
|
||||||
: html
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isObjectEqual (a, b) {
|
export function isObjectEqual (a, b) {
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './FullscreenButton.styl'
|
import styles from './FullscreenButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||||
const FullscreenButton = ({
|
const FullscreenButton = ({
|
||||||
onClick
|
onClick
|
||||||
}) => (
|
}) => (
|
||||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||||
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
|
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
.control-infoButton-panel-trash
|
.control-infoButton-panel-trash
|
||||||
z-index 200
|
z-index 200
|
||||||
margin-top 0px
|
margin-top 0px
|
||||||
|
top 50px
|
||||||
right 0px
|
right 0px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 25px 0 25px
|
padding 20px 25px 0 25px
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
|||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
class SnippetNoteDetail extends React.Component {
|
class SnippetNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -441,7 +441,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const isSuper = global.process.platform === 'darwin'
|
const isSuper = global.process.platform === 'darwin'
|
||||||
? e.metaKey
|
? e.metaKey
|
||||||
: e.ctrlKey
|
: e.ctrlKey
|
||||||
if (isSuper) {
|
if (isSuper && !e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.addSnippet()
|
this.addSnippet()
|
||||||
}
|
}
|
||||||
@@ -451,14 +451,14 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleModeButtonClick (e, index) {
|
handleModeButtonClick (e, index) {
|
||||||
const menu = new Menu()
|
const templetes = []
|
||||||
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
||||||
menu.append(new MenuItem({
|
templetes.push({
|
||||||
label: mode.name,
|
label: mode.name,
|
||||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
||||||
}))
|
})
|
||||||
})
|
})
|
||||||
menu.popup(remote.getCurrentWindow())
|
context.popup(templetes)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIndentTypeButtonClick (e) {
|
handleIndentTypeButtonClick (e) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import styles from './TagSelect.styl'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
class TagSelect extends React.Component {
|
class TagSelect extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -13,16 +14,26 @@ class TagSelect extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
newTag: ''
|
newTag: ''
|
||||||
}
|
}
|
||||||
|
this.addtagHandler = this.handleAddTag.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
|
ee.on('editor:add-tag', this.addtagHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
ee.off('editor:add-tag', this.addtagHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddTag () {
|
||||||
|
this.refs.newTag.focus()
|
||||||
|
}
|
||||||
|
|
||||||
handleNewTagInputKeyDown (e) {
|
handleNewTagInputKeyDown (e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 9:
|
case 9:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ee from 'browser/main/lib/eventEmitter'
|
|||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import debounceRender from 'react-debounce-render'
|
import debounceRender from 'react-debounce-render'
|
||||||
|
import searchFromNotes from 'browser/lib/search'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -35,11 +36,38 @@ class Detail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { location, data, config } = this.props
|
const { location, data, params, config } = this.props
|
||||||
let note = null
|
let note = null
|
||||||
|
|
||||||
if (location.query.key != null) {
|
if (location.query.key != null) {
|
||||||
const noteKey = location.query.key
|
const noteKey = location.query.key
|
||||||
note = data.noteMap.get(noteKey)
|
const allNotes = data.noteMap.map(note => note)
|
||||||
|
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||||
|
let displayedNotes = allNotes
|
||||||
|
|
||||||
|
if (location.pathname.match(/\/searched/)) {
|
||||||
|
const searchStr = params.searchword
|
||||||
|
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
||||||
|
: searchFromNotes(allNotes, searchStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
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/)) {
|
||||||
|
displayedNotes = trashedNotes
|
||||||
|
} else {
|
||||||
|
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteKeys = displayedNotes.map(note => note.key)
|
||||||
|
if (noteKeys.includes(noteKey)) {
|
||||||
|
note = data.noteMap.get(noteKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const electron = require('electron')
|
|||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -60,10 +59,10 @@ class Main extends React.Component {
|
|||||||
name: 'My Storage',
|
name: 'My Storage',
|
||||||
path: path.join(remote.app.getPath('home'), 'Boostnote')
|
path: path.join(remote.app.getPath('home'), 'Boostnote')
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
if (data.storage.folders[0] != null) {
|
if (data.storage.folders[0] != null) {
|
||||||
return data
|
return data
|
||||||
} else {
|
} else {
|
||||||
@@ -72,7 +71,7 @@ class Main extends React.Component {
|
|||||||
color: '#1278BD',
|
color: '#1278BD',
|
||||||
name: 'Default'
|
name: 'Default'
|
||||||
})
|
})
|
||||||
.then((_data) => {
|
.then(_data => {
|
||||||
return {
|
return {
|
||||||
storage: _data.storage,
|
storage: _data.storage,
|
||||||
notes: data.notes
|
notes: data.notes
|
||||||
@@ -80,7 +79,7 @@ class Main extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'ADD_STORAGE',
|
type: 'ADD_STORAGE',
|
||||||
@@ -98,16 +97,16 @@ class Main extends React.Component {
|
|||||||
{
|
{
|
||||||
name: 'example.html',
|
name: 'example.html',
|
||||||
mode: 'html',
|
mode: 'html',
|
||||||
content: '<html>\n<body>\n<h1 id=\'hello\'>Enjoy Boostnote!</h1>\n</body>\n</html>'
|
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'example.js',
|
name: 'example.js',
|
||||||
mode: 'javascript',
|
mode: 'javascript',
|
||||||
content: 'var boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)'
|
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then(note => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
@@ -120,7 +119,7 @@ class Main extends React.Component {
|
|||||||
title: 'Welcome to Boostnote!',
|
title: 'Welcome to Boostnote!',
|
||||||
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then(note => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
@@ -131,10 +130,10 @@ class Main extends React.Component {
|
|||||||
.then(defaultMarkdownNote)
|
.then(defaultMarkdownNote)
|
||||||
.then(() => data.storage)
|
.then(() => data.storage)
|
||||||
})
|
})
|
||||||
.then((storage) => {
|
.then(storage => {
|
||||||
hashHistory.push('/storages/' + storage.key)
|
hashHistory.push('/storages/' + storage.key)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -142,12 +141,7 @@ class Main extends React.Component {
|
|||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
const supportedThemes = [
|
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai']
|
||||||
'dark',
|
|
||||||
'white',
|
|
||||||
'solarized-dark',
|
|
||||||
'monokai'
|
|
||||||
]
|
|
||||||
|
|
||||||
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
||||||
document.body.setAttribute('data-theme', config.ui.theme)
|
document.body.setAttribute('data-theme', config.ui.theme)
|
||||||
@@ -162,19 +156,18 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
applyShortcuts()
|
applyShortcuts()
|
||||||
// Reload all data
|
// Reload all data
|
||||||
dataApi.init()
|
dataApi.init().then(data => {
|
||||||
.then((data) => {
|
dispatch({
|
||||||
dispatch({
|
type: 'INIT_ALL',
|
||||||
type: 'INIT_ALL',
|
storages: data.storages,
|
||||||
storages: data.storages,
|
notes: data.notes
|
||||||
notes: data.notes
|
|
||||||
})
|
|
||||||
|
|
||||||
if (data.storages.length < 1) {
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (data.storages.length < 1) {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,34 +192,40 @@ class Main extends React.Component {
|
|||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
// Change width of NoteList component.
|
// Change width of NoteList component.
|
||||||
if (this.state.isRightSliderFocused) {
|
if (this.state.isRightSliderFocused) {
|
||||||
this.setState({
|
this.setState(
|
||||||
isRightSliderFocused: false
|
{
|
||||||
}, () => {
|
isRightSliderFocused: false
|
||||||
const { dispatch } = this.props
|
},
|
||||||
const newListWidth = this.state.listWidth
|
() => {
|
||||||
// TODO: ConfigManager should dispatch itself.
|
const { dispatch } = this.props
|
||||||
ConfigManager.set({listWidth: newListWidth})
|
const newListWidth = this.state.listWidth
|
||||||
dispatch({
|
// TODO: ConfigManager should dispatch itself.
|
||||||
type: 'SET_LIST_WIDTH',
|
ConfigManager.set({ listWidth: newListWidth })
|
||||||
listWidth: newListWidth
|
dispatch({
|
||||||
})
|
type: 'SET_LIST_WIDTH',
|
||||||
})
|
listWidth: newListWidth
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change width of SideNav component.
|
// Change width of SideNav component.
|
||||||
if (this.state.isLeftSliderFocused) {
|
if (this.state.isLeftSliderFocused) {
|
||||||
this.setState({
|
this.setState(
|
||||||
isLeftSliderFocused: false
|
{
|
||||||
}, () => {
|
isLeftSliderFocused: false
|
||||||
const { dispatch } = this.props
|
},
|
||||||
const navWidth = this.state.navWidth
|
() => {
|
||||||
// TODO: ConfigManager should dispatch itself.
|
const { dispatch } = this.props
|
||||||
ConfigManager.set({ navWidth })
|
const navWidth = this.state.navWidth
|
||||||
dispatch({
|
// TODO: ConfigManager should dispatch itself.
|
||||||
type: 'SET_NAV_WIDTH',
|
ConfigManager.set({ navWidth })
|
||||||
navWidth
|
dispatch({
|
||||||
})
|
type: 'SET_NAV_WIDTH',
|
||||||
})
|
navWidth
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,8 +270,8 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hideLeftLists (noteDetail, noteList, mainBody) {
|
hideLeftLists (noteDetail, noteList, mainBody) {
|
||||||
this.setState({noteDetailWidth: noteDetail.style.left})
|
this.setState({ noteDetailWidth: noteDetail.style.left })
|
||||||
this.setState({mainBodyWidth: mainBody.style.left})
|
this.setState({ mainBodyWidth: mainBody.style.left })
|
||||||
noteDetail.style.left = '0px'
|
noteDetail.style.left = '0px'
|
||||||
mainBody.style.left = '0px'
|
mainBody.style.left = '0px'
|
||||||
noteList.style.display = 'none'
|
noteList.style.display = 'none'
|
||||||
@@ -294,33 +293,36 @@ class Main extends React.Component {
|
|||||||
<div
|
<div
|
||||||
className='Main'
|
className='Main'
|
||||||
styleName='root'
|
styleName='root'
|
||||||
onMouseMove={(e) => this.handleMouseMove(e)}
|
onMouseMove={e => this.handleMouseMove(e)}
|
||||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
>
|
>
|
||||||
<SideNav
|
<SideNav
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, ['dispatch', 'data', 'config', 'location'])}
|
||||||
'dispatch',
|
|
||||||
'data',
|
|
||||||
'config',
|
|
||||||
'location'
|
|
||||||
])}
|
|
||||||
width={this.state.navWidth}
|
width={this.state.navWidth}
|
||||||
/>
|
/>
|
||||||
{!config.isSideNavFolded &&
|
{!config.isSideNavFolded &&
|
||||||
<div styleName={this.state.isLeftSliderFocused ? 'slider--active' : 'slider'}
|
<div
|
||||||
style={{left: this.state.navWidth}}
|
styleName={
|
||||||
onMouseDown={(e) => this.handleLeftSlideMouseDown(e)}
|
this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
|
||||||
|
}
|
||||||
|
style={{ left: this.state.navWidth }}
|
||||||
|
onMouseDown={e => this.handleLeftSlideMouseDown(e)}
|
||||||
draggable='false'
|
draggable='false'
|
||||||
>
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>
|
</div>}
|
||||||
}
|
<div
|
||||||
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||||
id='main-body'
|
id='main-body'
|
||||||
ref='body'
|
ref='body'
|
||||||
style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
|
style={{
|
||||||
|
left: config.isSideNavFolded
|
||||||
|
? foldedNavigationWidth
|
||||||
|
: this.state.navWidth
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<TopBar style={{width: this.state.listWidth}}
|
<TopBar
|
||||||
|
style={{ width: this.state.listWidth }}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'config',
|
'config',
|
||||||
@@ -329,7 +331,8 @@ class Main extends React.Component {
|
|||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
<NoteList style={{width: this.state.listWidth}}
|
<NoteList
|
||||||
|
style={{ width: this.state.listWidth }}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'data',
|
'data',
|
||||||
@@ -338,15 +341,20 @@ class Main extends React.Component {
|
|||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
<div styleName={this.state.isRightSliderFocused ? 'slider-right--active' : 'slider-right'}
|
<div
|
||||||
style={{left: this.state.listWidth - 1}}
|
styleName={
|
||||||
onMouseDown={(e) => this.handleRightSlideMouseDown(e)}
|
this.state.isRightSliderFocused
|
||||||
|
? 'slider-right--active'
|
||||||
|
: 'slider-right'
|
||||||
|
}
|
||||||
|
style={{ left: this.state.listWidth - 1 }}
|
||||||
|
onMouseDown={e => this.handleRightSlideMouseDown(e)}
|
||||||
draggable='false'
|
draggable='false'
|
||||||
>
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>
|
</div>
|
||||||
<Detail
|
<Detail
|
||||||
style={{left: this.state.listWidth}}
|
style={{ left: this.state.listWidth }}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'data',
|
'data',
|
||||||
@@ -374,4 +382,4 @@ Main.propTypes = {
|
|||||||
data: PropTypes.shape({}).isRequired
|
data: PropTypes.shape({}).isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((x) => x)(CSSModules(Main, styles))
|
export default connect(x => x)(CSSModules(Main, styles))
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
|||||||
import Markdown from '../../lib/markdown'
|
import Markdown from '../../lib/markdown'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
const WP_POST_PATH = '/wp/v2/posts'
|
const WP_POST_PATH = '/wp/v2/posts'
|
||||||
|
|
||||||
function sortByCreatedAt (a, b) {
|
function sortByCreatedAt (a, b) {
|
||||||
@@ -417,10 +418,10 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSortByChange (e) {
|
handleSortByChange (e) {
|
||||||
const { dispatch } = this.props
|
const { dispatch, params: { folderKey } } = this.props
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
sortBy: e.target.value
|
[folderKey]: { sortBy: e.target.value }
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigManager.set(config)
|
ConfigManager.set(config)
|
||||||
@@ -491,55 +492,51 @@ class NoteList extends React.Component {
|
|||||||
const updateLabel = i18n.__('Update Blog')
|
const updateLabel = i18n.__('Update Blog')
|
||||||
const openBlogLabel = i18n.__('Open Blog')
|
const openBlogLabel = i18n.__('Open Blog')
|
||||||
|
|
||||||
const menu = new Menu()
|
const templates = []
|
||||||
|
|
||||||
if (location.pathname.match(/\/trash/)) {
|
if (location.pathname.match(/\/trash/)) {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: restoreNote,
|
label: restoreNote,
|
||||||
click: this.restoreNote
|
click: this.restoreNote
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: deleteLabel,
|
label: deleteLabel,
|
||||||
click: this.deleteNote
|
click: this.deleteNote
|
||||||
}))
|
})
|
||||||
} else {
|
} else {
|
||||||
if (!location.pathname.match(/\/starred/)) {
|
if (!location.pathname.match(/\/starred/)) {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: pinLabel,
|
label: pinLabel,
|
||||||
click: this.pinToTop
|
click: this.pinToTop
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: deleteLabel,
|
label: deleteLabel,
|
||||||
click: this.deleteNote
|
click: this.deleteNote
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: cloneNote,
|
label: cloneNote,
|
||||||
click: this.cloneNote.bind(this)
|
click: this.cloneNote.bind(this)
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: copyNoteLink,
|
label: copyNoteLink,
|
||||||
click: this.copyNoteLink(note)
|
click: this.copyNoteLink(note)
|
||||||
}))
|
})
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: updateLabel,
|
label: updateLabel,
|
||||||
click: this.publishMarkdown.bind(this)
|
click: this.publishMarkdown.bind(this)
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: openBlogLabel,
|
label: openBlogLabel,
|
||||||
click: () => this.openBlog.bind(this)(note)
|
click: () => this.openBlog.bind(this)(note)
|
||||||
}))
|
})
|
||||||
} else {
|
} else {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: publishLabel,
|
label: publishLabel,
|
||||||
click: this.publishMarkdown.bind(this)
|
click: this.publishMarkdown.bind(this)
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
menu.popup()
|
context.popup(templates)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedNotes (updateFunc, cleanSelection = true) {
|
updateSelectedNotes (updateFunc, cleanSelection = true) {
|
||||||
@@ -912,12 +909,13 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { location, config } = this.props
|
const { location, config, params: { folderKey } } = this.props
|
||||||
let { notes } = this.props
|
let { notes } = this.props
|
||||||
const { selectedNoteKeys } = this.state
|
const { selectedNoteKeys } = this.state
|
||||||
const sortFunc = config.sortBy === 'CREATED_AT'
|
const sortBy = _.get(config, [folderKey, 'sortBy'], config.sortBy.default)
|
||||||
|
const sortFunc = sortBy === 'CREATED_AT'
|
||||||
? sortByCreatedAt
|
? sortByCreatedAt
|
||||||
: config.sortBy === 'ALPHABETICAL'
|
: sortBy === 'ALPHABETICAL'
|
||||||
? sortByAlphabetical
|
? sortByAlphabetical
|
||||||
: sortByUpdatedAt
|
: sortByUpdatedAt
|
||||||
const sortedNotes = location.pathname.match(/\/starred|\/trash/)
|
const sortedNotes = location.pathname.match(/\/starred|\/trash/)
|
||||||
@@ -968,7 +966,7 @@ class NoteList extends React.Component {
|
|||||||
notes.length === 1 ||
|
notes.length === 1 ||
|
||||||
(autoSelectFirst && index === 0)
|
(autoSelectFirst && index === 0)
|
||||||
const dateDisplay = moment(
|
const dateDisplay = moment(
|
||||||
config.sortBy === 'CREATED_AT'
|
sortBy === 'CREATED_AT'
|
||||||
? note.createdAt : note.updatedAt
|
? note.createdAt : note.updatedAt
|
||||||
).fromNow('D')
|
).fromNow('D')
|
||||||
|
|
||||||
@@ -1017,7 +1015,7 @@ class NoteList extends React.Component {
|
|||||||
<i className='fa fa-angle-down' />
|
<i className='fa fa-angle-down' />
|
||||||
<select styleName='control-sortBy-select'
|
<select styleName='control-sortBy-select'
|
||||||
title={i18n.__('Select filter mode')}
|
title={i18n.__('Select filter mode')}
|
||||||
value={config.sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => this.handleSortByChange(e)}
|
onChange={(e) => this.handleSortByChange(e)}
|
||||||
>
|
>
|
||||||
<option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option>
|
<option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option>
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import StorageItemChild from 'browser/components/StorageItem'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { SortableElement } from 'react-sortable-hoc'
|
import { SortableElement } from 'react-sortable-hoc'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, dialog } = remote
|
const { dialog } = remote
|
||||||
const escapeStringRegexp = require('escape-string-regexp')
|
const escapeStringRegexp = require('escape-string-regexp')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
@@ -21,13 +22,15 @@ class StorageItem extends React.Component {
|
|||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
const { storage } = this.props
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpen: true
|
isOpen: !!storage.isOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderContextMenu (e) {
|
handleHeaderContextMenu (e) {
|
||||||
const menu = Menu.buildFromTemplate([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Add Folder'),
|
label: i18n.__('Add Folder'),
|
||||||
click: (e) => this.handleAddFolderButtonClick(e)
|
click: (e) => this.handleAddFolderButtonClick(e)
|
||||||
@@ -40,8 +43,6 @@ class StorageItem extends React.Component {
|
|||||||
click: (e) => this.handleUnlinkStorageClick(e)
|
click: (e) => this.handleUnlinkStorageClick(e)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
menu.popup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUnlinkStorageClick (e) {
|
handleUnlinkStorageClick (e) {
|
||||||
@@ -68,8 +69,18 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
handleToggleButtonClick (e) {
|
||||||
|
const { storage, dispatch } = this.props
|
||||||
|
const isOpen = !this.state.isOpen
|
||||||
|
dataApi.toggleStorage(storage.key, isOpen)
|
||||||
|
.then((storage) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'EXPAND_STORAGE',
|
||||||
|
storage,
|
||||||
|
isOpen
|
||||||
|
})
|
||||||
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpen: !this.state.isOpen
|
isOpen: isOpen
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +105,7 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonContextMenu (e, folder) {
|
handleFolderButtonContextMenu (e, folder) {
|
||||||
const menu = Menu.buildFromTemplate([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Rename Folder'),
|
label: i18n.__('Rename Folder'),
|
||||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||||
@@ -123,8 +134,6 @@ class StorageItem extends React.Component {
|
|||||||
click: (e) => this.handleFolderDeleteClick(e, folder)
|
click: (e) => this.handleFolderDeleteClick(e, folder)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
menu.popup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRenameFolderClick (e, folder) {
|
handleRenameFolderClick (e, folder) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
const { remote } = require('electron')
|
|
||||||
const { Menu } = remote
|
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import styles from './SideNav.styl'
|
import styles from './SideNav.styl'
|
||||||
import { openModal } from 'browser/main/lib/modal'
|
import { openModal } from 'browser/main/lib/modal'
|
||||||
@@ -19,6 +17,7 @@ import ListButton from './ListButton'
|
|||||||
import TagButton from './TagButton'
|
import TagButton from './TagButton'
|
||||||
import {SortableContainer} from 'react-sortable-hoc'
|
import {SortableContainer} from 'react-sortable-hoc'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
// TODO: should not use electron stuff v0.7
|
// TODO: should not use electron stuff v0.7
|
||||||
@@ -254,10 +253,9 @@ class SideNav extends React.Component {
|
|||||||
handleFilterButtonContextMenu (event) {
|
handleFilterButtonContextMenu (event) {
|
||||||
const { data } = this.props
|
const { data } = this.props
|
||||||
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
const menu = Menu.buildFromTemplate([
|
context.popup([
|
||||||
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
|
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
|
||||||
])
|
])
|
||||||
menu.popup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './StatusBar.styl'
|
import styles from './StatusBar.styl'
|
||||||
import ZoomManager from 'browser/main/lib/ZoomManager'
|
import ZoomManager from 'browser/main/lib/ZoomManager'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote, ipcRenderer } = electron
|
const { remote, ipcRenderer } = electron
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
||||||
|
|
||||||
@@ -26,16 +27,16 @@ class StatusBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleZoomButtonClick (e) {
|
handleZoomButtonClick (e) {
|
||||||
const menu = new Menu()
|
const templates = []
|
||||||
|
|
||||||
zoomOptions.forEach((zoom) => {
|
zoomOptions.forEach((zoom) => {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: Math.floor(zoom * 100) + '%',
|
label: Math.floor(zoom * 100) + '%',
|
||||||
click: () => this.handleZoomMenuItemClick(zoom)
|
click: () => this.handleZoomMenuItemClick(zoom)
|
||||||
}))
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.popup(remote.getCurrentWindow())
|
context.popup(templates)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleZoomMenuItemClick (zoomFactor) {
|
handleZoomMenuItemClick (zoomFactor) {
|
||||||
|
|||||||
@@ -156,8 +156,7 @@ class TopBar extends React.Component {
|
|||||||
if (this.state.isSearching) {
|
if (this.state.isSearching) {
|
||||||
el.blur()
|
el.blur()
|
||||||
} else {
|
} else {
|
||||||
el.focus()
|
el.select()
|
||||||
el.setSelectionRange(0, el.value.length)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ body
|
|||||||
font-weight 200
|
font-weight 200
|
||||||
-webkit-font-smoothing antialiased
|
-webkit-font-smoothing antialiased
|
||||||
|
|
||||||
|
::-webkit-scrollbar
|
||||||
|
width 12px
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb
|
||||||
|
background-color rgba(0, 0, 0, 0.15)
|
||||||
|
|
||||||
button, input, select, textarea
|
button, input, select, textarea
|
||||||
font-family DEFAULT_FONTS
|
font-family DEFAULT_FONTS
|
||||||
|
|
||||||
@@ -85,9 +91,11 @@ modalBackColor = white
|
|||||||
absolute top left bottom right
|
absolute top left bottom right
|
||||||
background-color modalBackColor
|
background-color modalBackColor
|
||||||
z-index modalZIndex + 1
|
z-index modalZIndex + 1
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
|
::-webkit-scrollbar-thumb
|
||||||
|
background-color rgba(0, 0, 0, 0.3)
|
||||||
.ModalBase
|
.ModalBase
|
||||||
.modalBack
|
.modalBack
|
||||||
background-color $ui-dark-backgroundColor
|
background-color $ui-dark-backgroundColor
|
||||||
@@ -128,6 +136,8 @@ body[data-theme="dark"]
|
|||||||
z-index modalZIndex + 5
|
z-index modalZIndex + 5
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
|
::-webkit-scrollbar-thumb
|
||||||
|
background-color rgba(0, 0, 0, 0.3)
|
||||||
.ModalBase
|
.ModalBase
|
||||||
.modalBack
|
.modalBack
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
@@ -135,9 +145,14 @@ body[data-theme="solarized-dark"]
|
|||||||
color: $ui-solarized-dark-text-color
|
color: $ui-solarized-dark-text-color
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
body[data-theme="monokai"]
|
||||||
|
::-webkit-scrollbar-thumb
|
||||||
|
background-color rgba(0, 0, 0, 0.3)
|
||||||
.ModalBase
|
.ModalBase
|
||||||
.modalBack
|
.modalBack
|
||||||
background-color $ui-monokai-backgroundColor
|
background-color $ui-monokai-backgroundColor
|
||||||
.sortableItemHelper
|
.sortableItemHelper
|
||||||
color: $ui-monokai-text-color
|
color: $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="default"]
|
||||||
|
.SideNav ::-webkit-scrollbar-thumb
|
||||||
|
background-color rgba(255, 255, 255, 0.3)
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const DEFAULT_CONFIG = {
|
|||||||
isSideNavFolded: false,
|
isSideNavFolded: false,
|
||||||
listWidth: 280,
|
listWidth: 280,
|
||||||
navWidth: 200,
|
navWidth: 200,
|
||||||
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
|
sortBy: {
|
||||||
|
default: 'UPDATED_AT' // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
|
||||||
|
},
|
||||||
sortTagsBy: 'ALPHABETICAL', // 'ALPHABETICAL', 'COUNTER'
|
sortTagsBy: 'ALPHABETICAL', // 'ALPHABETICAL', 'COUNTER'
|
||||||
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
||||||
amaEnabled: true,
|
amaEnabled: true,
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ function addStorage (input) {
|
|||||||
key,
|
key,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
type: input.type,
|
type: input.type,
|
||||||
path: input.path
|
path: input.path,
|
||||||
|
isOpen: false
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(newStorage)
|
return Promise.resolve(newStorage)
|
||||||
@@ -48,7 +49,8 @@ function addStorage (input) {
|
|||||||
key: newStorage.key,
|
key: newStorage.key,
|
||||||
type: newStorage.type,
|
type: newStorage.type,
|
||||||
name: newStorage.name,
|
name: newStorage.name,
|
||||||
path: newStorage.path
|
path: newStorage.path,
|
||||||
|
isOpen: false
|
||||||
})
|
})
|
||||||
|
|
||||||
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import i18n from 'browser/lib/i18n'
|
|||||||
|
|
||||||
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
||||||
const DESTINATION_FOLDER = 'attachments'
|
const DESTINATION_FOLDER = 'attachments'
|
||||||
|
const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
@@ -76,14 +77,14 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
|
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
|
||||||
* @param renderedHTML HTML of the current note
|
* @param markdownContent of the current note
|
||||||
* @param storagePath Storage path of the current note
|
* @param storagePath Storage path of the current note
|
||||||
* @param noteKey Key of the current note
|
* @param noteKey Key of the current note
|
||||||
*/
|
*/
|
||||||
function migrateAttachments (renderedHTML, storagePath, noteKey) {
|
function migrateAttachments (markdownContent, storagePath, noteKey) {
|
||||||
if (sander.existsSync(path.join(storagePath, 'images'))) {
|
if (noteKey !== undefined && sander.existsSync(path.join(storagePath, 'images'))) {
|
||||||
const attachments = getAttachmentsInContent(renderedHTML) || []
|
const attachments = getAttachmentsInMarkdownContent(markdownContent) || []
|
||||||
if (attachments !== []) {
|
if (attachments.length) {
|
||||||
createAttachmentDestinationFolder(storagePath, noteKey)
|
createAttachmentDestinationFolder(storagePath, noteKey)
|
||||||
}
|
}
|
||||||
for (const attachment of attachments) {
|
for (const attachment of attachments) {
|
||||||
@@ -106,7 +107,10 @@ function migrateAttachments (renderedHTML, storagePath, noteKey) {
|
|||||||
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
||||||
*/
|
*/
|
||||||
function fixLocalURLS (renderedHTML, storagePath) {
|
function fixLocalURLS (renderedHTML, storagePath) {
|
||||||
return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', '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))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,13 +190,13 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Returns all attachment paths of the given markdown
|
* @description Returns all attachment paths of the given markdown
|
||||||
* @param {String} markdownContent content in which the attachment paths should be found
|
* @param {String} markdownContent content in which the attachment paths should be found
|
||||||
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
|
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
|
||||||
*/
|
*/
|
||||||
function getAttachmentsInContent (markdownContent) {
|
function getAttachmentsInMarkdownContent (markdownContent) {
|
||||||
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
|
const preparedInput = markdownContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
|
||||||
const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
|
const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + ')' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + ')' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
|
||||||
return preparedInput.match(regexp)
|
return preparedInput.match(regexp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +207,7 @@ function getAttachmentsInContent (markdownContent) {
|
|||||||
* @returns {String[]} Absolute paths of the referenced attachments
|
* @returns {String[]} Absolute paths of the referenced attachments
|
||||||
*/
|
*/
|
||||||
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
||||||
const temp = getAttachmentsInContent(markdownContent) || []
|
const temp = getAttachmentsInMarkdownContent(markdownContent) || []
|
||||||
const result = []
|
const result = []
|
||||||
for (const relativePath of temp) {
|
for (const relativePath of temp) {
|
||||||
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
|
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
|
||||||
@@ -239,7 +243,8 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
|
|||||||
*/
|
*/
|
||||||
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
|
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
|
||||||
if (noteContent) {
|
if (noteContent) {
|
||||||
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
|
const preparedInput = noteContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
|
||||||
|
return preparedInput.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
|
||||||
}
|
}
|
||||||
return noteContent
|
return noteContent
|
||||||
}
|
}
|
||||||
@@ -277,7 +282,7 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
|
|||||||
}
|
}
|
||||||
const targetStorage = findStorage.findStorage(storageKey)
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
const attachmentsInNote = getAttachmentsInContent(markdownContent)
|
const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
|
||||||
const attachmentsInNoteOnlyFileNames = []
|
const attachmentsInNoteOnlyFileNames = []
|
||||||
if (attachmentsInNote) {
|
if (attachmentsInNote) {
|
||||||
for (let i = 0; i < attachmentsInNote.length; i++) {
|
for (let i = 0; i < attachmentsInNote.length; i++) {
|
||||||
@@ -348,7 +353,7 @@ function generateFileNotFoundMarkdown () {
|
|||||||
*/
|
*/
|
||||||
function isAttachmentLink (text) {
|
function isAttachmentLink (text) {
|
||||||
if (text) {
|
if (text) {
|
||||||
return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + escapeStringRegexp(path.sep) + '.*\\).*', 'gi')) != null
|
return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + '[' + PATH_SEPARATORS + ']' + '.*\\).*', 'gi')) != null
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -364,7 +369,7 @@ function isAttachmentLink (text) {
|
|||||||
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
||||||
if (storageKey != null && noteKey != null && linkText != null) {
|
if (storageKey != null && noteKey != null && linkText != null) {
|
||||||
const storagePath = findStorage.findStorage(storageKey).path
|
const storagePath = findStorage.findStorage(storageKey).path
|
||||||
const attachments = getAttachmentsInContent(linkText) || []
|
const attachments = getAttachmentsInMarkdownContent(linkText) || []
|
||||||
const replaceInstructions = []
|
const replaceInstructions = []
|
||||||
const copies = []
|
const copies = []
|
||||||
for (const attachment of attachments) {
|
for (const attachment of attachments) {
|
||||||
@@ -373,13 +378,13 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
|||||||
sander.exists(absPathOfAttachment)
|
sander.exists(absPathOfAttachment)
|
||||||
.then((fileExists) => {
|
.then((fileExists) => {
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
|
const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
|
||||||
replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
|
replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
|
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
|
||||||
.then((fileName) => {
|
.then((fileName) => {
|
||||||
const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
|
const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
|
||||||
replaceInstructions.push({
|
replaceInstructions.push({
|
||||||
regexp: replaceLinkRegExp,
|
regexp: replaceLinkRegExp,
|
||||||
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
|
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
|
||||||
@@ -408,7 +413,7 @@ module.exports = {
|
|||||||
generateAttachmentMarkdown,
|
generateAttachmentMarkdown,
|
||||||
handleAttachmentDrop,
|
handleAttachmentDrop,
|
||||||
handlePastImageEvent,
|
handlePastImageEvent,
|
||||||
getAttachmentsInContent,
|
getAttachmentsInMarkdownContent,
|
||||||
getAbsolutePathsOfAttachmentsInContent,
|
getAbsolutePathsOfAttachmentsInContent,
|
||||||
removeStorageAndNoteReferences,
|
removeStorageAndNoteReferences,
|
||||||
deleteAttachmentFolder,
|
deleteAttachmentFolder,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const dataApi = {
|
const dataApi = {
|
||||||
init: require('./init'),
|
init: require('./init'),
|
||||||
|
toggleStorage: require('./toggleStorage'),
|
||||||
addStorage: require('./addStorage'),
|
addStorage: require('./addStorage'),
|
||||||
renameStorage: require('./renameStorage'),
|
renameStorage: require('./renameStorage'),
|
||||||
removeStorage: require('./removeStorage'),
|
removeStorage: require('./removeStorage'),
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ function resolveStorageData (storageCache) {
|
|||||||
key: storageCache.key,
|
key: storageCache.key,
|
||||||
name: storageCache.name,
|
name: storageCache.name,
|
||||||
type: storageCache.type,
|
type: storageCache.type,
|
||||||
path: storageCache.path
|
path: storageCache.path,
|
||||||
|
isOpen: storageCache.isOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
|
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
|
||||||
|
|||||||
28
browser/main/lib/dataApi/toggleStorage.js
Normal file
28
browser/main/lib/dataApi/toggleStorage.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} key
|
||||||
|
* @param {Boolean} isOpen
|
||||||
|
* @return {Object} Storage meta data
|
||||||
|
*/
|
||||||
|
function toggleStorage (key, isOpen) {
|
||||||
|
let cachedStorageList
|
||||||
|
try {
|
||||||
|
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
|
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
||||||
|
} catch (err) {
|
||||||
|
console.log('error got')
|
||||||
|
console.error(err)
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
const targetStorage = _.find(cachedStorageList, {key: key})
|
||||||
|
if (targetStorage == null) return Promise.reject('Storage')
|
||||||
|
|
||||||
|
targetStorage.isOpen = isOpen
|
||||||
|
localStorage.setItem('storages', JSON.stringify(cachedStorageList))
|
||||||
|
|
||||||
|
return resolveStorageData(targetStorage)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = toggleStorage
|
||||||
@@ -12,8 +12,7 @@ class NewNoteModal extends React.Component {
|
|||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -35,19 +34,20 @@ class NewNoteModal extends React.Component {
|
|||||||
title: '',
|
title: '',
|
||||||
content: ''
|
content: ''
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then(note => {
|
||||||
const noteHash = note.key
|
const noteHash = note.key
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
|
|
||||||
hashHistory.push({
|
hashHistory.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {key: noteHash}
|
query: { key: noteHash }
|
||||||
})
|
})
|
||||||
ee.emit('list:jump', noteHash)
|
ee.emit('list:jump', noteHash)
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
this.props.close()
|
setTimeout(this.props.close, 200)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,13 +69,15 @@ class NewNoteModal extends React.Component {
|
|||||||
folder: folder,
|
folder: folder,
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
snippets: [{
|
snippets: [
|
||||||
name: '',
|
{
|
||||||
mode: 'text',
|
name: '',
|
||||||
content: ''
|
mode: 'text',
|
||||||
}]
|
content: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then(note => {
|
||||||
const noteHash = note.key
|
const noteHash = note.key
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
@@ -83,11 +85,11 @@ class NewNoteModal extends React.Component {
|
|||||||
})
|
})
|
||||||
hashHistory.push({
|
hashHistory.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {key: noteHash}
|
query: { key: noteHash }
|
||||||
})
|
})
|
||||||
ee.emit('list:jump', noteHash)
|
ee.emit('list:jump', noteHash)
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
this.props.close()
|
setTimeout(this.props.close, 200)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,49 +108,65 @@ class NewNoteModal extends React.Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div styleName='root'
|
<div
|
||||||
|
styleName='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
>
|
>
|
||||||
<div styleName='header'>
|
<div styleName='header'>
|
||||||
<div styleName='title'>{i18n.__('Make a note')}</div>
|
<div styleName='title'>{i18n.__('Make a note')}</div>
|
||||||
</div>
|
</div>
|
||||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
<ModalEscButton
|
||||||
|
handleEscButtonClick={e => this.handleCloseButtonClick(e)}
|
||||||
|
/>
|
||||||
<div styleName='control'>
|
<div styleName='control'>
|
||||||
<button styleName='control-button'
|
<button
|
||||||
onClick={(e) => this.handleMarkdownNoteButtonClick(e)}
|
styleName='control-button'
|
||||||
onKeyDown={(e) => this.handleMarkdownNoteButtonKeyDown(e)}
|
onClick={e => this.handleMarkdownNoteButtonClick(e)}
|
||||||
|
onKeyDown={e => this.handleMarkdownNoteButtonKeyDown(e)}
|
||||||
ref='markdownButton'
|
ref='markdownButton'
|
||||||
>
|
>
|
||||||
<i styleName='control-button-icon'
|
<i styleName='control-button-icon' className='fa fa-file-text-o' />
|
||||||
className='fa fa-file-text-o'
|
<br />
|
||||||
/><br />
|
<span styleName='control-button-label'>
|
||||||
<span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br />
|
{i18n.__('Markdown Note')}
|
||||||
<span styleName='control-button-description'>{i18n.__('This format is for creating text documents. Checklists, code blocks and Latex blocks are available.')}</span>
|
</span>
|
||||||
|
<br />
|
||||||
|
<span styleName='control-button-description'>
|
||||||
|
{i18n.__(
|
||||||
|
'This format is for creating text documents. Checklists, code blocks and Latex blocks are available.'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='control-button'
|
<button
|
||||||
onClick={(e) => this.handleSnippetNoteButtonClick(e)}
|
styleName='control-button'
|
||||||
onKeyDown={(e) => this.handleSnippetNoteButtonKeyDown(e)}
|
onClick={e => this.handleSnippetNoteButtonClick(e)}
|
||||||
|
onKeyDown={e => this.handleSnippetNoteButtonKeyDown(e)}
|
||||||
ref='snippetButton'
|
ref='snippetButton'
|
||||||
>
|
>
|
||||||
<i styleName='control-button-icon'
|
<i styleName='control-button-icon' className='fa fa-code' /><br />
|
||||||
className='fa fa-code'
|
<span styleName='control-button-label'>
|
||||||
/><br />
|
{i18n.__('Snippet Note')}
|
||||||
<span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br />
|
</span>
|
||||||
<span styleName='control-button-description'>{i18n.__('This format is for creating code snippets. Multiple snippets can be grouped into a single note.')}
|
<br />
|
||||||
|
<span styleName='control-button-description'>
|
||||||
|
{i18n.__(
|
||||||
|
'This format is for creating code snippets. Multiple snippets can be grouped into a single note.'
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NewNoteModal.propTypes = {
|
NewNoteModal.propTypes = {}
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(NewNoteModal, styles)
|
export default CSSModules(NewNoteModal, styles)
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ class SnippetEditor extends React.Component {
|
|||||||
dragDrop: false,
|
dragDrop: false,
|
||||||
foldGutter: true,
|
foldGutter: true,
|
||||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: {
|
||||||
|
pairs: '()[]{}\'\'""$$**``',
|
||||||
|
triples: '```"""\'\'\'',
|
||||||
|
explode: '[]{}``$$',
|
||||||
|
override: true
|
||||||
|
},
|
||||||
mode: 'null'
|
mode: 'null'
|
||||||
})
|
})
|
||||||
this.cm.setSize('100%', '100%')
|
this.cm.setSize('100%', '100%')
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
const { remote } = require('electron')
|
import context from 'browser/lib/context'
|
||||||
const { Menu, MenuItem } = remote
|
|
||||||
|
|
||||||
class SnippetList extends React.Component {
|
class SnippetList extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -21,18 +20,17 @@ class SnippetList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadSnippetList () {
|
reloadSnippetList () {
|
||||||
dataApi.fetchSnippet().then(snippets => this.setState({snippets}))
|
dataApi.fetchSnippet().then(snippets => {
|
||||||
|
this.setState({snippets})
|
||||||
|
this.props.onSnippetSelect(this.props.currentSnippet)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSnippetContextMenu (snippet) {
|
handleSnippetContextMenu (snippet) {
|
||||||
const menu = new Menu()
|
context.popup([{
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: i18n.__('Delete snippet'),
|
label: i18n.__('Delete snippet'),
|
||||||
click: () => {
|
click: () => this.deleteSnippet(snippet)
|
||||||
this.deleteSnippet(snippet)
|
}])
|
||||||
}
|
|
||||||
}))
|
|
||||||
menu.popup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSnippet (snippet) {
|
deleteSnippet (snippet) {
|
||||||
@@ -43,7 +41,7 @@ class SnippetList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSnippetClick (snippet) {
|
handleSnippetClick (snippet) {
|
||||||
this.props.onSnippetClick(snippet)
|
this.props.onSnippetSelect(snippet)
|
||||||
}
|
}
|
||||||
|
|
||||||
createSnippet () {
|
createSnippet () {
|
||||||
@@ -55,6 +53,16 @@ class SnippetList extends React.Component {
|
|||||||
}).catch(err => { throw err })
|
}).catch(err => { throw err })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineSnippetStyleName (snippet) {
|
||||||
|
const { currentSnippet } = this.props
|
||||||
|
if (currentSnippet == null) return
|
||||||
|
if (currentSnippet.id === snippet.id) {
|
||||||
|
return 'snippet-item-selected'
|
||||||
|
} else {
|
||||||
|
return 'snippet-item'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { snippets } = this.state
|
const { snippets } = this.state
|
||||||
return (
|
return (
|
||||||
@@ -70,7 +78,7 @@ class SnippetList extends React.Component {
|
|||||||
{
|
{
|
||||||
snippets.map((snippet) => (
|
snippets.map((snippet) => (
|
||||||
<li
|
<li
|
||||||
styleName='snippet-item'
|
styleName={this.defineSnippetStyleName(snippet)}
|
||||||
key={snippet.id}
|
key={snippet.id}
|
||||||
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
|
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
|
||||||
onClick={() => this.handleSnippetClick(snippet)}>
|
onClick={() => this.handleSnippetClick(snippet)}>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class SnippetTab extends React.Component {
|
|||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSnippetClick (snippet) {
|
handleSnippetSelect (snippet) {
|
||||||
const { currentSnippet } = this.state
|
const { currentSnippet } = this.state
|
||||||
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||||
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||||
@@ -66,8 +66,9 @@ class SnippetTab extends React.Component {
|
|||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='header'>{i18n.__('Snippets')}</div>
|
<div styleName='header'>{i18n.__('Snippets')}</div>
|
||||||
<SnippetList
|
<SnippetList
|
||||||
onSnippetClick={this.handleSnippetClick.bind(this)}
|
onSnippetSelect={this.handleSnippetSelect.bind(this)}
|
||||||
onSnippetDeleted={this.handleDeleteSnippet.bind(this)} />
|
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
||||||
|
currentSnippet={currentSnippet} />
|
||||||
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
||||||
|
|||||||
@@ -122,6 +122,10 @@
|
|||||||
&:hover
|
&:hover
|
||||||
background darken(#f5f5f5, 5)
|
background darken(#f5f5f5, 5)
|
||||||
|
|
||||||
|
.snippet-item-selected
|
||||||
|
@extend .snippet-list .snippet-item
|
||||||
|
background darken(#f5f5f5, 5)
|
||||||
|
|
||||||
.snippet-detail
|
.snippet-detail
|
||||||
width 70%
|
width 70%
|
||||||
height calc(100% - 200px)
|
height calc(100% - 200px)
|
||||||
@@ -142,6 +146,8 @@ body[data-theme="default"], body[data-theme="white"]
|
|||||||
background $ui-borderColor
|
background $ui-borderColor
|
||||||
&:hover
|
&:hover
|
||||||
background darken($ui-backgroundColor, 5)
|
background darken($ui-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-backgroundColor, 5)
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.snippets
|
.snippets
|
||||||
@@ -152,8 +158,12 @@ body[data-theme="dark"]
|
|||||||
background $ui-dark-borderColor
|
background $ui-dark-borderColor
|
||||||
&:hover
|
&:hover
|
||||||
background darken($ui-dark-backgroundColor, 5)
|
background darken($ui-dark-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-dark-backgroundColor, 5)
|
||||||
.snippet-detail
|
.snippet-detail
|
||||||
color white
|
color white
|
||||||
|
.group-control-button
|
||||||
|
colorDarkPrimaryButton()
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.snippets
|
.snippets
|
||||||
@@ -164,8 +174,12 @@ body[data-theme="solarized-dark"]
|
|||||||
background $ui-solarized-dark-borderColor
|
background $ui-solarized-dark-borderColor
|
||||||
&:hover
|
&:hover
|
||||||
background darken($ui-solarized-dark-backgroundColor, 5)
|
background darken($ui-solarized-dark-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-solarized-dark-backgroundColor, 5)
|
||||||
.snippet-detail
|
.snippet-detail
|
||||||
color white
|
color white
|
||||||
|
.group-control-button
|
||||||
|
colorSolarizedDarkPrimaryButton()
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
body[data-theme="monokai"]
|
||||||
.snippets
|
.snippets
|
||||||
@@ -176,5 +190,9 @@ body[data-theme="monokai"]
|
|||||||
background $ui-monokai-borderColor
|
background $ui-monokai-borderColor
|
||||||
&:hover
|
&:hover
|
||||||
background darken($ui-monokai-backgroundColor, 5)
|
background darken($ui-monokai-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-monokai-backgroundColor, 5)
|
||||||
.snippet-detail
|
.snippet-detail
|
||||||
color white
|
color white
|
||||||
|
.group-control-button
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'codemirror-mode-elixir'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { getLanguages } from 'browser/lib/Languages'
|
import { getLanguages } from 'browser/lib/Languages'
|
||||||
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -164,7 +165,7 @@ class UiTab extends React.Component {
|
|||||||
const { config, codemirrorTheme } = this.state
|
const { config, codemirrorTheme } = this.state
|
||||||
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
||||||
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
|
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
|
||||||
const customCSS = config.preview.customCSS
|
const fontFamily = normalizeEditorFontFamily(config.editor.fontFamily)
|
||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='group'>
|
<div styleName='group'>
|
||||||
@@ -262,8 +263,16 @@ class UiTab extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
<div styleName='code-mirror'>
|
<div styleName='code-mirror' style={{fontFamily}}>
|
||||||
<ReactCodeMirror ref={e => (this.codeMirrorInstance = e)} value={codemirrorSampleCode} options={{ lineNumbers: true, readOnly: true, mode: 'javascript', theme: codemirrorTheme }} />
|
<ReactCodeMirror
|
||||||
|
ref={e => (this.codeMirrorInstance = e)}
|
||||||
|
value={codemirrorSampleCode}
|
||||||
|
options={{
|
||||||
|
lineNumbers: true,
|
||||||
|
readOnly: true,
|
||||||
|
mode: 'javascript',
|
||||||
|
theme: codemirrorTheme
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -596,7 +605,19 @@ class UiTab extends React.Component {
|
|||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
{i18n.__('Allow custom CSS for preview')}
|
{i18n.__('Allow custom CSS for preview')}
|
||||||
<ReactCodeMirror onChange={e => this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} />
|
<div style={{fontFamily}}>
|
||||||
|
<ReactCodeMirror
|
||||||
|
width='400px'
|
||||||
|
height='400px'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
ref={e => (this.customCSSCM = e)}
|
||||||
|
value={config.preview.customCSS}
|
||||||
|
options={{
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'css',
|
||||||
|
theme: codemirrorTheme
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -360,6 +360,12 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.storageMap = new Map(state.storageMap)
|
state.storageMap = new Map(state.storageMap)
|
||||||
state.storageMap.set(action.storage.key, action.storage)
|
state.storageMap.set(action.storage.key, action.storage)
|
||||||
return state
|
return state
|
||||||
|
case 'EXPAND_STORAGE':
|
||||||
|
state = Object.assign({}, state)
|
||||||
|
state.storageMap = new Map(state.storageMap)
|
||||||
|
action.storage.isOpen = action.isOpen
|
||||||
|
state.storageMap.set(action.storage.key, action.storage)
|
||||||
|
return state
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
76
dev-scripts/dev.js
Normal file
76
dev-scripts/dev.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const webpack = require('webpack')
|
||||||
|
const WebpackDevServer = require('webpack-dev-server')
|
||||||
|
const config = require('../webpack.config')
|
||||||
|
const signale = require('signale')
|
||||||
|
const { spawn } = require('child_process')
|
||||||
|
const electron = require('electron')
|
||||||
|
const port = 8080
|
||||||
|
let server = null
|
||||||
|
let firstRun = true
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
|
hot: true,
|
||||||
|
inline: true,
|
||||||
|
quiet: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function startServer () {
|
||||||
|
config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
||||||
|
config.entry.main.unshift(
|
||||||
|
`webpack-dev-server/client?http://localhost:${port}/`,
|
||||||
|
'webpack/hot/dev-server'
|
||||||
|
)
|
||||||
|
const compiler = webpack(config)
|
||||||
|
server = new WebpackDevServer(compiler, options)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
server.listen(port, 'localhost', function (err) {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
signale.success(`Webpack Dev Server listening at localhost:${port}`)
|
||||||
|
signale.watch(`Waiting for webpack to bundle...`)
|
||||||
|
compiler.plugin('done', stats => {
|
||||||
|
if (!stats.hasErrors()) {
|
||||||
|
signale.success(`Bundle success !`)
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
if (!firstRun) {
|
||||||
|
console.log(stats.compilation.errors[0])
|
||||||
|
} else {
|
||||||
|
firstRun = false
|
||||||
|
reject(stats.compilation.errors[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function startElectron () {
|
||||||
|
spawn(electron, ['--hot', './index.js'])
|
||||||
|
.on('close', () => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
.on('error', err => {
|
||||||
|
signale.error(err)
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
.on('disconnect', () => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
.on('exit', () => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
startServer()
|
||||||
|
.then(() => {
|
||||||
|
startElectron()
|
||||||
|
signale.success('Electron started')
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
signale.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
@@ -2,10 +2,9 @@
|
|||||||
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
||||||
|
|
||||||
## Environments
|
## Environments
|
||||||
* npm: 4.x
|
|
||||||
* node: 7.x
|
|
||||||
|
|
||||||
You should use `npm v4.x` because `$ grunt pre-build` fails on `v5.x`.
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -21,17 +20,9 @@ $ yarn
|
|||||||
Build and run.
|
Build and run.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This command runs `yarn run webpack` and `yarn run hot` in parallel. It is the same as running these commands in two terminals.
|
|
||||||
|
|
||||||
The `webpack` will watch for code changes and then apply them automatically.
|
|
||||||
|
|
||||||
If the following error occurs: `Failed to load resource: net::ERR_CONNECTION_REFUSED`, please reload Boostnote.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### Notice
|
> ### Notice
|
||||||
> There are some cases where you have to refresh the app manually.
|
> There are some cases where you have to refresh the app manually.
|
||||||
> 1. When editing a constructor method of a component
|
> 1. When editing a constructor method of a component
|
||||||
@@ -44,8 +35,6 @@ You can build the program by using `grunt`. However, we don't recommend this bec
|
|||||||
|
|
||||||
So, we've prepared a separate script which just makes an executable file.
|
So, we've prepared a separate script which just makes an executable file.
|
||||||
|
|
||||||
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
||||||
|
|
||||||
## Umgebungen
|
## Umgebungen
|
||||||
* npm: 4.x
|
|
||||||
* node: 7.x
|
|
||||||
|
|
||||||
Du solltest `npm v4.x` benutzen weil `$ grunt pre-build` scheitert mit Version `v5.x`.
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
## Entwicklung
|
## Entwicklung
|
||||||
|
|
||||||
@@ -21,17 +20,9 @@ $ yarn
|
|||||||
Bauen und Ausführen.
|
Bauen und Ausführen.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Dieser Befehl startet `yarn run webpack` und `yarn run hot` parallel. Es hat den selben Effekt wie beide Befehle separat in zwei Terminals zu starten.
|
|
||||||
|
|
||||||
Das `webpack` überprüft den Code auf Änderungen und wendet diese dann automatisch an.
|
|
||||||
|
|
||||||
Wenn folgender Fehler passiert: `Failed to load resource: net::ERR_CONNECTION_REFUSED`, bitte Boostnote neu starten.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### Notiz
|
> ### Notiz
|
||||||
> Es gibt einige Fälle bei denen die App manuell zu refreshen ist.
|
> Es gibt einige Fälle bei denen die App manuell zu refreshen ist.
|
||||||
> 1. Wenn eine "constructor method" einer Komponente manuell editiert wird.
|
> 1. Wenn eine "constructor method" einer Komponente manuell editiert wird.
|
||||||
@@ -44,11 +35,10 @@ Du kannst das Programm unter Verwendung von `grunt` bauen. Jedoch empfehlen wir
|
|||||||
|
|
||||||
Deshalb haben wir ein separates Script vorbereitet welches eine ausführbare Datei erstellt.
|
Deshalb haben wir ein separates Script vorbereitet welches eine ausführbare Datei erstellt.
|
||||||
|
|
||||||
Dieser build funktioniert nicht mit npm v5.3.0. Deshalb musst du für den Build die Version v5.2.0 verwenden.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
```
|
```
|
||||||
|
|
||||||
Du findest die ausführbare Datein in dem Verzeichnis `dist`. Beachte, der auto updater funktioniert nicht da die app nicht signiert ist.
|
Du findest die ausführbare Datein in dem Verzeichnis `dist`. Beachte, der auto updater funktioniert nicht da die app nicht signiert ist.
|
||||||
|
|
||||||
Wenn du es für notwendig erachtest, kannst du codesign or authenticode mit dieser ausführbaren Datei verwenden.
|
Wenn du es für notwendig erachtest, kannst du codesign or authenticode mit dieser ausführbaren Datei verwenden.
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
Cette page est également disponible en [Anglais](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md)
|
Cette page est également disponible en [Anglais](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md)
|
||||||
|
|
||||||
## Environnements
|
## Environnements
|
||||||
* npm: 4.x
|
|
||||||
* node: 7.x
|
|
||||||
|
|
||||||
Il est conseillé d'utiliser `npm v4.x` car `$ grunt pre-build` ne marche pas sur la `v5.x`.
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
## Développement
|
## Développement
|
||||||
|
|
||||||
@@ -20,17 +19,9 @@ $ yarn
|
|||||||
Build et start
|
Build et start
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Cette commande lance `yarn run webpack` et `yarn run hot` en parallèle. Cela revient au même que si on utilisait ces deux commandes dans 2 terminaux.
|
|
||||||
|
|
||||||
La commande `webpack` va surveiller les changements de code et les appliquer automatiquement.
|
|
||||||
|
|
||||||
Si l'erreur suivante apparait : `Failed to load resource: net::ERR_CONNECTION_REFUSED`, relancez Boostnote.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### Notice
|
> ### Notice
|
||||||
> Il y a certains cas où vous voudrez relancer l'application manuellement.
|
> Il y a certains cas où vous voudrez relancer l'application manuellement.
|
||||||
> 1. Quand vous éditez la méthode constructeur dans un composant
|
> 1. Quand vous éditez la méthode constructeur dans un composant
|
||||||
@@ -43,8 +34,6 @@ Vous pouvez build le programme en utilisant `grunt`. Cependant, nous ne recomman
|
|||||||
|
|
||||||
Nous avons donc préparé un script séparé qui va rendre un fichier exécutable.
|
Nous avons donc préparé un script séparé qui va rendre un fichier exécutable.
|
||||||
|
|
||||||
Le build ne fonctionne pas sur `npm v5.3.0`. Il faut donc utiliser `npm v5.2.0` quand vous faites le build.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
# Build
|
# Build
|
||||||
|
|
||||||
|
## 環境
|
||||||
|
|
||||||
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
## 開発
|
## 開発
|
||||||
|
|
||||||
Webpack HRMを使います。
|
Webpack HRMを使います。
|
||||||
次の命令から私達がしておいた設定を使うことができます。
|
Boostnoteの最上位ディレクトリにて以下のコマンドを実行して、
|
||||||
|
デフォルトの設定の開発環境を起動させます。
|
||||||
|
|
||||||
依存するパッケージをインストールします。
|
依存するパッケージをインストールします。
|
||||||
|
|
||||||
@@ -14,30 +20,20 @@ $ yarn
|
|||||||
ビルドして実行します。
|
ビルドして実行します。
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
このコマンドは `yarn run webpack` と `yarn run hot`を並列に実行します。つまりこのコマンドは2つのターミナルで同時にこれらのコマンドを実行するのと同じことです。
|
|
||||||
|
|
||||||
そして、Webpackが自動的にコードの変更を確認し、それを適用してくれるようになります。
|
|
||||||
|
|
||||||
もし、 `Failed to load resource: net::ERR_CONNECTION_REFUSED`というエラーが起きた場合、Boostnoteをリロードしてください。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### 注意
|
> ### 注意
|
||||||
> 時々、直接リフレッシュをする必要があります。
|
> 時々、直接リフレッシュをする必要があります。
|
||||||
> 1. コンポネントのコンストラクター関数を編集する場合
|
> 1. コンポーネントのコンストラクタ関数を編集する場合
|
||||||
> 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポネントごとに書きなおされまが、この作業はコンストラクターで行われます。)
|
> 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポーネントごとに書きなおされますが、この作業はコンストラクタで行われます。)
|
||||||
|
|
||||||
## 配布
|
## 配布
|
||||||
|
|
||||||
Gruntを使います。
|
Gruntを使います。
|
||||||
実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeの仮定が含まれるので、使っては行けないです。
|
実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeを実行するタスクが含まれるので、使用しないでください。
|
||||||
|
|
||||||
それで、実行ファイルを作るスクリプトを用意しておきました。
|
代わりに、実行ファイルを作るスクリプトを用意しておきました。
|
||||||
|
|
||||||
このビルドはnpm v5.3.0では動かないのでv5.2.0で動かす必要があります。
|
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Build
|
# Build
|
||||||
|
|
||||||
## 환경
|
## 환경
|
||||||
* npm: 4.x
|
|
||||||
* node: 7.x
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
`$ grunt pre-build`를 `npm v5.x`에서 실행할 수 없기 때문에, 반드시 `npm v4.x`를 사용하셔야 합니다.
|
`$ grunt pre-build`를 `npm v5.x`에서 실행할 수 없기 때문에, 반드시 `npm v4.x`를 사용하셔야 합니다.
|
||||||
|
|
||||||
@@ -20,17 +21,9 @@ $ yarn
|
|||||||
그 다음, 아래의 명령으로 빌드를 끝내고 자동적으로 어플리케이션을 실행합니다.
|
그 다음, 아래의 명령으로 빌드를 끝내고 자동적으로 어플리케이션을 실행합니다.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
이 명령은 `yarn run webpack` 과 `yarn run hot`을 동시에 실행합니다. 이는 두개의 터미널에서 각각의 명령을 동시에 실행하는 것과 같습니다.
|
|
||||||
|
|
||||||
`Webpack`은 코드의 변화를 자동으로 탐지하여 적용시키는 역할을 합니다.
|
|
||||||
|
|
||||||
만약, `Failed to load resource: net::ERR_CONNECTION_REFUSED`과 같은 에러가 나타난다면 Boostnote를 리로드해주세요.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### 주의
|
> ### 주의
|
||||||
> 가끔 직접 리프레쉬를 해주어야 하는 경우가 있습니다.
|
> 가끔 직접 리프레쉬를 해주어야 하는 경우가 있습니다.
|
||||||
> 1. 콤포넌트의 컨스트럭터 함수를 수정할 경우
|
> 1. 콤포넌트의 컨스트럭터 함수를 수정할 경우
|
||||||
@@ -43,8 +36,6 @@ Boostnote에서는 배포 자동화를 위하여 그런트를 사용합니다.
|
|||||||
|
|
||||||
그래서, 실행파일만을 만드는 스크립트를 준비해 뒀습니다.
|
그래서, 실행파일만을 만드는 스크립트를 준비해 뒀습니다.
|
||||||
|
|
||||||
이 빌드는 npm v5.3.0에서는 작동하지 않습니다. 그러므로, 성공적으로 빌드하기 위해서는 v5.2.0을 사용해야 합니다.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
# Сборка
|
# Сборка
|
||||||
|
|
||||||
## Используемые инструменты
|
## Используемые инструменты
|
||||||
* npm: 4.x
|
|
||||||
* node: 7.x
|
|
||||||
|
|
||||||
Вы должны использовать `npm v4.x`, так как `$ grunt pre-build` не работает в `v5.x`.
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
## Разработка
|
## Разработка
|
||||||
|
|
||||||
@@ -20,17 +19,9 @@ $ yarn
|
|||||||
Соберите и запустите.
|
Соберите и запустите.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Эта команда выполняет `yarn run webpack` и `yarn run hot` параллельно. Результат будет такой же, если вы выполните эти две команды раздельно.
|
|
||||||
|
|
||||||
`Webpack` будет следить за изменениями в коде и будет применять их автоматически.
|
|
||||||
|
|
||||||
Если возникает следующая ошибка: `Failed to load resource: net::ERR_CONNECTION_REFUSED`, пожалуйста, перезапустите Boostnote.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### Примечание
|
> ### Примечание
|
||||||
> В некоторых случаях вам необходимо обновить приложение вручную.
|
> В некоторых случаях вам необходимо обновить приложение вручную.
|
||||||
> 1. При редактировании метода конструктора компонента
|
> 1. При редактировании метода конструктора компонента
|
||||||
@@ -41,9 +32,7 @@ $ yarn run dev-start
|
|||||||
Мы используем Grunt для автоматического деплоя.
|
Мы используем Grunt для автоматического деплоя.
|
||||||
Вы можете создать задачу, используя `grunt`. Однако мы не рекомендуем этого делать, так как задача по умолчанию включает в себя код и аутентификацию.
|
Вы можете создать задачу, используя `grunt`. Однако мы не рекомендуем этого делать, так как задача по умолчанию включает в себя код и аутентификацию.
|
||||||
|
|
||||||
Мы подготовили отдельный скрипт, который просто создает исполняемый файл:
|
Мы подготовили отдельный скрипт, который просто создает исполняемый файл.
|
||||||
|
|
||||||
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
|
|||||||
@@ -1,37 +1,27 @@
|
|||||||
# 构建Boostnote
|
# 构建Boostnote
|
||||||
|
|
||||||
## 环境
|
## 环境
|
||||||
* npm: 4.x
|
|
||||||
* node: 7.x
|
|
||||||
|
|
||||||
因为`$ grunt pre-build`的问题,您只能使用`npm v4.x`而不能使用`npm v5.x`。
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
我们使用Webpack HMR来开发Boostnote。
|
我们使用Webpack HMR来开发Boostnote。
|
||||||
在代码根目录下运行下列指令可以以默认配置运行Boostnote。
|
在代码根目录下运行下列指令可以以默认配置运行Boostnote。
|
||||||
|
|
||||||
### 首先使用yarn安装所需的依赖包。
|
### 首先使用yarn安装所需的依赖包。
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn
|
$ yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
### 接着编译并且运行Boostnote。
|
### 接着编译并且运行Boostnote。
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
这个指令相当于在两个终端内同时运行`yarn run webpack`和`yarn run hot`。
|
|
||||||
|
|
||||||
如果出现错误`Failed to load resource: net::ERR_CONNECTION_REFUSED`,请尝试重新运行Boostnote。
|
|
||||||

|
|
||||||
|
|
||||||
### 然后您就可以进行开发了
|
|
||||||
|
|
||||||
当您对代码作出更改的时候,`webpack`会自动抓取并应用所有代码更改。
|
|
||||||
|
|
||||||
> ### 提示
|
> ### 提示
|
||||||
> 在如下情况中,您可能需要重新运行Boostnote才能应用代码更改
|
> 在如下情况中,您可能需要重新运行Boostnote才能应用代码更改
|
||||||
> 1. 当您在修改了一个组件的构造函数的时候When editing a constructor method of a component
|
> 1. 当您在修改了一个组件的构造函数的时候When editing a constructor method of a component
|
||||||
@@ -39,18 +29,16 @@ $ yarn run dev-start
|
|||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
我们使用Grunt来自动部署Boostnote。
|
我们使用Grunt来自动部署Boostnote。
|
||||||
因为部署需要协同设计(codesign)与验证码(authenticode),所以您可以但我们不建议通过`grunt`来部署。
|
因为部署需要协同设计(codesign)与验证码(authenticode),所以您可以但我们不建议通过`grunt`来部署。
|
||||||
所以我们准备了一个脚本文件来生成执行文件。
|
所以我们准备了一个脚本文件来生成执行文件。
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
```
|
```
|
||||||
|
|
||||||
您只能使用`npm v5.2.0`而不能使用`npm v5.3.0`。
|
接下来您就可以在`dist`目录中找到可执行文件。
|
||||||
|
|
||||||
接下来您就可以在`dist`目录中找到可执行文件。
|
|
||||||
|
|
||||||
> ### 提示
|
> ### 提示
|
||||||
> 因为此可执行文件并没有被注册,所以自动更新不可用。
|
> 因为此可执行文件并没有被注册,所以自动更新不可用。
|
||||||
> 如果需要,您也可将协同设计(codesign)与验证码(authenticode)使用于这个可执行文件中。
|
> 如果需要,您也可将协同设计(codesign)与验证码(authenticode)使用于这个可执行文件中。
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
此文件還提供下列的語言 [日文](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [韓文](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [俄文](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [簡體中文](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [法文](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [德文](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
此文件還提供下列的語言 [日文](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [韓文](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [俄文](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [簡體中文](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [法文](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [德文](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
||||||
|
|
||||||
## 環境
|
## 環境
|
||||||
* npm: 4.x
|
|
||||||
* node: 7.x
|
|
||||||
|
|
||||||
`$ grunt pre-build` 在 `npm v5.x` 有問題,所以只能用 `npm v4.x` 。
|
* npm: 6.x
|
||||||
|
* node: 8.x
|
||||||
|
|
||||||
## 開發
|
## 開發
|
||||||
|
|
||||||
@@ -22,18 +21,9 @@ $ yarn
|
|||||||
**開始開發**
|
**開始開發**
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn run dev-start
|
$ yarn run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
上述指令同時運行了 `yarn run webpack` 及 `yarn run hot`,相當於將這兩個指令在不同的 terminal 中運行。
|
|
||||||
|
|
||||||
`webpack` 會同時監控修改過的程式碼,並
|
|
||||||
The `webpack` will watch for code changes and then apply them automatically.
|
|
||||||
|
|
||||||
If the following error occurs: `Failed to load resource: net::ERR_CONNECTION_REFUSED`, please reload Boostnote.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### Notice
|
> ### Notice
|
||||||
> There are some cases where you have to refresh the app manually.
|
> There are some cases where you have to refresh the app manually.
|
||||||
> 1. When editing a constructor method of a component
|
> 1. When editing a constructor method of a component
|
||||||
@@ -46,8 +36,6 @@ You can build the program by using `grunt`. However, we don't recommend this bec
|
|||||||
|
|
||||||
So, we've prepared a separate script which just makes an executable file.
|
So, we've prepared a separate script which just makes an executable file.
|
||||||
|
|
||||||
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -136,6 +136,15 @@ const file = {
|
|||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Format Table',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('code:format-table')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Print',
|
label: 'Print',
|
||||||
accelerator: 'CommandOrControl+P',
|
accelerator: 'CommandOrControl+P',
|
||||||
@@ -209,6 +218,16 @@ const edit = {
|
|||||||
label: 'Select All',
|
label: 'Select All',
|
||||||
accelerator: 'Command+A',
|
accelerator: 'Command+A',
|
||||||
selector: 'selectAll:'
|
selector: 'selectAll:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Add Tag',
|
||||||
|
accelerator: 'CommandOrControl+Shift+T',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('editor:add-tag')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -235,14 +254,14 @@ const view = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next Note',
|
label: 'Next Note',
|
||||||
accelerator: 'Control+J',
|
accelerator: 'CommandOrControl+]',
|
||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('list:next')
|
mainWindow.webContents.send('list:next')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Previous Note',
|
label: 'Previous Note',
|
||||||
accelerator: 'Control+K',
|
accelerator: 'CommandOrControl+[',
|
||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('list:prior')
|
mainWindow.webContents.send('list:prior')
|
||||||
}
|
}
|
||||||
@@ -267,6 +286,19 @@ const view = {
|
|||||||
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Toggle Side Bar',
|
||||||
|
accelerator: 'CommandOrControl+B',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('editor:fullscreen')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
role: 'zoomin',
|
role: 'zoomin',
|
||||||
accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+='
|
accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+='
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ const config = new Config()
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
var showMenu = process.platform !== 'win32'
|
var showMenu = process.platform !== 'win32'
|
||||||
const windowSize = config.get('windowsize') || { width: 1080, height: 720 }
|
const windowSize = config.get('windowsize') || { x: null, y: null, width: 1080, height: 720 }
|
||||||
|
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
|
x: windowSize.x,
|
||||||
|
y: windowSize.y,
|
||||||
width: windowSize.width,
|
width: windowSize.width,
|
||||||
height: windowSize.height,
|
height: windowSize.height,
|
||||||
minWidth: 500,
|
minWidth: 500,
|
||||||
@@ -59,6 +61,7 @@ if (process.platform === 'darwin') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainWindow.on('resize', _.throttle(storeWindowSize, 500))
|
mainWindow.on('resize', _.throttle(storeWindowSize, 500))
|
||||||
|
mainWindow.on('move', _.throttle(storeWindowSize, 500))
|
||||||
|
|
||||||
function storeWindowSize () {
|
function storeWindowSize () {
|
||||||
try {
|
try {
|
||||||
|
|||||||
118
lib/main.html
118
lib/main.html
@@ -1,72 +1,83 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
<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="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="shortcut icon" href="../resources/favicon.ico">
|
||||||
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
|
<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="../node_modules/codemirror/addon/dialog/dialog.css">
|
||||||
<title>Boostnote</title>
|
<title>Boostnote</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'OpenSans';
|
font-family: 'OpenSans';
|
||||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
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.woff') format('woff'), /* Modern Browsers */
|
||||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
@font-face {
|
||||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
font-family: 'Lato';
|
||||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||||
font-style: normal;
|
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||||
font-weight: normal;
|
font-style: normal;
|
||||||
text-rendering: optimizeLegibility;
|
font-weight: normal;
|
||||||
}
|
text-rendering: optimizeLegibility;
|
||||||
#loadingCover{
|
}
|
||||||
background-color: #f4f4f4;
|
|
||||||
position: absolute;
|
#loadingCover {
|
||||||
top: 0;
|
background-color: #f4f4f4;
|
||||||
bottom: 0;
|
position: absolute;
|
||||||
left: 0;
|
top: 0;
|
||||||
right: 0;
|
bottom: 0;
|
||||||
box-sizing: border-box;
|
left: 0;
|
||||||
padding: 65px 0;
|
right: 0;
|
||||||
font-family: sans-serif;
|
box-sizing: border-box;
|
||||||
}
|
padding: 65px 0;
|
||||||
#loadingCover img{
|
font-family: sans-serif;
|
||||||
display: block;
|
}
|
||||||
margin: 75px auto 5px;
|
|
||||||
width: 160px;
|
#loadingCover img {
|
||||||
height: 160px;
|
display: block;
|
||||||
}
|
margin: 75px auto 5px;
|
||||||
#loadingCover .message{
|
width: 160px;
|
||||||
font-size: 30px;
|
height: 160px;
|
||||||
text-align: center;
|
}
|
||||||
line-height: 1.6;
|
|
||||||
font-weight: 100;
|
#loadingCover .message {
|
||||||
color: #888;
|
font-size: 30px;
|
||||||
}
|
text-align: center;
|
||||||
.CodeEditor {
|
line-height: 1.6;
|
||||||
opacity: 1 !important;
|
font-weight: 100;
|
||||||
pointer-events: auto !important;
|
color: #888;
|
||||||
}
|
}
|
||||||
.CodeMirror-ruler {
|
|
||||||
border-left-color: rgba(142, 142, 142, 0.5);
|
.CodeEditor {
|
||||||
mix-blend-mode: difference;
|
opacity: 1 !important;
|
||||||
}
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left-color: rgba(142, 142, 142, 0.5);
|
||||||
|
mix-blend-mode: difference;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="loadingCover">
|
<div id="loadingCover">
|
||||||
<img src="../resources/app.png">
|
<img src="../resources/app.png">
|
||||||
<div class='message'><i class="fa fa-spinner fa-spin" spin></i></div>
|
<div class='message'>
|
||||||
|
<i class="fa fa-spinner fa-spin" spin></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="content"></div>
|
<div id="content"></div>
|
||||||
@@ -130,4 +141,5 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
35
package-lock.json
generated
35
package-lock.json
generated
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "boost",
|
|
||||||
"version": "0.10.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"i18n-2": {
|
|
||||||
"version": "0.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/i18n-2/-/i18n-2-0.7.2.tgz",
|
|
||||||
"integrity": "sha512-Rdh6vfpNhL7q61cNf27x7QGULTi1TcGLVdFb5OJ6dOiJo+EkOTqEg0+3xgyeEMgYhopUBsh2IiSkFkjM+EhEmA==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "3.1.0",
|
|
||||||
"sprintf": "0.1.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
|
||||||
},
|
|
||||||
"sprintf": {
|
|
||||||
"version": "0.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/sprintf/-/sprintf-0.1.5.tgz",
|
|
||||||
"integrity": "sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
package.json
22
package.json
@@ -1,23 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.11.7",
|
"version": "0.11.8",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron ./index.js",
|
"start": "electron ./index.js",
|
||||||
"hot": "electron ./index.js --hot",
|
|
||||||
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js",
|
|
||||||
"compile": "grunt compile",
|
"compile": "grunt compile",
|
||||||
"test": "PWD=$(pwd) NODE_ENV=test ava --serial",
|
"test": "PWD=$(pwd) NODE_ENV=test ava --serial",
|
||||||
"jest": "jest",
|
"jest": "jest",
|
||||||
"fix": "eslint . --fix",
|
"fix": "eslint . --fix",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\""
|
"dev": "node dev-scripts/dev.js"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"electron-version": "2.0.3"
|
"electron-version": "2.0.7"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -51,11 +49,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rokt33r/markdown-it-math": "^4.0.1",
|
"@rokt33r/markdown-it-math": "^4.0.1",
|
||||||
"@rokt33r/season": "^5.3.0",
|
"@rokt33r/season": "^5.3.0",
|
||||||
|
"@susisu/mte-kernel": "^2.0.0",
|
||||||
"aws-sdk": "^2.48.0",
|
"aws-sdk": "^2.48.0",
|
||||||
"aws-sdk-mobile-analytics": "^0.9.2",
|
"aws-sdk-mobile-analytics": "^0.9.2",
|
||||||
"codemirror": "^5.37.0",
|
"chart.js": "^2.7.2",
|
||||||
|
"codemirror": "^5.39.0",
|
||||||
"codemirror-mode-elixir": "^1.1.1",
|
"codemirror-mode-elixir": "^1.1.1",
|
||||||
"electron-config": "^0.2.1",
|
"electron-config": "^1.0.0",
|
||||||
"electron-gh-releases": "^2.0.2",
|
"electron-gh-releases": "^2.0.2",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^1.0.5",
|
||||||
"file-url": "^2.0.2",
|
"file-url": "^2.0.2",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"lodash": "^4.11.1",
|
"lodash": "^4.11.1",
|
||||||
"lodash-move": "^1.1.1",
|
"lodash-move": "^1.1.1",
|
||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
"markdown-it-admonition": "https://github.com/johannbre/markdown-it-admonition.git",
|
"markdown-it-admonition": "^1.0.4",
|
||||||
"markdown-it-emoji": "^1.1.1",
|
"markdown-it-emoji": "^1.1.1",
|
||||||
"markdown-it-footnote": "^3.0.0",
|
"markdown-it-footnote": "^3.0.0",
|
||||||
"markdown-it-imsize": "^2.0.1",
|
"markdown-it-imsize": "^2.0.1",
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
"markdown-it-plantuml": "^1.1.0",
|
"markdown-it-plantuml": "^1.1.0",
|
||||||
"markdown-it-smartarrows": "^1.0.1",
|
"markdown-it-smartarrows": "^1.0.1",
|
||||||
"mdurl": "^1.0.1",
|
"mdurl": "^1.0.1",
|
||||||
|
"mermaid": "^8.0.0-rc.8",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
"mousetrap": "^1.6.1",
|
"mousetrap": "^1.6.1",
|
||||||
"mousetrap-global-bind": "^1.1.0",
|
"mousetrap-global-bind": "^1.1.0",
|
||||||
@@ -117,8 +118,8 @@
|
|||||||
"css-loader": "^0.19.0",
|
"css-loader": "^0.19.0",
|
||||||
"devtron": "^1.1.0",
|
"devtron": "^1.1.0",
|
||||||
"dom-storage": "^2.0.2",
|
"dom-storage": "^2.0.2",
|
||||||
"electron": "2.0.3",
|
"electron": "2.0.7",
|
||||||
"electron-packager": "^6.0.0",
|
"electron-packager": "^12.0.0",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^3.13.1",
|
||||||
"eslint-config-standard": "^6.2.1",
|
"eslint-config-standard": "^6.2.1",
|
||||||
"eslint-config-standard-jsx": "^3.2.0",
|
"eslint-config-standard-jsx": "^3.2.0",
|
||||||
@@ -142,6 +143,7 @@
|
|||||||
"react-router": "^2.4.0",
|
"react-router": "^2.4.0",
|
||||||
"react-router-redux": "^4.0.4",
|
"react-router-redux": "^4.0.4",
|
||||||
"react-test-renderer": "^15.6.2",
|
"react-test-renderer": "^15.6.2",
|
||||||
|
"signale": "^1.2.1",
|
||||||
"standard": "^8.4.0",
|
"standard": "^8.4.0",
|
||||||
"style-loader": "^0.12.4",
|
"style-loader": "^0.12.4",
|
||||||
"stylus": "^0.52.4",
|
"stylus": "^0.52.4",
|
||||||
|
|||||||
10
readme.md
10
readme.md
@@ -1,4 +1,4 @@
|
|||||||
:mega: We've launched [Boostnote Bounty Program](http://bit.ly/2I5Tpik).
|
:mega: The Boostnote team launches [IssueHunt](https://issuehunt.io/) for sustainable open-source ecosystem.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -19,13 +19,15 @@ Thank you to all the people who already contributed to Boostnote!
|
|||||||
<a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a>
|
<a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a>
|
||||||
|
|
||||||
## Supporting Boostnote
|
## Supporting Boostnote
|
||||||
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/BoostIO/Boostnote/blob/master/Backers.md). If you'd like to join them, please consider:
|
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers.
|
||||||
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
|
|
||||||
|
Any issues on Boostnote can be funded by anyone and that money will be distributed to contributors and maintainers. If you'd like to join them, please consider:
|
||||||
|
- [Become a backer on IssueHunt](https://issuehunt.io/repos/53266139).
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||||
- [Twitter](https://twitter.com/boostnoteapp)
|
- [Twitter](https://twitter.com/boostnoteapp)
|
||||||
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzcwNDU3NDU3ODI0LTU1ZDgwZDNiZTNmN2RhOTY4OTM5ODY0ODUzMTRiNmQ0ZDMzZDRiYzg2YmQ5ZDYzZTQxYjMxYzBlNTM4NjcyYjM)
|
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LThkNmMzY2VlZjVhYTNiYjE5YjQyZGVjNTJlYTY1OGMyZTFjNGU5YTUyYjUzOWZhYTU4OTVlNDYyNDFjYWMzNDM)
|
||||||
- [Blog](https://boostlog.io/tags/boostnote)
|
- [Blog](https://boostlog.io/tags/boostnote)
|
||||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||||
|
|
||||||
|
|||||||
@@ -169,6 +169,43 @@ it('should replace the all ":storage" path with the actual storage path', functi
|
|||||||
expect(actual).toEqual(expectedOutput)
|
expect(actual).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should replace the ":storage" path with the actual storage path when they have different path separators', function () {
|
||||||
|
const storageFolder = systemUnderTest.DESTINATION_FOLDER
|
||||||
|
const testInput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src=":storage' + mdurl.encode(path.win32.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href=":storage' + mdurl.encode(path.posix.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const storagePath = '<<dummyStoragePath>>'
|
||||||
|
const expectedOutput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const actual = systemUnderTest.fixLocalURLS(testInput, storagePath)
|
||||||
|
expect(actual).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
it('should test that generateAttachmentMarkdown works correct both with previews and without', function () {
|
it('should test that generateAttachmentMarkdown works correct both with previews and without', function () {
|
||||||
const fileName = 'fileName'
|
const fileName = 'fileName'
|
||||||
const path = 'path'
|
const path = 'path'
|
||||||
@@ -180,27 +217,35 @@ it('should test that generateAttachmentMarkdown works correct both with previews
|
|||||||
expect(actual).toEqual(expected)
|
expect(actual).toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should test that getAttachmentsInContent finds all attachments', function () {
|
it('should test that migrateAttachments work when they have different path separators', function () {
|
||||||
const testInput =
|
sander.existsSync = jest.fn(() => true)
|
||||||
'<html>\n' +
|
const dummyStoragePath = 'dummyStoragePath'
|
||||||
' <head>\n' +
|
const imagesPath = path.join(dummyStoragePath, 'images')
|
||||||
' //header\n' +
|
const attachmentsPath = path.join(dummyStoragePath, 'attachments')
|
||||||
' </head>\n' +
|
const noteKey = 'noteKey'
|
||||||
' <body data-theme="default">\n' +
|
const testInput = '"# Test\n' +
|
||||||
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
'\n' +
|
||||||
' <p data-line="2">\n' +
|
'\n' +
|
||||||
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
'"'
|
||||||
' </p>\n' +
|
|
||||||
' <p data-line="4">\n' +
|
systemUnderTest.migrateAttachments(testInput, dummyStoragePath, noteKey)
|
||||||
' <a href=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
|
||||||
' </p>\n' +
|
expect(sander.existsSync.mock.calls[0][0]).toBe(imagesPath)
|
||||||
' <p data-line="6">\n' +
|
expect(sander.existsSync.mock.calls[1][0]).toBe(path.join(imagesPath, '0.3b88d0dc.png'))
|
||||||
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
expect(sander.existsSync.mock.calls[2][0]).toBe(path.join(attachmentsPath, '0.3b88d0dc.png'))
|
||||||
' </p>\n' +
|
expect(sander.existsSync.mock.calls[3][0]).toBe(path.join(imagesPath, '0.2cb8875c.pdf'))
|
||||||
' </body>\n' +
|
expect(sander.existsSync.mock.calls[4][0]).toBe(path.join(attachmentsPath, '0.2cb8875c.pdf'))
|
||||||
'</html>'
|
})
|
||||||
const actual = systemUnderTest.getAttachmentsInContent(testInput)
|
|
||||||
const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp.png', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx.pdf', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
|
it('should test that getAttachmentsInMarkdownContent finds all attachments when they have different path separators', function () {
|
||||||
|
const testInput = '"# Test\n' +
|
||||||
|
'\n' +
|
||||||
|
'\n' +
|
||||||
|
'\n' +
|
||||||
|
'"'
|
||||||
|
|
||||||
|
const actual = systemUnderTest.getAttachmentsInMarkdownContent(testInput)
|
||||||
|
const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.3b88d0dc.png', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '2cb8875c.pdf', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'bbf49b02.jpg']
|
||||||
expect(actual).toEqual(expect.arrayContaining(expected))
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -274,6 +319,21 @@ it('should remove the all ":storage" and noteKey references', function () {
|
|||||||
expect(actual).toEqual(expectedOutput)
|
expect(actual).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should make sure that "removeStorageAndNoteReferences" works with markdown content as well', function () {
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.DESTINATION_FOLDER + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey)
|
||||||
|
expect(actual).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
it('should delete the correct attachment folder if a note is deleted', function () {
|
it('should delete the correct attachment folder if a note is deleted', function () {
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
const storageKey = 'storageKey'
|
const storageKey = 'storageKey'
|
||||||
|
|||||||
38
tests/dataApi/toggleStorage-test.js
Normal file
38
tests/dataApi/toggleStorage-test.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const toggleStorage = require('browser/main/lib/dataApi/toggleStorage')
|
||||||
|
|
||||||
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
|
global.window = document.defaultView
|
||||||
|
global.navigator = window.navigator
|
||||||
|
|
||||||
|
const Storage = require('dom-storage')
|
||||||
|
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
||||||
|
const path = require('path')
|
||||||
|
const _ = require('lodash')
|
||||||
|
const TestDummy = require('../fixtures/TestDummy')
|
||||||
|
const sander = require('sander')
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
|
const storagePath = path.join(os.tmpdir(), 'test/toggle-storage')
|
||||||
|
|
||||||
|
test.beforeEach((t) => {
|
||||||
|
t.context.storage = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
||||||
|
})
|
||||||
|
|
||||||
|
test.serial('Toggle a storage location', (t) => {
|
||||||
|
const storageKey = t.context.storage.cache.key
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(function doTest () {
|
||||||
|
return toggleStorage(storageKey, true)
|
||||||
|
})
|
||||||
|
.then(function assert (data) {
|
||||||
|
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
|
t.true(_.find(cachedStorageList, {key: storageKey}).isOpen === true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after(function after () {
|
||||||
|
localStorage.clear()
|
||||||
|
sander.rimrafSync(storagePath)
|
||||||
|
})
|
||||||
73
tests/lib/escapeHtmlCharacters-test.js
Normal file
73
tests/lib/escapeHtmlCharacters-test.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
const { escapeHtmlCharacters } = require('browser/lib/utils')
|
||||||
|
const test = require('ava')
|
||||||
|
|
||||||
|
test('escapeHtmlCharacters should return the original string if nothing needed to escape', t => {
|
||||||
|
const input = 'Nothing to be escaped'
|
||||||
|
const expected = 'Nothing to be escaped'
|
||||||
|
const actual = escapeHtmlCharacters(input)
|
||||||
|
t.is(actual, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('escapeHtmlCharacters should skip code block if that option is enabled', t => {
|
||||||
|
const input = ` <no escape>
|
||||||
|
<escapeMe>`
|
||||||
|
const expected = ` <no escape>
|
||||||
|
<escapeMe>`
|
||||||
|
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
||||||
|
t.is(actual, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', t => {
|
||||||
|
const input = '4 spaces &'
|
||||||
|
const expected = '4 spaces &'
|
||||||
|
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
||||||
|
t.is(actual, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', t => {
|
||||||
|
const input = ` <no escape>
|
||||||
|
<escapeMe>`
|
||||||
|
const expected = ` <no escape>
|
||||||
|
<escapeMe>`
|
||||||
|
const actual = escapeHtmlCharacters(input)
|
||||||
|
t.is(actual, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("escapeHtmlCharacters should NOT escape & character if it's a part of an escaped character", t => {
|
||||||
|
const input = 'Do not escape & or " but do escape &'
|
||||||
|
const expected = 'Do not escape & or " but do escape &'
|
||||||
|
const actual = escapeHtmlCharacters(input)
|
||||||
|
t.is(actual, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('escapeHtmlCharacters should skip char if in code block', t => {
|
||||||
|
const input = `
|
||||||
|
\`\`\`
|
||||||
|
<dontescapeme>
|
||||||
|
\`\`\`
|
||||||
|
das<das>dasd
|
||||||
|
dasdasdasd
|
||||||
|
\`\`\`
|
||||||
|
<dontescapeme>
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
|
const expected = `
|
||||||
|
\`\`\`
|
||||||
|
<dontescapeme>
|
||||||
|
\`\`\`
|
||||||
|
das<das>dasd
|
||||||
|
dasdasdasd
|
||||||
|
\`\`\`
|
||||||
|
<dontescapeme>
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
|
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
|
||||||
|
t.is(actual, expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('escapeHtmlCharacters should return the correct result', t => {
|
||||||
|
const input = '& < > " \''
|
||||||
|
const expected = '& < > " ''
|
||||||
|
const actual = escapeHtmlCharacters(input)
|
||||||
|
t.is(actual, expected)
|
||||||
|
})
|
||||||
@@ -12,6 +12,7 @@ test('getTodoStatus should return a correct hash object', t => {
|
|||||||
['- [ ] a\n- [x] a\n', { total: 2, completed: 1 }],
|
['- [ ] a\n- [x] a\n', { total: 2, completed: 1 }],
|
||||||
['+ [ ] a\n', { total: 1, completed: 0 }],
|
['+ [ ] a\n', { total: 1, completed: 0 }],
|
||||||
['+ [ ] a\n+ [x] a\n', { total: 2, completed: 1 }],
|
['+ [ ] a\n+ [x] a\n', { total: 2, completed: 1 }],
|
||||||
|
['+ [ ] a\n+ [X] a\n', { total: 2, completed: 1 }],
|
||||||
['+ [ ] a\n+ [testx] a\n', { total: 1, completed: 0 }],
|
['+ [ ] a\n+ [testx] a\n', { total: 1, completed: 0 }],
|
||||||
['+ [ ] a\n+ [xtest] a\n', { total: 1, completed: 0 }],
|
['+ [ ] a\n+ [xtest] a\n', { total: 1, completed: 0 }],
|
||||||
['+ [ ] a\n+ foo[x]bar a\n', { total: 1, completed: 0 }],
|
['+ [ ] a\n+ foo[x]bar a\n', { total: 1, completed: 0 }],
|
||||||
|
|||||||
16
tests/lib/normalize-editor-font-family-test.js
Normal file
16
tests/lib/normalize-editor-font-family-test.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Unit test for browser/lib/normalizeEditorFontFamily
|
||||||
|
*/
|
||||||
|
import test from 'ava'
|
||||||
|
import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily'
|
||||||
|
import consts from '../../browser/lib/consts'
|
||||||
|
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||||
|
|
||||||
|
test('normalizeEditorFontFamily() should return default font family (string[])', t => {
|
||||||
|
t.is(normalizeEditorFontFamily(), defaultEditorFontFamily.join(', '))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', t => {
|
||||||
|
const arg = 'font1, font2'
|
||||||
|
t.is(normalizeEditorFontFamily(arg), `${arg}, ${defaultEditorFontFamily.join(', ')}`)
|
||||||
|
})
|
||||||
@@ -5,7 +5,7 @@ const { parse } = require('browser/lib/RcParser')
|
|||||||
// Unit test
|
// Unit test
|
||||||
test('RcParser should return a json object', t => {
|
test('RcParser should return a json object', t => {
|
||||||
const validJson = { 'editor': { 'keyMap': 'vim', 'switchPreview': 'BLUR', 'theme': 'monokai' }, 'hotkey': { 'toggleMain': 'Control + L' }, 'listWidth': 135, 'navWidth': 135 }
|
const validJson = { 'editor': { 'keyMap': 'vim', 'switchPreview': 'BLUR', 'theme': 'monokai' }, 'hotkey': { 'toggleMain': 'Control + L' }, 'listWidth': 135, 'navWidth': 135 }
|
||||||
const allJson = { 'amaEnabled': true, 'editor': { 'fontFamily': 'Monaco, Consolas', 'fontSize': '14', 'indentSize': '2', 'indentType': 'space', 'keyMap': 'vim', 'switchPreview': 'BLUR', 'theme': 'monokai' }, 'hotkey': { 'toggleMain': 'Cmd + Alt + L' }, 'isSideNavFolded': false, 'listStyle': 'DEFAULT', 'listWidth': 174, 'navWidth': 200, 'preview': { 'codeBlockTheme': 'dracula', 'fontFamily': 'Lato', 'fontSize': '14', 'lineNumber': true }, 'sortBy': 'UPDATED_AT', 'ui': { 'defaultNote': 'ALWAYS_ASK', 'disableDirectWrite': false, 'theme': 'default' }, 'zoom': 1 }
|
const allJson = { 'amaEnabled': true, 'editor': { 'fontFamily': 'Monaco, Consolas', 'fontSize': '14', 'indentSize': '2', 'indentType': 'space', 'keyMap': 'vim', 'switchPreview': 'BLUR', 'theme': 'monokai' }, 'hotkey': { 'toggleMain': 'Cmd + Alt + L' }, 'isSideNavFolded': false, 'listStyle': 'DEFAULT', 'listWidth': 174, 'navWidth': 200, 'preview': { 'codeBlockTheme': 'dracula', 'fontFamily': 'Lato', 'fontSize': '14', 'lineNumber': true }, 'sortBy': { 'default': 'UPDATED_AT' }, 'ui': { 'defaultNote': 'ALWAYS_ASK', 'disableDirectWrite': false, 'theme': 'default' }, 'zoom': 1 }
|
||||||
|
|
||||||
// [input, expected]
|
// [input, expected]
|
||||||
const validTestCases = [
|
const validTestCases = [
|
||||||
|
|||||||
@@ -4,20 +4,24 @@ const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
|
|||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
entry: {
|
entry: {
|
||||||
main: './browser/main/index.js'
|
main: ['./browser/main/index.js']
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['', '.js', '.jsx', '.styl'],
|
extensions: ['', '.js', '.jsx', '.styl'],
|
||||||
packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
|
packageMains: [
|
||||||
|
'webpack',
|
||||||
|
'browser',
|
||||||
|
'web',
|
||||||
|
'browserify',
|
||||||
|
['jam', 'main'],
|
||||||
|
'main'
|
||||||
|
],
|
||||||
alias: {
|
alias: {
|
||||||
'lib': path.join(__dirname, 'lib'),
|
lib: path.join(__dirname, 'lib'),
|
||||||
'browser': path.join(__dirname, 'browser')
|
browser: path.join(__dirname, 'browser')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [new webpack.NoErrorsPlugin(), new NodeTargetPlugin()],
|
||||||
new webpack.NoErrorsPlugin(),
|
|
||||||
new NodeTargetPlugin()
|
|
||||||
],
|
|
||||||
stylus: {
|
stylus: {
|
||||||
use: [require('nib')()],
|
use: [require('nib')()],
|
||||||
import: [
|
import: [
|
||||||
@@ -43,14 +47,13 @@ var config = {
|
|||||||
react: 'var React',
|
react: 'var React',
|
||||||
'react-dom': 'var ReactDOM',
|
'react-dom': 'var ReactDOM',
|
||||||
'react-redux': 'var ReactRedux',
|
'react-redux': 'var ReactRedux',
|
||||||
'codemirror': 'var CodeMirror',
|
codemirror: 'var CodeMirror',
|
||||||
'redux': 'var Redux',
|
redux: 'var Redux',
|
||||||
'raphael': 'var Raphael',
|
raphael: 'var Raphael',
|
||||||
'flowchart': 'var flowchart',
|
flowchart: 'var flowchart',
|
||||||
'sequence-diagram': 'var Diagram'
|
'sequence-diagram': 'var Diagram'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config
|
module.exports = config
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user