Skip to content

Commit 2cfe8de

Browse files
committed
Merge branch 'master' into precommit-command
2 parents e52bcf3 + a6eddb5 commit 2cfe8de

File tree

139 files changed

+223146
-563
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+223146
-563
lines changed

.vscode/launch.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
8+
{
9+
"type": "node",
10+
"request": "launch",
11+
"name": "BoostNote Main",
12+
"protocol": "inspector",
13+
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
14+
"runtimeArgs": [
15+
"--remote-debugging-port=9223",
16+
"--hot",
17+
"${workspaceFolder}/index.js"
18+
],
19+
"windows": {
20+
"runtimeExecutable": "${workspaceFolder}/node_modeules/.bin/electron.cmd"
21+
}
22+
},
23+
{
24+
"type": "chrome",
25+
"request": "attach",
26+
"name": "BoostNote Renderer",
27+
"port": 9223,
28+
"webRoot": "${workspaceFolder}",
29+
"sourceMapPathOverrides": {
30+
"webpack:///./~/*": "${webRoot}/node_modules/*",
31+
"webpack:///*": "${webRoot}/*"
32+
}
33+
}
34+
],
35+
"compounds": [
36+
{
37+
"name": "BostNote All",
38+
"configurations": ["BoostNote Main", "BoostNote Renderer"]
39+
}
40+
]
41+
}

.vscode/tasks.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "2.0.0",
5+
"tasks": [
6+
{
7+
"label": "Build Boostnote",
8+
"group": "build",
9+
"type": "npm",
10+
"script": "watch",
11+
"isBackground": true,
12+
"presentation": {
13+
"reveal": "always",
14+
},
15+
"problemMatcher": {
16+
"pattern":[
17+
{
18+
"regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$",
19+
"file": 1,
20+
"location": 2,
21+
"message": 3
22+
}
23+
]
24+
}
25+
}
26+
]
27+
}

ISSUE_TEMPLATE.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
# Current behavior
22

33
<!--
4-
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
4+
Let us know what is currently happening.
55
6-
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
6+
Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.
7+
8+
If your issue is regarding Boostnote mobile, please open an issue in the Boostnote Mobile repo 👉 https://github.com/BoostIO/boostnote-mobile.
79
-->
810

911
# Expected behavior
1012

13+
<!--
14+
Let us know what you think should happen!
15+
-->
16+
1117
# Steps to reproduce
1218

19+
<!--
20+
Please be thorough, issues we can reproduce are easier to fix!
21+
-->
22+
1323
1.
1424
2.
1525
3.

PULL_REQUEST_TEMPLATE.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!--
2+
Before submitting this PR, please make sure that:
3+
- You have read and understand the contributing.md
4+
- You have checked docs/code_style.md for information on code style
5+
-->
6+
## Description
7+
<!--
8+
Tell us what your PR does.
9+
Please attach a screenshot/ video/gif image describing your PR if possible.
10+
-->
11+
12+
## Issue fixed
13+
<!--
14+
Please list out all issue fixed with this PR here.
15+
-->
16+
17+
<!--
18+
Please make sure you fill in these checkboxes,
19+
your PR will be reviewed faster if we know exactly what it does.
20+
21+
Change :white_circle: to :radio_button: in all the options that apply
22+
-->
23+
## Type of changes
24+
25+
- :white_circle: Bug fix (Change that fixed an issue)
26+
- :white_circle: Breaking change (Change that can cause existing functionality to change)
27+
- :white_circle: Improvement (Change that improves the code. Maybe performance or development improvement)
28+
- :white_circle: Feature (Change that adds new functionality)
29+
- :white_circle: Documentation change (Change that modifies documentation. Maybe typo fixes)
30+
31+
## Checklist:
32+
33+
- :white_circle: My code follows [the project code style](docs/code_style.md)
34+
- :white_circle: I have written test for my code and it has been tested
35+
- :white_circle: All existing tests have been passed
36+
- :white_circle: I have attached a screenshot/video to visualize my change if possible

browser/components/CodeEditor.js

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
1111
import iconv from 'iconv-lite'
1212
import crypto from 'crypto'
1313
import consts from 'browser/lib/consts'
14+
import styles from '../components/CodeEditor.styl'
1415
import fs from 'fs'
15-
const { ipcRenderer } = require('electron')
16+
const { ipcRenderer, remote } = require('electron')
1617
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
18+
const spellcheck = require('browser/lib/spellcheck')
19+
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
20+
import TurndownService from 'turndown'
21+
import { gfm } from 'turndown-plugin-gfm'
1722

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

@@ -28,7 +33,7 @@ export default class CodeEditor extends React.Component {
2833
leading: false,
2934
trailing: true
3035
})
31-
this.changeHandler = e => this.handleChange(e)
36+
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
3237
this.focusHandler = () => {
3338
ipcRenderer.send('editor:focused', true)
3439
}
@@ -57,9 +62,18 @@ export default class CodeEditor extends React.Component {
5762
}
5863
this.searchHandler = (e, msg) => this.handleSearch(msg)
5964
this.searchState = null
65+
this.scrollToLineHandeler = this.scrollToLine.bind(this)
6066

6167
this.formatTable = () => this.handleFormatTable()
68+
this.contextMenuHandler = function (editor, event) {
69+
const menu = buildEditorContextMenu(editor, event)
70+
if (menu != null) {
71+
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
72+
}
73+
}
6274
this.editorActivityHandler = () => this.handleEditorActivity()
75+
76+
this.turndownService = new TurndownService()
6377
}
6478

6579
handleSearch (msg) {
@@ -125,6 +139,7 @@ export default class CodeEditor extends React.Component {
125139
componentDidMount () {
126140
const { rulers, enableRulers } = this.props
127141
const expandSnippet = this.expandSnippet.bind(this)
142+
eventEmitter.on('line:jump', this.scrollToLineHandeler)
128143

129144
const defaultSnippet = [
130145
{
@@ -226,6 +241,7 @@ export default class CodeEditor extends React.Component {
226241
this.editor.on('blur', this.blurHandler)
227242
this.editor.on('change', this.changeHandler)
228243
this.editor.on('paste', this.pasteHandler)
244+
this.editor.on('contextmenu', this.contextMenuHandler)
229245
eventEmitter.on('top:search', this.searchHandler)
230246

231247
eventEmitter.emit('code:init')
@@ -242,6 +258,10 @@ export default class CodeEditor extends React.Component {
242258

243259
this.textEditorInterface = new TextEditorInterface(this.editor)
244260
this.tableEditor = new TableEditor(this.textEditorInterface)
261+
if (this.props.spellCheck) {
262+
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
263+
}
264+
245265
eventEmitter.on('code:format-table', this.formatTable)
246266

247267
this.tableEditorOptions = options({
@@ -311,22 +331,28 @@ export default class CodeEditor extends React.Component {
311331
const snippetLines = snippets[i].content.split('\n')
312332
let cursorLineNumber = 0
313333
let cursorLinePosition = 0
334+
335+
let cursorIndex
314336
for (let j = 0; j < snippetLines.length; j++) {
315-
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
337+
cursorIndex = snippetLines[j].indexOf(templateCursorString)
338+
316339
if (cursorIndex !== -1) {
317340
cursorLineNumber = j
318341
cursorLinePosition = cursorIndex
319-
cm.replaceRange(
320-
snippets[i].content.replace(templateCursorString, ''),
321-
wordBeforeCursor.range.from,
322-
wordBeforeCursor.range.to
323-
)
324-
cm.setCursor({
325-
line: cursor.line + cursorLineNumber,
326-
ch: cursorLinePosition
327-
})
342+
343+
break
328344
}
329345
}
346+
347+
cm.replaceRange(
348+
snippets[i].content.replace(templateCursorString, ''),
349+
wordBeforeCursor.range.from,
350+
wordBeforeCursor.range.to
351+
)
352+
cm.setCursor({
353+
line: cursor.line + cursorLineNumber,
354+
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
355+
})
330356
} else {
331357
cm.replaceRange(
332358
snippets[i].content,
@@ -383,9 +409,11 @@ export default class CodeEditor extends React.Component {
383409
this.editor.off('paste', this.pasteHandler)
384410
eventEmitter.off('top:search', this.searchHandler)
385411
this.editor.off('scroll', this.scrollHandler)
412+
this.editor.off('contextmenu', this.contextMenuHandler)
386413
const editorTheme = document.getElementById('editorTheme')
387414
editorTheme.removeEventListener('load', this.loadStyleHandler)
388415

416+
spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
389417
eventEmitter.off('code:format-table', this.formatTable)
390418
}
391419

@@ -453,6 +481,16 @@ export default class CodeEditor extends React.Component {
453481
needRefresh = true
454482
}
455483

484+
if (prevProps.spellCheck !== this.props.spellCheck) {
485+
if (this.props.spellCheck === false) {
486+
spellcheck.setLanguage(this.editor, spellcheck.SPELLCHECK_DISABLED)
487+
let elem = document.getElementById('editor-bottom-panel')
488+
elem.parentNode.removeChild(elem)
489+
} else {
490+
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
491+
}
492+
}
493+
456494
if (needRefresh) {
457495
this.editor.refresh()
458496
}
@@ -466,16 +504,23 @@ export default class CodeEditor extends React.Component {
466504
CodeMirror.autoLoadMode(this.editor, syntax.mode)
467505
}
468506

469-
handleChange (e) {
470-
this.value = this.editor.getValue()
507+
handleChange (editor, changeObject) {
508+
spellcheck.handleChange(editor, changeObject)
509+
this.value = editor.getValue()
471510
if (this.props.onChange) {
472-
this.props.onChange(e)
511+
this.props.onChange(editor)
473512
}
474513
}
475514

476515
moveCursorTo (row, col) {}
477516

478-
scrollToLine (num) {}
517+
scrollToLine (event, num) {
518+
const cursor = {
519+
line: num,
520+
ch: 1
521+
}
522+
this.editor.setCursor(cursor)
523+
}
479524

480525
focus () {
481526
this.editor.focus()
@@ -538,7 +583,11 @@ export default class CodeEditor extends React.Component {
538583
)
539584
return prevChar === '](' && nextChar === ')'
540585
}
541-
if (dataTransferItem.type.match('image')) {
586+
587+
const pastedHtml = clipboardData.getData('text/html')
588+
if (pastedHtml !== '') {
589+
this.handlePasteHtml(e, editor, pastedHtml)
590+
} else if (dataTransferItem.type.match('image')) {
542591
attachmentManagement.handlePastImageEvent(
543592
this,
544593
storageKey,
@@ -608,6 +657,12 @@ export default class CodeEditor extends React.Component {
608657
})
609658
}
610659

660+
handlePasteHtml (e, editor, pastedHtml) {
661+
e.preventDefault()
662+
const markdown = this.turndownService.turndown(pastedHtml)
663+
editor.replaceSelection(markdown)
664+
}
665+
611666
mapNormalResponse (response, pastedTxt) {
612667
return this.decodeResponse(response).then(body => {
613668
return new Promise((resolve, reject) => {
@@ -690,6 +745,25 @@ export default class CodeEditor extends React.Component {
690745
/>
691746
)
692747
}
748+
749+
createSpellCheckPanel () {
750+
const panel = document.createElement('div')
751+
panel.className = 'panel bottom'
752+
panel.id = 'editor-bottom-panel'
753+
const dropdown = document.createElement('select')
754+
dropdown.title = 'Spellcheck'
755+
dropdown.className = styles['spellcheck-select']
756+
dropdown.addEventListener('change', (e) => spellcheck.setLanguage(this.editor, dropdown.value))
757+
const options = spellcheck.getAvailableDictionaries()
758+
for (const op of options) {
759+
const option = document.createElement('option')
760+
option.value = op.value
761+
option.innerHTML = op.label
762+
dropdown.appendChild(option)
763+
}
764+
panel.appendChild(dropdown)
765+
return panel
766+
}
693767
}
694768

695769
CodeEditor.propTypes = {
@@ -700,7 +774,8 @@ CodeEditor.propTypes = {
700774
className: PropTypes.string,
701775
onBlur: PropTypes.func,
702776
onChange: PropTypes.func,
703-
readOnly: PropTypes.bool
777+
readOnly: PropTypes.bool,
778+
spellCheck: PropTypes.bool
704779
}
705780

706781
CodeEditor.defaultProps = {
@@ -710,5 +785,6 @@ CodeEditor.defaultProps = {
710785
fontSize: 14,
711786
fontFamily: 'Monaco, Consolas',
712787
indentSize: 4,
713-
indentType: 'space'
788+
indentType: 'space',
789+
spellCheck: false
714790
}

browser/components/CodeEditor.styl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.codeEditor-typo
2+
text-decoration underline wavy red
3+
4+
.spellcheck-select
5+
border: none
6+
text-decoration underline wavy red

0 commit comments

Comments
 (0)