1
0
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:
Baptiste Augrain
2018-08-20 20:14:45 +02:00
parent 039f73711a
commit 657806c8cf
9 changed files with 269 additions and 112 deletions

View File

@@ -141,61 +141,9 @@ export default class CodeEditor extends React.Component {
triples: '```"""\'\'\'',
explode: '[]{}``$$',
override: true
},
extraKeys: {
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')
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
cm.execCommand('goLineStart')
if (tabs) {
cm.execCommand('insertTab')
} else {
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')
} else {
cm.execCommand('insertSoftTab')
}
}
}
},
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
}
return CodeMirror.Pass
}
}
})
this.setMode(this.props.mode)
this.editor.on('focus', this.focusHandler)
@@ -216,8 +164,135 @@ export default class CodeEditor extends React.Component {
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
CodeMirror.Vim.map('ZZ', ':q', 'normal')
this.setState({ isReady: true })
this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
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) {
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')
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
cm.execCommand('goLineStart')
if (tabs) {
cm.execCommand('insertTab')
} else {
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')
} else {
cm.execCommand('insertSoftTab')
}
}
}
},
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
}
return CodeMirror.Pass
}
})
if(this.props.enableTableEditor) {
const opts = options({
smartCursor: true
})
const editorKeyMap = CodeMirror.normalizeKeyMap({
'Tab' : () => { this.tableEditor.nextCell(opts) },
'Shift-Tab' : () => { this.tableEditor.previousCell(opts) },
'Enter' : () => { this.tableEditor.nextRow(opts) },
'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) }
})
const updateActiveState = () => {
const active = this.tableEditor.cursorIsInTable(opts)
if (active) {
this.editor.setOption('extraKeys', editorKeyMap)
} else {
this.editor.setOption('extraKeys', defaultKeyMap)
this.tableEditor.resetSmartCursor()
}
}
this.editor.on('cursorActivity', () => {
if (!editorIntf.transaction) {
updateActiveState()
}
})
this.editor.on('changes', () => {
if (!editorIntf.transaction) {
updateActiveState()
}
})
editorIntf.onDidFinishTransaction = () => {
updateActiveState()
}
} else {
this.editor.setOption('extraKeys', defaultKeyMap)
}
}
expandSnippet (line, cursor, cm, snippets) {

View File

@@ -235,7 +235,7 @@ class MarkdownEditor extends React.Component {
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
const storage = findStorage(storageKey)
return (
<div className={className == null
? 'MarkdownEditor'
@@ -266,6 +266,7 @@ class MarkdownEditor extends React.Component {
storageKey={storageKey}
noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
/>

View File

@@ -158,6 +158,7 @@ class MarkdownSplitEditor extends React.Component {
rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
storageKey={storageKey}
noteKey={noteKey}
onChange={this.handleOnChange.bind(this)}

View File

@@ -1,53 +1,117 @@
import { Point } from '@susisu/mte-kernel'
export default class TextEditorInterface {
constructor (editor) {
this.editor = editor
}
getCursorPosition () {
const pos = this.editor.getCursor()
return new Point(pos.line, pos.ch)
}
setCursorPosition (pos) {
this.editor.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 () {
return this.editor.lastLine()
}
acceptsTableEdit (row) {
return true
}
getLine (row) {
return this.editor.getLine(row)
}
insertLine (row, line) {
this.editor.replaceRange(line, {line: row, ch: 0})
}
deleteLine (row) {
this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length})
}
replaceLines (startRow, endRow, lines) {
endRow-- // because endRow is a first line after a table.
const endRowCh = this.editor.getLine(endRow).length
this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh})
}
transact (func) {
func()
}
}
import { Point } from '@susisu/mte-kernel'
export default class TextEditorInterface {
constructor (editor) {
this.editor = editor
this.doc = editor.getDoc()
this.transaction = false
}
getCursorPosition() {
const { line, ch } = this.doc.getCursor()
return new Point(line, ch)
}
setCursorPosition(pos) {
this.doc.setCursor({
line: pos.row,
ch: pos.column
})
}
setSelectionRange(range) {
this.doc.setSelection(
{ line: range.start.row, ch: range.start.column },
{ line: range.end.row, ch: range.end.column }
)
}
getLastRow() {
return this.doc.lineCount() - 1
}
acceptsTableEdit() {
return true
}
getLine(row) {
return this.doc.getLine(row)
}
insertLine(row, line) {
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) {
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) {
const lastRow = this.getLastRow()
if (endRow > lastRow) {
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) {
this.transaction = true
func()
this.transaction = false
if (this.onDidFinishTransaction) {
this.onDidFinishTransaction.call(undefined)
}
}
}

View File

@@ -691,6 +691,7 @@ class SnippetNoteDetail extends React.Component {
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
/>

View File

@@ -46,7 +46,8 @@ export const DEFAULT_CONFIG = {
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false,
type: 'SPLIT',
fetchUrlTitle: true
fetchUrlTitle: true,
enableTableEditor: false
},
preview: {
fontSize: '14',

View File

@@ -86,7 +86,8 @@ class UiTab extends React.Component {
switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value,
scrollPastEnd: this.refs.scrollPastEnd.checked,
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked,
enableTableEditor: this.refs.enableTableEditor.checked
},
preview: {
fontSize: this.refs.previewFontSize.value,
@@ -419,6 +420,17 @@ class UiTab extends React.Component {
{i18n.__('Bring in web page title when pasting URL on editor')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.enableTableEditor}
ref='enableTableEditor'
type='checkbox'
/>&nbsp;
{i18n.__('Enable smart table editor')}
</label>
</div>
<div styleName='group-header2'>{i18n.__('Preview')}</div>
<div styleName='group-section'>