diff --git a/package.json b/package.json index d049e070..8ec2a59a 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@hackmd/lz-string": "~1.4.4", "@hackmd/meta-marked": "~0.4.4", "@passport-next/passport-openid": "~1.0.0", + "@susisu/mte-kernel": "^2.1.0", "archiver": "~3.1.1", "async": "~3.1.0", "aws-sdk": "~2.503.0", diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js index 5c94be69..ec7e4ae6 100644 --- a/public/js/lib/editor/index.js +++ b/public/js/lib/editor/index.js @@ -4,6 +4,7 @@ import config from './config' import statusBarTemplate from './statusbar.html' import toolBarTemplate from './toolbar.html' import './markdown-lint' +import { initTableEditor } from './table-editor' /* config section */ const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault @@ -623,6 +624,8 @@ export default class Editor { placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)" }) + this.tableEditor = initTableEditor(this.editor) + return this.editor } diff --git a/public/js/lib/editor/table-editor.js b/public/js/lib/editor/table-editor.js new file mode 100644 index 00000000..df6ef80b --- /dev/null +++ b/public/js/lib/editor/table-editor.js @@ -0,0 +1,199 @@ +/* global CodeMirror, $ */ +import { TableEditor, Point, options, Alignment, FormatType } from '@susisu/mte-kernel' + +// port of the code from: https://github.com/susisu/mte-demo/blob/master/src/main.js + +// text editor interface +// see https://doc.esdoc.org/github.com/susisu/mte-kernel/class/lib/text-editor.js~ITextEditor.html +class TextEditorInterface { + constructor (editor) { + this.editor = editor + this.doc = editor.getDoc() + this.transaction = false + this.onDidFinishTransaction = null + } + + 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) + } + } +} + +export function initTableEditor (editor) { + // create an interface to the text editor + const editorIntf = new TextEditorInterface(editor) + // create a table editor object + const tableEditor = new TableEditor(editorIntf) + // options for the table editor + const opts = options({ + smartCursor: true, + formatType: FormatType.NORMAL + }) + // keymap of the commands + // from https://github.com/susisu/mte-demo/blob/master/src/main.js + const keyMap = CodeMirror.normalizeKeyMap({ + Tab: () => { tableEditor.nextCell(opts) }, + 'Shift-Tab': () => { tableEditor.previousCell(opts) }, + Enter: () => { tableEditor.nextRow(opts) }, + 'Ctrl-Enter': () => { tableEditor.escape(opts) }, + 'Cmd-Enter': () => { tableEditor.escape(opts) }, + 'Shift-Ctrl-Left': () => { tableEditor.alignColumn(Alignment.LEFT, opts) }, + 'Shift-Cmd-Left': () => { tableEditor.alignColumn(Alignment.LEFT, opts) }, + 'Shift-Ctrl-Right': () => { tableEditor.alignColumn(Alignment.RIGHT, opts) }, + 'Shift-Cmd-Right': () => { tableEditor.alignColumn(Alignment.RIGHT, opts) }, + 'Shift-Ctrl-Up': () => { tableEditor.alignColumn(Alignment.CENTER, opts) }, + 'Shift-Cmd-Up': () => { tableEditor.alignColumn(Alignment.CENTER, opts) }, + 'Shift-Ctrl-Down': () => { tableEditor.alignColumn(Alignment.NONE, opts) }, + 'Shift-Cmd-Down': () => { tableEditor.alignColumn(Alignment.NONE, opts) }, + 'Ctrl-Left': () => { tableEditor.moveFocus(0, -1, opts) }, + 'Cmd-Left': () => { tableEditor.moveFocus(0, -1, opts) }, + 'Ctrl-Right': () => { tableEditor.moveFocus(0, 1, opts) }, + 'Cmd-Right': () => { tableEditor.moveFocus(0, 1, opts) }, + 'Ctrl-Up': () => { tableEditor.moveFocus(-1, 0, opts) }, + 'Cmd-Up': () => { tableEditor.moveFocus(-1, 0, opts) }, + 'Ctrl-Down': () => { tableEditor.moveFocus(1, 0, opts) }, + 'Cmd-Down': () => { tableEditor.moveFocus(1, 0, opts) }, + 'Ctrl-K Ctrl-I': () => { tableEditor.insertRow(opts) }, + 'Cmd-K Cmd-I': () => { tableEditor.insertRow(opts) }, + 'Ctrl-L Ctrl-I': () => { tableEditor.deleteRow(opts) }, + 'Cmd-L Cmd-I': () => { tableEditor.deleteRow(opts) }, + 'Ctrl-K Ctrl-J': () => { tableEditor.insertColumn(opts) }, + 'Cmd-K Cmd-J': () => { tableEditor.insertColumn(opts) }, + 'Ctrl-L Ctrl-J': () => { tableEditor.deleteColumn(opts) }, + 'Cmd-L Cmd-J': () => { tableEditor.deleteColumn(opts) }, + 'Alt-Shift-Ctrl-Left': () => { tableEditor.moveColumn(-1, opts) }, + 'Alt-Shift-Cmd-Left': () => { tableEditor.moveColumn(-1, opts) }, + 'Alt-Shift-Ctrl-Right': () => { tableEditor.moveColumn(1, opts) }, + 'Alt-Shift-Cmd-Right': () => { tableEditor.moveColumn(1, opts) }, + 'Alt-Shift-Ctrl-Up': () => { tableEditor.moveRow(-1, opts) }, + 'Alt-Shift-Cmd-Up': () => { tableEditor.moveRow(-1, opts) }, + 'Alt-Shift-Ctrl-Down': () => { tableEditor.moveRow(1, opts) }, + 'Alt-Shift-Cmd-Down': () => { tableEditor.moveRow(1, opts) } + }) + // enable keymap if the cursor is in a table + function updateActiveState () { + const tableTools = $('.toolbar .table-tools') + const active = tableEditor.cursorIsInTable(opts) + if (active) { + tableTools.show() + tableTools.parent().scrollLeft(tableTools.parent()[0].scrollWidth) + editor.setOption('extraKeys', keyMap) + } else { + tableTools.hide() + editor.setOption('extraKeys', null) + tableEditor.resetSmartCursor() + } + } + // event subscriptions + editor.on('cursorActivity', () => { + if (!editorIntf.transaction) { + updateActiveState() + } + }) + editor.on('changes', () => { + if (!editorIntf.transaction) { + updateActiveState() + } + }) + editorIntf.onDidFinishTransaction = () => { + updateActiveState() + } + + return tableEditor +} diff --git a/webpack.common.js b/webpack.common.js index fce94b35..cba00716 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -401,6 +401,9 @@ module.exports = { module: { rules: [{ + test: /\.mjs$/, + type: 'javascript/auto' + }, { test: /\.js$/, use: [{ loader: 'babel-loader' }], exclude: [/node_modules/, /public\/vendor/]