Repository navigation
Expand file tree
/
Copy pathcursorUp.tsx
More file actions
135 lines (118 loc) · 5.47 KB
/
Copy pathcursorUp.tsx
File metadata and controls
135 lines (118 loc) · 5.47 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
import Command from '../@types/Command'
import Dispatch from '../@types/Dispatch'
import State from '../@types/State'
import { addMulticursorActionCreator as addMulticursor } from '../actions/addMulticursor'
import { cursorUpActionCreator as cursorUp } from '../actions/cursorUp'
import { removeMulticursorActionCreator as removeMulticursor } from '../actions/removeMulticursor'
import { setCursorActionCreator as setCursor } from '../actions/setCursor'
import CursorUpIcon from '../components/icons/CursorUp'
import { HOME_PATH, HOME_TOKEN } from '../constants'
import * as selection from '../device/selection'
import attributeEquals from '../selectors/attributeEquals'
import documentSort from '../selectors/documentSort'
import { getChildrenSorted } from '../selectors/getChildren'
import hasMulticursor from '../selectors/hasMulticursor'
import isMulticursorPath from '../selectors/isMulticursorPath'
import isTableCol2 from '../selectors/isTableCol2'
import prevSibling from '../selectors/prevSibling'
import prevTableCousin from '../selectors/prevTableCousin'
import rootedParentOf from '../selectors/rootedParentOf'
import appendToPath from '../util/appendToPath'
import head from '../util/head'
import isRoot from '../util/isRoot'
import parentOf from '../util/parentOf'
import throttleByAnimationFrame from '../util/throttleByAnimationFrame'
const cursorUpCommand = {
id: 'cursorUp',
label: 'Cursor Up' as const,
longDescription: (
<>
<p>Move the cursor to the previous thought at the same level.</p>
<p>Use Cursor Up to walk through siblings — items in a list, days in a week — without changing depth.</p>
<p>Hold shift while pressing Up to extend the selection through neighbouring thoughts.</p>
</>
),
keyboard: [{ key: 'ArrowUp' }, { key: 'ArrowUp', shift: true }],
hideFromHelp: true,
multicursor: false,
svg: CursorUpIcon,
canExecute: state => {
const { cursor } = state
if (!cursor) return true
// use default browser behavior in prose mode
const parentId = head(rootedParentOf(state, cursor))
const isProseView = attributeEquals(state, parentId, '=view', 'Prose')
const isProseMode = isProseView && selection.offset()! > 0
if (isProseMode) return false
// use default browser if selection is on the second or greater line of a multi-line editable
return selection.isOnFirstLine()
},
exec: throttleByAnimationFrame((dispatch: Dispatch, getState: () => State, e: KeyboardEvent) => {
if (e.shiftKey) {
const state = getState()
const isMulticursorEmpty = !hasMulticursor(state)
const isCurrentCursorMulticursor = state.cursor && isMulticursorPath(state, state.cursor)
const { cursor } = state
const path = cursor || HOME_PATH
const pathParent = rootedParentOf(state, path)
const prevThought = cursor
? // if cursor exists, get the previous sibling
prevSibling(state, cursor)
: // otherwise, get the last thought in the home context
getChildrenSorted(state, HOME_TOKEN).slice(-1)[0]
const prevPath =
// in the second column of a table view, extend to the previous thought at the same depth (the previous cousin), crossing col1 row boundaries instead of falling through to the col1 parent
cursor && isTableCol2(state, cursor)
? prevTableCousin(state, cursor)
: prevThought
? // non-first child path
appendToPath(parentOf(path), prevThought.id)
: // when the cursor is on the first child in a context, move up a level
!isRoot(pathParent)
? pathParent
: null
// if there is no previous path, do nothing
if (!prevPath) return
const isPrevPathMulticursor = prevPath && isMulticursorPath(state, prevPath)
dispatch([
// Update the multicursor before moving the cursor, since setCursor computes state.expanded and a
// selected thought must not expand its own children.
// https://github.com/cybersemics/em/issues/4738
dispatch => {
// New multicursor set
if (isMulticursorEmpty) {
// Add the current cursor to the multicursor, if it exists
if (state.cursor) {
dispatch(addMulticursor({ path: state.cursor }))
}
// Add the previous cursor to the multicursor, if it exists
if (prevPath) {
dispatch(addMulticursor({ path: prevPath }))
}
return
}
// Extend the multicursor set to the previous cursor
if (isCurrentCursorMulticursor && !isPrevPathMulticursor && prevPath) {
dispatch(addMulticursor({ path: prevPath }))
return
}
// Remove the previous cursor from the multicursor set
if (isCurrentCursorMulticursor && isPrevPathMulticursor && state.cursor) {
dispatch(removeMulticursor({ path: state.cursor }))
return
}
},
setCursor({ path: prevPath, preserveMulticursor: true }),
])
requestAnimationFrame(() => {
selection.clear()
})
} else {
const state = getState()
const firstPath = hasMulticursor(state) ? documentSort(state, Object.values(state.multicursors))[0] : null
// when a multiselect is active, collapse it and move the cursor to the first selected thought in document order
dispatch(firstPath ? setCursor({ path: firstPath }) : cursorUp())
}
}),
} satisfies Command
export default cursorUpCommand