From 2e9b478824c7642e10c1254830d49fa3c1e405e0 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Wed, 18 Apr 2018 19:31:10 +0700 Subject: [PATCH 01/30] added text expansion support --- browser/components/CodeEditor.js | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 872e9ad7..a717a740 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -94,6 +94,18 @@ export default class CodeEditor extends React.Component { componentDidMount () { const { rulers, enableRulers } = this.props + const storagePath = findStorage(this.props.storageKey).path + const expandDataFile = path.join(storagePath, 'expandData.json') + if (!fs.existsSync(expandDataFile)) { + const defaultExpandData = [ + { + matches: ['expandme'], + content: 'Here I am' + } + ]; + fs.writeFileSync(expandDataFile, JSON.stringify(defaultExpandData), 'utf8') + } + const expandData = JSON.parse(fs.readFileSync(expandDataFile, 'utf8')) this.value = this.props.value this.editor = CodeMirror(this.refs.root, { @@ -116,6 +128,8 @@ export default class CodeEditor extends React.Component { Tab: function (cm) { const cursor = cm.getCursor() const line = cm.getLine(cursor.line) + const cursorPosition = cursor.ch + const charBeforeCursor = line.substr(cursorPosition - 1, 1) if (cm.somethingSelected()) cm.indentSelection('add') else { const tabs = cm.getOption('indentWithTabs') @@ -127,6 +141,66 @@ export default class CodeEditor extends React.Component { cm.execCommand('insertSoftTab') } cm.execCommand('goLineEnd') + } else if (charBeforeCursor !== ' ') { + // text expansion on tab key + let tempCursorPosition = cursorPosition; + let wordBeforeCursor = ''; + const emptyChars = /\t|\s|\r|\n/ + + // to prevent the word to expand is long that will crash the whole app + // the safeStop is there to stop user to expand words that longer than 20 chars + const safeStop = 20; + + while (tempCursorPosition > 0) { + let currentChar = line.substr(tempCursorPosition - 1, 1); + // if char is not an empty char + if (!emptyChars.test(currentChar)) { + wordBeforeCursor = currentChar + wordBeforeCursor + } else if (wordBeforeCursor.length >= safeStop) { + throw new Error("Your text expansion word is too long !") + } else { + break + } + tempCursorPosition--; + } + + for (let i = 0; i < expandData.length; i++) { + if (Array.isArray(expandData[i].matches)) { + if (expandData[i].matches.indexOf(wordBeforeCursor) !== -1) { + let range = { + from: {line: cursor.line, ch: cursor.ch}, + to: {line: cursor.line, ch: tempCursorPosition} + } + cm.replaceRange( + expandData[i].content, + range.from, + range.to + ) + return // stop if text is expanded + } + } + else if (typeof(expandData[i].matches) === 'string') { + if (expandData[i].matches === wordBeforeCursor) { + let range = { + from: {line: cursor.line, ch: cursor.ch}, + to: {line: cursor.line, ch: tempCursorPosition} + } + cm.replaceRange( + expandData[i].content, + range.from, + range.to + ) + return // stop if text is expanded + } + } + } + + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + } else { if (tabs) { cm.execCommand('insertTab') From 50d2f90621419d1efea7e640958bef3da758bf88 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Thu, 19 Apr 2018 13:40:33 +0700 Subject: [PATCH 02/30] added snippet config in setting --- browser/components/CodeEditor.js | 136 ++++++++++-------- .../modals/PreferencesModal/SnippetEditor.js | 66 +++++++++ .../modals/PreferencesModal/SnippetTab.js | 74 ++++++++++ .../modals/PreferencesModal/SnippetTab.styl | 106 ++++++++++++++ browser/main/modals/PreferencesModal/index.js | 12 +- 5 files changed, 333 insertions(+), 61 deletions(-) create mode 100644 browser/main/modals/PreferencesModal/SnippetEditor.js create mode 100644 browser/main/modals/PreferencesModal/SnippetTab.js create mode 100644 browser/main/modals/PreferencesModal/SnippetTab.styl diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index a717a740..57705560 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -96,18 +96,25 @@ export default class CodeEditor extends React.Component { const { rulers, enableRulers } = this.props const storagePath = findStorage(this.props.storageKey).path const expandDataFile = path.join(storagePath, 'expandData.json') + const emptyChars = /\t|\s|\r|\n/ if (!fs.existsSync(expandDataFile)) { const defaultExpandData = [ { - matches: ['expandme'], - content: 'Here I am' - } + matches: ['lorem', 'ipsum'], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + }, + { match: 'h1', content: '# '}, + { match: 'h2', content: '## '}, + { match: 'h3', content: '### '}, + { match: 'h4', content: '#### '}, + { match: 'h5', content: '##### '}, + { match: 'h6', content: '###### '} ]; fs.writeFileSync(expandDataFile, JSON.stringify(defaultExpandData), 'utf8') } const expandData = JSON.parse(fs.readFileSync(expandDataFile, 'utf8')) + const expandSnippet = this.expandSnippet.bind(this) this.value = this.props.value - this.editor = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), value: this.props.value, @@ -141,64 +148,14 @@ export default class CodeEditor extends React.Component { cm.execCommand('insertSoftTab') } cm.execCommand('goLineEnd') - } else if (charBeforeCursor !== ' ') { - // text expansion on tab key - let tempCursorPosition = cursorPosition; - let wordBeforeCursor = ''; - const emptyChars = /\t|\s|\r|\n/ - - // to prevent the word to expand is long that will crash the whole app - // the safeStop is there to stop user to expand words that longer than 20 chars - const safeStop = 20; - - while (tempCursorPosition > 0) { - let currentChar = line.substr(tempCursorPosition - 1, 1); - // if char is not an empty char - if (!emptyChars.test(currentChar)) { - wordBeforeCursor = currentChar + wordBeforeCursor - } else if (wordBeforeCursor.length >= safeStop) { - throw new Error("Your text expansion word is too long !") + } else if (!emptyChars.test(charBeforeCursor) || cursor.ch !== 0) { + // text expansion on tab key if the char before is alphabet + if (expandSnippet(line, cursor, cm, expandData) === false) { + if (tabs) { + cm.execCommand('insertTab') } else { - break + cm.execCommand('insertSoftTab') } - tempCursorPosition--; - } - - for (let i = 0; i < expandData.length; i++) { - if (Array.isArray(expandData[i].matches)) { - if (expandData[i].matches.indexOf(wordBeforeCursor) !== -1) { - let range = { - from: {line: cursor.line, ch: cursor.ch}, - to: {line: cursor.line, ch: tempCursorPosition} - } - cm.replaceRange( - expandData[i].content, - range.from, - range.to - ) - return // stop if text is expanded - } - } - else if (typeof(expandData[i].matches) === 'string') { - if (expandData[i].matches === wordBeforeCursor) { - let range = { - from: {line: cursor.line, ch: cursor.ch}, - to: {line: cursor.line, ch: tempCursorPosition} - } - cm.replaceRange( - expandData[i].content, - range.from, - range.to - ) - return // stop if text is expanded - } - } - } - - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') } } else { @@ -244,6 +201,65 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.map('ZZ', ':q', 'normal') } + expandSnippet(line, cursor, cm, expandData) { + let wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch) + for (let i = 0; i < expandData.length; i++) { + if (Array.isArray(expandData[i].matches)) { + if (expandData[i].matches.indexOf(wordBeforeCursor.text) !== -1) { + cm.replaceRange( + expandData[i].content, + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) + return true + } + } + else if (typeof(expandData[i].matches) === 'string') { + if (expandData[i].match === wordBeforeCursor.text) { + cm.replaceRange( + expandData[i].content, + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) + return true + } + } + } + + return false + } + + getWordBeforeCursor(line, lineNumber, cursorPosition) { + let wordBeforeCursor = '' + let originCursorPosition = cursorPosition + const emptyChars = /\t|\s|\r|\n/ + + // to prevent the word to expand is long that will crash the whole app + // the safeStop is there to stop user to expand words that longer than 20 chars + const safeStop = 20 + + while (cursorPosition > 0) { + let currentChar = line.substr(cursorPosition - 1, 1) + // if char is not an empty char + if (!emptyChars.test(currentChar)) { + wordBeforeCursor = currentChar + wordBeforeCursor + } else if (wordBeforeCursor.length >= safeStop) { + throw new Error("Your text expansion word is too long !") + } else { + break + } + cursorPosition--; + } + + return { + text: wordBeforeCursor, + range: { + from: {line: lineNumber, ch: originCursorPosition}, + to: {line: lineNumber, ch: cursorPosition} + } + } + } + quitEditor () { document.querySelector('textarea').blur() } diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js new file mode 100644 index 00000000..92da3ae6 --- /dev/null +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -0,0 +1,66 @@ +import CodeMirror from 'codemirror' +import PropTypes from 'prop-types' +import React from 'react' +import _ from 'lodash' + +const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] +const buildCMRulers = (rulers, enableRulers) => + enableRulers ? rulers.map(ruler => ({ column: ruler })) : [] + +export default class SnippetEditor extends React.Component { + constructor (props) { + super(props) + } + + componentDidMount () { + const { rulers, enableRulers } = this.props + let cm = CodeMirror(this.refs.root, { + rulers: buildCMRulers(rulers, enableRulers), + lineNumbers: this.props.displayLineNumbers, + lineWrapping: true, + theme: this.props.theme, + indentUnit: this.props.indentSize, + tabSize: this.props.indentSize, + indentWithTabs: this.props.indentType !== 'space', + keyMap: this.props.keyMap, + scrollPastEnd: this.props.scrollPastEnd, + dragDrop: false, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + autoCloseBrackets: true, + }) + cm.setValue("asdasd") + cm.setSize("100%", "100%") + } + + render() { + const { fontSize } = this.props + let fontFamily = this.props.fontFamily + fontFamily = _.isString(fontFamily) && fontFamily.length > 0 + ? [fontFamily].concat(defaultEditorFontFamily) + : defaultEditorFontFamily + return ( +
+
+ ) + } +} + +SnippetEditor.defaultProps = { + readOnly: false, + theme: 'xcode', + keyMap: 'sublime', + fontSize: 14, + fontFamily: 'Monaco, Consolas', + indentSize: 4, + indentType: 'space' +} diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js new file mode 100644 index 00000000..74787943 --- /dev/null +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -0,0 +1,74 @@ +import PropTypes from 'prop-types' +import React from 'react' +import CSSModules from 'browser/lib/CSSModules' +import dataApi from 'browser/main/lib/dataApi' +import styles from './SnippetTab.styl' +import ConfigManager from 'browser/main/lib/ConfigManager' +import SnippetEditor from './SnippetEditor'; +import i18n from 'browser/lib/i18n' + +class SnippetTab extends React.Component { + constructor (props) { + super(props) + + this.state = { + snippets: [ + { id: 'abcsajisdjiasd', name: 'Hello' } + ] + } + } + + renderSnippetList () { + let { snippets } = this.state + return ( + snippets.map((snippet) => ( +
+ {snippet.name} +
+ )) + ) + } + + render () { + const { config } = this.props + + let editorFontSize = parseInt(config.editor.fontSize, 10) + if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 + let editorIndentSize = parseInt(config.editor.indentSize, 10) + if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 + return ( +
+
{i18n.__('Snippets')}
+
+ {this.renderSnippetList()} +
+
+
+
{i18n.__('Snippet name')}
+
+ +
+
+
+ +
+
+
+ ) + } +} + +SnippetTab.PropTypes = { +} + +export default CSSModules(SnippetTab, styles) \ No newline at end of file diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl new file mode 100644 index 00000000..363c4c95 --- /dev/null +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -0,0 +1,106 @@ +@import('./Tab') + +.root + padding 15px + white-space pre + line-height 1.4 + color alpha($ui-text-color, 90%) + width 100% + font-size 14px + +.group + margin-bottom 45px + +.group-header + @extend .header + color $ui-text-color + +.group-header2 + font-size 20px + color $ui-text-color + margin-bottom 15px + margin-top 30px + +.group-section + margin-bottom 20px + display flex + line-height 30px + +.group-section-label + width 150px + text-align left + margin-right 10px + font-size 14px + +.group-section-control + flex 1 + margin-left 5px + +.group-section-control select + outline none + border 1px solid $ui-borderColor + font-size 16px + height 30px + width 250px + margin-bottom 5px + background-color transparent + +.group-section-control-input + height 30px + vertical-align middle + width 400px + font-size $tab--button-font-size + border solid 1px $border-color + border-radius 2px + padding 0 5px + outline none + &:disabled + background-color $ui-input--disabled-backgroundColor + +.group-checkBoxSection + margin-bottom 15px + display flex + line-height 30px + padding-left 15px + +.group-control + padding-top 10px + box-sizing border-box + height 40px + text-align right + :global + .alert + display inline-block + position absolute + top 60px + right 15px + font-size 14px + .success + color #1EC38B + .error + color red + .warning + color #FFA500 + +.snippet-list + width 30% + height calc(100% - 200px) + background #f5f5f5 + position absolute + + .snippet-item + width 100% + height 50px + font-size 20px + line-height 50px + padding 0 10px + cursor pointer + + &:hover + background darken(#f5f5f5, 5) + +.snippet-detail + width 70% + height calc(100% - 200px) + position absolute + left 33% diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index 70e25a88..0a8b8cae 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -6,6 +6,7 @@ import UiTab from './UiTab' import InfoTab from './InfoTab' import Crowdfunding from './Crowdfunding' import StoragesTab from './StoragesTab' +import SnippetTab from './SnippetTab' import Blog from './Blog' import ModalEscButton from 'browser/components/ModalEscButton' import CSSModules from 'browser/lib/CSSModules' @@ -86,6 +87,14 @@ class Preferences extends React.Component { haveToSave={alert => this.setState({BlogAlert: alert})} /> ) + case 'SNIPPET': + return ( + + ) case 'STORAGES': default: return ( @@ -123,7 +132,8 @@ class Preferences extends React.Component { {target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert}, {target: 'INFO', label: i18n.__('About')}, {target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')}, - {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert} + {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}, + {target: 'SNIPPET', label: i18n.__('Snippets')} ] const navButtons = tabs.map((tab) => { From d3b3e458004ea9f2b767930bfb1f8aa38d954d43 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Thu, 19 Apr 2018 19:05:46 +0700 Subject: [PATCH 03/30] improved style for snippet list --- browser/components/CodeEditor.js | 2 +- .../modals/PreferencesModal/SnippetEditor.js | 33 ++++++++++++++++--- .../modals/PreferencesModal/SnippetTab.js | 22 ++++++++++--- .../modals/PreferencesModal/SnippetTab.styl | 14 ++++++-- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 57705560..166b25bb 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -148,7 +148,7 @@ export default class CodeEditor extends React.Component { cm.execCommand('insertSoftTab') } cm.execCommand('goLineEnd') - } else if (!emptyChars.test(charBeforeCursor) || cursor.ch !== 0) { + } else if (!emptyChars.test(charBeforeCursor) || cursor.ch > 1) { // text expansion on tab key if the char before is alphabet if (expandSnippet(line, cursor, cm, expandData) === false) { if (tabs) { diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 92da3ae6..15110f34 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -2,6 +2,8 @@ import CodeMirror from 'codemirror' import PropTypes from 'prop-types' import React from 'react' import _ from 'lodash' +import fs from 'fs' +import { findStorage } from 'browser/lib/findStorage' const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const buildCMRulers = (rulers, enableRulers) => @@ -14,7 +16,7 @@ export default class SnippetEditor extends React.Component { componentDidMount () { const { rulers, enableRulers } = this.props - let cm = CodeMirror(this.refs.root, { + this.cm = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), lineNumbers: this.props.displayLineNumbers, lineWrapping: true, @@ -29,11 +31,34 @@ export default class SnippetEditor extends React.Component { gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], autoCloseBrackets: true, }) - cm.setValue("asdasd") - cm.setSize("100%", "100%") + this.cm.setSize("100%", "100%") + let snippetId = this.props.snippetId + + const storagePath = findStorage(this.props.storageKey).path + const expandDataFile = path.join(storagePath, 'expandData.json') + if (!fs.existsSync(expandDataFile)) { + const defaultExpandData = [ + { + matches: ['lorem', 'ipsum'], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + }, + { match: 'h1', content: '# '}, + { match: 'h2', content: '## '}, + { match: 'h3', content: '### '}, + { match: 'h4', content: '#### '}, + { match: 'h5', content: '##### '}, + { match: 'h6', content: '###### '} + ]; + fs.writeFileSync(expandDataFile, JSON.stringify(defaultExpandData), 'utf8') + } + const expandData = JSON.parse(fs.readFileSync(expandDataFile, 'utf8')) } - render() { + componentWillReceiveProps(newProps) { + this.cm.setValue(newProps.value) + } + + render () { const { fontSize } = this.props let fontFamily = this.props.fontFamily fontFamily = _.isString(fontFamily) && fontFamily.length > 0 diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index 74787943..7d46cc65 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -13,16 +13,24 @@ class SnippetTab extends React.Component { this.state = { snippets: [ - { id: 'abcsajisdjiasd', name: 'Hello' } - ] + { id: 'abcsajisdjiasd', name: 'Hello', content: 'asdddddddsaddddddd' }, + { id: 'btbjieejbiebfe', name: 'Hello 2', content: 'asdddddddsaddddddd' } + ], + currentSnippet: null } } + handleSnippetClick(id) { + this.setState({'currentSnippet': id}) + } + renderSnippetList () { let { snippets } = this.state return ( snippets.map((snippet) => ( -
+
this.handleSnippetClick(snippet.id)}> {snippet.name}
)) @@ -42,6 +50,7 @@ class SnippetTab extends React.Component {
{this.renderSnippetList()}
+ {this.state.currentSnippet ?
{i18n.__('Snippet name')}
@@ -50,7 +59,8 @@ class SnippetTab extends React.Component {
- + scrollPastEnd={config.editor.scrollPastEnd} + snippetId={this.state.currentSnippet} />
+ : ''} ) } diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl index 363c4c95..4f32bb3f 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.styl +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -91,10 +91,20 @@ .snippet-item width 100% height 50px - font-size 20px + font-size 15px line-height 50px - padding 0 10px + padding 0 5% cursor pointer + position relative + + &::after + width 90% + height 1px + background rgba(0, 0, 0, 0.1) + position absolute + top 100% + left 5% + content '' &:hover background darken(#f5f5f5, 5) From ff2e39901ae5e87bc19b5807f92ef6aadb42c72e Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Fri, 20 Apr 2018 23:15:17 +0700 Subject: [PATCH 04/30] added delete snippet, update snippet, create snippet and save on snippet change --- browser/components/CodeEditor.js | 77 +++++------- browser/lib/consts.js | 5 +- browser/main/lib/dataApi/createSnippet.js | 23 ++++ browser/main/lib/dataApi/deleteSnippet.js | 20 +++ browser/main/lib/dataApi/index.js | 3 + browser/main/lib/dataApi/updateSnippet.js | 34 +++++ .../modals/PreferencesModal/SnippetEditor.js | 87 +++++++------ .../modals/PreferencesModal/SnippetTab.js | 119 ++++++++++++++---- .../modals/PreferencesModal/SnippetTab.styl | 11 +- browser/main/modals/PreferencesModal/index.js | 4 +- 10 files changed, 273 insertions(+), 110 deletions(-) create mode 100644 browser/main/lib/dataApi/createSnippet.js create mode 100644 browser/main/lib/dataApi/deleteSnippet.js create mode 100644 browser/main/lib/dataApi/updateSnippet.js diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 166b25bb..7ae1ee51 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -9,7 +9,9 @@ import { findStorage } from 'browser/lib/findStorage' import fs from 'fs' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' -const { ipcRenderer } = require('electron') +import crypto from 'crypto' +import consts from 'browser/lib/consts' +const { ipcRenderer, remote } = require('electron') CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' @@ -94,25 +96,24 @@ export default class CodeEditor extends React.Component { componentDidMount () { const { rulers, enableRulers } = this.props - const storagePath = findStorage(this.props.storageKey).path - const expandDataFile = path.join(storagePath, 'expandData.json') const emptyChars = /\t|\s|\r|\n/ - if (!fs.existsSync(expandDataFile)) { - const defaultExpandData = [ + if (!fs.existsSync(consts.SNIPPET_FILE)) { + const defaultSnippets = [ { - matches: ['lorem', 'ipsum'], + id: crypto.randomBytes(16).toString('hex'), + name: 'Dummy text', + prefix: ['lorem', 'ipsum'], content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' }, - { match: 'h1', content: '# '}, - { match: 'h2', content: '## '}, - { match: 'h3', content: '### '}, - { match: 'h4', content: '#### '}, - { match: 'h5', content: '##### '}, - { match: 'h6', content: '###### '} - ]; - fs.writeFileSync(expandDataFile, JSON.stringify(defaultExpandData), 'utf8') + { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 1', prefix: ['h1'], content: '# ' }, + { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 2', prefix: ['h2'], content: '## ' }, + { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 3', prefix: ['h3'], content: '### ' }, + { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 4', prefix: ['h4'], content: '#### ' }, + { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 5', prefix: ['h5'], content: '##### ' }, + { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 6', prefix: ['h6'], content: '###### ' } + ] + fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippets, null, 4), 'utf8') } - const expandData = JSON.parse(fs.readFileSync(expandDataFile, 'utf8')) const expandSnippet = this.expandSnippet.bind(this) this.value = this.props.value this.editor = CodeMirror(this.refs.root, { @@ -150,14 +151,14 @@ export default class CodeEditor extends React.Component { cm.execCommand('goLineEnd') } else if (!emptyChars.test(charBeforeCursor) || cursor.ch > 1) { // text expansion on tab key if the char before is alphabet - if (expandSnippet(line, cursor, cm, expandData) === false) { + const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8')) + if (expandSnippet(line, cursor, cm, snippets) === false) { if (tabs) { cm.execCommand('insertTab') } else { cm.execCommand('insertSoftTab') } } - } else { if (tabs) { cm.execCommand('insertTab') @@ -201,37 +202,25 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.map('ZZ', ':q', 'normal') } - expandSnippet(line, cursor, cm, expandData) { - let wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch) - for (let i = 0; i < expandData.length; i++) { - if (Array.isArray(expandData[i].matches)) { - if (expandData[i].matches.indexOf(wordBeforeCursor.text) !== -1) { - cm.replaceRange( - expandData[i].content, - wordBeforeCursor.range.from, - wordBeforeCursor.range.to - ) - return true - } - } - else if (typeof(expandData[i].matches) === 'string') { - if (expandData[i].match === wordBeforeCursor.text) { - cm.replaceRange( - expandData[i].content, - wordBeforeCursor.range.from, - wordBeforeCursor.range.to - ) - return true - } + expandSnippet (line, cursor, cm, snippets) { + const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch) + for (let i = 0; i < snippets.length; i++) { + if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) { + cm.replaceRange( + snippets[i].content, + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) + return true } } return false } - getWordBeforeCursor(line, lineNumber, cursorPosition) { + getWordBeforeCursor (line, lineNumber, cursorPosition) { let wordBeforeCursor = '' - let originCursorPosition = cursorPosition + const originCursorPosition = cursorPosition const emptyChars = /\t|\s|\r|\n/ // to prevent the word to expand is long that will crash the whole app @@ -239,16 +228,16 @@ export default class CodeEditor extends React.Component { const safeStop = 20 while (cursorPosition > 0) { - let currentChar = line.substr(cursorPosition - 1, 1) + const currentChar = line.substr(cursorPosition - 1, 1) // if char is not an empty char if (!emptyChars.test(currentChar)) { wordBeforeCursor = currentChar + wordBeforeCursor } else if (wordBeforeCursor.length >= safeStop) { - throw new Error("Your text expansion word is too long !") + throw new Error('Your snippet trigger is too long !') } else { break } - cursorPosition--; + cursorPosition-- } return { diff --git a/browser/lib/consts.js b/browser/lib/consts.js index c6b2ea5b..34afad36 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -12,6 +12,8 @@ const themes = fs.readdirSync(themePath) }) themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light') +const snippetFile = path.join(remote.app.getPath('appData'), 'Boostnote', 'snippets.json') + const consts = { FOLDER_COLORS: [ '#E10051', @@ -31,7 +33,8 @@ const consts = { 'Dodger Blue', 'Violet Eggplant' ], - THEMES: ['default'].concat(themes) + THEMES: ['default'].concat(themes), + SNIPPET_FILE: snippetFile } module.exports = consts diff --git a/browser/main/lib/dataApi/createSnippet.js b/browser/main/lib/dataApi/createSnippet.js new file mode 100644 index 00000000..366a1bb1 --- /dev/null +++ b/browser/main/lib/dataApi/createSnippet.js @@ -0,0 +1,23 @@ +const { remote } = require('electron') +import fs from 'fs' +import path from 'path' +import crypto from 'crypto' +import consts from 'browser/lib/consts' + +function createSnippet(snippets) { + return new Promise((resolve, reject) => { + const newSnippet = { + id: crypto.randomBytes(16).toString('hex'), + name: 'Unnamed snippet', + prefix: [], + content: '' + } + snippets.push(newSnippet) + fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + if (err) reject(err) + resolve(snippets) + }) + }) +} + +module.exports = createSnippet diff --git a/browser/main/lib/dataApi/deleteSnippet.js b/browser/main/lib/dataApi/deleteSnippet.js new file mode 100644 index 00000000..e988fd55 --- /dev/null +++ b/browser/main/lib/dataApi/deleteSnippet.js @@ -0,0 +1,20 @@ +const { remote } = require('electron') +import fs from 'fs' +import path from 'path' +import consts from 'browser/lib/consts' + +function deleteSnippet (snippets, snippetId) { + return new Promise((resolve, reject) => { + for(let i = 0; i < snippets.length; i++) { + if (snippets[i].id === snippetId) { + snippets.splice(i, 1); + fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + if (err) reject(err) + resolve(snippets) + }) + } + } + }) +} + +module.exports = deleteSnippet \ No newline at end of file diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js index 311ca2f3..bf42e8ec 100644 --- a/browser/main/lib/dataApi/index.js +++ b/browser/main/lib/dataApi/index.js @@ -13,6 +13,9 @@ const dataApi = { deleteNote: require('./deleteNote'), moveNote: require('./moveNote'), migrateFromV5Storage: require('./migrateFromV5Storage'), + createSnippet: require('./createSnippet'), + deleteSnippet: require('./deleteSnippet'), + updateSnippet: require('./updateSnippet'), _migrateFromV6Storage: require('./migrateFromV6Storage'), _resolveStorageData: require('./resolveStorageData'), diff --git a/browser/main/lib/dataApi/updateSnippet.js b/browser/main/lib/dataApi/updateSnippet.js new file mode 100644 index 00000000..07a3ea98 --- /dev/null +++ b/browser/main/lib/dataApi/updateSnippet.js @@ -0,0 +1,34 @@ +import fs from 'fs' +import consts from 'browser/lib/consts' + +function updateSnippet (snippet) { + return new Promise((resolve, reject) => { + let snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf-8')) + + for (let i = 0; i < snippets.length; i++) { + let currentSnippet = snippets[i] + + if (currentSnippet.id === snippet.id) { + if ( + currentSnippet.name === snippet.name && + currentSnippet.prefix === snippet.prefix && + currentSnippet.content === snippet.content + ) { + // if everything is the same then don't write to disk + resolve(snippets) + } else { + currentSnippet.name = snippet.name + currentSnippet.prefix = snippet.prefix + currentSnippet.content = snippet.content + + fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + if (err) reject(err) + resolve(snippets) + }) + } + } + } + }) +} + +module.exports = updateSnippet diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 15110f34..e72c2b46 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -1,18 +1,18 @@ import CodeMirror from 'codemirror' -import PropTypes from 'prop-types' import React from 'react' import _ from 'lodash' import fs from 'fs' -import { findStorage } from 'browser/lib/findStorage' +import path from 'path' +import consts from 'browser/lib/consts' +import dataApi from 'browser/main/lib/dataApi' + +const { remote } = require('electron') const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const buildCMRulers = (rulers, enableRulers) => enableRulers ? rulers.map(ruler => ({ column: ruler })) : [] export default class SnippetEditor extends React.Component { - constructor (props) { - super(props) - } componentDidMount () { const { rulers, enableRulers } = this.props @@ -29,33 +29,48 @@ export default class SnippetEditor extends React.Component { dragDrop: false, foldGutter: true, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseBrackets: true, + autoCloseBrackets: true }) - this.cm.setSize("100%", "100%") - let snippetId = this.props.snippetId + this.cm.setSize('100%', '100%') + this.snippet = this.props.snippet + const snippetId = this.snippet.id + this.loadSnippet(snippetId) + let changeDelay = null - const storagePath = findStorage(this.props.storageKey).path - const expandDataFile = path.join(storagePath, 'expandData.json') - if (!fs.existsSync(expandDataFile)) { - const defaultExpandData = [ - { - matches: ['lorem', 'ipsum'], - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' - }, - { match: 'h1', content: '# '}, - { match: 'h2', content: '## '}, - { match: 'h3', content: '### '}, - { match: 'h4', content: '#### '}, - { match: 'h5', content: '##### '}, - { match: 'h6', content: '###### '} - ]; - fs.writeFileSync(expandDataFile, JSON.stringify(defaultExpandData), 'utf8') - } - const expandData = JSON.parse(fs.readFileSync(expandDataFile, 'utf8')) + this.cm.on('change', () => { + this.snippet.content = this.cm.getValue() + + clearTimeout(changeDelay) + changeDelay = setTimeout(() => { + this.saveSnippet() + }, 500) + }) } - componentWillReceiveProps(newProps) { - this.cm.setValue(newProps.value) + saveSnippet () { + dataApi.updateSnippet(this.snippet).catch((err) => {throw err}) + } + + loadSnippet (snippetId) { + const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8')) + + for (let i = 0; i < snippets.length; i++) { + if (snippets[i].id === snippetId) { + this.cm.setValue(snippets[i].content) + } + } + } + + componentWillReceiveProps (newProps) { + if (this.snippet.id !== newProps.snippet.id) { + // when user changed to a new snippet on the snippetList.js + this.loadSnippet(newProps.snippet.id) + } else { + // when snippet name or prefix being changed from snippetTab.js + this.snippet.name = newProps.snippet.name + this.snippet.prefix = newProps.snippet.prefix.replace(/\s/g, '').split(/\,/).filter(val => val) + this.saveSnippet() + } } render () { @@ -65,17 +80,13 @@ export default class SnippetEditor extends React.Component { ? [fontFamily].concat(defaultEditorFontFamily) : defaultEditorFontFamily return ( -
-
+ position: 'absolute', + width: '100%', + height: '90%' + }} /> ) } } diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index 7d46cc65..30fbb099 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -1,39 +1,84 @@ -import PropTypes from 'prop-types' import React from 'react' import CSSModules from 'browser/lib/CSSModules' -import dataApi from 'browser/main/lib/dataApi' import styles from './SnippetTab.styl' -import ConfigManager from 'browser/main/lib/ConfigManager' -import SnippetEditor from './SnippetEditor'; +import fs from 'fs' +import SnippetEditor from './SnippetEditor' import i18n from 'browser/lib/i18n' +import path from 'path' +import dataApi from 'browser/main/lib/dataApi' +import consts from 'browser/lib/consts' + +const { remote } = require('electron') + +const { Menu, MenuItem } = remote class SnippetTab extends React.Component { constructor (props) { super(props) this.state = { - snippets: [ - { id: 'abcsajisdjiasd', name: 'Hello', content: 'asdddddddsaddddddd' }, - { id: 'btbjieejbiebfe', name: 'Hello 2', content: 'asdddddddsaddddddd' } - ], + snippets: [], currentSnippet: null } } - handleSnippetClick(id) { - this.setState({'currentSnippet': id}) + componentDidMount () { + this.snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8')) + + this.setState({snippets: this.snippets}) + } + + handleSnippetClick (snippet) { + let currentSnippet = Object.assign({}, snippet) + currentSnippet.prefix = currentSnippet.prefix.join(', ') + this.setState({currentSnippet}) + } + + handleSnippetContextMenu(snippet) { + let menu = new Menu() + menu.append(new MenuItem({ + label: 'Delete', + click: () => { + this.deleteSnippet(snippet.id) + } + })) + menu.popup() + } + + deleteSnippet(id) { + dataApi.deleteSnippet(this.snippets, id).then((snippets) => { + this.snippets = snippets + this.setState(this.snippets) + }).catch(err => {throw err}) + } + + createSnippet() { + dataApi.createSnippet(this.snippets).then((snippets) => { + this.snippets = snippets + this.setState(this.snippets) + // scroll to end of list when added new snippet + let snippetList = document.getElementById("snippets") + snippetList.scrollTop = snippetList.scrollHeight + }).catch(err => {throw err}) } renderSnippetList () { - let { snippets } = this.state + const { snippets } = this.state return ( - snippets.map((snippet) => ( -
this.handleSnippetClick(snippet.id)}> - {snippet.name} -
- )) +
    + { + snippets.map((snippet) => ( +
  • this.handleSnippetContextMenu(snippet)} + onClick={() => { + this.handleSnippetClick(snippet)}}> + {snippet.name} +
  • + )) + } +
) } @@ -48,14 +93,42 @@ class SnippetTab extends React.Component {
{i18n.__('Snippets')}
+
+
+ +
+
{this.renderSnippetList()}
- {this.state.currentSnippet ? -
+ {this.state.currentSnippet ?
{i18n.__('Snippet name')}
- + { + const newSnippet = Object.assign({}, this.state.currentSnippet) + newSnippet.name = e.target.value + this.setState({ currentSnippet: newSnippet }) + }} + type='text' /> +
+
+
+
{i18n.__('Snippet prefix')}
+
+ { + const newSnippet = Object.assign({}, this.state.currentSnippet) + newSnippet.prefix = e.target.value + this.setState({ currentSnippet: newSnippet }) + }} + type='text' />
@@ -71,7 +144,7 @@ class SnippetTab extends React.Component { rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} scrollPastEnd={config.editor.scrollPastEnd} - snippetId={this.state.currentSnippet} /> + snippet={this.state.currentSnippet} />
: ''} @@ -83,4 +156,4 @@ class SnippetTab extends React.Component { SnippetTab.PropTypes = { } -export default CSSModules(SnippetTab, styles) \ No newline at end of file +export default CSSModules(SnippetTab, styles) diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl index 4f32bb3f..8f122858 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.styl +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -57,6 +57,15 @@ &:disabled background-color $ui-input--disabled-backgroundColor +.group-control-button + height 30px + border none + border-top-right-radius 2px + border-bottom-right-radius 2px + colorPrimaryButton() + vertical-align middle + padding 0 20px + .group-checkBoxSection margin-bottom 15px display flex @@ -85,11 +94,9 @@ .snippet-list width 30% height calc(100% - 200px) - background #f5f5f5 position absolute .snippet-item - width 100% height 50px font-size 15px line-height 50px diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index 0a8b8cae..f3fc3751 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -89,10 +89,10 @@ class Preferences extends React.Component { ) case 'SNIPPET': return ( - ) case 'STORAGES': From 8925f7c381dadc6386b90204cb0a1c4c7cb9127e Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Fri, 20 Apr 2018 23:58:52 +0700 Subject: [PATCH 05/30] fixed eslint error --- browser/main/lib/dataApi/createSnippet.js | 8 +++----- browser/main/lib/dataApi/deleteSnippet.js | 10 ++++------ browser/main/lib/dataApi/updateSnippet.js | 5 ++--- .../modals/PreferencesModal/SnippetEditor.js | 5 +---- .../modals/PreferencesModal/SnippetTab.js | 20 +++++++++---------- 5 files changed, 19 insertions(+), 29 deletions(-) diff --git a/browser/main/lib/dataApi/createSnippet.js b/browser/main/lib/dataApi/createSnippet.js index 366a1bb1..d2847ae4 100644 --- a/browser/main/lib/dataApi/createSnippet.js +++ b/browser/main/lib/dataApi/createSnippet.js @@ -1,13 +1,11 @@ -const { remote } = require('electron') import fs from 'fs' -import path from 'path' import crypto from 'crypto' import consts from 'browser/lib/consts' -function createSnippet(snippets) { +function createSnippet (snippets) { return new Promise((resolve, reject) => { - const newSnippet = { - id: crypto.randomBytes(16).toString('hex'), + const newSnippet = { + id: crypto.randomBytes(16).toString('hex'), name: 'Unnamed snippet', prefix: [], content: '' diff --git a/browser/main/lib/dataApi/deleteSnippet.js b/browser/main/lib/dataApi/deleteSnippet.js index e988fd55..17587b5f 100644 --- a/browser/main/lib/dataApi/deleteSnippet.js +++ b/browser/main/lib/dataApi/deleteSnippet.js @@ -1,20 +1,18 @@ -const { remote } = require('electron') import fs from 'fs' -import path from 'path' import consts from 'browser/lib/consts' function deleteSnippet (snippets, snippetId) { return new Promise((resolve, reject) => { - for(let i = 0; i < snippets.length; i++) { + for (let i = 0; i < snippets.length; i++) { if (snippets[i].id === snippetId) { - snippets.splice(i, 1); + snippets.splice(i, 1) fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { if (err) reject(err) resolve(snippets) }) } } - }) + }) } -module.exports = deleteSnippet \ No newline at end of file +module.exports = deleteSnippet diff --git a/browser/main/lib/dataApi/updateSnippet.js b/browser/main/lib/dataApi/updateSnippet.js index 07a3ea98..60677bb8 100644 --- a/browser/main/lib/dataApi/updateSnippet.js +++ b/browser/main/lib/dataApi/updateSnippet.js @@ -3,10 +3,10 @@ import consts from 'browser/lib/consts' function updateSnippet (snippet) { return new Promise((resolve, reject) => { - let snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf-8')) + const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf-8')) for (let i = 0; i < snippets.length; i++) { - let currentSnippet = snippets[i] + const currentSnippet = snippets[i] if (currentSnippet.id === snippet.id) { if ( @@ -20,7 +20,6 @@ function updateSnippet (snippet) { currentSnippet.name = snippet.name currentSnippet.prefix = snippet.prefix currentSnippet.content = snippet.content - fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { if (err) reject(err) resolve(snippets) diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index e72c2b46..20b1f66a 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -2,12 +2,9 @@ import CodeMirror from 'codemirror' import React from 'react' import _ from 'lodash' import fs from 'fs' -import path from 'path' import consts from 'browser/lib/consts' import dataApi from 'browser/main/lib/dataApi' -const { remote } = require('electron') - const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const buildCMRulers = (rulers, enableRulers) => enableRulers ? rulers.map(ruler => ({ column: ruler })) : [] @@ -48,7 +45,7 @@ export default class SnippetEditor extends React.Component { } saveSnippet () { - dataApi.updateSnippet(this.snippet).catch((err) => {throw err}) + dataApi.updateSnippet(this.snippet).catch((err) => { throw err }) } loadSnippet (snippetId) { diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index 30fbb099..f72f9a4d 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -4,7 +4,6 @@ import styles from './SnippetTab.styl' import fs from 'fs' import SnippetEditor from './SnippetEditor' import i18n from 'browser/lib/i18n' -import path from 'path' import dataApi from 'browser/main/lib/dataApi' import consts from 'browser/lib/consts' @@ -29,13 +28,13 @@ class SnippetTab extends React.Component { } handleSnippetClick (snippet) { - let currentSnippet = Object.assign({}, snippet) + const currentSnippet = Object.assign({}, snippet) currentSnippet.prefix = currentSnippet.prefix.join(', ') this.setState({currentSnippet}) } - handleSnippetContextMenu(snippet) { - let menu = new Menu() + handleSnippetContextMenu (snippet) { + const menu = new Menu() menu.append(new MenuItem({ label: 'Delete', click: () => { @@ -45,21 +44,21 @@ class SnippetTab extends React.Component { menu.popup() } - deleteSnippet(id) { + deleteSnippet (id) { dataApi.deleteSnippet(this.snippets, id).then((snippets) => { this.snippets = snippets this.setState(this.snippets) - }).catch(err => {throw err}) + }).catch(err => { throw err }) } - createSnippet() { + createSnippet () { dataApi.createSnippet(this.snippets).then((snippets) => { this.snippets = snippets this.setState(this.snippets) // scroll to end of list when added new snippet - let snippetList = document.getElementById("snippets") + const snippetList = document.getElementById('snippets') snippetList.scrollTop = snippetList.scrollHeight - }).catch(err => {throw err}) + }).catch(err => { throw err }) } renderSnippetList () { @@ -72,8 +71,7 @@ class SnippetTab extends React.Component { styleName='snippet-item' key={snippet.id} onContextMenu={() => this.handleSnippetContextMenu(snippet)} - onClick={() => { - this.handleSnippetClick(snippet)}}> + onClick={() => this.handleSnippetClick(snippet)}> {snippet.name} )) From 358458a937b68023180d14f787610db75c668c07 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Sat, 21 Apr 2018 09:04:30 +0700 Subject: [PATCH 06/30] Fixed appdata path error --- browser/lib/consts.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/browser/lib/consts.js b/browser/lib/consts.js index 34afad36..2eb9bd39 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -12,7 +12,15 @@ const themes = fs.readdirSync(themePath) }) themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light') -const snippetFile = path.join(remote.app.getPath('appData'), 'Boostnote', 'snippets.json') +const snippetFile = process.env.NODE_ENV === 'production' + ? path.join(app.getPath('appData'), 'Boostnote', 'snippets.json') + : require('path').resolve(path.join(getAppData(), 'Boostnote', 'snippets.json')) + +function getAppData () { + return process.env.APPDATA || (process.platform == 'darwin' + ? process.env.HOME + 'Library/Preferences' + : '/.config') +} const consts = { FOLDER_COLORS: [ From ddcd7225987d25f864f34a1e6c876569a208d2e9 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Sat, 21 Apr 2018 09:07:49 +0700 Subject: [PATCH 07/30] fix eslint error --- browser/lib/consts.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/lib/consts.js b/browser/lib/consts.js index 2eb9bd39..197f7806 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -12,13 +12,13 @@ const themes = fs.readdirSync(themePath) }) themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light') -const snippetFile = process.env.NODE_ENV === 'production' +const snippetFile = process.env.NODE_ENV === 'production' ? path.join(app.getPath('appData'), 'Boostnote', 'snippets.json') : require('path').resolve(path.join(getAppData(), 'Boostnote', 'snippets.json')) function getAppData () { - return process.env.APPDATA || (process.platform == 'darwin' - ? process.env.HOME + 'Library/Preferences' + return process.env.APPDATA || (process.platform === 'darwin' + ? process.env.HOME + 'Library/Preferences' : '/.config') } From a7b85b123e232f4c7a6eb2057ca2852b11e25f16 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Sat, 21 Apr 2018 09:40:32 +0700 Subject: [PATCH 08/30] fixed get appdata path error --- browser/lib/consts.js | 4 ++-- browser/main/modals/PreferencesModal/SnippetTab.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/lib/consts.js b/browser/lib/consts.js index 197f7806..fa8232fb 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -14,12 +14,12 @@ themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light const snippetFile = process.env.NODE_ENV === 'production' ? path.join(app.getPath('appData'), 'Boostnote', 'snippets.json') - : require('path').resolve(path.join(getAppData(), 'Boostnote', 'snippets.json')) + : path.join(getAppData(), 'Boostnote', 'snippets.json') function getAppData () { return process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + 'Library/Preferences' - : '/.config') + : require('os').homedir() + '/.config') } const consts = { diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index f72f9a4d..9cb870e4 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -36,7 +36,7 @@ class SnippetTab extends React.Component { handleSnippetContextMenu (snippet) { const menu = new Menu() menu.append(new MenuItem({ - label: 'Delete', + label: i18n.__('Delete snippet'), click: () => { this.deleteSnippet(snippet.id) } From a7d0a4bdac6d5361091eb8221b97a52bb6d86fac Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Sun, 22 Apr 2018 09:27:47 +0700 Subject: [PATCH 09/30] clean up some redundant code, changed to path.sep, remove some default snippet --- browser/components/CodeEditor.js | 33 ++++++++++++++------------------ browser/lib/consts.js | 4 ++-- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 7ae1ee51..e9f3931b 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -96,25 +96,20 @@ export default class CodeEditor extends React.Component { componentDidMount () { const { rulers, enableRulers } = this.props - const emptyChars = /\t|\s|\r|\n/ - if (!fs.existsSync(consts.SNIPPET_FILE)) { - const defaultSnippets = [ - { - id: crypto.randomBytes(16).toString('hex'), - name: 'Dummy text', - prefix: ['lorem', 'ipsum'], - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' - }, - { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 1', prefix: ['h1'], content: '# ' }, - { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 2', prefix: ['h2'], content: '## ' }, - { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 3', prefix: ['h3'], content: '### ' }, - { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 4', prefix: ['h4'], content: '#### ' }, - { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 5', prefix: ['h5'], content: '##### ' }, - { id: crypto.randomBytes(16).toString('hex'), name: 'Heading 6', prefix: ['h6'], content: '###### ' } - ] - fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippets, null, 4), 'utf8') - } const expandSnippet = this.expandSnippet.bind(this) + + const defaultSnippet = [ + { + id: crypto.randomBytes(16).toString('hex'), + name: 'Dummy text', + prefix: ['lorem', 'ipsum'], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + } + ] + if (!fs.existsSync(consts.SNIPPET_FILE)) { + fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8') + } + this.value = this.props.value this.editor = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), @@ -149,7 +144,7 @@ export default class CodeEditor extends React.Component { cm.execCommand('insertSoftTab') } cm.execCommand('goLineEnd') - } else if (!emptyChars.test(charBeforeCursor) || 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')) if (expandSnippet(line, cursor, cm, snippets) === false) { diff --git a/browser/lib/consts.js b/browser/lib/consts.js index fa8232fb..12227d58 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -18,8 +18,8 @@ const snippetFile = process.env.NODE_ENV === 'production' function getAppData () { return process.env.APPDATA || (process.platform === 'darwin' - ? process.env.HOME + 'Library/Preferences' - : require('os').homedir() + '/.config') + ? process.env.HOME + 'Library' + path.sep + 'Preferences' + : require('os').homedir() + path.sep + '.config') } const consts = { From e88694b049e42b66de9b9202d9e10a5b0579e20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 27 Apr 2018 08:22:54 +0700 Subject: [PATCH 10/30] added fetch snippet refactored some code fixed snippet content empty on changed from list fixed snippet name not updating on list when changed --- browser/main/lib/dataApi/createSnippet.js | 25 +++++---- browser/main/lib/dataApi/deleteSnippet.js | 19 ++++--- browser/main/lib/dataApi/fetchSnippet.js | 21 ++++++++ browser/main/lib/dataApi/index.js | 1 + .../modals/PreferencesModal/SnippetEditor.js | 41 ++++++-------- .../modals/PreferencesModal/SnippetTab.js | 53 ++++++++++++------- 6 files changed, 95 insertions(+), 65 deletions(-) create mode 100644 browser/main/lib/dataApi/fetchSnippet.js diff --git a/browser/main/lib/dataApi/createSnippet.js b/browser/main/lib/dataApi/createSnippet.js index d2847ae4..0c6cd648 100644 --- a/browser/main/lib/dataApi/createSnippet.js +++ b/browser/main/lib/dataApi/createSnippet.js @@ -1,19 +1,22 @@ import fs from 'fs' import crypto from 'crypto' import consts from 'browser/lib/consts' +import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet' -function createSnippet (snippets) { +function createSnippet () { return new Promise((resolve, reject) => { - const newSnippet = { - id: crypto.randomBytes(16).toString('hex'), - name: 'Unnamed snippet', - prefix: [], - content: '' - } - snippets.push(newSnippet) - fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { - if (err) reject(err) - resolve(snippets) + fetchSnippet().then((snippets) => { + const newSnippet = { + id: crypto.randomBytes(16).toString('hex'), + name: 'Unnamed snippet', + prefix: [], + content: '' + } + snippets.push(newSnippet) + fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + if (err) reject(err) + resolve(newSnippet) + }) }) }) } diff --git a/browser/main/lib/dataApi/deleteSnippet.js b/browser/main/lib/dataApi/deleteSnippet.js index 17587b5f..d8d9c68a 100644 --- a/browser/main/lib/dataApi/deleteSnippet.js +++ b/browser/main/lib/dataApi/deleteSnippet.js @@ -1,17 +1,16 @@ import fs from 'fs' import consts from 'browser/lib/consts' +import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet' -function deleteSnippet (snippets, snippetId) { +function deleteSnippet (snippet) { return new Promise((resolve, reject) => { - for (let i = 0; i < snippets.length; i++) { - if (snippets[i].id === snippetId) { - snippets.splice(i, 1) - fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { - if (err) reject(err) - resolve(snippets) - }) - } - } + fetchSnippet().then((snippets) => { + snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id) + fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + if (err) reject(err) + resolve(snippet) + }) + }) }) } diff --git a/browser/main/lib/dataApi/fetchSnippet.js b/browser/main/lib/dataApi/fetchSnippet.js new file mode 100644 index 00000000..ff5d86d5 --- /dev/null +++ b/browser/main/lib/dataApi/fetchSnippet.js @@ -0,0 +1,21 @@ +import fs from 'fs' +import crypto from 'crypto' +import consts from 'browser/lib/consts' + +function fetchSnippet (id) { + return new Promise((resolve, reject) => { + fs.readFile(consts.SNIPPET_FILE, 'utf8', (err, data) => { + if (err) { + reject(err) + } + const snippets = JSON.parse(data) + if (id) { + const snippet = snippets.find(snippet => { return snippet.id === id }) + resolve(snippet) + } + resolve(snippets) + }) + }) +} + +module.exports = fetchSnippet \ No newline at end of file diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js index bf42e8ec..7c57e016 100644 --- a/browser/main/lib/dataApi/index.js +++ b/browser/main/lib/dataApi/index.js @@ -16,6 +16,7 @@ const dataApi = { createSnippet: require('./createSnippet'), deleteSnippet: require('./deleteSnippet'), updateSnippet: require('./updateSnippet'), + fetchSnippet: require('./fetchSnippet'), _migrateFromV6Storage: require('./migrateFromV6Storage'), _resolveStorageData: require('./resolveStorageData'), diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 20b1f66a..f0368c5d 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -12,6 +12,7 @@ const buildCMRulers = (rulers, enableRulers) => export default class SnippetEditor extends React.Component { componentDidMount () { + this.props.onRef(this) const { rulers, enableRulers } = this.props this.cm = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), @@ -29,9 +30,6 @@ export default class SnippetEditor extends React.Component { autoCloseBrackets: true }) this.cm.setSize('100%', '100%') - this.snippet = this.props.snippet - const snippetId = this.snippet.id - this.loadSnippet(snippetId) let changeDelay = null this.cm.on('change', () => { @@ -44,32 +42,25 @@ export default class SnippetEditor extends React.Component { }) } + componentWillUnmount () { + this.props.onRef(undefined) + } + + onSnippetChanged (newSnippet) { + this.snippet = newSnippet + this.cm.setValue(this.snippet.content) + } + + onSnippetNameOrPrefixChanged (newSnippet) { + this.snippet.name = newSnippet.name + this.snippet.prefix = newSnippet.prefix.toString().replace(/\s/g, '').split(',') + this.saveSnippet() + } + saveSnippet () { dataApi.updateSnippet(this.snippet).catch((err) => { throw err }) } - loadSnippet (snippetId) { - const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8')) - - for (let i = 0; i < snippets.length; i++) { - if (snippets[i].id === snippetId) { - this.cm.setValue(snippets[i].content) - } - } - } - - componentWillReceiveProps (newProps) { - if (this.snippet.id !== newProps.snippet.id) { - // when user changed to a new snippet on the snippetList.js - this.loadSnippet(newProps.snippet.id) - } else { - // when snippet name or prefix being changed from snippetTab.js - this.snippet.name = newProps.snippet.name - this.snippet.prefix = newProps.snippet.prefix.replace(/\s/g, '').split(/\,/).filter(val => val) - this.saveSnippet() - } - } - render () { const { fontSize } = this.props let fontFamily = this.props.fontFamily diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index 9cb870e4..d2bf80e2 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -19,18 +19,30 @@ class SnippetTab extends React.Component { snippets: [], currentSnippet: null } + this.changeDelay = null } componentDidMount () { - this.snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8')) - - this.setState({snippets: this.snippets}) + this.reloadSnippetList() } handleSnippetClick (snippet) { - const currentSnippet = Object.assign({}, snippet) - currentSnippet.prefix = currentSnippet.prefix.join(', ') - this.setState({currentSnippet}) + if (this.state.currentSnippet === null || this.state.currentSnippet.id !== snippet.id) { + dataApi.fetchSnippet(snippet.id).then(changedSnippet => { + // notify the snippet editor to load the content of the new snippet + this.snippetEditor.onSnippetChanged(changedSnippet) + this.setState({currentSnippet: changedSnippet}) + }) + } + } + + handleSnippetNameOrPrefixChange () { + clearTimeout(this.changeDelay) + this.changeDelay = setTimeout(() => { + // notify the snippet editor that the name or prefix of snippet has been changed + this.snippetEditor.onSnippetNameOrPrefixChanged(this.state.currentSnippet) + this.reloadSnippetList() + }, 500) } handleSnippetContextMenu (snippet) { @@ -38,23 +50,25 @@ class SnippetTab extends React.Component { menu.append(new MenuItem({ label: i18n.__('Delete snippet'), click: () => { - this.deleteSnippet(snippet.id) + this.deleteSnippet(snippet) } })) menu.popup() } - deleteSnippet (id) { - dataApi.deleteSnippet(this.snippets, id).then((snippets) => { - this.snippets = snippets - this.setState(this.snippets) + reloadSnippetList () { + dataApi.fetchSnippet().then(snippets => this.setState({snippets})) + } + + deleteSnippet (snippet) { + dataApi.deleteSnippet(snippet).then(() => { + this.reloadSnippetList() }).catch(err => { throw err }) } createSnippet () { - dataApi.createSnippet(this.snippets).then((snippets) => { - this.snippets = snippets - this.setState(this.snippets) + dataApi.createSnippet().then(() => { + this.reloadSnippetList() // scroll to end of list when added new snippet const snippetList = document.getElementById('snippets') snippetList.scrollTop = snippetList.scrollHeight @@ -100,17 +114,18 @@ class SnippetTab extends React.Component {
{this.renderSnippetList()}
- {this.state.currentSnippet ?
+
{i18n.__('Snippet name')}
{ const newSnippet = Object.assign({}, this.state.currentSnippet) newSnippet.name = e.target.value this.setState({ currentSnippet: newSnippet }) + this.handleSnippetNameOrPrefixChange() }} type='text' />
@@ -120,11 +135,12 @@ class SnippetTab extends React.Component {
{ const newSnippet = Object.assign({}, this.state.currentSnippet) newSnippet.prefix = e.target.value this.setState({ currentSnippet: newSnippet }) + this.handleSnippetNameOrPrefixChange() }} type='text' />
@@ -142,10 +158,9 @@ class SnippetTab extends React.Component { rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} scrollPastEnd={config.editor.scrollPastEnd} - snippet={this.state.currentSnippet} /> + onRef={ref => this.snippetEditor = ref} />
- : ''}
) } From 78957cf12804fca2180a843438012bfd338896d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 27 Apr 2018 08:25:23 +0700 Subject: [PATCH 11/30] fixed eslint error --- browser/main/lib/dataApi/fetchSnippet.js | 2 +- browser/main/modals/PreferencesModal/SnippetTab.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/main/lib/dataApi/fetchSnippet.js b/browser/main/lib/dataApi/fetchSnippet.js index ff5d86d5..94a7e687 100644 --- a/browser/main/lib/dataApi/fetchSnippet.js +++ b/browser/main/lib/dataApi/fetchSnippet.js @@ -18,4 +18,4 @@ function fetchSnippet (id) { }) } -module.exports = fetchSnippet \ No newline at end of file +module.exports = fetchSnippet diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index d2bf80e2..2f22895a 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -158,7 +158,7 @@ class SnippetTab extends React.Component { rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} scrollPastEnd={config.editor.scrollPastEnd} - onRef={ref => this.snippetEditor = ref} /> + onRef={ref => { this.snippetEditor = ref }} /> From 291d76674bb3f4eb7c05d0780c3be4e444145df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 27 Apr 2018 11:16:45 +0700 Subject: [PATCH 12/30] refactored snippet dataApi for easy testing and added some test. Fixed old snippet still display when deleted --- browser/main/lib/dataApi/createSnippet.js | 20 ++++---- browser/main/lib/dataApi/deleteSnippet.js | 6 +-- browser/main/lib/dataApi/fetchSnippet.js | 4 +- browser/main/lib/dataApi/updateSnippet.js | 6 +-- .../modals/PreferencesModal/SnippetTab.js | 4 ++ tests/dataApi/createSnippet-test.js | 34 +++++++++++++ tests/dataApi/deleteSnippet-test.js | 38 +++++++++++++++ tests/dataApi/updateSnippet-test.js | 48 +++++++++++++++++++ 8 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 tests/dataApi/createSnippet-test.js create mode 100644 tests/dataApi/deleteSnippet-test.js create mode 100644 tests/dataApi/updateSnippet-test.js diff --git a/browser/main/lib/dataApi/createSnippet.js b/browser/main/lib/dataApi/createSnippet.js index 0c6cd648..5d189217 100644 --- a/browser/main/lib/dataApi/createSnippet.js +++ b/browser/main/lib/dataApi/createSnippet.js @@ -3,20 +3,22 @@ import crypto from 'crypto' import consts from 'browser/lib/consts' import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet' -function createSnippet () { +function createSnippet (snippetFile) { return new Promise((resolve, reject) => { - fetchSnippet().then((snippets) => { - const newSnippet = { - id: crypto.randomBytes(16).toString('hex'), - name: 'Unnamed snippet', - prefix: [], - content: '' - } + const newSnippet = { + id: crypto.randomBytes(16).toString('hex'), + name: 'Unnamed snippet', + prefix: [], + content: '' + } + fetchSnippet(null, snippetFile).then((snippets) => { snippets.push(newSnippet) - fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { if (err) reject(err) resolve(newSnippet) }) + }).catch((err) => { + reject(err) }) }) } diff --git a/browser/main/lib/dataApi/deleteSnippet.js b/browser/main/lib/dataApi/deleteSnippet.js index d8d9c68a..0e446886 100644 --- a/browser/main/lib/dataApi/deleteSnippet.js +++ b/browser/main/lib/dataApi/deleteSnippet.js @@ -2,11 +2,11 @@ import fs from 'fs' import consts from 'browser/lib/consts' import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet' -function deleteSnippet (snippet) { +function deleteSnippet (snippet, snippetFile) { return new Promise((resolve, reject) => { - fetchSnippet().then((snippets) => { + fetchSnippet(null, snippetFile).then((snippets) => { snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id) - fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { if (err) reject(err) resolve(snippet) }) diff --git a/browser/main/lib/dataApi/fetchSnippet.js b/browser/main/lib/dataApi/fetchSnippet.js index 94a7e687..8d5a6efe 100644 --- a/browser/main/lib/dataApi/fetchSnippet.js +++ b/browser/main/lib/dataApi/fetchSnippet.js @@ -2,9 +2,9 @@ import fs from 'fs' import crypto from 'crypto' import consts from 'browser/lib/consts' -function fetchSnippet (id) { +function fetchSnippet (id, snippetFile) { return new Promise((resolve, reject) => { - fs.readFile(consts.SNIPPET_FILE, 'utf8', (err, data) => { + fs.readFile(snippetFile || consts.SNIPPET_FILE, 'utf8', (err, data) => { if (err) { reject(err) } diff --git a/browser/main/lib/dataApi/updateSnippet.js b/browser/main/lib/dataApi/updateSnippet.js index 60677bb8..f2310b8e 100644 --- a/browser/main/lib/dataApi/updateSnippet.js +++ b/browser/main/lib/dataApi/updateSnippet.js @@ -1,9 +1,9 @@ import fs from 'fs' import consts from 'browser/lib/consts' -function updateSnippet (snippet) { +function updateSnippet (snippet, snippetFile) { return new Promise((resolve, reject) => { - const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf-8')) + const snippets = JSON.parse(fs.readFileSync(snippetFile || consts.SNIPPET_FILE, 'utf-8')) for (let i = 0; i < snippets.length; i++) { const currentSnippet = snippets[i] @@ -20,7 +20,7 @@ function updateSnippet (snippet) { currentSnippet.name = snippet.name currentSnippet.prefix = snippet.prefix currentSnippet.content = snippet.content - fs.writeFile(consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { + fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { if (err) reject(err) resolve(snippets) }) diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index 2f22895a..ddbfc1b0 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -63,6 +63,10 @@ class SnippetTab extends React.Component { deleteSnippet (snippet) { dataApi.deleteSnippet(snippet).then(() => { this.reloadSnippetList() + // prevent old snippet still display when deleted + if (snippet.id === this.state.currentSnippet.id) { + this.setState({currentSnippet: null}) + } }).catch(err => { throw err }) } diff --git a/tests/dataApi/createSnippet-test.js b/tests/dataApi/createSnippet-test.js new file mode 100644 index 00000000..97e64cbb --- /dev/null +++ b/tests/dataApi/createSnippet-test.js @@ -0,0 +1,34 @@ +const test = require('ava') +const createSnippet = require('browser/main/lib/dataApi/createSnippet') +const sander = require('sander') +const os = require('os') +const path = require('path') + +const snippetFilePath = path.join(os.tmpdir(), `test${path.sep}create-snippet`) +const snippetFile = path.join(snippetFilePath, 'snippets.json') + +test.beforeEach((t) => { + sander.writeFileSync(snippetFile, '[]') +}) + +test.serial('Create a snippet', (t) => { + return Promise.resolve() + .then(function doTest () { + return Promise.all([ + createSnippet(snippetFile) + ]) + }) + .then(function assert (data) { + data = data[0] + const snippets = JSON.parse(sander.readFileSync(snippetFile)) + const snippet = snippets.find(currentSnippet => currentSnippet.id === data.id) + t.not(snippet, undefined) + t.is(snippet.name, data.name) + t.deepEqual(snippet.prefix, data.prefix) + t.is(snippet.content, data.content) + }) +}) + +test.after.always(() => { + sander.rimrafSync(snippetFilePath) +}) diff --git a/tests/dataApi/deleteSnippet-test.js b/tests/dataApi/deleteSnippet-test.js new file mode 100644 index 00000000..3b2f0e91 --- /dev/null +++ b/tests/dataApi/deleteSnippet-test.js @@ -0,0 +1,38 @@ +const test = require('ava') +const deleteSnippet = require('browser/main/lib/dataApi/deleteSnippet') +const sander = require('sander') +const os = require('os') +const path = require('path') +const crypto = require('crypto') + +const snippetFilePath = path.join(os.tmpdir(), `test${path.sep}delete-snippet`) +const snippetFile = path.join(snippetFilePath, 'snippets.json') +const newSnippet = { + id: crypto.randomBytes(16).toString('hex'), + name: 'Unnamed snippet', + prefix: [], + content: '' +} + +test.beforeEach((t) => { + sander.writeFileSync(snippetFile, JSON.stringify([newSnippet])) +}) + +test.serial('Delete a snippet', (t) => { + return Promise.resolve() + .then(function doTest () { + return Promise.all([ + deleteSnippet(newSnippet, snippetFile) + ]) + }) + .then(function assert (data) { + data = data[0] + const snippets = JSON.parse(sander.readFileSync(snippetFile)) + const snippet = snippets.find(currentSnippet => currentSnippet.id === data.id) + t.is(snippets.length, 0) + }) +}) + +test.after.always(() => { + sander.rimrafSync(snippetFilePath) +}) diff --git a/tests/dataApi/updateSnippet-test.js b/tests/dataApi/updateSnippet-test.js new file mode 100644 index 00000000..5bd252a0 --- /dev/null +++ b/tests/dataApi/updateSnippet-test.js @@ -0,0 +1,48 @@ +const test = require('ava') +const updateSnippet = require('browser/main/lib/dataApi/updateSnippet') +const sander = require('sander') +const os = require('os') +const path = require('path') +const crypto = require('crypto') + +const snippetFilePath = path.join(os.tmpdir(), `test${path.sep}update-snippet`) +const snippetFile = path.join(snippetFilePath, 'snippets.json') +const oldSnippet = { + id: crypto.randomBytes(16).toString('hex'), + name: 'Initial snippet', + prefix: [], + content: '' +} + +const newSnippet = { + id: oldSnippet.id, + name: 'new name', + prefix: ['prefix'], + content: 'new content' +} + + +test.beforeEach((t) => { + sander.writeFileSync(snippetFile, JSON.stringify([oldSnippet])) +}) + +test.serial('Update a snippet', (t) => { + return Promise.resolve() + .then(function doTest () { + return Promise.all([ + updateSnippet(newSnippet, snippetFile) + ]) + }) + .then(function assert () { + const snippets = JSON.parse(sander.readFileSync(snippetFile)) + const snippet = snippets.find(currentSnippet => currentSnippet.id === newSnippet.id) + t.not(snippet, undefined) + t.is(snippet.name, newSnippet.name) + t.deepEqual(snippet.prefix, newSnippet.prefix) + t.is(snippet.content, newSnippet.content) + }) +}) + +test.after.always(() => { + sander.rimrafSync(snippetFilePath) +}) From 2e09501c8ad3e733ea7ac2a76186f7461c356a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 27 Apr 2018 11:19:37 +0700 Subject: [PATCH 13/30] fixed eslint error --- tests/dataApi/updateSnippet-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/dataApi/updateSnippet-test.js b/tests/dataApi/updateSnippet-test.js index 5bd252a0..aa296025 100644 --- a/tests/dataApi/updateSnippet-test.js +++ b/tests/dataApi/updateSnippet-test.js @@ -21,7 +21,6 @@ const newSnippet = { content: 'new content' } - test.beforeEach((t) => { sander.writeFileSync(snippetFile, JSON.stringify([oldSnippet])) }) From 8c43f3d567405719d7eb0d0e33b1d71678a3ebf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 27 Apr 2018 11:27:51 +0700 Subject: [PATCH 14/30] removed path.sep and use path.join to concatenate path --- browser/lib/consts.js | 5 +++-- tests/dataApi/createSnippet-test.js | 2 +- tests/dataApi/deleteSnippet-test.js | 2 +- tests/dataApi/updateSnippet-test.js | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/browser/lib/consts.js b/browser/lib/consts.js index 12227d58..3d414a14 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -2,6 +2,7 @@ const path = require('path') const fs = require('sander') const { remote } = require('electron') const { app } = remote +const os = require('os') const themePath = process.env.NODE_ENV === 'production' ? path.join(app.getAppPath(), './node_modules/codemirror/theme') @@ -18,8 +19,8 @@ const snippetFile = process.env.NODE_ENV === 'production' function getAppData () { return process.env.APPDATA || (process.platform === 'darwin' - ? process.env.HOME + 'Library' + path.sep + 'Preferences' - : require('os').homedir() + path.sep + '.config') + ? path.join(process.env.HOME, 'Library', 'Preferences') + : path.join(os.homedir(), '.config')) } const consts = { diff --git a/tests/dataApi/createSnippet-test.js b/tests/dataApi/createSnippet-test.js index 97e64cbb..f06cf861 100644 --- a/tests/dataApi/createSnippet-test.js +++ b/tests/dataApi/createSnippet-test.js @@ -4,7 +4,7 @@ const sander = require('sander') const os = require('os') const path = require('path') -const snippetFilePath = path.join(os.tmpdir(), `test${path.sep}create-snippet`) +const snippetFilePath = path.join(os.tmpdir(), 'test', 'create-snippet') const snippetFile = path.join(snippetFilePath, 'snippets.json') test.beforeEach((t) => { diff --git a/tests/dataApi/deleteSnippet-test.js b/tests/dataApi/deleteSnippet-test.js index 3b2f0e91..f1d85af3 100644 --- a/tests/dataApi/deleteSnippet-test.js +++ b/tests/dataApi/deleteSnippet-test.js @@ -5,7 +5,7 @@ const os = require('os') const path = require('path') const crypto = require('crypto') -const snippetFilePath = path.join(os.tmpdir(), `test${path.sep}delete-snippet`) +const snippetFilePath = path.join(os.tmpdir(), 'test', 'delete-snippet') const snippetFile = path.join(snippetFilePath, 'snippets.json') const newSnippet = { id: crypto.randomBytes(16).toString('hex'), diff --git a/tests/dataApi/updateSnippet-test.js b/tests/dataApi/updateSnippet-test.js index aa296025..662548bd 100644 --- a/tests/dataApi/updateSnippet-test.js +++ b/tests/dataApi/updateSnippet-test.js @@ -5,7 +5,7 @@ const os = require('os') const path = require('path') const crypto = require('crypto') -const snippetFilePath = path.join(os.tmpdir(), `test${path.sep}update-snippet`) +const snippetFilePath = path.join(os.tmpdir(), 'test', 'update-snippet') const snippetFile = path.join(snippetFilePath, 'snippets.json') const oldSnippet = { id: crypto.randomBytes(16).toString('hex'), From 106f5a53ffe90d18226a682df56dcf44aaffa9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 27 Apr 2018 11:42:34 +0700 Subject: [PATCH 15/30] added import fs module that was removed by accident --- browser/components/CodeEditor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index d3addab3..301915f5 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -8,6 +8,7 @@ import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' import crypto from 'crypto' import consts from 'browser/lib/consts' +import fs from 'fs' const { ipcRenderer, remote } = require('electron') CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' From 2bc0bce1b5368cb5d91192048fc19289802cc211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Wed, 2 May 2018 09:05:29 +0700 Subject: [PATCH 16/30] removed redundant function to get appdata --- browser/lib/consts.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/browser/lib/consts.js b/browser/lib/consts.js index 3d414a14..3837ef44 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -13,15 +13,9 @@ const themes = fs.readdirSync(themePath) }) themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light') -const snippetFile = process.env.NODE_ENV === 'production' +const snippetFile = process.env.NODE_ENV !== 'test' ? path.join(app.getPath('appData'), 'Boostnote', 'snippets.json') - : path.join(getAppData(), 'Boostnote', 'snippets.json') - -function getAppData () { - return process.env.APPDATA || (process.platform === 'darwin' - ? path.join(process.env.HOME, 'Library', 'Preferences') - : path.join(os.homedir(), '.config')) -} + : '' // return nothing as we specified different path to snippets.json in test const consts = { FOLDER_COLORS: [ From f5a9d3928c30f1fa747863ef0de514ed516ee7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Wed, 2 May 2018 10:03:55 +0700 Subject: [PATCH 17/30] disabled code highlight in snippet editor --- browser/main/modals/PreferencesModal/SnippetEditor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index f0368c5d..250b0e78 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -27,7 +27,8 @@ export default class SnippetEditor extends React.Component { dragDrop: false, foldGutter: true, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseBrackets: true + autoCloseBrackets: true, + mode: 'null' }) this.cm.setSize('100%', '100%') let changeDelay = null From ce594b0b5a4e92e50ecc75e7a7384b991f721b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Thu, 17 May 2018 00:14:09 +0700 Subject: [PATCH 18/30] added note template feature --- browser/components/CodeEditor.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 301915f5..9859716d 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -197,13 +197,33 @@ export default class CodeEditor extends React.Component { expandSnippet (line, cursor, cm, snippets) { 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) { - cm.replaceRange( - snippets[i].content, - wordBeforeCursor.range.from, - wordBeforeCursor.range.to - ) + if (snippets[i].content.indexOf(templateCursorString) !== -1) { + let snippetLines = snippets[i].content.split('\n') + let cursorLineNumber = 0 + let cursorLinePosition = 0 + for (let j = 0; j < snippetLines.length; j++) { + let cursorIndex = snippetLines[j].indexOf(templateCursorString) + if (cursorIndex !== -1) { + cursorLineNumber = j + cursorLinePosition = cursorIndex + cm.replaceRange( + snippets[i].content.replace(templateCursorString, ''), + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) + cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition }) + } + } + } else { + cm.replaceRange( + snippets[i].content, + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) + } return true } } From 2b2f17525e247bb3ababd7039241d20fb22631dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Mon, 21 May 2018 18:32:41 +0700 Subject: [PATCH 19/30] cleaned up redundant variables, fixed eslint fix command, split snippetList into component --- browser/components/CodeEditor.js | 6 +- browser/components/MarkdownPreview.js | 2 +- browser/lib/consts.js | 1 - browser/main/SideNav/index.js | 4 +- .../main/lib/dataApi/attachmentManagement.js | 8 +- browser/main/lib/dataApi/fetchSnippet.js | 1 - .../modals/PreferencesModal/SnippetEditor.js | 7 +- .../modals/PreferencesModal/SnippetList.js | 87 ++++++++++++ .../modals/PreferencesModal/SnippetTab.js | 132 +++++------------- .../modals/PreferencesModal/SnippetTab.styl | 23 +++ package.json | 4 +- tests/dataApi/deleteSnippet-test.js | 1 - yarn.lock | 31 ++-- 13 files changed, 182 insertions(+), 125 deletions(-) create mode 100644 browser/main/modals/PreferencesModal/SnippetList.js diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 9859716d..492ba7b3 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -9,7 +9,7 @@ import iconv from 'iconv-lite' import crypto from 'crypto' import consts from 'browser/lib/consts' import fs from 'fs' -const { ipcRenderer, remote } = require('electron') +const { ipcRenderer } = require('electron') CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' @@ -201,11 +201,11 @@ export default class CodeEditor extends React.Component { for (let i = 0; i < snippets.length; i++) { if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) { if (snippets[i].content.indexOf(templateCursorString) !== -1) { - let snippetLines = snippets[i].content.split('\n') + const snippetLines = snippets[i].content.split('\n') let cursorLineNumber = 0 let cursorLinePosition = 0 for (let j = 0; j < snippetLines.length; j++) { - let cursorIndex = snippetLines[j].indexOf(templateCursorString) + const cursorIndex = snippetLines[j].indexOf(templateCursorString) if (cursorIndex !== -1) { cursorLineNumber = j cursorLinePosition = cursorIndex diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index aa920975..55d85bea 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -393,7 +393,7 @@ export default class MarkdownPreview extends React.Component { value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) }) } - let renderedHTML = this.markdown.render(value) + const renderedHTML = this.markdown.render(value) this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { diff --git a/browser/lib/consts.js b/browser/lib/consts.js index 3837ef44..e744e87a 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -2,7 +2,6 @@ const path = require('path') const fs = require('sander') const { remote } = require('electron') const { app } = remote -const os = require('os') const themePath = process.env.NODE_ENV === 'production' ? path.join(app.getAppPath(), './node_modules/codemirror/theme') diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index 6b53478e..0e6bb088 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -183,7 +183,7 @@ class SideNav extends React.Component { ).filter( note => activeTags.every(tag => note.tags.includes(tag)) ) - let relatedTags = new Set() + const relatedTags = new Set() relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag))) return relatedTags } @@ -222,7 +222,7 @@ class SideNav extends React.Component { handleClickNarrowToTag (tag) { const { router } = this.context const { location } = this.props - let listOfTags = this.getActiveTags(location.pathname) + const listOfTags = this.getActiveTags(location.pathname) const indexOfTag = listOfTags.indexOf(tag) if (indexOfTag > -1) { listOfTags.splice(indexOfTag, 1) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 6d4d7406..ac0986e1 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -104,8 +104,8 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { const fileType = file['type'] copyAttachment(filePath, storageKey, noteKey).then((fileName) => { - let showPreview = fileType.startsWith('image') - let imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) + const showPreview = fileType.startsWith('image') + const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) codeEditor.insertAttachmentMd(imageMd) }) } @@ -139,7 +139,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) createAttachmentDestinationFolder(targetStorage.path, noteKey) - let imageName = `${uniqueSlug()}.png` + const imageName = `${uniqueSlug()}.png` const imagePath = path.join(destinationDir, imageName) reader.onloadend = function () { @@ -147,7 +147,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem base64data += base64data.replace('+', ' ') const binaryData = new Buffer(base64data, 'base64').toString('binary') fs.writeFile(imagePath, binaryData, 'binary') - let imageMd = generateAttachmentMarkdown(imageName, imagePath, true) + const imageMd = generateAttachmentMarkdown(imageName, imagePath, true) codeEditor.insertAttachmentMd(imageMd) } reader.readAsDataURL(blob) diff --git a/browser/main/lib/dataApi/fetchSnippet.js b/browser/main/lib/dataApi/fetchSnippet.js index 8d5a6efe..456a5090 100644 --- a/browser/main/lib/dataApi/fetchSnippet.js +++ b/browser/main/lib/dataApi/fetchSnippet.js @@ -1,5 +1,4 @@ import fs from 'fs' -import crypto from 'crypto' import consts from 'browser/lib/consts' function fetchSnippet (id, snippetFile) { diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 250b0e78..010dcac8 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -1,8 +1,6 @@ import CodeMirror from 'codemirror' import React from 'react' import _ from 'lodash' -import fs from 'fs' -import consts from 'browser/lib/consts' import dataApi from 'browser/main/lib/dataApi' const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] @@ -71,10 +69,7 @@ export default class SnippetEditor extends React.Component { return (
) } diff --git a/browser/main/modals/PreferencesModal/SnippetList.js b/browser/main/modals/PreferencesModal/SnippetList.js new file mode 100644 index 00000000..3cf28cf6 --- /dev/null +++ b/browser/main/modals/PreferencesModal/SnippetList.js @@ -0,0 +1,87 @@ +import React from 'react' +import styles from './SnippetTab.styl' +import CSSModules from 'browser/lib/CSSModules' +import dataApi from 'browser/main/lib/dataApi' +import i18n from 'browser/lib/i18n' +import eventEmitter from 'browser/main/lib/eventEmitter' +const { remote } = require('electron') +const { Menu, MenuItem } = remote + +class SnippetList extends React.Component { + constructor (props) { + super(props) + this.state = { + snippets: [] + } + } + + componentDidMount () { + this.reloadSnippetList() + eventEmitter.on('snippetList:reload', this.reloadSnippetList.bind(this)) + } + + reloadSnippetList () { + dataApi.fetchSnippet().then(snippets => this.setState({snippets})) + } + + handleSnippetContextMenu (snippet) { + const menu = new Menu() + menu.append(new MenuItem({ + label: i18n.__('Delete snippet'), + click: () => { + this.deleteSnippet(snippet) + } + })) + menu.popup() + } + + deleteSnippet (snippet) { + dataApi.deleteSnippet(snippet).then(() => { + this.reloadSnippetList() + this.props.onSnippetDeleted(snippet) + }).catch(err => { throw err }) + } + + handleSnippetClick (snippet) { + this.props.onSnippetClick(snippet) + } + + createSnippet () { + dataApi.createSnippet().then(() => { + this.reloadSnippetList() + // scroll to end of list when added new snippet + const snippetList = document.getElementById('snippets') + snippetList.scrollTop = snippetList.scrollHeight + }).catch(err => { throw err }) + } + + render () { + const { snippets } = this.state + return ( +
+
+
+ +
+
+
    + { + snippets.map((snippet) => ( +
  • this.handleSnippetContextMenu(snippet)} + onClick={() => this.handleSnippetClick(snippet)}> + {snippet.name} +
  • + )) + } +
+
+ ) + } +} + +export default CSSModules(SnippetList, styles) diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index ddbfc1b0..67e9ace6 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -1,33 +1,33 @@ import React from 'react' import CSSModules from 'browser/lib/CSSModules' import styles from './SnippetTab.styl' -import fs from 'fs' import SnippetEditor from './SnippetEditor' import i18n from 'browser/lib/i18n' import dataApi from 'browser/main/lib/dataApi' -import consts from 'browser/lib/consts' - -const { remote } = require('electron') - -const { Menu, MenuItem } = remote +import SnippetList from './SnippetList' +import eventEmitter from 'browser/main/lib/eventEmitter' class SnippetTab extends React.Component { constructor (props) { super(props) - this.state = { - snippets: [], currentSnippet: null } this.changeDelay = null } - componentDidMount () { - this.reloadSnippetList() + handleSnippetNameOrPrefixChange () { + clearTimeout(this.changeDelay) + this.changeDelay = setTimeout(() => { + // notify the snippet editor that the name or prefix of snippet has been changed + this.snippetEditor.onSnippetNameOrPrefixChanged(this.state.currentSnippet) + eventEmitter.emit('snippetList:reload') + }, 500) } handleSnippetClick (snippet) { - if (this.state.currentSnippet === null || this.state.currentSnippet.id !== snippet.id) { + const { currentSnippet } = this.state + if (currentSnippet === null || currentSnippet.id !== snippet.id) { dataApi.fetchSnippet(snippet.id).then(changedSnippet => { // notify the snippet editor to load the content of the new snippet this.snippetEditor.onSnippetChanged(changedSnippet) @@ -36,70 +36,27 @@ class SnippetTab extends React.Component { } } - handleSnippetNameOrPrefixChange () { - clearTimeout(this.changeDelay) - this.changeDelay = setTimeout(() => { - // notify the snippet editor that the name or prefix of snippet has been changed - this.snippetEditor.onSnippetNameOrPrefixChanged(this.state.currentSnippet) - this.reloadSnippetList() - }, 500) + onSnippetNameOrPrefixChanged (e, type) { + const newSnippet = Object.assign({}, this.state.currentSnippet) + if (type === 'name') { + newSnippet.name = e.target.value + } else { + newSnippet.prefix = e.target.value + } + this.setState({ currentSnippet: newSnippet }) + this.handleSnippetNameOrPrefixChange() } - handleSnippetContextMenu (snippet) { - const menu = new Menu() - menu.append(new MenuItem({ - label: i18n.__('Delete snippet'), - click: () => { - this.deleteSnippet(snippet) - } - })) - menu.popup() - } - - reloadSnippetList () { - dataApi.fetchSnippet().then(snippets => this.setState({snippets})) - } - - deleteSnippet (snippet) { - dataApi.deleteSnippet(snippet).then(() => { - this.reloadSnippetList() - // prevent old snippet still display when deleted - if (snippet.id === this.state.currentSnippet.id) { - this.setState({currentSnippet: null}) - } - }).catch(err => { throw err }) - } - - createSnippet () { - dataApi.createSnippet().then(() => { - this.reloadSnippetList() - // scroll to end of list when added new snippet - const snippetList = document.getElementById('snippets') - snippetList.scrollTop = snippetList.scrollHeight - }).catch(err => { throw err }) - } - - renderSnippetList () { - const { snippets } = this.state - return ( -
    - { - snippets.map((snippet) => ( -
  • this.handleSnippetContextMenu(snippet)} - onClick={() => this.handleSnippetClick(snippet)}> - {snippet.name} -
  • - )) - } -
- ) + handleDeleteSnippet (snippet) { + // prevent old snippet still display when deleted + if (snippet.id === this.state.currentSnippet.id) { + this.setState({currentSnippet: null}) + } } render () { - const { config } = this.props + const { config, storageKey } = this.props + const { currentSnippet } = this.state let editorFontSize = parseInt(config.editor.fontSize, 10) if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 @@ -108,29 +65,17 @@ class SnippetTab extends React.Component { return (
{i18n.__('Snippets')}
-
-
-
- -
-
- {this.renderSnippetList()} -
-
+ +
{i18n.__('Snippet name')}
{ - const newSnippet = Object.assign({}, this.state.currentSnippet) - newSnippet.name = e.target.value - this.setState({ currentSnippet: newSnippet }) - this.handleSnippetNameOrPrefixChange() - }} + value={currentSnippet ? currentSnippet.name : ''} + onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'name') }} type='text' />
@@ -139,19 +84,14 @@ class SnippetTab extends React.Component {
{ - const newSnippet = Object.assign({}, this.state.currentSnippet) - newSnippet.prefix = e.target.value - this.setState({ currentSnippet: newSnippet }) - this.handleSnippetNameOrPrefixChange() - }} + value={currentSnippet ? currentSnippet.prefix : ''} + onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'prefix') }} type='text' />
{ .then(function assert (data) { data = data[0] const snippets = JSON.parse(sander.readFileSync(snippetFile)) - const snippet = snippets.find(currentSnippet => currentSnippet.id === data.id) t.is(snippets.length, 0) }) }) diff --git a/yarn.lock b/yarn.lock index a0e4d96f..a8817ebd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2550,6 +2550,12 @@ doctrine@^2.0.0: esutils "^2.0.2" isarray "^1.0.0" +doctrine@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + dependencies: + esutils "^2.0.2" + dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -2963,13 +2969,14 @@ eslint-plugin-promise@~3.4.0: version "3.4.2" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.4.2.tgz#1be2793eafe2d18b5b123b8136c269f804fe7122" -eslint-plugin-react@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.2.0.tgz#25c77a4ec307e3eebb248ea3350960e372ab6406" +eslint-plugin-react@^7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.8.2.tgz#e95c9c47fece55d2303d1a67c9d01b930b88a51d" dependencies: - doctrine "^2.0.0" + doctrine "^2.0.2" has "^1.0.1" - jsx-ast-utils "^2.0.0" + jsx-ast-utils "^2.0.1" + prop-types "^15.6.0" eslint-plugin-react@~6.7.1: version "6.7.1" @@ -5330,9 +5337,9 @@ jsx-ast-utils@^1.3.3: version "1.4.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" -jsx-ast-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.0.tgz#ec06a3d60cf307e5e119dac7bad81e89f096f0f8" +jsx-ast-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" dependencies: array-includes "^3.0.3" @@ -6962,6 +6969,14 @@ prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8: loose-envify "^1.3.1" object-assign "^4.1.1" +prop-types@^15.6.0: + version "15.6.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + prop-types@~15.5.7: version "15.5.10" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" From 713615e28bc0ca922557401eb84580c64ac03f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Mon, 21 May 2018 18:37:17 +0700 Subject: [PATCH 20/30] applied style for snippetEditor --- browser/main/modals/PreferencesModal/SnippetEditor.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 010dcac8..6874f55a 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -1,13 +1,15 @@ import CodeMirror from 'codemirror' import React from 'react' import _ from 'lodash' +import styles from './SnippetTab.styl' +import CSSModules from 'react-css-modules' import dataApi from 'browser/main/lib/dataApi' const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const buildCMRulers = (rulers, enableRulers) => enableRulers ? rulers.map(ruler => ({ column: ruler })) : [] -export default class SnippetEditor extends React.Component { +class SnippetEditor extends React.Component { componentDidMount () { this.props.onRef(this) @@ -84,3 +86,5 @@ SnippetEditor.defaultProps = { indentSize: 4, indentType: 'space' } + +export default CSSModules(SnippetEditor, styles) From 680c2a2904c6f34351bcaaffcfa37d678ed5c313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 25 May 2018 18:39:11 +0700 Subject: [PATCH 21/30] changed cssmodule & applied style for solarized dark theme --- .../modals/PreferencesModal/SnippetEditor.js | 2 +- .../main/modals/PreferencesModal/SnippetTab.styl | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 6874f55a..f0e93dec 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -2,7 +2,7 @@ import CodeMirror from 'codemirror' import React from 'react' import _ from 'lodash' import styles from './SnippetTab.styl' -import CSSModules from 'react-css-modules' +import CSSModules from 'browser/lib/CSSModules' import dataApi from 'browser/main/lib/dataApi' const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl index 76a9b0f5..cf8430a1 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.styl +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -1,6 +1,6 @@ @import('./Tab') @import('./ConfigTab') - + .root padding 15px white-space pre @@ -143,4 +143,16 @@ body[data-theme="dark"] &:hover background darken(#2E3235, 5) .snippet-detail - color white \ No newline at end of file + color white + +body[data-theme="solarized-dark"] + .snippets + background: #083e4c + .snippet-item + color white + &::after + background rgba(255, 255, 255 0.1) + &:hover + background #0a4d5e + .snippet-detail + color white From 4caee1e1035e8bf5d0338257f8a1f6e798d173ea Mon Sep 17 00:00:00 2001 From: bimlas Date: Tue, 22 May 2018 13:30:33 +0200 Subject: [PATCH 22/30] Update to the latest Electron version (1.8.7) It inculdes security fixes, bug fixes and improvementts. See: https://electronjs.org/releases Reason for upgrading from 1.7.X to 1.8.X: "We can not reasonably back port every bug fix and every feature to older versions of Electron. 1.8.x is now a stable release line and if you require this fix you should update to that stream." https://github.com/electron/electron/issues/9860#issuecomment-365517773 --- package.json | 4 ++-- yarn.lock | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index d0368702..3d0a1686 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\"" }, "config": { - "electron-version": "1.7.11" + "electron-version": "1.8.7" }, "repository": { "type": "git", @@ -118,7 +118,7 @@ "css-loader": "^0.19.0", "devtron": "^1.1.0", "dom-storage": "^2.0.2", - "electron": "1.7.11", + "electron": "1.8.7", "electron-packager": "^6.0.0", "eslint": "^3.13.1", "eslint-config-standard": "^6.2.1", diff --git a/yarn.lock b/yarn.lock index fcf87c4a..8cb988c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,7 +1,6 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 - "@ava/babel-plugin-throws-helper@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-2.0.0.tgz#2fc1fe3c211a71071a4eca7b8f7af5842cd1ae7c" @@ -79,9 +78,9 @@ fs-plus "2.x" optimist "~0.4.0" -"@types/node@^7.0.18": - version "7.0.65" - resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.65.tgz#c160979ff66c4842adc76cc181a11b5e8722d13d" +"@types/node@^8.0.24": + version "8.10.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3" abab@^1.0.3, abab@^1.0.4: version "1.0.4" @@ -2577,11 +2576,11 @@ electron-winstaller@^2.2.0: lodash.template "^4.2.2" temp "^0.8.3" -electron@1.7.11: - version "1.7.11" - resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.11.tgz#993b6aa79e0e79a7cfcc369f4c813fbd9a0b08d9" +electron@1.8.7: + version "1.8.7" + resolved "https://registry.yarnpkg.com/electron/-/electron-1.8.7.tgz#373c1dc4589d7ab4acd49aff8db4a1c0a6c3bcc1" dependencies: - "@types/node" "^7.0.18" + "@types/node" "^8.0.24" electron-download "^3.0.1" extract-zip "^1.0.3" From 10500c3c1cf2459d3b8b047f6539cdfeafa899ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Mon, 28 May 2018 15:15:09 +0700 Subject: [PATCH 23/30] changed all colors to variables & styled for monokai theme --- .../modals/PreferencesModal/SnippetTab.styl | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl index cf8430a1..118c56ed 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.styl +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -133,26 +133,48 @@ width 100% height 90% +body[data-theme="default"], body[data-theme="white"] + .snippets + background $ui-backgroundColor + .snippet-item + color black + &::after + background $ui-borderColor + &:hover + background darken($ui-backgroundColor, 5) + body[data-theme="dark"] .snippets - background: #2E3235 + background $ui-dark-backgroundColor .snippet-item color white &::after - background rgba(255, 255, 255 0.1) + background $ui-dark-borderColor &:hover - background darken(#2E3235, 5) + background darken($ui-dark-backgroundColor, 5) .snippet-detail color white body[data-theme="solarized-dark"] .snippets - background: #083e4c + background $ui-solarized-dark-backgroundColor .snippet-item color white &::after - background rgba(255, 255, 255 0.1) + background $ui-solarized-dark-borderColor &:hover - background #0a4d5e + background darken($ui-solarized-dark-backgroundColor, 5) + .snippet-detail + color white + +body[data-theme="monokai"] + .snippets + background $ui-monokai-backgroundColor + .snippet-item + color White + &::after + background $ui-monokai-borderColor + &:hover + background darken($ui-monokai-backgroundColor, 5) .snippet-detail color white From ea768f982eae519f87c0f678c140159b0fa48daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Tue, 29 May 2018 00:37:59 +0700 Subject: [PATCH 24/30] Added custom markdown css --- browser/components/MarkdownEditor.js | 2 ++ browser/components/MarkdownPreview.js | 20 +++++++++------ browser/components/MarkdownSplitEditor.js | 2 ++ browser/main/lib/ConfigManager.js | 2 ++ browser/main/modals/PreferencesModal/UiTab.js | 25 ++++++++++++++++++- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 2bd5d951..2a1016cb 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -296,6 +296,8 @@ class MarkdownEditor extends React.Component { showCopyNotification={config.ui.showCopyNotification} storagePath={storage.path} noteKey={noteKey} + customCSS={config.preview.customCSS} + allowCustomCSS={config.preview.allowCustomCSS} />
) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 69340ae5..7e4a3853 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -32,7 +32,7 @@ const CSS_FILES = [ `${appPath}/node_modules/codemirror/lib/codemirror.css` ] -function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) { +function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) { return ` @font-face { font-family: 'Lato'; @@ -62,7 +62,9 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scro url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'), url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype'); } +${allowCustomCSS ? customCSS : ''} ${markdownStyle} + body { font-family: '${fontFamily.join("','")}'; font-size: ${fontSize}px; @@ -209,9 +211,9 @@ export default class MarkdownPreview extends React.Component { handleSaveAsHtml () { this.exportAsDocument('html', (noteContent, exportTasks) => { - const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams() + const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams() - const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) + 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] @@ -347,14 +349,16 @@ export default class MarkdownPreview extends React.Component { prevProps.lineNumber !== this.props.lineNumber || prevProps.showCopyNotification !== this.props.showCopyNotification || prevProps.theme !== this.props.theme || - prevProps.scrollPastEnd !== this.props.scrollPastEnd) { + prevProps.scrollPastEnd !== this.props.scrollPastEnd || + prevProps.allowCustomCSS !== this.props.allowCustomCSS || + prevProps.customCSS !== this.props.customCSS) { this.applyStyle() this.rewriteIframe() } } getStyleParams () { - const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme } = 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) @@ -363,14 +367,14 @@ export default class MarkdownPreview extends React.Component { ? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily) : defaultCodeBlockFontFamily - return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} + return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} } applyStyle () { - const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = 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) + this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) } GetCodeThemeLink (theme) { diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 2bee5c24..7016d0f4 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -141,6 +141,8 @@ class MarkdownSplitEditor extends React.Component { showCopyNotification={config.ui.showCopyNotification} storagePath={storage.path} noteKey={noteKey} + customCSS={config.preview.customCSS} + allowCustomCSS={config.preview.allowCustomCSS} />
) diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 228692d6..e23ba5fc 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -59,6 +59,8 @@ export const DEFAULT_CONFIG = { scrollPastEnd: false, smartQuotes: true, breaks: true, + allowCustomCSS: true, + customCSS: 'h1 { color: red }', sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE' }, blog: { diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index e059d72d..173b8465 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -28,6 +28,8 @@ class UiTab extends React.Component { componentDidMount () { CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript') + CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css') + this.customCSSCM.getCodeMirror().setSize(null, '300px') this.handleSettingDone = () => { this.setState({UiAlert: { type: 'success', @@ -98,7 +100,9 @@ class UiTab extends React.Component { scrollPastEnd: this.refs.previewScrollPastEnd.checked, smartQuotes: this.refs.previewSmartQuotes.checked, breaks: this.refs.previewBreaks.checked, - sanitize: this.refs.previewSanitize.value + sanitize: this.refs.previewSanitize.value, + allowCustomCSS: this.refs.previewAllowCustomCSS.checked, + customCSS: this.customCSSCM.getCodeMirror().getValue() } } @@ -159,6 +163,7 @@ class UiTab extends React.Component { 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 enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none' + const customCSS = config.preview.customCSS return (
@@ -569,6 +574,24 @@ class UiTab extends React.Component { />
+
+ +
+
+
+ {i18n.__('Custom CSS')} +
+
+ this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} /> +
+
- this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} /> +
+ this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} /> +
From 72fbefa300ac9d669d5135b503f7707fcbc61f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sun, 3 Jun 2018 08:51:53 +0700 Subject: [PATCH 27/30] moved custom markdown checkbox & fixed codemirror size --- browser/main/modals/PreferencesModal/UiTab.js | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 3a4c7142..e14f2279 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -29,7 +29,7 @@ class UiTab extends React.Component { componentDidMount () { CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript') CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css') - this.customCSSCM.getCodeMirror().setSize(null, '250px') + this.customCSSCM.getCodeMirror().setSize('400px', '400px') this.handleSettingDone = () => { this.setState({UiAlert: { type: 'success', @@ -574,24 +574,18 @@ class UiTab extends React.Component { />
-
-