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,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) {
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
/>
|
||||
{i18n.__('Enable smart table editor')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-header2'>{i18n.__('Preview')}</div>
|
||||
<div styleName='group-section'>
|
||||
|
||||
Reference in New Issue
Block a user