mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
add smart table editor
This commit is contained in:
@@ -141,8 +141,35 @@ export default class CodeEditor extends React.Component {
|
|||||||
triples: '```"""\'\'\'',
|
triples: '```"""\'\'\'',
|
||||||
explode: '[]{}``$$',
|
explode: '[]{}``$$',
|
||||||
override: true
|
override: true
|
||||||
},
|
}
|
||||||
extraKeys: {
|
})
|
||||||
|
|
||||||
|
this.setMode(this.props.mode)
|
||||||
|
|
||||||
|
this.editor.on('focus', this.focusHandler)
|
||||||
|
this.editor.on('blur', this.blurHandler)
|
||||||
|
this.editor.on('change', this.changeHandler)
|
||||||
|
this.editor.on('paste', this.pasteHandler)
|
||||||
|
eventEmitter.on('top:search', this.searchHandler)
|
||||||
|
|
||||||
|
eventEmitter.emit('code:init')
|
||||||
|
this.editor.on('scroll', this.scrollHandler)
|
||||||
|
|
||||||
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
|
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||||
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||||
|
this.setState({ isReady: true })
|
||||||
|
|
||||||
|
const editorIntf = new TextEditorInterface(this.editor)
|
||||||
|
this.tableEditor = new TableEditor(editorIntf)
|
||||||
|
eventEmitter.on('code:format-table', this.formatTable)
|
||||||
|
|
||||||
|
const defaultKeyMap = CodeMirror.normalizeKeyMap({
|
||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
const cursor = cm.getCursor()
|
const cursor = cm.getCursor()
|
||||||
const line = cm.getLine(cursor.line)
|
const line = cm.getLine(cursor.line)
|
||||||
@@ -193,31 +220,79 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
return CodeMirror.Pass
|
return CodeMirror.Pass
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setMode(this.props.mode)
|
if(this.props.enableTableEditor) {
|
||||||
|
const opts = options({
|
||||||
|
smartCursor: true
|
||||||
|
})
|
||||||
|
|
||||||
this.editor.on('focus', this.focusHandler)
|
const editorKeyMap = CodeMirror.normalizeKeyMap({
|
||||||
this.editor.on('blur', this.blurHandler)
|
'Tab' : () => { this.tableEditor.nextCell(opts) },
|
||||||
this.editor.on('change', this.changeHandler)
|
'Shift-Tab' : () => { this.tableEditor.previousCell(opts) },
|
||||||
this.editor.on('paste', this.pasteHandler)
|
'Enter' : () => { this.tableEditor.nextRow(opts) },
|
||||||
eventEmitter.on('top:search', this.searchHandler)
|
'Ctrl-Enter' : () => { this.tableEditor.escape(opts) },
|
||||||
|
'Cmd-Enter' : () => { this.tableEditor.escape(opts) },
|
||||||
|
'Shift-Ctrl-Left' : () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) },
|
||||||
|
'Shift-Cmd-Left' : () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) },
|
||||||
|
'Shift-Ctrl-Right' : () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) },
|
||||||
|
'Shift-Cmd-Right' : () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) },
|
||||||
|
'Shift-Ctrl-Up' : () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) },
|
||||||
|
'Shift-Cmd-Up' : () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) },
|
||||||
|
'Shift-Ctrl-Down' : () => { this.tableEditor.alignColumn(Alignment.NONE, opts) },
|
||||||
|
'Shift-Cmd-Down' : () => { this.tableEditor.alignColumn(Alignment.NONE, opts) },
|
||||||
|
'Ctrl-Left' : () => { this.tableEditor.moveFocus(0, -1, opts) },
|
||||||
|
'Cmd-Left' : () => { this.tableEditor.moveFocus(0, -1, opts) },
|
||||||
|
'Ctrl-Right' : () => { this.tableEditor.moveFocus(0, 1, opts) },
|
||||||
|
'Cmd-Right' : () => { this.tableEditor.moveFocus(0, 1, opts) },
|
||||||
|
'Ctrl-Up' : () => { this.tableEditor.moveFocus(-1, 0, opts) },
|
||||||
|
'Cmd-Up' : () => { this.tableEditor.moveFocus(-1, 0, opts) },
|
||||||
|
'Ctrl-Down' : () => { this.tableEditor.moveFocus(1, 0, opts) },
|
||||||
|
'Cmd-Down' : () => { this.tableEditor.moveFocus(1, 0, opts) },
|
||||||
|
'Ctrl-K Ctrl-I' : () => { this.tableEditor.insertRow(opts) },
|
||||||
|
'Cmd-K Cmd-I' : () => { this.tableEditor.insertRow(opts) },
|
||||||
|
'Ctrl-L Ctrl-I' : () => { this.tableEditor.deleteRow(opts) },
|
||||||
|
'Cmd-L Cmd-I' : () => { this.tableEditor.deleteRow(opts) },
|
||||||
|
'Ctrl-K Ctrl-J' : () => { this.tableEditor.insertColumn(opts) },
|
||||||
|
'Cmd-K Cmd-J' : () => { this.tableEditor.insertColumn(opts) },
|
||||||
|
'Ctrl-L Ctrl-J' : () => { this.tableEditor.deleteColumn(opts) },
|
||||||
|
'Cmd-L Cmd-J' : () => { this.tableEditor.deleteColumn(opts) },
|
||||||
|
'Alt-Shift-Ctrl-Left' : () => { this.tableEditor.moveColumn(-1, opts) },
|
||||||
|
'Alt-Shift-Cmd-Left' : () => { this.tableEditor.moveColumn(-1, opts) },
|
||||||
|
'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, opts) },
|
||||||
|
'Alt-Shift-Cmd-Right' : () => { this.tableEditor.moveColumn(1, opts) },
|
||||||
|
'Alt-Shift-Ctrl-Up' : () => { this.tableEditor.moveRow(-1, opts) },
|
||||||
|
'Alt-Shift-Cmd-Up' : () => { this.tableEditor.moveRow(-1, opts) },
|
||||||
|
'Alt-Shift-Ctrl-Down' : () => { this.tableEditor.moveRow(1, opts) },
|
||||||
|
'Alt-Shift-Cmd-Down' : () => { this.tableEditor.moveRow(1, opts) }
|
||||||
|
})
|
||||||
|
|
||||||
eventEmitter.emit('code:init')
|
const updateActiveState = () => {
|
||||||
this.editor.on('scroll', this.scrollHandler)
|
const active = this.tableEditor.cursorIsInTable(opts)
|
||||||
|
if (active) {
|
||||||
|
this.editor.setOption('extraKeys', editorKeyMap)
|
||||||
|
} else {
|
||||||
|
this.editor.setOption('extraKeys', defaultKeyMap)
|
||||||
|
this.tableEditor.resetSmartCursor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const editorTheme = document.getElementById('editorTheme')
|
this.editor.on('cursorActivity', () => {
|
||||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
if (!editorIntf.transaction) {
|
||||||
|
updateActiveState()
|
||||||
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
|
}
|
||||||
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
})
|
||||||
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
this.editor.on('changes', () => {
|
||||||
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
if (!editorIntf.transaction) {
|
||||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
updateActiveState()
|
||||||
this.setState({ isReady: true })
|
}
|
||||||
this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
|
})
|
||||||
eventEmitter.on('code:format-table', this.formatTable)
|
editorIntf.onDidFinishTransaction = () => {
|
||||||
|
updateActiveState()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.editor.setOption('extraKeys', defaultKeyMap)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSnippet (line, cursor, cm, snippets) {
|
expandSnippet (line, cursor, cm, snippets) {
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
rulers={config.editor.rulers}
|
rulers={config.editor.rulers}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
onChange={this.handleOnChange.bind(this)}
|
onChange={this.handleOnChange.bind(this)}
|
||||||
|
|||||||
@@ -3,51 +3,115 @@ import { Point } from '@susisu/mte-kernel'
|
|||||||
export default class TextEditorInterface {
|
export default class TextEditorInterface {
|
||||||
constructor (editor) {
|
constructor (editor) {
|
||||||
this.editor = editor
|
this.editor = editor
|
||||||
|
this.doc = editor.getDoc()
|
||||||
|
this.transaction = false
|
||||||
}
|
}
|
||||||
|
|
||||||
getCursorPosition() {
|
getCursorPosition() {
|
||||||
const pos = this.editor.getCursor()
|
const { line, ch } = this.doc.getCursor()
|
||||||
return new Point(pos.line, pos.ch)
|
return new Point(line, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCursorPosition(pos) {
|
setCursorPosition(pos) {
|
||||||
this.editor.setCursor({line: pos.row, ch: pos.column})
|
this.doc.setCursor({
|
||||||
}
|
line: pos.row,
|
||||||
|
ch: pos.column
|
||||||
setSelectionRange (range) {
|
|
||||||
this.editor.setSelection({
|
|
||||||
anchor: {line: range.start.row, ch: range.start.column},
|
|
||||||
head: {line: range.end.row, ch: range.end.column}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastRow () {
|
setSelectionRange(range) {
|
||||||
return this.editor.lastLine()
|
this.doc.setSelection(
|
||||||
|
{ line: range.start.row, ch: range.start.column },
|
||||||
|
{ line: range.end.row, ch: range.end.column }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptsTableEdit (row) {
|
getLastRow() {
|
||||||
|
return this.doc.lineCount() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptsTableEdit() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
getLine(row) {
|
getLine(row) {
|
||||||
return this.editor.getLine(row)
|
return this.doc.getLine(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
insertLine(row, line) {
|
insertLine(row, line) {
|
||||||
this.editor.replaceRange(line, {line: row, ch: 0})
|
const lastRow = this.getLastRow()
|
||||||
|
if (row > lastRow) {
|
||||||
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
'\n' + line,
|
||||||
|
{ line: lastRow, ch: lastLine.length },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.doc.replaceRange(
|
||||||
|
line + '\n',
|
||||||
|
{ line: row, ch: 0 },
|
||||||
|
{ line: row, ch: 0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteLine(row) {
|
deleteLine(row) {
|
||||||
this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length})
|
const lastRow = this.getLastRow()
|
||||||
|
if (row >= lastRow) {
|
||||||
|
if (lastRow > 0) {
|
||||||
|
const preLastLine = this.getLine(lastRow - 1)
|
||||||
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
'',
|
||||||
|
{ line: lastRow - 1, ch: preLastLine.length },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
'',
|
||||||
|
{ line: lastRow, ch: 0 },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.doc.replaceRange(
|
||||||
|
'',
|
||||||
|
{ line: row, ch: 0 },
|
||||||
|
{ line: row + 1, ch: 0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceLines(startRow, endRow, lines) {
|
replaceLines(startRow, endRow, lines) {
|
||||||
endRow-- // because endRow is a first line after a table.
|
const lastRow = this.getLastRow()
|
||||||
const endRowCh = this.editor.getLine(endRow).length
|
if (endRow > lastRow) {
|
||||||
this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh})
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
lines.join('\n'),
|
||||||
|
{ line: startRow, ch: 0 },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.doc.replaceRange(
|
||||||
|
lines.join('\n') + '\n',
|
||||||
|
{ line: startRow, ch: 0 },
|
||||||
|
{ line: endRow, ch: 0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transact(func) {
|
transact(func) {
|
||||||
|
this.transaction = true
|
||||||
func()
|
func()
|
||||||
|
this.transaction = false
|
||||||
|
if (this.onDidFinishTransaction) {
|
||||||
|
this.onDidFinishTransaction.call(undefined)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -691,6 +691,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
type: 'SPLIT',
|
type: 'SPLIT',
|
||||||
fetchUrlTitle: true
|
fetchUrlTitle: true,
|
||||||
|
enableTableEditor: false
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
fontSize: '14',
|
fontSize: '14',
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class UiTab extends React.Component {
|
|||||||
switchPreview: this.refs.editorSwitchPreview.value,
|
switchPreview: this.refs.editorSwitchPreview.value,
|
||||||
keyMap: this.refs.editorKeyMap.value,
|
keyMap: this.refs.editorKeyMap.value,
|
||||||
scrollPastEnd: this.refs.scrollPastEnd.checked,
|
scrollPastEnd: this.refs.scrollPastEnd.checked,
|
||||||
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
|
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked,
|
||||||
|
enableTableEditor: this.refs.enableTableEditor.checked
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
fontSize: this.refs.previewFontSize.value,
|
fontSize: this.refs.previewFontSize.value,
|
||||||
@@ -420,6 +421,17 @@ class UiTab extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.editor.enableTableEditor}
|
||||||
|
ref='enableTableEditor'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Enable smart table editor')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-header2'>{i18n.__('Preview')}</div>
|
<div styleName='group-header2'>{i18n.__('Preview')}</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>
|
<div styleName='group-section-label'>
|
||||||
|
|||||||
@@ -175,5 +175,6 @@
|
|||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags",
|
"Allow dangerous html tags": "Allow dangerous html tags",
|
||||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||||
|
"Enable smart table editor": "Enable smart table editor"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,5 +152,6 @@
|
|||||||
"Allow styles": "Accepter les styles",
|
"Allow styles": "Accepter les styles",
|
||||||
"Allow dangerous html tags": "Accepter les tags html dangereux",
|
"Allow dangerous html tags": "Accepter les tags html dangereux",
|
||||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.",
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.",
|
||||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠"
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠",
|
||||||
|
"Enable smart table editor": "Activer l'intelligent éditeur de tableaux"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user