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) => {