diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 91e7683a..e9e1dfbf 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -14,16 +14,27 @@ const { ipcRenderer } = require('electron') CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' -const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] +const defaultEditorFontFamily = [ + 'Monaco', + 'Menlo', + 'Ubuntu Mono', + 'Consolas', + 'source-code-pro', + 'monospace' +] const buildCMRulers = (rulers, enableRulers) => - enableRulers ? rulers.map(ruler => ({column: ruler})) : [] + (enableRulers ? rulers.map(ruler => ({ column: ruler })) : []) export default class CodeEditor extends React.Component { constructor (props) { super(props) - this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true}) - this.changeHandler = (e) => this.handleChange(e) + this.state = { isReady: false } + this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, { + leading: false, + trailing: true + }) + this.changeHandler = e => this.handleChange(e) this.focusHandler = () => { ipcRenderer.send('editor:focused', true) } @@ -39,11 +50,15 @@ export default class CodeEditor extends React.Component { } this.props.onBlur != null && this.props.onBlur(e) - const {storageKey, noteKey} = this.props - attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey) + const { storageKey, noteKey } = this.props + attachmentManagement.deleteAttachmentsNotPresentInNote( + this.editor.getValue(), + storageKey, + noteKey + ) } this.pasteHandler = (editor, e) => this.handlePaste(editor, e) - this.loadStyleHandler = (e) => { + this.loadStyleHandler = e => { this.editor.refresh() } this.searchHandler = (e, msg) => this.handleSearch(msg) @@ -62,7 +77,10 @@ export default class CodeEditor extends React.Component { cm.addOverlay(component.searchState) function makeOverlay (query, style) { - query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi') + query = new RegExp( + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), + 'gi' + ) return { token: function (stream) { query.lastIndex = stream.pos @@ -94,7 +112,11 @@ export default class CodeEditor extends React.Component { } ] 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 @@ -131,9 +153,14 @@ export default class CodeEditor extends React.Component { cm.execCommand('insertSoftTab') } 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 - 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 (tabs) { cm.execCommand('insertTab') @@ -154,7 +181,7 @@ export default class CodeEditor extends React.Component { // Do nothing }, Enter: 'boostNewLineAndIndentContinueMarkdownList', - 'Ctrl-C': (cm) => { + 'Ctrl-C': cm => { if (cm.getOption('keyMap').substr(0, 3) === 'vim') { document.execCommand('copy') } @@ -182,10 +209,15 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor) CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) CodeMirror.Vim.map('ZZ', ':q', 'normal') + this.setState({ isReady: true }) } 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 = ':{}' for (let i = 0; i < snippets.length; i++) { if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) { @@ -203,7 +235,10 @@ export default class CodeEditor extends React.Component { wordBeforeCursor.range.from, wordBeforeCursor.range.to ) - cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition }) + cm.setCursor({ + line: cursor.line + cursorLineNumber, + ch: cursorLinePosition + }) } } } else { @@ -245,8 +280,8 @@ export default class CodeEditor extends React.Component { return { text: wordBeforeCursor, range: { - from: {line: lineNumber, ch: originCursorPosition}, - to: {line: lineNumber, ch: cursorPosition} + from: { line: lineNumber, ch: originCursorPosition }, + to: { line: lineNumber, ch: cursorPosition } } } } @@ -268,7 +303,7 @@ export default class CodeEditor extends React.Component { componentDidUpdate (prevProps, prevState) { let needRefresh = false - const {rulers, enableRulers} = this.props + const { rulers, enableRulers } = this.props if (prevProps.mode !== this.props.mode) { this.setMode(this.props.mode) } @@ -286,7 +321,10 @@ export default class CodeEditor extends React.Component { needRefresh = true } - if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) { + if ( + prevProps.enableRulers !== enableRulers || + prevProps.rulers !== rulers + ) { this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers)) } @@ -326,11 +364,9 @@ export default class CodeEditor extends React.Component { } } - moveCursorTo (row, col) { - } + moveCursorTo (row, col) {} - scrollToLine (num) { - } + scrollToLine (num) {} focus () { this.editor.focus() @@ -358,8 +394,13 @@ export default class CodeEditor extends React.Component { handleDropImage (dropEvent) { dropEvent.preventDefault() - const {storageKey, noteKey} = this.props - attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent) + const { storageKey, noteKey } = this.props + attachmentManagement.handleAttachmentDrop( + this, + storageKey, + noteKey, + dropEvent + ) } insertAttachmentMd (imageMd) { @@ -368,34 +409,44 @@ export default class CodeEditor extends React.Component { handlePaste (editor, e) { const clipboardData = e.clipboardData - const {storageKey, noteKey} = this.props + const { storageKey, noteKey } = this.props const dataTransferItem = clipboardData.items[0] const pastedTxt = clipboardData.getData('text') - const isURL = (str) => { + const isURL = str => { const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ return matcher.test(str) } - const isInLinkTag = (editor) => { + const isInLinkTag = editor => { const startCursor = editor.getCursor('start') const prevChar = editor.getRange( - {line: startCursor.line, ch: startCursor.ch - 2}, - {line: startCursor.line, ch: startCursor.ch} + { line: startCursor.line, ch: startCursor.ch - 2 }, + { line: startCursor.line, ch: startCursor.ch } ) const endCursor = editor.getCursor('end') const nextChar = editor.getRange( - {line: endCursor.line, ch: endCursor.ch}, - {line: endCursor.line, ch: endCursor.ch + 1} + { line: endCursor.line, ch: endCursor.ch }, + { line: endCursor.line, ch: endCursor.ch + 1 } ) return prevChar === '](' && nextChar === ')' } if (dataTransferItem.type.match('image')) { - attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem) - } else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { + attachmentManagement.handlePastImageEvent( + this, + storageKey, + noteKey, + dataTransferItem + ) + } else if ( + this.props.fetchUrlTitle && + isURL(pastedTxt) && + !isInLinkTag(editor) + ) { this.handlePasteUrl(e, editor, pastedTxt) } if (attachmentManagement.isAttachmentLink(pastedTxt)) { - attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) - .then((modifiedText) => { + attachmentManagement + .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) + .then(modifiedText => { this.editor.replaceSelection(modifiedText) }) e.preventDefault() @@ -413,39 +464,49 @@ export default class CodeEditor extends React.Component { const taggedUrl = `<${pastedTxt}>` editor.replaceSelection(taggedUrl) - const isImageReponse = (response) => { - return response.headers.has('content-type') && + const isImageReponse = response => { + return ( + response.headers.has('content-type') && response.headers.get('content-type').match(/^image\/.+$/) + ) } - const replaceTaggedUrl = (replacement) => { + const replaceTaggedUrl = replacement => { const value = editor.getValue() const cursor = editor.getCursor() 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.setCursor(newCursor) } fetch(pastedTxt, { 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) { - return this.decodeResponse(response).then((body) => { + return this.decodeResponse(response).then(body => { return new Promise((resolve, reject) => { try { - const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html') + const parsedBody = new window.DOMParser().parseFromString( + body, + 'text/html' + ) const linkWithTitle = `[${parsedBody.title}](${pastedTxt})` resolve(linkWithTitle) } catch (e) { @@ -473,10 +534,13 @@ export default class CodeEditor extends React.Component { const _charset = headers.has('content-type') ? this.extractContentTypeCharset(headers.get('content-type')) : undefined - return response.arrayBuffer().then((buff) => { + return response.arrayBuffer().then(buff => { return new Promise((resolve, reject) => { 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()) } catch (e) { reject(e) @@ -486,15 +550,18 @@ export default class CodeEditor extends React.Component { } extractContentTypeCharset (contentType) { - return contentType.split(';').filter((str) => { - return str.trim().toLowerCase().startsWith('charset') - }).map((str) => { - return str.replace(/['"]/g, '').split('=')[1] - })[0] + return contentType + .split(';') + .filter(str => { + return str.trim().toLowerCase().startsWith('charset') + }) + .map(str => { + return str.replace(/['"]/g, '').split('=')[1] + })[0] } render () { - const {className, fontSize} = this.props + const { className, fontSize } = this.props let fontFamily = this.props.fontFamily fontFamily = _.isString(fontFamily) && fontFamily.length > 0 ? [fontFamily].concat(defaultEditorFontFamily) @@ -502,18 +569,16 @@ export default class CodeEditor extends React.Component { const width = this.props.width return (
this.handleDropImage(e)} + onDrop={e => this.handleDropImage(e)} /> ) } diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 4a745055..b4a2aa80 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -24,15 +24,24 @@ const path = require('path') const dialog = remote.dialog const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1] -const appPath = 'file://' + (process.env.NODE_ENV === 'production' - ? app.getAppPath() - : path.resolve()) +const appPath = + 'file://' + + (process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()) const CSS_FILES = [ `${appPath}/node_modules/katex/dist/katex.min.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 ` @font-face { font-family: 'Lato'; @@ -137,17 +146,29 @@ if (!OSX) { defaultFontFamily.unshift('Microsoft YaHei') 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 { constructor (props) { super(props) - - this.contextMenuHandler = (e) => this.handleContextMenu(e) - this.mouseDownHandler = (e) => this.handleMouseDown(e) - this.mouseUpHandler = (e) => this.handleMouseUp(e) - this.DoubleClickHandler = (e) => this.handleDoubleClick(e) - this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true}) - this.checkboxClickHandler = (e) => this.handleCheckboxClick(e) + this.state = { + isReady: false + } + this.contextMenuHandler = e => this.handleContextMenu(e) + this.mouseDownHandler = e => this.handleMouseDown(e) + this.mouseUpHandler = e => this.handleMouseUp(e) + this.DoubleClickHandler = e => this.handleDoubleClick(e) + this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, { + leading: false, + trailing: true + }) + this.checkboxClickHandler = e => this.handleCheckboxClick(e) this.saveAsTextHandler = () => this.handleSaveAsText() this.saveAsMdHandler = () => this.handleSaveAsMd() this.saveAsHtmlHandler = () => this.handleSaveAsHtml() @@ -214,31 +235,56 @@ export default class MarkdownPreview extends React.Component { handleSaveAsHtml () { this.exportAsDocument('html', (noteContent, exportTasks) => { - const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams() + const { + fontFamily, + fontSize, + codeBlockFontFamily, + lineNumber, + codeBlockTheme, + scrollPastEnd, + theme, + allowCustomCSS, + customCSS + } = this.getStyleParams() - const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) + const inlineStyles = buildStyle( + fontFamily, + fontSize, + codeBlockFontFamily, + lineNumber, + scrollPastEnd, + theme, + allowCustomCSS, + customCSS + ) let body = this.markdown.render(escapeHtmlCharacters(noteContent)) 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://', '') exportTasks.push({ src: file, dst: 'css' }) }) - attachmentsAbsolutePaths.forEach((attachment) => { + attachmentsAbsolutePaths.forEach(attachment => { exportTasks.push({ src: attachment, dst: attachmentManagement.DESTINATION_FOLDER }) }) - body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey) + body = attachmentManagement.removeStorageAndNoteReferences( + body, + this.props.noteKey + ) let styles = '' - files.forEach((file) => { + files.forEach(file => { styles += `` }) @@ -260,42 +306,51 @@ export default class MarkdownPreview extends React.Component { exportAsDocument (fileType, contentFormatter) { const options = { - filters: [ - {name: 'Documents', extensions: [fileType]} - ], + filters: [{ name: 'Documents', extensions: [fileType] }], properties: ['openFile', 'createDirectory'] } - dialog.showSaveDialog(remote.getCurrentWindow(), options, - (filename) => { - if (filename) { - const content = this.props.value - const storage = this.props.storagePath + dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => { + if (filename) { + const content = this.props.value + const storage = this.props.storagePath - exportNote(storage, content, filename, contentFormatter) - .then((res) => { - dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`}) - }).catch((err) => { - dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export') - throw err - }) - } - }) + exportNote(storage, content, filename, contentFormatter) + .then(res => { + dialog.showMessageBox(remote.getCurrentWindow(), { + type: 'info', + message: `Exported to ${filename}` + }) + }) + .catch(err => { + dialog.showErrorBox( + 'Export error', + err ? err.message || err : 'Unexpected error during export' + ) + throw err + }) + } + }) } 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 - node.innerText = mdurl.decode(href) === innerText - ? href - : innerText + node.innerText = mdurl.decode(href) === innerText ? href : innerText } } componentDidMount () { 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 = ` @@ -303,7 +358,7 @@ export default class MarkdownPreview extends React.Component { ` - CSS_FILES.forEach((file) => { + CSS_FILES.forEach(file => { styles += `` }) @@ -311,26 +366,66 @@ export default class MarkdownPreview extends React.Component { this.rewriteIframe() this.applyStyle() - this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler) - this.refs.root.contentWindow.document.addEventListener('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) + this.refs.root.contentWindow.document.addEventListener( + 'mousedown', + this.mouseDownHandler + ) + this.refs.root.contentWindow.document.addEventListener( + '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-md', this.saveAsMdHandler) eventEmitter.on('export:save-html', this.saveAsHtmlHandler) eventEmitter.on('print', this.printHandler) + setTimeout(() => this.setState({ isReady: true })) } componentWillUnmount () { - this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler) - this.refs.root.contentWindow.document.removeEventListener('mousedown', 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) + this.refs.root.contentWindow.document.body.removeEventListener( + 'contextmenu', + this.contextMenuHandler + ) + this.refs.root.contentWindow.document.removeEventListener( + 'mousedown', + 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-md', this.saveAsMdHandler) eventEmitter.off('export:save-html', this.saveAsHtmlHandler) @@ -339,14 +434,17 @@ export default class MarkdownPreview extends React.Component { componentDidUpdate (prevProps) { if (prevProps.value !== this.props.value) this.rewriteIframe() - if (prevProps.smartQuotes !== this.props.smartQuotes || - prevProps.sanitize !== this.props.sanitize || - prevProps.smartArrows !== this.props.smartArrows || - prevProps.breaks !== this.props.breaks) { + if ( + prevProps.smartQuotes !== this.props.smartQuotes || + prevProps.sanitize !== this.props.sanitize || + prevProps.smartArrows !== this.props.smartArrows || + prevProps.breaks !== this.props.breaks + ) { this.initMarkdown() this.rewriteIframe() } - if (prevProps.fontFamily !== this.props.fontFamily || + if ( + prevProps.fontFamily !== this.props.fontFamily || prevProps.fontSize !== this.props.fontSize || prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily || prevProps.codeBlockTheme !== this.props.codeBlockTheme || @@ -355,34 +453,82 @@ export default class MarkdownPreview extends React.Component { prevProps.theme !== this.props.theme || prevProps.scrollPastEnd !== this.props.scrollPastEnd || prevProps.allowCustomCSS !== this.props.allowCustomCSS || - prevProps.customCSS !== this.props.customCSS) { + prevProps.customCSS !== this.props.customCSS + ) { this.applyStyle() this.rewriteIframe() } } 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 fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0 - ? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily) - : defaultFontFamily - codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0 - ? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily) - : defaultCodeBlockFontFamily + ? fontFamily + .split(',') + .map(fontName => fontName.trim()) + .concat(defaultFontFamily) + : 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 () { - 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('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) + this.getWindow().document.getElementById( + 'codeTheme' + ).href = this.GetCodeThemeLink(codeBlockTheme) + this.getWindow().document.getElementById('style').innerHTML = buildStyle( + fontFamily, + fontSize, + codeBlockFontFamily, + lineNumber, + scrollPastEnd, + theme, + allowCustomCSS, + customCSS + ) } GetCodeThemeLink (theme) { - theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default' + theme = consts.THEMES.some(_theme => _theme === theme) && + theme !== 'default' ? theme : 'elegant' return theme.startsWith('solarized') @@ -391,71 +537,103 @@ export default class MarkdownPreview extends React.Component { } rewriteIframe () { - _.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => { - el.removeEventListener('click', this.checkboxClickHandler) - }) + _.forEach( + this.refs.root.contentWindow.document.querySelectorAll( + 'input[type="checkbox"]' + ), + el => { + el.removeEventListener('click', this.checkboxClickHandler) + } + ) - _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { - el.removeEventListener('click', this.linkClickHandler) - }) + _.forEach( + 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 this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme) const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g) if (codeBlocks !== null) { - codeBlocks.forEach((codeBlock) => { - value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) + codeBlocks.forEach(codeBlock => { + value = value.replace( + codeBlock, + htmlTextHelper.encodeEntities(codeBlock) + ) }) } let renderedHTML = this.markdown.render(value) attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey) - this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) + 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) - }) + _.forEach( + this.refs.root.contentWindow.document.querySelectorAll( + 'input[type="checkbox"]' + ), + el => { + el.addEventListener('click', this.checkboxClickHandler) + } + ) - _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { - this.fixDecodedURI(el) - el.addEventListener('click', this.linkClickHandler) - }) + _.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 = consts.THEMES.some(_theme => _theme === codeBlockTheme) ? codeBlockTheme : 'default' - _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => { - let syntax = CodeMirror.findModeByName(convertModeName(el.className)) - if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') - CodeMirror.requireMode(syntax.mode, () => { - const content = htmlTextHelper.decodeEntities(el.innerHTML) - const copyIcon = document.createElement('i') - copyIcon.innerHTML = '' - copyIcon.onclick = (e) => { - copy(content) - if (showCopyNotification) { - this.notify('Saved to Clipboard!', { - body: 'Paste it wherever you want!', - silent: true - }) + _.forEach( + this.refs.root.contentWindow.document.querySelectorAll('.code code'), + el => { + let syntax = CodeMirror.findModeByName(convertModeName(el.className)) + if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') + CodeMirror.requireMode(syntax.mode, () => { + const content = htmlTextHelper.decodeEntities(el.innerHTML) + const copyIcon = document.createElement('i') + copyIcon.innerHTML = + '' + copyIcon.onclick = e => { + copy(content) + if (showCopyNotification) { + this.notify('Saved to Clipboard!', { + body: 'Paste it wherever you want!', + silent: true + }) + } } - } - el.parentNode.appendChild(copyIcon) - el.innerHTML = '' - if (codeBlockTheme.indexOf('solarized') === 0) { - const [refThema, color] = codeBlockTheme.split(' ') - el.parentNode.className += ` cm-s-${refThema} cm-s-${color}` - } else { - el.parentNode.className += ` cm-s-${codeBlockTheme}` - } - CodeMirror.runMode(content, syntax.mime, el, { - tabSize: indentSize + el.parentNode.appendChild(copyIcon) + el.innerHTML = '' + if (codeBlockTheme.indexOf('solarized') === 0) { + const [refThema, color] = codeBlockTheme.split(' ') + el.parentNode.className += ` cm-s-${refThema} cm-s-${color}` + } else { + el.parentNode.className += ` cm-s-${codeBlockTheme}` + } + CodeMirror.runMode(content, syntax.mime, el, { + tabSize: indentSize + }) }) - }) - }) + } + ) const opts = {} // if (this.props.theme === 'dark') { // opts['font-color'] = '#DDD' @@ -463,37 +641,47 @@ export default class MarkdownPreview extends React.Component { // opts['element-color'] = '#DDD' // opts['fill'] = '#3A404C' // } - _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => { - Raphael.setWindow(this.getWindow()) - try { - const diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML)) - el.innerHTML = '' - diagram.drawSVG(el, opts) - _.forEach(el.querySelectorAll('a'), (el) => { - el.addEventListener('click', this.linkClickHandler) - }) - } catch (e) { - console.error(e) - el.className = 'flowchart-error' - el.innerHTML = 'Flowchart parse error: ' + e.message + _.forEach( + this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), + el => { + Raphael.setWindow(this.getWindow()) + try { + const diagram = flowchart.parse( + htmlTextHelper.decodeEntities(el.innerHTML) + ) + el.innerHTML = '' + diagram.drawSVG(el, opts) + _.forEach(el.querySelectorAll('a'), el => { + el.addEventListener('click', this.linkClickHandler) + }) + } 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) => { - Raphael.setWindow(this.getWindow()) - try { - const diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML)) - el.innerHTML = '' - diagram.drawSVG(el, {theme: 'simple'}) - _.forEach(el.querySelectorAll('a'), (el) => { - el.addEventListener('click', this.linkClickHandler) - }) - } catch (e) { - console.error(e) - el.className = 'sequence-error' - el.innerHTML = 'Sequence diagram parse error: ' + e.message + _.forEach( + this.refs.root.contentWindow.document.querySelectorAll('.sequence'), + el => { + Raphael.setWindow(this.getWindow()) + try { + const diagram = SequenceDiagram.parse( + htmlTextHelper.decodeEntities(el.innerHTML) + ) + el.innerHTML = '' + diagram.drawSVG(el, { theme: 'simple' }) + _.forEach(el.querySelectorAll('a'), el => { + el.addEventListener('click', this.linkClickHandler) + }) + } catch (e) { + console.error(e) + el.className = 'sequence-error' + el.innerHTML = 'Sequence diagram parse error: ' + e.message + } } - }) + ) } focus () { @@ -505,7 +693,9 @@ export default class MarkdownPreview extends React.Component { } 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++) { let block = blocks[index] @@ -525,7 +715,11 @@ export default class MarkdownPreview extends React.Component { notify (title, options) { 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) } @@ -540,7 +734,9 @@ export default class MarkdownPreview extends React.Component { const regexNoteInternalLink = /main.html#(.+)/ if (regexNoteInternalLink.test(linkHash)) { 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) { this.getWindow().scrollTo(0, targetElement.offsetTop) @@ -574,11 +770,15 @@ export default class MarkdownPreview extends React.Component { render () { const { className, style, tabIndex } = this.props return ( - \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({ type: 'UPDATE_NOTE', note: note @@ -131,10 +130,10 @@ class Main extends React.Component { .then(defaultMarkdownNote) .then(() => data.storage) }) - .then((storage) => { + .then(storage => { hashHistory.push('/storages/' + storage.key) }) - .catch((err) => { + .catch(err => { throw err }) } @@ -142,12 +141,7 @@ class Main extends React.Component { componentDidMount () { const { dispatch, config } = this.props - const supportedThemes = [ - 'dark', - 'white', - 'solarized-dark', - 'monokai' - ] + const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai'] if (supportedThemes.indexOf(config.ui.theme) !== -1) { document.body.setAttribute('data-theme', config.ui.theme) @@ -162,19 +156,18 @@ class Main extends React.Component { } applyShortcuts() // Reload all data - dataApi.init() - .then((data) => { - dispatch({ - type: 'INIT_ALL', - storages: data.storages, - notes: data.notes - }) - - if (data.storages.length < 1) { - this.init() - } + dataApi.init().then(data => { + dispatch({ + type: 'INIT_ALL', + storages: data.storages, + notes: data.notes }) + if (data.storages.length < 1) { + this.init() + } + }) + eventEmitter.on('editor:fullscreen', this.toggleFullScreen) } @@ -199,34 +192,40 @@ class Main extends React.Component { handleMouseUp (e) { // Change width of NoteList component. if (this.state.isRightSliderFocused) { - this.setState({ - isRightSliderFocused: false - }, () => { - const { dispatch } = this.props - const newListWidth = this.state.listWidth - // TODO: ConfigManager should dispatch itself. - ConfigManager.set({listWidth: newListWidth}) - dispatch({ - type: 'SET_LIST_WIDTH', - listWidth: newListWidth - }) - }) + this.setState( + { + isRightSliderFocused: false + }, + () => { + const { dispatch } = this.props + const newListWidth = this.state.listWidth + // TODO: ConfigManager should dispatch itself. + ConfigManager.set({ listWidth: newListWidth }) + dispatch({ + type: 'SET_LIST_WIDTH', + listWidth: newListWidth + }) + } + ) } // Change width of SideNav component. if (this.state.isLeftSliderFocused) { - this.setState({ - isLeftSliderFocused: false - }, () => { - const { dispatch } = this.props - const navWidth = this.state.navWidth - // TODO: ConfigManager should dispatch itself. - ConfigManager.set({ navWidth }) - dispatch({ - type: 'SET_NAV_WIDTH', - navWidth - }) - }) + this.setState( + { + isLeftSliderFocused: false + }, + () => { + const { dispatch } = this.props + const navWidth = this.state.navWidth + // TODO: ConfigManager should dispatch itself. + ConfigManager.set({ navWidth }) + dispatch({ + type: 'SET_NAV_WIDTH', + navWidth + }) + } + ) } } @@ -271,8 +270,8 @@ class Main extends React.Component { } hideLeftLists (noteDetail, noteList, mainBody) { - this.setState({noteDetailWidth: noteDetail.style.left}) - this.setState({mainBodyWidth: mainBody.style.left}) + this.setState({ noteDetailWidth: noteDetail.style.left }) + this.setState({ mainBodyWidth: mainBody.style.left }) noteDetail.style.left = '0px' mainBody.style.left = '0px' noteList.style.display = 'none' @@ -294,33 +293,36 @@ class Main extends React.Component {
this.handleMouseMove(e)} - onMouseUp={(e) => this.handleMouseUp(e)} + onMouseMove={e => this.handleMouseMove(e)} + onMouseUp={e => this.handleMouseUp(e)} > {!config.isSideNavFolded && -
this.handleLeftSlideMouseDown(e)} +
this.handleLeftSlideMouseDown(e)} draggable='false' >
-
- } -
} +
- - -
this.handleRightSlideMouseDown(e)} +
this.handleRightSlideMouseDown(e)} draggable='false' >
x)(CSSModules(Main, styles)) +export default connect(x => x)(CSSModules(Main, styles)) diff --git a/lib/main.html b/lib/main.html index 22527d99..7366fa04 100644 --- a/lib/main.html +++ b/lib/main.html @@ -1,72 +1,83 @@ + - + + Boostnote +
-
+
+ +
@@ -130,4 +141,5 @@ } - + + \ No newline at end of file