Skip to content

Commit 56cc64e

Browse files
authored
Feature/support table tools (#1300)
Feature/support table tools
2 parents c9df47d + efff8d0 commit 56cc64e

File tree

7 files changed

+366
-0
lines changed

7 files changed

+366
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@hackmd/lz-string": "~1.4.4",
3939
"@hackmd/meta-marked": "~0.4.4",
4040
"@passport-next/passport-openid": "~1.0.0",
41+
"@susisu/mte-kernel": "^2.1.0",
4142
"archiver": "~3.1.1",
4243
"async": "~3.1.0",
4344
"aws-sdk": "~2.503.0",

public/css/index.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,32 @@ body.night{
2525
border: 1px solid #343434;
2626
}
2727

28+
.toolbar > .btn-toolbar {
29+
white-space: nowrap;
30+
overflow-y: auto;
31+
scrollbar-width: none;
32+
}
33+
34+
.toolbar > .btn-toolbar::-webkit-scrollbar {
35+
display: none;
36+
}
37+
38+
.toolbar > .btn-toolbar > .btn-group {
39+
float: none;
40+
}
41+
42+
.toolbar > .btn-toolbar > .btn-group > span {
43+
display: inline-block;
44+
float: left;
45+
color: #fff;
46+
padding: 5px;
47+
line-height: 22px;
48+
}
49+
50+
.toolbar > .btn-toolbar > .btn-group > span.separator {
51+
color: #4d4d4d;
52+
}
53+
2854
.toolbar > .btn-toolbar > .btn-group > .btn {
2955
background-color: #1c1c1e;
3056
padding: 5px;

public/js/lib/editor/index.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import config from './config'
44
import statusBarTemplate from './statusbar.html'
55
import toolBarTemplate from './toolbar.html'
66
import './markdown-lint'
7+
import { initTableEditor } from './table-editor'
8+
import { options, Alignment, FormatType } from '@susisu/mte-kernel'
79

810
/* config section */
911
const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
@@ -160,6 +162,19 @@ export default class Editor {
160162
var makeLine = $('#makeLine')
161163
var makeComment = $('#makeComment')
162164

165+
var insertRow = $('#insertRow')
166+
var deleteRow = $('#deleteRow')
167+
var moveRowUp = $('#moveRowUp')
168+
var moveRowDown = $('#moveRowDown')
169+
var insertColumn = $('#insertColumn')
170+
var deleteColumn = $('#deleteColumn')
171+
var moveColumnLeft = $('#moveColumnLeft')
172+
var moveColumnRight = $('#moveColumnRight')
173+
var alignLeft = $('#alignLeft')
174+
var alignCenter = $('#alignCenter')
175+
var alignRight = $('#alignRight')
176+
var alignNone = $('#alignNone')
177+
163178
makeBold.click(() => {
164179
utils.wrapTextWith(this.editor, this.editor, '**')
165180
this.editor.focus()
@@ -219,6 +234,72 @@ export default class Editor {
219234
makeComment.click(() => {
220235
utils.insertText(this.editor, '> []')
221236
})
237+
238+
// table tools UI
239+
const opts = options({
240+
smartCursor: true,
241+
formatType: FormatType.NORMAL
242+
})
243+
244+
insertRow.click(() => {
245+
this.tableEditor.insertRow(opts)
246+
this.editor.focus()
247+
})
248+
249+
deleteRow.click(() => {
250+
this.tableEditor.deleteRow(opts)
251+
this.editor.focus()
252+
})
253+
254+
moveRowUp.click(() => {
255+
this.tableEditor.moveRow(-1, opts)
256+
this.editor.focus()
257+
})
258+
259+
moveRowDown.click(() => {
260+
this.tableEditor.moveRow(1, opts)
261+
this.editor.focus()
262+
})
263+
264+
insertColumn.click(() => {
265+
this.tableEditor.insertColumn(opts)
266+
this.editor.focus()
267+
})
268+
269+
deleteColumn.click(() => {
270+
this.tableEditor.deleteColumn(opts)
271+
this.editor.focus()
272+
})
273+
274+
moveColumnLeft.click(() => {
275+
this.tableEditor.moveColumn(-1, opts)
276+
this.editor.focus()
277+
})
278+
279+
moveColumnRight.click(() => {
280+
this.tableEditor.moveColumn(1, opts)
281+
this.editor.focus()
282+
})
283+
284+
alignLeft.click(() => {
285+
this.tableEditor.alignColumn(Alignment.LEFT, opts)
286+
this.editor.focus()
287+
})
288+
289+
alignCenter.click(() => {
290+
this.tableEditor.alignColumn(Alignment.CENTER, opts)
291+
this.editor.focus()
292+
})
293+
294+
alignRight.click(() => {
295+
this.tableEditor.alignColumn(Alignment.RIGHT, opts)
296+
this.editor.focus()
297+
})
298+
299+
alignNone.click(() => {
300+
this.tableEditor.alignColumn(Alignment.NONE, opts)
301+
this.editor.focus()
302+
})
222303
}
223304

224305
addStatusBar () {
@@ -623,6 +704,8 @@ export default class Editor {
623704
placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
624705
})
625706

707+
this.tableEditor = initTableEditor(this.editor)
708+
626709
return this.editor
627710
}
628711

public/js/lib/editor/table-editor.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/* global CodeMirror, $ */
2+
import { TableEditor, Point, options, Alignment, FormatType } from '@susisu/mte-kernel'
3+
4+
// port of the code from: https://github.com/susisu/mte-demo/blob/master/src/main.js
5+
6+
// text editor interface
7+
// see https://doc.esdoc.org/github.com/susisu/mte-kernel/class/lib/text-editor.js~ITextEditor.html
8+
class TextEditorInterface {
9+
constructor (editor) {
10+
this.editor = editor
11+
this.doc = editor.getDoc()
12+
this.transaction = false
13+
this.onDidFinishTransaction = null
14+
}
15+
16+
getCursorPosition () {
17+
const { line, ch } = this.doc.getCursor()
18+
return new Point(line, ch)
19+
}
20+
21+
setCursorPosition (pos) {
22+
this.doc.setCursor({ line: pos.row, ch: pos.column })
23+
}
24+
25+
setSelectionRange (range) {
26+
this.doc.setSelection(
27+
{ line: range.start.row, ch: range.start.column },
28+
{ line: range.end.row, ch: range.end.column }
29+
)
30+
}
31+
32+
getLastRow () {
33+
return this.doc.lineCount() - 1
34+
}
35+
36+
acceptsTableEdit () {
37+
return true
38+
}
39+
40+
getLine (row) {
41+
return this.doc.getLine(row)
42+
}
43+
44+
insertLine (row, line) {
45+
const lastRow = this.getLastRow()
46+
if (row > lastRow) {
47+
const lastLine = this.getLine(lastRow)
48+
this.doc.replaceRange(
49+
'\n' + line,
50+
{ line: lastRow, ch: lastLine.length },
51+
{ line: lastRow, ch: lastLine.length }
52+
)
53+
} else {
54+
this.doc.replaceRange(
55+
line + '\n',
56+
{ line: row, ch: 0 },
57+
{ line: row, ch: 0 }
58+
)
59+
}
60+
}
61+
62+
deleteLine (row) {
63+
const lastRow = this.getLastRow()
64+
if (row >= lastRow) {
65+
if (lastRow > 0) {
66+
const preLastLine = this.getLine(lastRow - 1)
67+
const lastLine = this.getLine(lastRow)
68+
this.doc.replaceRange(
69+
'',
70+
{ line: lastRow - 1, ch: preLastLine.length },
71+
{ line: lastRow, ch: lastLine.length }
72+
)
73+
} else {
74+
const lastLine = this.getLine(lastRow)
75+
this.doc.replaceRange(
76+
'',
77+
{ line: lastRow, ch: 0 },
78+
{ line: lastRow, ch: lastLine.length }
79+
)
80+
}
81+
} else {
82+
this.doc.replaceRange(
83+
'',
84+
{ line: row, ch: 0 },
85+
{ line: row + 1, ch: 0 }
86+
)
87+
}
88+
}
89+
90+
replaceLines (startRow, endRow, lines) {
91+
const lastRow = this.getLastRow()
92+
if (endRow > lastRow) {
93+
const lastLine = this.getLine(lastRow)
94+
this.doc.replaceRange(
95+
lines.join('\n'),
96+
{ line: startRow, ch: 0 },
97+
{ line: lastRow, ch: lastLine.length }
98+
)
99+
} else {
100+
this.doc.replaceRange(
101+
lines.join('\n') + '\n',
102+
{ line: startRow, ch: 0 },
103+
{ line: endRow, ch: 0 }
104+
)
105+
}
106+
}
107+
108+
transact (func) {
109+
this.transaction = true
110+
func()
111+
this.transaction = false
112+
if (this.onDidFinishTransaction) {
113+
this.onDidFinishTransaction.call(undefined)
114+
}
115+
}
116+
}
117+
118+
export function initTableEditor (editor) {
119+
// create an interface to the text editor
120+
const editorIntf = new TextEditorInterface(editor)
121+
// create a table editor object
122+
const tableEditor = new TableEditor(editorIntf)
123+
// options for the table editor
124+
const opts = options({
125+
smartCursor: true,
126+
formatType: FormatType.NORMAL
127+
})
128+
// keymap of the commands
129+
// from https://github.com/susisu/mte-demo/blob/master/src/main.js
130+
const keyMap = CodeMirror.normalizeKeyMap({
131+
Tab: () => { tableEditor.nextCell(opts) },
132+
'Shift-Tab': () => { tableEditor.previousCell(opts) },
133+
Enter: () => { tableEditor.nextRow(opts) },
134+
'Ctrl-Enter': () => { tableEditor.escape(opts) },
135+
'Cmd-Enter': () => { tableEditor.escape(opts) },
136+
'Shift-Ctrl-Left': () => { tableEditor.alignColumn(Alignment.LEFT, opts) },
137+
'Shift-Cmd-Left': () => { tableEditor.alignColumn(Alignment.LEFT, opts) },
138+
'Shift-Ctrl-Right': () => { tableEditor.alignColumn(Alignment.RIGHT, opts) },
139+
'Shift-Cmd-Right': () => { tableEditor.alignColumn(Alignment.RIGHT, opts) },
140+
'Shift-Ctrl-Up': () => { tableEditor.alignColumn(Alignment.CENTER, opts) },
141+
'Shift-Cmd-Up': () => { tableEditor.alignColumn(Alignment.CENTER, opts) },
142+
'Shift-Ctrl-Down': () => { tableEditor.alignColumn(Alignment.NONE, opts) },
143+
'Shift-Cmd-Down': () => { tableEditor.alignColumn(Alignment.NONE, opts) },
144+
'Ctrl-Left': () => { tableEditor.moveFocus(0, -1, opts) },
145+
'Cmd-Left': () => { tableEditor.moveFocus(0, -1, opts) },
146+
'Ctrl-Right': () => { tableEditor.moveFocus(0, 1, opts) },
147+
'Cmd-Right': () => { tableEditor.moveFocus(0, 1, opts) },
148+
'Ctrl-Up': () => { tableEditor.moveFocus(-1, 0, opts) },
149+
'Cmd-Up': () => { tableEditor.moveFocus(-1, 0, opts) },
150+
'Ctrl-Down': () => { tableEditor.moveFocus(1, 0, opts) },
151+
'Cmd-Down': () => { tableEditor.moveFocus(1, 0, opts) },
152+
'Ctrl-K Ctrl-I': () => { tableEditor.insertRow(opts) },
153+
'Cmd-K Cmd-I': () => { tableEditor.insertRow(opts) },
154+
'Ctrl-L Ctrl-I': () => { tableEditor.deleteRow(opts) },
155+
'Cmd-L Cmd-I': () => { tableEditor.deleteRow(opts) },
156+
'Ctrl-K Ctrl-J': () => { tableEditor.insertColumn(opts) },
157+
'Cmd-K Cmd-J': () => { tableEditor.insertColumn(opts) },
158+
'Ctrl-L Ctrl-J': () => { tableEditor.deleteColumn(opts) },
159+
'Cmd-L Cmd-J': () => { tableEditor.deleteColumn(opts) },
160+
'Alt-Shift-Ctrl-Left': () => { tableEditor.moveColumn(-1, opts) },
161+
'Alt-Shift-Cmd-Left': () => { tableEditor.moveColumn(-1, opts) },
162+
'Alt-Shift-Ctrl-Right': () => { tableEditor.moveColumn(1, opts) },
163+
'Alt-Shift-Cmd-Right': () => { tableEditor.moveColumn(1, opts) },
164+
'Alt-Shift-Ctrl-Up': () => { tableEditor.moveRow(-1, opts) },
165+
'Alt-Shift-Cmd-Up': () => { tableEditor.moveRow(-1, opts) },
166+
'Alt-Shift-Ctrl-Down': () => { tableEditor.moveRow(1, opts) },
167+
'Alt-Shift-Cmd-Down': () => { tableEditor.moveRow(1, opts) }
168+
})
169+
// enable keymap if the cursor is in a table
170+
function updateActiveState () {
171+
const tableTools = $('.toolbar .table-tools')
172+
const active = tableEditor.cursorIsInTable(opts)
173+
if (active) {
174+
tableTools.show()
175+
tableTools.parent().scrollLeft(tableTools.parent()[0].scrollWidth)
176+
editor.setOption('extraKeys', keyMap)
177+
} else {
178+
tableTools.hide()
179+
editor.setOption('extraKeys', null)
180+
tableEditor.resetSmartCursor()
181+
}
182+
}
183+
// event subscriptions
184+
editor.on('cursorActivity', () => {
185+
if (!editorIntf.transaction) {
186+
updateActiveState()
187+
}
188+
})
189+
editor.on('changes', () => {
190+
if (!editorIntf.transaction) {
191+
updateActiveState()
192+
}
193+
})
194+
editorIntf.onDidFinishTransaction = () => {
195+
updateActiveState()
196+
}
197+
198+
return tableEditor
199+
}

public/js/lib/editor/toolbar.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,47 @@
4444
<i class="fa fa-comment fa-fw"></i>
4545
</a>
4646
</div>
47+
<span class="btn-group table-tools hidden-xs" style="display: none;">
48+
<span class="separator" style="margin-left: -10px;">|</span>
49+
<span>Row</span>
50+
<a id="insertRow" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Insert Row">
51+
<i class="fa fa-plus-circle fa-fw"></i>
52+
</a>
53+
<a id="deleteRow" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Delete Row">
54+
<i class="fa fa-minus-circle fa-fw"></i>
55+
</a>
56+
<a id="moveRowUp" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Move Row Up">
57+
<i class="fa fa-long-arrow-up fa-fw"></i>
58+
</a>
59+
<a id="moveRowDown" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Move Row Down">
60+
<i class="fa fa-long-arrow-down fa-fw"></i>
61+
</a>
62+
<span>Column</span>
63+
<a id="insertColumn" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Insert Column">
64+
<i class="fa fa-plus-circle fa-fw"></i>
65+
</a>
66+
<a id="deleteColumn" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Delete Column">
67+
<i class="fa fa-minus-circle fa-fw"></i>
68+
</a>
69+
<a id="moveColumnLeft" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Move Column Left">
70+
<i class="fa fa-long-arrow-left fa-fw"></i>
71+
</a>
72+
<a id="moveColumnRight" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Move Column Right">
73+
<i class="fa fa-long-arrow-right fa-fw"></i>
74+
</a>
75+
<span>Alignment</span>
76+
<a id="alignLeft" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Align Left">
77+
<i class="fa fa-align-left fa-fw"></i>
78+
</a>
79+
<a id="alignCenter" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Align Center">
80+
<i class="fa fa-align-center fa-fw"></i>
81+
</a>
82+
<a id="alignRight" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Align Right">
83+
<i class="fa fa-align-right fa-fw"></i>
84+
</a>
85+
<a id="alignNone" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Align None">
86+
<i class="fa fa-ban fa-fw"></i>
87+
</a>
88+
</span>
4789
</div>
4890
</div>

0 commit comments

Comments
 (0)