-
Notifications
You must be signed in to change notification settings - Fork 178
Expand file tree
/
Copy pathinput.js
More file actions
206 lines (187 loc) · 7.68 KB
/
input.js
File metadata and controls
206 lines (187 loc) · 7.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// This file defines a number of helpers for wiring up user input to
// table-related functionality.
import {Slice, Fragment} from "prosemirror-model"
import {Selection, TextSelection} from "prosemirror-state"
import {keydownHandler} from "prosemirror-keymap"
import {key, nextCell, cellAround, inSameTable,
isInTable, selectionCell} from "./util"
import {CellSelection} from "./cellselection"
import {TableMap} from "./tablemap"
import {pastedCells, fitSlice, clipCells, insertCells} from "./copypaste"
import {tableNodeTypes} from "./schema"
export const handleKeyDown = keydownHandler({
"ArrowLeft": arrow("horiz", -1),
"ArrowRight": arrow("horiz", 1),
"ArrowUp": arrow("vert", -1),
"ArrowDown": arrow("vert", 1),
"Shift-ArrowLeft": shiftArrow("horiz", -1),
"Shift-ArrowRight": shiftArrow("horiz", 1),
"Shift-ArrowUp": shiftArrow("vert", -1),
"Shift-ArrowDown": shiftArrow("vert", 1),
"Backspace": deleteCellSelection,
"Mod-Backspace": deleteCellSelection,
"Delete": deleteCellSelection,
"Mod-Delete": deleteCellSelection
})
function maybeSetSelection(state, dispatch, selection) {
if (selection.eq(state.selection)) return false
if (dispatch) dispatch(state.tr.setSelection(selection).scrollIntoView())
return true
}
function arrow(axis, dir) {
return (state, dispatch, view) => {
let sel = state.selection
if (sel instanceof CellSelection) {
return maybeSetSelection(state, dispatch, Selection.near(sel.$headCell, dir))
}
if (axis != "horiz" && !sel.empty) return false
let end = atEndOfCell(view, axis, dir)
if (end == null) return false
if (axis == "horiz") {
return maybeSetSelection(state, dispatch, Selection.near(state.doc.resolve(sel.head + dir), dir))
} else {
let $cell = state.doc.resolve(end), $next = nextCell($cell, axis, dir), newSel
if ($next) newSel = Selection.near($next, 1)
else if (dir < 0) newSel = Selection.near(state.doc.resolve($cell.before(-1)), -1)
else newSel = Selection.near(state.doc.resolve($cell.after(-1)), 1)
return maybeSetSelection(state, dispatch, newSel)
}
}
}
function shiftArrow(axis, dir) {
return (state, dispatch, view) => {
let sel = state.selection
if (!(sel instanceof CellSelection)) {
let end = atEndOfCell(view, axis, dir)
if (end == null) return false
sel = new CellSelection(state.doc.resolve(end))
}
let $head = nextCell(sel.$headCell, axis, dir)
if (!$head) return false
return maybeSetSelection(state, dispatch, new CellSelection(sel.$anchorCell, $head))
}
}
function deleteCellSelection(state, dispatch) {
let sel = state.selection
if (!(sel instanceof CellSelection)) return false
if (dispatch) {
let tr = state.tr, baseContent = tableNodeTypes(state.schema).cell.createAndFill().content
sel.forEachCell((cell, pos) => {
if (!cell.content.eq(baseContent))
tr.replace(tr.mapping.map(pos + 1), tr.mapping.map(pos + cell.nodeSize - 1),
new Slice(baseContent, 0, 0))
})
if (tr.docChanged) dispatch(tr)
}
return true
}
export function handleTripleClick(view, pos) {
let doc = view.state.doc, $cell = cellAround(doc.resolve(pos))
if (!$cell) return false
view.dispatch(view.state.tr.setSelection(new CellSelection($cell)))
return true
}
export function handlePaste(view, _, slice) {
if (!isInTable(view.state)) return false
let cells = pastedCells(slice), sel = view.state.selection
if (sel instanceof CellSelection) {
if (!cells) cells = {width: 1, height: 1, rows: [Fragment.from(fitSlice(tableNodeTypes(view.state.schema).cell, slice))]}
let table = sel.$anchorCell.node(-1), start = sel.$anchorCell.start(-1)
let rect = TableMap.get(table).rectBetween(sel.$anchorCell.pos - start, sel.$headCell.pos - start)
cells = clipCells(cells, rect.right - rect.left, rect.bottom - rect.top)
insertCells(view.state, view.dispatch, start, rect, cells)
return true
} else if (cells) {
let $cell = selectionCell(view.state), start = $cell.start(-1)
insertCells(view.state, view.dispatch, start, TableMap.get($cell.node(-1)).findCell($cell.pos - start), cells)
return true
} else {
return false
}
}
export function handleMouseDown(view, startEvent) {
if (startEvent.ctrlKey ||
startEvent.metaKey ||
startEvent.which !== 1 /*not left click*/) {
return;
}
let startDOMCell = domInCell(view, startEvent.target), $anchor
if (startEvent.shiftKey && (view.state.selection instanceof CellSelection)) {
// Adding to an existing cell selection
setCellSelection(view.state.selection.$anchorCell, startEvent)
startEvent.preventDefault()
} else if (startEvent.shiftKey && startDOMCell &&
($anchor = cellAround(view.state.selection.$anchor)) != null &&
cellUnderMouse(view, startEvent).pos != $anchor.pos) {
// Adding to a selection that starts in another cell (causing a
// cell selection to be created).
setCellSelection($anchor, startEvent)
startEvent.preventDefault()
} else if (!startDOMCell) {
// Not in a cell, let the default behavior happen.
return
}
// Create and dispatch a cell selection between the given anchor and
// the position under the mouse.
function setCellSelection($anchor, event) {
let $head = cellUnderMouse(view, event)
let starting = key.getState(view.state) == null
if (!$head || !inSameTable($anchor, $head)) {
if (starting) $head = $anchor
else return
}
let selection = new CellSelection($anchor, $head)
if (starting || !view.state.selection.eq(selection)) {
let tr = view.state.tr.setSelection(selection)
if (starting) tr.setMeta(key, $anchor.pos)
view.dispatch(tr)
}
}
// Stop listening to mouse motion events.
function stop() {
view.root.removeEventListener("mouseup", stop)
view.root.removeEventListener("dragstart", stop)
view.root.removeEventListener("mousemove", move)
if (key.getState(view.state) != null) view.dispatch(view.state.tr.setMeta(key, -1))
}
function move(event) {
let anchor = key.getState(view.state), $anchor
if (anchor != null) {
// Continuing an existing cross-cell selection
$anchor = view.state.doc.resolve(anchor)
} else if (domInCell(view, event.target) != startDOMCell) {
// Moving out of the initial cell -- start a new cell selection
$anchor = cellUnderMouse(view, startEvent)
if (!$anchor) return stop()
}
if ($anchor) setCellSelection($anchor, event)
}
view.root.addEventListener("mouseup", stop)
view.root.addEventListener("dragstart", stop)
view.root.addEventListener("mousemove", move)
}
// Check whether the cursor is at the end of a cell (so that further
// motion would move out of the cell)
function atEndOfCell(view, axis, dir) {
if (!(view.state.selection instanceof TextSelection)) return null
let {$head} = view.state.selection
for (let d = $head.depth - 1; d >= 0; d--) {
let parent = $head.node(d), index = dir < 0 ? $head.index(d) : $head.indexAfter(d)
if (index != (dir < 0 ? 0 : parent.childCount)) return null
if (parent.type.spec.tableRole == "cell" || parent.type.spec.tableRole == "header_cell") {
let cellPos = $head.before(d)
let dirStr = axis == "vert" ? (dir > 0 ? "down" : "up") : (dir > 0 ? "right" : "left")
return view.endOfTextblock(dirStr) ? cellPos : null
}
}
return null
}
function domInCell(view, dom) {
for (; dom && dom != view.dom; dom = dom.parentNode)
if (dom.nodeName == "TD" || dom.nodeName == "TH") return dom
}
function cellUnderMouse(view, event) {
let mousePos = view.posAtCoords({left: event.clientX, top: event.clientY})
if (!mousePos) return null
return mousePos ? cellAround(view.state.doc.resolve(mousePos.pos)) : null
}