-
Notifications
You must be signed in to change notification settings - Fork 16
/
list_cmds.ts
133 lines (118 loc) · 4.68 KB
/
list_cmds.ts
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
import { EditorState, Transaction, TextSelection } from "prosemirror-state"
import { canSplit } from "prosemirror-transform"
import { EditorView } from "prosemirror-view"
import { noteSchema } from "./schema"
import { Fragment, Slice } from "prosemirror-model"
// current logic:
// if 1) cursor at the end of title and 2) only one title
// then 1) create a top bullet_list as needed and 2) move cursor
//
// TODO future
// if 1) cursor at the end of title
// then 1) create a top bullet_list as needed
// 2) move cursor
export function createTopList(
state: EditorState<any>,
dispatch: (tr: Transaction<any>) => void,
view: EditorView<any>
): boolean {
if (
dispatch &&
// if only a title
// @ts-ignore
state.doc.content.content.length == 1 &&
// @ts-ignore
state.doc.content.content[0].type == noteSchema.nodes.h1 && // TODO 'title' later
// and selected nothing
state.selection.empty
// TODO and at the end of the title
// TODO how is this already working without this line
// state.selection.atEnd
) {
let bullet_list = noteSchema.nodes.bullet_list.create(
null,
noteSchema.nodes.list_item.create(
null,
noteSchema.nodes.paragraph.create()
)
)
let pos = state.selection.$from.pos
let tr = state.tr.insert(pos, bullet_list)
tr.setSelection(TextSelection.atEnd(tr.doc))
dispatch(tr.scrollIntoView())
return true
}
return false
}
// NOTE
// the default `splitListItem` from `prosemirror-schema-list`, uses `tr.split` to create a new <li>,
// which by default inherit all the attrs, including `block_id`
//
// this is a forked hijack to fix that
//
// related
// - https://discuss.prosemirror.net/t/confused-about-typesafter-in-split-and-how-to-disable-attrs-inheriting/2952
// - https://github.com/prosemirror/prosemirror-schema-list/blob/master/src/schema-list.js
//
// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Build a command that splits a non-empty textblock at the top level
// of a list item by also splitting that list item.
export function splitListItemAndStripAttrs(itemType) {
return function(state, dispatch) {
let {$from, $to, node} = state.selection
if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) return false
let grandParent = $from.node(-1)
if (grandParent.type != itemType) return false
if ($from.parent.content.size == 0) {
// In an empty block. If this is a nested list, the wrapping
// list item should be split. Otherwise, bail out and let next
// command handle lifting.
if ($from.depth == 2 || $from.node(-3).type != itemType ||
$from.index(-2) != $from.node(-2).childCount - 1) return false
if (dispatch) {
let wrap = Fragment.empty, keepItem = $from.index(-1) > 0
// Build a fragment containing empty versions of the structure
// from the outer list item to the parent node of the cursor
for (let d = $from.depth - (keepItem ? 1 : 2); d >= $from.depth - 3; d--)
wrap = Fragment.from($from.node(d).copy(wrap))
// Add a second list item with an empty default start node
wrap = wrap.append(Fragment.from(itemType.createAndFill()))
let tr = state.tr.replace($from.before(keepItem ? null : -1), $from.after(-3), new Slice(wrap, keepItem ? 3 : 2, 2))
tr.setSelection(state.selection.constructor.near(tr.doc.resolve($from.pos + (keepItem ? 3 : 2))))
dispatch(tr.scrollIntoView())
}
return true
}
let nextType = $to.pos == $from.end() ? grandParent.contentMatchAt(0).defaultType : null
let tr = state.tr.delete($from.pos, $to.pos)
let types = nextType && [null, {type: nextType}]
// NOTE
//
// still not sure about the logic behind this `nextType` and `types`
//
// seems that
// 1. cursor at the end of a paragraph
// 1. nextType is a `paragraph` (a NodeType)
// 2. cursor in the middle of a para
// 1. nextType is null
//
if (!canSplit(tr.doc, $from.pos, 2, types)) return false
// HACK
//
// NOTE
// I tried to *do the right thing* by modifying this `types` before `canSplit`,
// yet changing it caused `canSplit` to return false
// spent a day digging into the code,
// `validContent`, `matchFragment`, eventually `matchType`
// in the original version, the parameter in `matchType` is a `text`,
// yet in the hijacked one, it's a `list_item`
//
// stopped debugging, made a patch here, and it worked
//
types = types ?? []
types[0] = {type: itemType}
types[0]['attrs'] = {block_id: null}
if (dispatch) dispatch(tr.split($from.pos, 2, types).scrollIntoView())
return true
}
}