Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 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
98 changes: 96 additions & 2 deletions browser/components/CodeEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
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 } = require('electron')

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,73 @@ 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)
const templateCursorString = ':{}'
for (let i = 0; i < snippets.length; i++) {
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
for (let j = 0; j < snippetLines.length; j++) {
const 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
}
}

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
2 changes: 1 addition & 1 deletion browser/components/MarkdownPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
7 changes: 6 additions & 1 deletion browser/lib/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const themes = fs.readdirSync(themePath)
})
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')

const snippetFile = process.env.NODE_ENV !== 'test'
? path.join(app.getPath('appData'), 'Boostnote', 'snippets.json')
: '' // return nothing as we specified different path to snippets.json in test

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

module.exports = consts
4 changes: 2 additions & 2 deletions browser/main/SideNav/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions browser/main/lib/dataApi/attachmentManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
Expand Down Expand Up @@ -139,15 +139,15 @@ 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 () {
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
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)
Expand Down
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
20 changes: 20 additions & 0 deletions browser/main/lib/dataApi/fetchSnippet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import fs from 'fs'
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
Loading