From 45436f65afd66f607338bf042594ed15a147f5fa Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Fri, 30 Nov 2018 18:03:23 +0000 Subject: [PATCH 01/14] Issue #2469 almost done, missing refactor to reduce calls on code mirror --- browser/components/CodeEditor.js | 37 +++++++++++++++++++++-- browser/lib/newNote.js | 3 +- browser/main/Detail/SnippetNoteDetail.js | 6 +++- browser/main/lib/dataApi/createNote.js | 3 +- browser/main/lib/dataApi/updateNote.js | 6 ++-- browser/main/lib/dataApi/updateSnippet.js | 4 ++- 6 files changed, 51 insertions(+), 8 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 130cc86e..2ceca341 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -34,6 +34,7 @@ export default class CodeEditor extends React.Component { trailing: true }) this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject) + this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject) this.focusHandler = () => { ipcRenderer.send('editor:focused', true) } @@ -208,12 +209,13 @@ export default class CodeEditor extends React.Component { } return CodeMirror.Pass } - }) + }) this.value = this.props.value this.editor = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), value: this.props.value, + linesHighlighted:this.props.linesHighlighted, lineNumbers: this.props.displayLineNumbers, lineWrapping: true, theme: this.props.theme, @@ -240,6 +242,7 @@ export default class CodeEditor extends React.Component { this.editor.on('focus', this.focusHandler) this.editor.on('blur', this.blurHandler) this.editor.on('change', this.changeHandler) + this.editor.on("gutterClick",this.highlightHandler) this.editor.on('paste', this.pasteHandler) this.editor.on('contextmenu', this.contextMenuHandler) eventEmitter.on('top:search', this.searchHandler) @@ -316,9 +319,11 @@ export default class CodeEditor extends React.Component { this.setState({ clientWidth: this.refs.root.clientWidth }) + + this.initialHighlighting() } - expandSnippet (line, cursor, cm, snippets) { + expandSnippet (line, cursor, cm, snippets) { const wordBeforeCursor = this.getWordBeforeCursor( line, cursor.line, @@ -512,6 +517,19 @@ export default class CodeEditor extends React.Component { } } + handleHighlight (editor, changeObject) { + if(!editor.options.linesHighlighted.includes(changeObject)){ + editor.options.linesHighlighted.push(changeObject) + editor.addLineClass(changeObject,'text',"CodeMirror-activeline-background") + }else{ + editor.options.linesHighlighted.splice(editor.options.linesHighlighted.indexOf(changeObject),1) + editor.removeLineClass(changeObject,'text',"CodeMirror-activeline-background") + } + if (this.props.onChange) { + this.props.onChange(editor) + } + } + moveCursorTo (row, col) {} scrollToLine (event, num) { @@ -536,6 +554,7 @@ export default class CodeEditor extends React.Component { this.value = this.props.value this.editor.setValue(this.props.value) this.editor.clearHistory() + this.restartHighlighting() this.editor.on('change', this.changeHandler) this.editor.refresh() } @@ -546,6 +565,11 @@ export default class CodeEditor extends React.Component { this.editor.setCursor(cursor) } + restartHighlighting(){ + this.editor.options.linesHighlighted = this.props.linesHighlighted + this.initialHighlighting(); + } + handleDropImage (dropEvent) { dropEvent.preventDefault() const { storageKey, noteKey } = this.props @@ -683,6 +707,15 @@ export default class CodeEditor extends React.Component { }) } + initialHighlighting(){ + var count = this.editor.lineCount(), i; + for (i = 0; i < count; i++) { + if(this.editor.options.linesHighlighted.includes(i)){ + this.editor.addLineClass(i,'text',"CodeMirror-activeline-background") + } + } + } + mapImageResponse (response, pastedTxt) { return new Promise((resolve, reject) => { try { diff --git a/browser/lib/newNote.js b/browser/lib/newNote.js index 0b64d0e1..9b701a02 100644 --- a/browser/lib/newNote.js +++ b/browser/lib/newNote.js @@ -56,7 +56,8 @@ export function createSnippetNote (storage, folder, dispatch, location, params, { name: '', mode: config.editor.snippetDefaultLanguage || 'text', - content: '' + content: '', + linesHighlighted:[], } ] }) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 4a38ffe5..b8d0fe5b 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -410,6 +410,8 @@ class SnippetNoteDetail extends React.Component { return (e) => { const snippets = this.state.note.snippets.slice() snippets[index].content = this.refs['code-' + index].value + snippets[index].linesHighlighted=e.options.linesHighlighted + this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})})) this.setState(state => ({ note: state.note @@ -602,7 +604,8 @@ class SnippetNoteDetail extends React.Component { note.snippets = note.snippets.concat([{ name: '', mode: config.editor.snippetDefaultLanguage || 'text', - content: '' + content: '', + linesHighlighted:[] }]) const snippetIndex = note.snippets.length - 1 @@ -705,6 +708,7 @@ class SnippetNoteDetail extends React.Component { : { if (err) reject(err) resolve(snippets) From 1668ef6bb4aba35258488a9fa9984e00952d90e8 Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Fri, 30 Nov 2018 18:32:14 +0000 Subject: [PATCH 02/14] Small changes --- browser/components/CodeEditor.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 2ceca341..adcb6bf0 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -518,11 +518,12 @@ export default class CodeEditor extends React.Component { } handleHighlight (editor, changeObject) { - if(!editor.options.linesHighlighted.includes(changeObject)){ - editor.options.linesHighlighted.push(changeObject) + let lines =editor.options.linesHighlighted + if(!lines.includes(changeObject)){ + lines.push(changeObject) editor.addLineClass(changeObject,'text',"CodeMirror-activeline-background") }else{ - editor.options.linesHighlighted.splice(editor.options.linesHighlighted.indexOf(changeObject),1) + lines.splice(lines.indexOf(changeObject),1) editor.removeLineClass(changeObject,'text',"CodeMirror-activeline-background") } if (this.props.onChange) { @@ -567,7 +568,7 @@ export default class CodeEditor extends React.Component { restartHighlighting(){ this.editor.options.linesHighlighted = this.props.linesHighlighted - this.initialHighlighting(); + this.initialHighlighting() } handleDropImage (dropEvent) { @@ -708,7 +709,7 @@ export default class CodeEditor extends React.Component { } initialHighlighting(){ - var count = this.editor.lineCount(), i; + var count = this.editor.lineCount(), i for (i = 0; i < count; i++) { if(this.editor.options.linesHighlighted.includes(i)){ this.editor.addLineClass(i,'text',"CodeMirror-activeline-background") From a9442a019f9263dc3fddc038dda055fdbb781707 Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Fri, 30 Nov 2018 19:20:40 +0000 Subject: [PATCH 03/14] Changes to pass tests and lint code --- browser/components/CodeEditor.js | 34 +++++++++++------------ browser/lib/newNote.js | 2 +- browser/main/Detail/SnippetNoteDetail.js | 4 +-- browser/main/lib/dataApi/createNote.js | 2 +- browser/main/lib/dataApi/updateNote.js | 4 +-- browser/main/lib/dataApi/updateSnippet.js | 4 +-- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index adcb6bf0..8af89ded 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -209,13 +209,13 @@ export default class CodeEditor extends React.Component { } return CodeMirror.Pass } - }) + }) this.value = this.props.value this.editor = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), value: this.props.value, - linesHighlighted:this.props.linesHighlighted, + linesHighlighted: this.props.linesHighlighted, lineNumbers: this.props.displayLineNumbers, lineWrapping: true, theme: this.props.theme, @@ -242,7 +242,7 @@ export default class CodeEditor extends React.Component { this.editor.on('focus', this.focusHandler) this.editor.on('blur', this.blurHandler) this.editor.on('change', this.changeHandler) - this.editor.on("gutterClick",this.highlightHandler) + this.editor.on('gutterClick', this.highlightHandler) this.editor.on('paste', this.pasteHandler) this.editor.on('contextmenu', this.contextMenuHandler) eventEmitter.on('top:search', this.searchHandler) @@ -320,10 +320,10 @@ export default class CodeEditor extends React.Component { clientWidth: this.refs.root.clientWidth }) - this.initialHighlighting() + this.initialHighlighting() } - expandSnippet (line, cursor, cm, snippets) { + expandSnippet (line, cursor, cm, snippets) { const wordBeforeCursor = this.getWordBeforeCursor( line, cursor.line, @@ -518,13 +518,13 @@ export default class CodeEditor extends React.Component { } handleHighlight (editor, changeObject) { - let lines =editor.options.linesHighlighted - if(!lines.includes(changeObject)){ + const lines = editor.options.linesHighlighted + if (!lines.includes(changeObject)) { lines.push(changeObject) - editor.addLineClass(changeObject,'text',"CodeMirror-activeline-background") - }else{ - lines.splice(lines.indexOf(changeObject),1) - editor.removeLineClass(changeObject,'text',"CodeMirror-activeline-background") + editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background') + } else { + lines.splice(lines.indexOf(changeObject), 1) + editor.removeLineClass(changeObject, 'text', 'CodeMirror-activeline-background') } if (this.props.onChange) { this.props.onChange(editor) @@ -566,7 +566,7 @@ export default class CodeEditor extends React.Component { this.editor.setCursor(cursor) } - restartHighlighting(){ + restartHighlighting () { this.editor.options.linesHighlighted = this.props.linesHighlighted this.initialHighlighting() } @@ -708,11 +708,11 @@ export default class CodeEditor extends React.Component { }) } - initialHighlighting(){ - var count = this.editor.lineCount(), i - for (i = 0; i < count; i++) { - if(this.editor.options.linesHighlighted.includes(i)){ - this.editor.addLineClass(i,'text',"CodeMirror-activeline-background") + initialHighlighting () { + const count = this.editor.lineCount() + for (let i = 0; i < count; i++) { + if (this.editor.options.linesHighlighted.includes(i)) { + this.editor.addLineClass(i, 'text', 'CodeMirror-activeline-background') } } } diff --git a/browser/lib/newNote.js b/browser/lib/newNote.js index 9b701a02..2e407207 100644 --- a/browser/lib/newNote.js +++ b/browser/lib/newNote.js @@ -57,7 +57,7 @@ export function createSnippetNote (storage, folder, dispatch, location, params, name: '', mode: config.editor.snippetDefaultLanguage || 'text', content: '', - linesHighlighted:[], + linesHighlighted: [] } ] }) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index b8d0fe5b..3d1027ea 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -410,7 +410,7 @@ class SnippetNoteDetail extends React.Component { return (e) => { const snippets = this.state.note.snippets.slice() snippets[index].content = this.refs['code-' + index].value - snippets[index].linesHighlighted=e.options.linesHighlighted + snippets[index].linesHighlighted = e.options.linesHighlighted this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})})) this.setState(state => ({ @@ -605,7 +605,7 @@ class SnippetNoteDetail extends React.Component { name: '', mode: config.editor.snippetDefaultLanguage || 'text', content: '', - linesHighlighted:[] + linesHighlighted: [] }]) const snippetIndex = note.snippets.length - 1 diff --git a/browser/main/lib/dataApi/createNote.js b/browser/main/lib/dataApi/createNote.js index 60bf1f42..0b6e6fbe 100644 --- a/browser/main/lib/dataApi/createNote.js +++ b/browser/main/lib/dataApi/createNote.js @@ -24,7 +24,7 @@ function validateInput (input) { name: '', mode: 'text', content: '', - linesHighlighted:[], + linesHighlighted: [] }] } break diff --git a/browser/main/lib/dataApi/updateNote.js b/browser/main/lib/dataApi/updateNote.js index 1c8bd882..b7a28613 100644 --- a/browser/main/lib/dataApi/updateNote.js +++ b/browser/main/lib/dataApi/updateNote.js @@ -52,7 +52,7 @@ function validateInput (input) { name: '', mode: 'text', content: '', - linesHighlighted:[], + linesHighlighted: [] }] } else { validatedInput.snippets = input.snippets @@ -98,7 +98,7 @@ function updateNote (storageKey, noteKey, input) { name: '', mode: 'text', content: '', - linesHighlighted:[], + linesHighlighted: [] }] } : { diff --git a/browser/main/lib/dataApi/updateSnippet.js b/browser/main/lib/dataApi/updateSnippet.js index 7a296130..f132d83f 100644 --- a/browser/main/lib/dataApi/updateSnippet.js +++ b/browser/main/lib/dataApi/updateSnippet.js @@ -12,8 +12,8 @@ function updateSnippet (snippet, snippetFile) { if ( currentSnippet.name === snippet.name && currentSnippet.prefix === snippet.prefix && - currentSnippet.content === snippet.content && - currentSnippet.linesHighlighted===snippet.linesHighlighted + currentSnippet.content === snippet.content && + currentSnippet.linesHighlighted === snippet.linesHighlighted ) { // if everything is the same then don't write to disk resolve(snippets) From 1a0e15e04ceac7e793f2791302484b5929610965 Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Wed, 5 Dec 2018 14:12:29 +0000 Subject: [PATCH 04/14] Fixed bug when switching to markdown notes --- browser/components/CodeEditor.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 8af89ded..d889a0a7 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -519,6 +519,11 @@ export default class CodeEditor extends React.Component { handleHighlight (editor, changeObject) { const lines = editor.options.linesHighlighted + + if (lines == null) { + return + } + if (!lines.includes(changeObject)) { lines.push(changeObject) editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background') @@ -566,11 +571,6 @@ export default class CodeEditor extends React.Component { this.editor.setCursor(cursor) } - restartHighlighting () { - this.editor.options.linesHighlighted = this.props.linesHighlighted - this.initialHighlighting() - } - handleDropImage (dropEvent) { dropEvent.preventDefault() const { storageKey, noteKey } = this.props @@ -709,6 +709,10 @@ export default class CodeEditor extends React.Component { } initialHighlighting () { + if (this.editor.options.linesHighlighted == null) { + return + } + const count = this.editor.lineCount() for (let i = 0; i < count; i++) { if (this.editor.options.linesHighlighted.includes(i)) { @@ -717,6 +721,11 @@ export default class CodeEditor extends React.Component { } } + restartHighlighting () { + this.editor.options.linesHighlighted = this.props.linesHighlighted + this.initialHighlighting() + } + mapImageResponse (response, pastedTxt) { return new Promise((resolve, reject) => { try { From b5604ba0a9db506aa2537f4963d312ef8cad4899 Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Thu, 6 Dec 2018 13:23:23 +0000 Subject: [PATCH 05/14] Changes to optimize initial highlighting Now iterates over highlighted lines instead of all lines of the snippet --- browser/components/CodeEditor.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index d889a0a7..4027b262 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -713,11 +713,16 @@ export default class CodeEditor extends React.Component { return } - const count = this.editor.lineCount() - for (let i = 0; i < count; i++) { - if (this.editor.options.linesHighlighted.includes(i)) { - this.editor.addLineClass(i, 'text', 'CodeMirror-activeline-background') + const totalHighlightedLines = this.editor.options.linesHighlighted.length + const totalAvailableLines = this.editor.lineCount() + + for (let i = 0; i < totalHighlightedLines; i++) { + const lineNumber = this.editor.options.linesHighlighted[i] + if (lineNumber > totalAvailableLines) { + // make sure that we skip the invalid lines althrough this case should not be happened. + continue } + this.editor.addLineClass(lineNumber, 'text', 'CodeMirror-activeline-background') } } From 191295b6de2090a5cd8bdddf1825a04cadba6d05 Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Fri, 7 Dec 2018 23:41:51 +0000 Subject: [PATCH 06/14] Added array of linesHighlighted to default snippet This makes the default snippet also handle highlight on the lines, because this snippet is created in the code without the normal snippet constructor --- browser/main/Main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/browser/main/Main.js b/browser/main/Main.js index c426f2bd..d13bde0d 100644 --- a/browser/main/Main.js +++ b/browser/main/Main.js @@ -96,12 +96,14 @@ class Main extends React.Component { { name: 'example.html', mode: 'html', - content: "\n\n

Enjoy Boostnote!

\n\n" + content: "\n\n

Enjoy Boostnote!

\n\n", + linesHighlighted:[] }, { name: 'example.js', mode: 'javascript', - content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)" + content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)", + linesHighlighted:[] } ] }) From f483f8fdf04f64b591ef547caf7eb5f4759e262d Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Sat, 8 Dec 2018 12:07:28 +0000 Subject: [PATCH 07/14] Fix so that linesHighlighted defaults to [] when does't find it --- browser/main/lib/dataApi/updateNote.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/browser/main/lib/dataApi/updateNote.js b/browser/main/lib/dataApi/updateNote.js index b7a28613..c89bad54 100644 --- a/browser/main/lib/dataApi/updateNote.js +++ b/browser/main/lib/dataApi/updateNote.js @@ -118,6 +118,8 @@ function updateNote (storageKey, noteKey, input) { if (noteData.type === 'SNIPPET_NOTE') { noteData.title + if(noteData.linesHighlighted = null) + noteData.linesHighlighted = []; } Object.assign(noteData, input, { From 492294e11dc7522427542230af6e8a107d39d6ba Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Sat, 8 Dec 2018 12:19:42 +0000 Subject: [PATCH 08/14] Reset of linesHighlighted --- browser/main/lib/dataApi/createSnippet.js | 3 ++- browser/main/lib/dataApi/migrateFromV5Storage.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/browser/main/lib/dataApi/createSnippet.js b/browser/main/lib/dataApi/createSnippet.js index 5d189217..1e7435ad 100644 --- a/browser/main/lib/dataApi/createSnippet.js +++ b/browser/main/lib/dataApi/createSnippet.js @@ -9,7 +9,8 @@ function createSnippet (snippetFile) { id: crypto.randomBytes(16).toString('hex'), name: 'Unnamed snippet', prefix: [], - content: '' + content: '', + linesHighlighted: [], } fetchSnippet(null, snippetFile).then((snippets) => { snippets.push(newSnippet) diff --git a/browser/main/lib/dataApi/migrateFromV5Storage.js b/browser/main/lib/dataApi/migrateFromV5Storage.js index b11e66e9..0a20d648 100644 --- a/browser/main/lib/dataApi/migrateFromV5Storage.js +++ b/browser/main/lib/dataApi/migrateFromV5Storage.js @@ -87,7 +87,8 @@ function importAll (storage, data) { snippets: [{ name: article.mode, mode: article.mode, - content: article.content + content: article.content, + linesHighlighted: article.linesHighlighted, }] } notes.push(newNote) From 62609a29184e84000a0e97cc39190ab21dbea01c Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Sat, 8 Dec 2018 17:18:40 +0000 Subject: [PATCH 09/14] Fixed last nonfunctional changes made earlier Now iterates in the SnippetNoteDetail constructor the snippets and if linesHighlighted is not defined assigns an empty array --- browser/main/Detail/SnippetNoteDetail.js | 6 ++++++ browser/main/Main.js | 4 ++-- browser/main/lib/dataApi/createSnippet.js | 2 +- browser/main/lib/dataApi/migrateFromV5Storage.js | 2 +- browser/main/lib/dataApi/updateNote.js | 2 -- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 3d1027ea..7f10cb0b 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -52,6 +52,12 @@ class SnippetNoteDetail extends React.Component { }) } + this.state.note.snippets.forEach(function (s) { + if (s.linesHighlighted === undefined) { + s.linesHighlighted = [] + } + }) + this.scrollToNextTabThreshold = 0.7 this.generateToc = () => this.handleGenerateToc() } diff --git a/browser/main/Main.js b/browser/main/Main.js index d13bde0d..a9268eba 100644 --- a/browser/main/Main.js +++ b/browser/main/Main.js @@ -97,13 +97,13 @@ class Main extends React.Component { name: 'example.html', mode: 'html', content: "\n\n

Enjoy Boostnote!

\n\n", - linesHighlighted:[] + linesHighlighted: [] }, { name: 'example.js', mode: 'javascript', content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)", - linesHighlighted:[] + linesHighlighted: [] } ] }) diff --git a/browser/main/lib/dataApi/createSnippet.js b/browser/main/lib/dataApi/createSnippet.js index 1e7435ad..2e585c9f 100644 --- a/browser/main/lib/dataApi/createSnippet.js +++ b/browser/main/lib/dataApi/createSnippet.js @@ -10,7 +10,7 @@ function createSnippet (snippetFile) { name: 'Unnamed snippet', prefix: [], content: '', - linesHighlighted: [], + linesHighlighted: [] } fetchSnippet(null, snippetFile).then((snippets) => { snippets.push(newSnippet) diff --git a/browser/main/lib/dataApi/migrateFromV5Storage.js b/browser/main/lib/dataApi/migrateFromV5Storage.js index 0a20d648..eb3a9fb8 100644 --- a/browser/main/lib/dataApi/migrateFromV5Storage.js +++ b/browser/main/lib/dataApi/migrateFromV5Storage.js @@ -88,7 +88,7 @@ function importAll (storage, data) { name: article.mode, mode: article.mode, content: article.content, - linesHighlighted: article.linesHighlighted, + linesHighlighted: article.linesHighlighted }] } notes.push(newNote) diff --git a/browser/main/lib/dataApi/updateNote.js b/browser/main/lib/dataApi/updateNote.js index c89bad54..b7a28613 100644 --- a/browser/main/lib/dataApi/updateNote.js +++ b/browser/main/lib/dataApi/updateNote.js @@ -118,8 +118,6 @@ function updateNote (storageKey, noteKey, input) { if (noteData.type === 'SNIPPET_NOTE') { noteData.title - if(noteData.linesHighlighted = null) - noteData.linesHighlighted = []; } Object.assign(noteData, input, { From 4fb11b68e4d47425e7eb7c46477f341360eb025c Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Thu, 13 Dec 2018 13:13:01 +0000 Subject: [PATCH 10/14] Markdown functionality --- browser/components/MarkdownEditor.js | 5 +++-- browser/components/MarkdownSplitEditor.js | 9 +++++---- browser/lib/newNote.js | 3 ++- browser/main/Detail/MarkdownNoteDetail.js | 11 ++++++++++- browser/main/Detail/SnippetNoteDetail.js | 1 + browser/main/NoteList/index.js | 3 ++- browser/main/lib/dataApi/createNote.js | 1 + browser/main/lib/dataApi/migrateFromV5Storage.js | 3 ++- browser/main/lib/dataApi/updateNote.js | 6 +++++- 9 files changed, 31 insertions(+), 11 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 20ce9451..009a6b76 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -232,7 +232,7 @@ class MarkdownEditor extends React.Component { } render () { - const {className, value, config, storageKey, noteKey} = this.props + const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props let editorFontSize = parseInt(config.editor.fontSize, 10) if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 @@ -275,7 +275,8 @@ class MarkdownEditor extends React.Component { noteKey={noteKey} fetchUrlTitle={config.editor.fetchUrlTitle} enableTableEditor={config.editor.enableTableEditor} - onChange={(e) => this.handleChange(e)} + linesHighlighted={linesHighlighted} + onChange={(e) => this.handleChange(e).bind(this)} onBlur={(e) => this.handleBlur(e)} spellCheck={config.editor.spellcheck} /> diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 0ab52a56..0a6c812a 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -24,9 +24,9 @@ class MarkdownSplitEditor extends React.Component { this.refs.code.setValue(value) } - handleOnChange () { + handleOnChange (e) { this.value = this.refs.code.value - this.props.onChange() + this.props.onChange(e) } handleScroll (e) { @@ -136,7 +136,7 @@ class MarkdownSplitEditor extends React.Component { } render () { - const {config, value, storageKey, noteKey} = this.props + const {config, value, storageKey, noteKey, linesHighlighted} = this.props const storage = findStorage(storageKey) let editorFontSize = parseInt(config.editor.fontSize, 10) if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 @@ -169,7 +169,8 @@ class MarkdownSplitEditor extends React.Component { enableTableEditor={config.editor.enableTableEditor} storageKey={storageKey} noteKey={noteKey} - onChange={this.handleOnChange.bind(this)} + linesHighlighted={linesHighlighted} + onChange={(e) => this.handleOnChange(e).bind(this)} onScroll={this.handleScroll.bind(this)} spellCheck={config.editor.spellcheck} /> diff --git a/browser/lib/newNote.js b/browser/lib/newNote.js index 2e407207..9511f847 100644 --- a/browser/lib/newNote.js +++ b/browser/lib/newNote.js @@ -18,7 +18,8 @@ export function createMarkdownNote (storage, folder, dispatch, location, params, folder: folder, title: '', tags, - content: '' + content: '', + linesHighlighted: [] }) .then(note => { const noteHash = note.key diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index b4e7a5b3..34dae369 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -39,12 +39,19 @@ class MarkdownNoteDetail extends React.Component { isMovingNote: false, note: Object.assign({ title: '', - content: '' + content: '', + linesHighlighted: [] }, props.note), isLockButtonShown: false, isLocked: false, editorType: props.config.editor.type } + + let lines = this.state.note.linesHighlighted + if (lines === undefined) { + lines = [] + } + this.dispatchTimer = null this.toggleLockButton = this.handleToggleLockButton.bind(this) @@ -361,6 +368,7 @@ class MarkdownNoteDetail extends React.Component { value={note.content} storageKey={note.storage} noteKey={note.key} + linesHighlighted={note.linesHighlighted} onChange={this.handleUpdateContent.bind(this)} ignorePreviewPointerEvents={ignorePreviewPointerEvents} /> @@ -371,6 +379,7 @@ class MarkdownNoteDetail extends React.Component { value={note.content} storageKey={note.storage} noteKey={note.key} + linesHighlighted={note.linesHighlighted} onChange={this.handleUpdateContent.bind(this)} ignorePreviewPointerEvents={ignorePreviewPointerEvents} /> diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 7f10cb0b..252bafc8 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -706,6 +706,7 @@ class SnippetNoteDetail extends React.Component { ? this.handleCodeChange(index)(e)} ref={'code-' + index} ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents} diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 08d7d2e2..9a152e14 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -707,7 +707,8 @@ class NoteList extends React.Component { type: firstNote.type, folder: folder.key, title: firstNote.title + ' ' + i18n.__('copy'), - content: firstNote.content + content: firstNote.content, + linesHighlighted: firstNote.linesHighlighted }) .then((note) => { attachmentManagement.cloneAttachments(firstNote, note) diff --git a/browser/main/lib/dataApi/createNote.js b/browser/main/lib/dataApi/createNote.js index 0b6e6fbe..5bfa2457 100644 --- a/browser/main/lib/dataApi/createNote.js +++ b/browser/main/lib/dataApi/createNote.js @@ -16,6 +16,7 @@ function validateInput (input) { switch (input.type) { case 'MARKDOWN_NOTE': if (!_.isString(input.content)) input.content = '' + if (!_.isArray(input.linesHighlighted)) input.linesHighlighted = [] break case 'SNIPPET_NOTE': if (!_.isString(input.description)) input.description = '' diff --git a/browser/main/lib/dataApi/migrateFromV5Storage.js b/browser/main/lib/dataApi/migrateFromV5Storage.js index eb3a9fb8..78d78746 100644 --- a/browser/main/lib/dataApi/migrateFromV5Storage.js +++ b/browser/main/lib/dataApi/migrateFromV5Storage.js @@ -69,7 +69,8 @@ function importAll (storage, data) { isStarred: false, title: article.title, content: '# ' + article.title + '\n\n' + article.content, - key: noteKey + key: noteKey, + linesHighlighted: article.linesHighlighted } notes.push(newNote) } else { diff --git a/browser/main/lib/dataApi/updateNote.js b/browser/main/lib/dataApi/updateNote.js index b7a28613..ce9fabcf 100644 --- a/browser/main/lib/dataApi/updateNote.js +++ b/browser/main/lib/dataApi/updateNote.js @@ -39,6 +39,9 @@ function validateInput (input) { if (input.content != null) { if (!_.isString(input.content)) validatedInput.content = '' else validatedInput.content = input.content + + if (!_.isArray(input.linesHighlighted)) validatedInput.linesHighlighted = [] + else validatedInput.linesHighlighted = input.linesHighlighted } return validatedInput case 'SNIPPET_NOTE': @@ -103,7 +106,8 @@ function updateNote (storageKey, noteKey, input) { } : { type: 'MARKDOWN_NOTE', - content: '' + content: '', + linesHighlighted: [] } noteData.title = '' if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.') From f2a0f59b0804289363b35b70939fb0e1d2ad0074 Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Thu, 13 Dec 2018 13:27:20 +0000 Subject: [PATCH 11/14] Fixed error on call to bind. --- browser/components/MarkdownEditor.js | 2 +- browser/components/MarkdownSplitEditor.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 009a6b76..6c91ea1d 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -276,7 +276,7 @@ class MarkdownEditor extends React.Component { fetchUrlTitle={config.editor.fetchUrlTitle} enableTableEditor={config.editor.enableTableEditor} linesHighlighted={linesHighlighted} - onChange={(e) => this.handleChange(e).bind(this)} + onChange={(e) => this.handleChange.bind(this)(e)} onBlur={(e) => this.handleBlur(e)} spellCheck={config.editor.spellcheck} /> diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 0a6c812a..c1b9bd84 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -170,7 +170,7 @@ class MarkdownSplitEditor extends React.Component { storageKey={storageKey} noteKey={noteKey} linesHighlighted={linesHighlighted} - onChange={(e) => this.handleOnChange(e).bind(this)} + onChange={(e) => this.handleOnChange.bind(this)(e)} onScroll={this.handleScroll.bind(this)} spellCheck={config.editor.spellcheck} /> From ac1ce6043b7fcfd31b63590f4a899e2509eba3e4 Mon Sep 17 00:00:00 2001 From: Duarte-Frazao Date: Thu, 13 Dec 2018 20:19:02 +0000 Subject: [PATCH 12/14] Fixed legacy default Markdown/Snippet notes bug Fixed a bug where a note or snippet is created before the pull request and you ran Boostnote for the first time after the pr and you firstly created a note or markdown and only then returned to the old default notes and you couldn't highlight --- browser/components/CodeEditor.js | 4 ---- browser/main/Detail/MarkdownNoteDetail.js | 7 +------ browser/main/Detail/SnippetNoteDetail.js | 13 +++---------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 4027b262..2f4f1c8a 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -520,10 +520,6 @@ export default class CodeEditor extends React.Component { handleHighlight (editor, changeObject) { const lines = editor.options.linesHighlighted - if (lines == null) { - return - } - if (!lines.includes(changeObject)) { lines.push(changeObject) editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background') diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 34dae369..bc6cd499 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -47,11 +47,6 @@ class MarkdownNoteDetail extends React.Component { editorType: props.config.editor.type } - let lines = this.state.note.linesHighlighted - if (lines === undefined) { - lines = [] - } - this.dispatchTimer = null this.toggleLockButton = this.handleToggleLockButton.bind(this) @@ -78,7 +73,7 @@ class MarkdownNoteDetail extends React.Component { if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) { if (this.saveQueue != null) this.saveNow() this.setState({ - note: Object.assign({}, nextProps.note) + note: Object.assign({linesHighlighted: []}, nextProps.note) }, () => { this.refs.content.reload() if (this.refs.tags) this.refs.tags.reset() diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 252bafc8..554e7c99 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -48,16 +48,10 @@ class SnippetNoteDetail extends React.Component { note: Object.assign({ description: '' }, props.note, { - snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet)) + snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet)) }) } - this.state.note.snippets.forEach(function (s) { - if (s.linesHighlighted === undefined) { - s.linesHighlighted = [] - } - }) - this.scrollToNextTabThreshold = 0.7 this.generateToc = () => this.handleGenerateToc() } @@ -82,8 +76,9 @@ class SnippetNoteDetail extends React.Component { const nextNote = Object.assign({ description: '' }, nextProps.note, { - snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet)) + snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet)) }) + this.setState({ snippetIndex: 0, note: nextNote @@ -694,10 +689,8 @@ class SnippetNoteDetail extends React.Component { const viewList = note.snippets.map((snippet, index) => { const isActive = this.state.snippetIndex === index - let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode)) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') - return
Date: Wed, 19 Dec 2018 16:34:16 +0000 Subject: [PATCH 13/14] Corrections to make line highlighting robust, added tests Lines now save correctly with different inputs, making sure that different inputs like enter, delete, paste and where it's deleted stay consistent when saving. Included in the create/update snippet/note tests the structure from lines highlighting saved to the files. --- browser/components/CodeEditor.js | 69 +++++++++++++++++++++++++++++ tests/dataApi/createNote-test.js | 13 +++++- tests/dataApi/createSnippet-test.js | 1 + tests/dataApi/updateNote-test.js | 19 ++++++-- 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 2f4f1c8a..a438009f 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -511,12 +511,50 @@ export default class CodeEditor extends React.Component { handleChange (editor, changeObject) { spellcheck.handleChange(editor, changeObject) + + this.updateHighlight(editor, changeObject) + this.value = editor.getValue() if (this.props.onChange) { this.props.onChange(editor) } } + incrementLines (start, linesAdded, linesRemoved, editor) { + let highlightedLines = editor.options.linesHighlighted + + const totalHighlightedLines = highlightedLines.length + + let offset = linesAdded - linesRemoved + + // Store new items to be added as we're changing the lines + let newLines = [] + + let i = totalHighlightedLines + + while (i--) { + const lineNumber = highlightedLines[i] + + // Interval that will need to be updated + // Between start and (start + offset) remove highlight + if (lineNumber >= start) { + highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1) + + // Lines that need to be relocated + if (lineNumber >= (start + linesRemoved)) { + newLines.push(lineNumber + offset) + } + } + } + + // Adding relocated lines + highlightedLines.push(...newLines) + + if (this.props.onChange) { + this.props.onChange(editor) + } + } + handleHighlight (editor, changeObject) { const lines = editor.options.linesHighlighted @@ -532,6 +570,37 @@ export default class CodeEditor extends React.Component { } } + updateHighlight (editor, changeObject) { + const linesAdded = changeObject.text.length - 1 + const linesRemoved = changeObject.removed.length - 1 + + // If no lines added or removed return + if (linesAdded === 0 && linesRemoved === 0) { + return + } + + let start = changeObject.from.line + + switch (changeObject.origin) { + case '+insert", "undo': + start += 1 + break + + case 'paste': + case '+delete': + case '+input': + if (changeObject.to.ch !== 0 || changeObject.from.ch !== 0) { + start += 1 + } + break + + default: + return + } + + this.incrementLines(start, linesAdded, linesRemoved, editor) + } + moveCursorTo (row, col) {} scrollToLine (event, num) { diff --git a/tests/dataApi/createNote-test.js b/tests/dataApi/createNote-test.js index 47446aab..3606dfd4 100644 --- a/tests/dataApi/createNote-test.js +++ b/tests/dataApi/createNote-test.js @@ -25,13 +25,16 @@ test.serial('Create a note', (t) => { const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key + const randLinesHighlightedArray = new Array(10).fill().map(() => Math.round(Math.random() * 10)) + const input1 = { type: 'SNIPPET_NOTE', description: faker.lorem.lines(), snippets: [{ name: faker.system.fileName(), mode: 'text', - content: faker.lorem.lines() + content: faker.lorem.lines(), + linesHighlighted: randLinesHighlightedArray }], tags: faker.lorem.words().split(' '), folder: folderKey @@ -42,7 +45,8 @@ test.serial('Create a note', (t) => { type: 'MARKDOWN_NOTE', content: faker.lorem.lines(), tags: faker.lorem.words().split(' '), - folder: folderKey + folder: folderKey, + linesHighlighted: randLinesHighlightedArray } input2.title = input2.content.split('\n').shift() @@ -59,6 +63,7 @@ test.serial('Create a note', (t) => { t.is(storageKey, data1.storage) const jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson')) + t.is(input1.title, data1.title) t.is(input1.title, jsonData1.title) t.is(input1.description, data1.description) @@ -71,6 +76,8 @@ test.serial('Create a note', (t) => { t.is(input1.snippets[0].content, jsonData1.snippets[0].content) t.is(input1.snippets[0].name, data1.snippets[0].name) t.is(input1.snippets[0].name, jsonData1.snippets[0].name) + t.deepEqual(input1.snippets[0].linesHighlighted, data1.snippets[0].linesHighlighted) + t.deepEqual(input1.snippets[0].linesHighlighted, jsonData1.snippets[0].linesHighlighted) t.is(storageKey, data2.storage) const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson')) @@ -80,6 +87,8 @@ test.serial('Create a note', (t) => { t.is(input2.content, jsonData2.content) t.is(input2.tags.length, data2.tags.length) t.is(input2.tags.length, jsonData2.tags.length) + t.deepEqual(input2.linesHighlighted, data2.linesHighlighted) + t.deepEqual(input2.linesHighlighted, jsonData2.linesHighlighted) }) }) diff --git a/tests/dataApi/createSnippet-test.js b/tests/dataApi/createSnippet-test.js index f06cf861..638b76ca 100644 --- a/tests/dataApi/createSnippet-test.js +++ b/tests/dataApi/createSnippet-test.js @@ -26,6 +26,7 @@ test.serial('Create a snippet', (t) => { t.is(snippet.name, data.name) t.deepEqual(snippet.prefix, data.prefix) t.is(snippet.content, data.content) + t.deepEqual(snippet.linesHighlighted, data.linesHighlighted) }) }) diff --git a/tests/dataApi/updateNote-test.js b/tests/dataApi/updateNote-test.js index 6043ee1e..da47c30c 100644 --- a/tests/dataApi/updateNote-test.js +++ b/tests/dataApi/updateNote-test.js @@ -26,13 +26,17 @@ test.serial('Update a note', (t) => { const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key + const randLinesHighlightedArray = new Array(10).fill().map(() => Math.round(Math.random() * 10)) + const randLinesHighlightedArray2 = new Array(15).fill().map(() => Math.round(Math.random() * 15)) + const input1 = { type: 'SNIPPET_NOTE', description: faker.lorem.lines(), snippets: [{ name: faker.system.fileName(), mode: 'text', - content: faker.lorem.lines() + content: faker.lorem.lines(), + linesHighlighted: randLinesHighlightedArray }], tags: faker.lorem.words().split(' '), folder: folderKey @@ -43,7 +47,8 @@ test.serial('Update a note', (t) => { type: 'MARKDOWN_NOTE', content: faker.lorem.lines(), tags: faker.lorem.words().split(' '), - folder: folderKey + folder: folderKey, + linesHighlighted: randLinesHighlightedArray } input2.title = input2.content.split('\n').shift() @@ -53,7 +58,8 @@ test.serial('Update a note', (t) => { snippets: [{ name: faker.system.fileName(), mode: 'text', - content: faker.lorem.lines() + content: faker.lorem.lines(), + linesHighlighted: randLinesHighlightedArray2 }], tags: faker.lorem.words().split(' ') } @@ -62,7 +68,8 @@ test.serial('Update a note', (t) => { const input4 = { type: 'MARKDOWN_NOTE', content: faker.lorem.lines(), - tags: faker.lorem.words().split(' ') + tags: faker.lorem.words().split(' '), + linesHighlighted: randLinesHighlightedArray2 } input4.title = input4.content.split('\n').shift() @@ -99,6 +106,8 @@ test.serial('Update a note', (t) => { t.is(input3.snippets[0].content, jsonData1.snippets[0].content) t.is(input3.snippets[0].name, data1.snippets[0].name) t.is(input3.snippets[0].name, jsonData1.snippets[0].name) + t.deepEqual(input3.snippets[0].linesHighlighted, data1.snippets[0].linesHighlighted) + t.deepEqual(input3.snippets[0].linesHighlighted, jsonData1.snippets[0].linesHighlighted) const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson')) t.is(input4.title, data2.title) @@ -107,6 +116,8 @@ test.serial('Update a note', (t) => { t.is(input4.content, jsonData2.content) t.is(input4.tags.length, data2.tags.length) t.is(input4.tags.length, jsonData2.tags.length) + t.deepEqual(input4.linesHighlighted, data2.linesHighlighted) + t.deepEqual(input4.linesHighlighted, jsonData2.linesHighlighted) }) }) From e93bf1cfe757821c8c5f618d5168cb50a092ad80 Mon Sep 17 00:00:00 2001 From: duartefrazao Date: Sat, 22 Dec 2018 11:44:30 +0000 Subject: [PATCH 14/14] Removed bind in MarkdownEditor and MarkdownSplitEditor --- browser/components/MarkdownEditor.js | 2 +- browser/components/MarkdownSplitEditor.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 6c91ea1d..0043ea5c 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -276,7 +276,7 @@ class MarkdownEditor extends React.Component { fetchUrlTitle={config.editor.fetchUrlTitle} enableTableEditor={config.editor.enableTableEditor} linesHighlighted={linesHighlighted} - onChange={(e) => this.handleChange.bind(this)(e)} + onChange={(e) => this.handleChange(e)} onBlur={(e) => this.handleBlur(e)} spellCheck={config.editor.spellcheck} /> diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index c1b9bd84..2f76b9fc 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -170,7 +170,7 @@ class MarkdownSplitEditor extends React.Component { storageKey={storageKey} noteKey={noteKey} linesHighlighted={linesHighlighted} - onChange={(e) => this.handleOnChange.bind(this)(e)} + onChange={(e) => this.handleOnChange(e)} onScroll={this.handleScroll.bind(this)} spellCheck={config.editor.spellcheck} />