Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2e9b478
added text expansion support
ZeroX-DG Apr 18, 2018
50d2f90
added snippet config in setting
ZeroX-DG Apr 19, 2018
d3b3e45
improved style for snippet list
ZeroX-DG Apr 19, 2018
ff2e399
added delete snippet, update snippet, create snippet and save on snip…
ZeroX-DG Apr 20, 2018
8925f7c
fixed eslint error
ZeroX-DG Apr 20, 2018
358458a
Fixed appdata path error
ZeroX-DG Apr 21, 2018
ddcd722
fix eslint error
ZeroX-DG Apr 21, 2018
a7b85b1
fixed get appdata path error
ZeroX-DG Apr 21, 2018
a7d0a4b
clean up some redundant code, changed to path.sep, remove some defaul…
ZeroX-DG Apr 22, 2018
5e7bdf7
Merge branch 'master' into text-expansion-support
Rokt33r Apr 26, 2018
e88694b
added fetch snippet
ZeroX-DG Apr 27, 2018
78957cf
fixed eslint error
ZeroX-DG Apr 27, 2018
291d766
refactored snippet dataApi for easy testing and added some test. Fixe…
ZeroX-DG Apr 27, 2018
a2592e4
Merge branch 'text-expansion-support' of https://github.com/ZeroX-DG/…
ZeroX-DG Apr 27, 2018
2e09501
fixed eslint error
ZeroX-DG Apr 27, 2018
8c43f3d
removed path.sep and use path.join to concatenate path
ZeroX-DG Apr 27, 2018
106f5a5
added import fs module that was removed by accident
ZeroX-DG Apr 27, 2018
2bc0bce
removed redundant function to get appdata
ZeroX-DG May 2, 2018
f5a9d39
disabled code highlight in snippet editor
ZeroX-DG May 2, 2018
ce594b0
added note template feature
ZeroX-DG May 16, 2018
2b2f175
cleaned up redundant variables, fixed eslint fix command, split snipp…
ZeroX-DG May 21, 2018
713615e
applied style for snippetEditor
ZeroX-DG May 21, 2018
680c2a2
changed cssmodule & applied style for solarized dark theme
ZeroX-DG May 25, 2018
c2c5081
resolved conflict
ZeroX-DG May 25, 2018
10500c3
changed all colors to variables & styled for monokai theme
ZeroX-DG May 28, 2018
172ea82
fixed yarn.lock conflict
ZeroX-DG May 28, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 77 additions & 3 deletions browser/components/CodeEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
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'
import fs from 'fs'
const { ipcRenderer, remote } = require('electron')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote is never used, please remove it.


CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'

Expand Down Expand Up @@ -92,8 +94,21 @@ export default class CodeEditor extends React.Component {

componentDidMount () {
const { rulers, enableRulers } = this.props
this.value = this.props.value
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),
value: this.props.value,
Expand All @@ -114,6 +129,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')
Expand All @@ -125,6 +142,16 @@ export default class CodeEditor extends React.Component {
cm.execCommand('insertSoftTab')
}
cm.execCommand('goLineEnd')
} 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) {
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
}
} else {
if (tabs) {
cm.execCommand('insertTab')
Expand Down Expand Up @@ -168,6 +195,53 @@ export default class CodeEditor extends React.Component {
CodeMirror.Vim.map('ZZ', ':q', 'normal')
}

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) {
let wordBeforeCursor = ''
const 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) {
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 snippet trigger 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()
}
Expand Down
14 changes: 13 additions & 1 deletion browser/lib/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const path = require('path')
const fs = require('sander')
const { remote } = require('electron')
const { app } = remote
const os = require('os')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove os, because it is never used.


const themePath = process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './node_modules/codemirror/theme')
Expand All @@ -12,6 +13,16 @@ const themes = fs.readdirSync(themePath)
})
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')
: 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'))
}

const consts = {
FOLDER_COLORS: [
'#E10051',
Expand All @@ -31,7 +42,8 @@ const consts = {
'Dodger Blue',
'Violet Eggplant'
],
THEMES: ['default'].concat(themes)
THEMES: ['default'].concat(themes),
SNIPPET_FILE: snippetFile
}

module.exports = consts
26 changes: 26 additions & 0 deletions browser/main/lib/dataApi/createSnippet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import fs from 'fs'
import crypto from 'crypto'
import consts from 'browser/lib/consts'
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'

function createSnippet (snippetFile) {
return new Promise((resolve, reject) => {
const newSnippet = {
id: crypto.randomBytes(16).toString('hex'),
name: 'Unnamed snippet',
prefix: [],
content: ''
}
fetchSnippet(null, snippetFile).then((snippets) => {
snippets.push(newSnippet)
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err)
resolve(newSnippet)
})
}).catch((err) => {
reject(err)
})
})
}

module.exports = createSnippet
17 changes: 17 additions & 0 deletions browser/main/lib/dataApi/deleteSnippet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import fs from 'fs'
import consts from 'browser/lib/consts'
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'

function deleteSnippet (snippet, snippetFile) {
return new Promise((resolve, reject) => {
fetchSnippet(null, snippetFile).then((snippets) => {
snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id)
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err)
resolve(snippet)
})
})
})
}

module.exports = deleteSnippet
21 changes: 21 additions & 0 deletions browser/main/lib/dataApi/fetchSnippet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import fs from 'fs'
import crypto from 'crypto'
import consts from 'browser/lib/consts'

function fetchSnippet (id, snippetFile) {
return new Promise((resolve, reject) => {
fs.readFile(snippetFile || 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
4 changes: 4 additions & 0 deletions browser/main/lib/dataApi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const dataApi = {
deleteNote: require('./deleteNote'),
moveNote: require('./moveNote'),
migrateFromV5Storage: require('./migrateFromV5Storage'),
createSnippet: require('./createSnippet'),
deleteSnippet: require('./deleteSnippet'),
updateSnippet: require('./updateSnippet'),
fetchSnippet: require('./fetchSnippet'),

_migrateFromV6Storage: require('./migrateFromV6Storage'),
_resolveStorageData: require('./resolveStorageData'),
Expand Down
33 changes: 33 additions & 0 deletions browser/main/lib/dataApi/updateSnippet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import fs from 'fs'
import consts from 'browser/lib/consts'

function updateSnippet (snippet, snippetFile) {
return new Promise((resolve, reject) => {
const snippets = JSON.parse(fs.readFileSync(snippetFile || consts.SNIPPET_FILE, 'utf-8'))

for (let i = 0; i < snippets.length; i++) {
const 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(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err)
resolve(snippets)
})
}
}
}
})
}

module.exports = updateSnippet
90 changes: 90 additions & 0 deletions browser/main/modals/PreferencesModal/SnippetEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import CodeMirror from 'codemirror'
import React from 'react'
import _ from 'lodash'
import fs from 'fs'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove fs because fs is never used.

import consts from 'browser/lib/consts'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove consts because consts is never used.

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 {

componentDidMount () {
this.props.onRef(this)
const { rulers, enableRulers } = this.props
this.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
})
this.cm.setSize('100%', '100%')
let changeDelay = null

this.cm.on('change', () => {
this.snippet.content = this.cm.getValue()

clearTimeout(changeDelay)
changeDelay = setTimeout(() => {
this.saveSnippet()
}, 500)
})
}

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 })
}

render () {
const { fontSize } = this.props
let fontFamily = this.props.fontFamily
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily
return (
<div styleName='SnippetEditor' ref='root' tabIndex='-1' style={{
fontFamily: fontFamily.join(', '),
fontSize: fontSize,
position: 'absolute',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please write style of position, width, height to other stylus file.

width: '100%',
height: '90%'
}} />
)
}
}

SnippetEditor.defaultProps = {
readOnly: false,
theme: 'xcode',
keyMap: 'sublime',
fontSize: 14,
fontFamily: 'Monaco, Consolas',
indentSize: 4,
indentType: 'space'
}
Loading