@dxos/react-ui-editor 0.8.2-main.fbd8ed0 → 0.8.2-staging.4d6ad0f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +1731 -926
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +3 -64
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/index.cjs +1912 -1111
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +3 -75
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +1731 -926
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +3 -64
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/util.d.ts +4 -6
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +21 -0
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +1 -0
- package/dist/types/src/{testing → components/Popover}/RefPopover.d.ts +1 -1
- package/dist/types/src/components/Popover/RefPopover.d.ts.map +1 -0
- package/dist/types/src/components/Popover/index.d.ts +3 -0
- package/dist/types/src/components/Popover/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +2 -5
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/annotations.d.ts +4 -1
- package/dist/types/src/extensions/annotations.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +1 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
- package/dist/types/src/extensions/command/command.d.ts +1 -2
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts +14 -2
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +2 -0
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +7 -8
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +1 -1
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/command/typeahead.d.ts +17 -0
- package/dist/types/src/extensions/command/typeahead.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +2 -12
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +4 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/json.d.ts +7 -0
- package/dist/types/src/extensions/json.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/{editorAction.d.ts → action.d.ts} +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts +2 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -2
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/commands.d.ts +9 -0
- package/dist/types/src/extensions/outliner/commands.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.d.ts +5 -0
- package/dist/types/src/extensions/outliner/editor.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/index.d.ts +3 -0
- package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts +10 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/selection.d.ts +12 -0
- package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.d.ts +79 -0
- package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts.map +1 -0
- package/dist/types/src/stories/Command.stories.d.ts +7 -0
- package/dist/types/src/stories/Command.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorComments.stories.d.ts → Comments.stories.d.ts} +3 -3
- package/dist/types/src/stories/Comments.stories.d.ts.map +1 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts +12 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorSpecial.stories.d.ts → Experimental.stories.d.ts} +3 -6
- package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Markdown.stories.d.ts +46 -0
- package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Outliner.stories.d.ts +26 -0
- package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Preview.stories.d.ts +10 -0
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorBasic.stories.d.ts → TextEditor.stories.d.ts} +9 -39
- package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{story-utils.d.ts → util.d.ts} +6 -6
- package/dist/types/src/stories/util.d.ts.map +1 -0
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -1
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts +2 -0
- package/dist/types/src/testing/util.d.ts.map +1 -0
- package/package.json +40 -34
- package/src/components/EditorToolbar/EditorToolbar.tsx +81 -57
- package/src/components/EditorToolbar/index.ts +7 -1
- package/src/components/EditorToolbar/util.ts +3 -4
- package/src/components/Popover/RefDropdownMenu.tsx +77 -0
- package/src/{testing → components/Popover}/RefPopover.tsx +5 -4
- package/src/components/Popover/index.ts +6 -0
- package/src/components/index.ts +1 -0
- package/src/defaults.ts +10 -13
- package/src/extensions/annotations.ts +41 -64
- package/src/extensions/autocomplete.ts +5 -6
- package/src/extensions/automerge/automerge.stories.tsx +2 -7
- package/src/extensions/automerge/automerge.test.tsx +3 -2
- package/src/extensions/automerge/sync.ts +3 -3
- package/src/extensions/awareness/awareness-provider.ts +4 -4
- package/src/extensions/awareness/awareness.ts +7 -7
- package/src/extensions/blast.ts +9 -9
- package/src/extensions/command/command.ts +1 -3
- package/src/extensions/command/hint.ts +7 -7
- package/src/extensions/command/index.ts +2 -0
- package/src/extensions/command/menu.ts +43 -49
- package/src/extensions/command/typeahead.ts +116 -0
- package/src/extensions/comments.ts +4 -69
- package/src/extensions/factories.ts +13 -0
- package/src/extensions/index.ts +2 -0
- package/src/extensions/json.ts +56 -0
- package/src/extensions/markdown/bundle.ts +13 -9
- package/src/extensions/markdown/decorate.ts +7 -7
- package/src/extensions/markdown/image.ts +2 -2
- package/src/extensions/markdown/index.ts +1 -2
- package/src/extensions/markdown/styles.ts +2 -1
- package/src/extensions/markdown/table.ts +3 -3
- package/src/extensions/outliner/commands.ts +242 -0
- package/src/extensions/outliner/editor.test.ts +33 -0
- package/src/extensions/outliner/editor.ts +180 -0
- package/src/extensions/outliner/index.ts +6 -0
- package/src/extensions/outliner/outliner.test.ts +99 -0
- package/src/extensions/outliner/outliner.ts +162 -0
- package/src/extensions/outliner/selection.ts +50 -0
- package/src/extensions/outliner/tree.test.ts +164 -0
- package/src/extensions/outliner/tree.ts +315 -0
- package/src/extensions/preview/preview.ts +5 -5
- package/src/stories/Command.stories.tsx +97 -0
- package/src/stories/{TextEditorComments.stories.tsx → Comments.stories.tsx} +13 -14
- package/src/{components/EditorToolbar → stories}/EditorToolbar.stories.tsx +26 -20
- package/src/stories/{TextEditorSpecial.stories.tsx → Experimental.stories.tsx} +9 -30
- package/src/stories/Markdown.stories.tsx +121 -0
- package/src/stories/Outliner.stories.tsx +108 -0
- package/src/stories/{TextEditorPreview.stories.tsx → Preview.stories.tsx} +46 -136
- package/src/stories/TextEditor.stories.tsx +256 -0
- package/src/stories/{story-utils.tsx → util.tsx} +21 -22
- package/src/styles/theme.ts +12 -5
- package/src/styles/tokens.ts +1 -2
- package/src/testing/index.ts +1 -1
- package/src/testing/util.ts +5 -0
- package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts +0 -53
- package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/comment.d.ts +0 -18
- package/dist/types/src/components/EditorToolbar/comment.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/editorAction.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/outliner.d.ts +0 -12
- package/dist/types/src/extensions/markdown/outliner.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts +0 -13
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +0 -1
- package/dist/types/src/stories/story-utils.d.ts.map +0 -1
- package/dist/types/src/testing/RefPopover.d.ts.map +0 -1
- package/src/components/EditorToolbar/comment.ts +0 -30
- package/src/extensions/markdown/outliner.ts +0 -235
- package/src/stories/TextEditorBasic.stories.tsx +0 -333
- /package/src/extensions/markdown/{editorAction.ts → action.ts} +0 -0
@@ -0,0 +1,315 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
6
|
+
import { StateField, type Transaction, type Extension, type EditorState } from '@codemirror/state';
|
7
|
+
import { Facet } from '@codemirror/state';
|
8
|
+
import { type SyntaxNode } from '@lezer/common';
|
9
|
+
|
10
|
+
import { invariant } from '@dxos/invariant';
|
11
|
+
|
12
|
+
import { type Range } from '../../types';
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Represents a single item in the tree.
|
16
|
+
*/
|
17
|
+
export interface Item {
|
18
|
+
type: 'root' | 'bullet' | 'task' | 'unknown';
|
19
|
+
index: number;
|
20
|
+
level: number;
|
21
|
+
node: SyntaxNode;
|
22
|
+
parent?: Item;
|
23
|
+
nextSibling?: Item;
|
24
|
+
prevSibling?: Item;
|
25
|
+
children: Item[];
|
26
|
+
/**
|
27
|
+
* Actual range.
|
28
|
+
* Starts at the start of the line containing the item and ends at the end of the line before the
|
29
|
+
* first child or next sibling.
|
30
|
+
*/
|
31
|
+
lineRange: Range;
|
32
|
+
/**
|
33
|
+
* Range of the editable content.
|
34
|
+
* This doesn't include the list or task marker or indentation.
|
35
|
+
*/
|
36
|
+
contentRange: Range;
|
37
|
+
}
|
38
|
+
|
39
|
+
export const itemToJSON = ({ type, index, level, lineRange, contentRange, children }: Item): any => {
|
40
|
+
return { type, index, level, lineRange, contentRange, children: children.map(itemToJSON) };
|
41
|
+
};
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Tree assumes the entire document is a single contiguous well-formed hierarchy of markdown LiteItem nodes.
|
45
|
+
*/
|
46
|
+
export class Tree implements Item {
|
47
|
+
type: Item['type'] = 'root';
|
48
|
+
index = -1;
|
49
|
+
level = -1;
|
50
|
+
node: Item['node'];
|
51
|
+
lineRange: Item['lineRange'];
|
52
|
+
contentRange: Item['contentRange'];
|
53
|
+
children: Item['children'] = [];
|
54
|
+
|
55
|
+
constructor(node: SyntaxNode) {
|
56
|
+
this.node = node;
|
57
|
+
this.lineRange = { from: node.from, to: node.to };
|
58
|
+
this.contentRange = this.lineRange;
|
59
|
+
}
|
60
|
+
|
61
|
+
toJSON() {
|
62
|
+
return itemToJSON(this);
|
63
|
+
}
|
64
|
+
|
65
|
+
get root(): Item {
|
66
|
+
return this;
|
67
|
+
}
|
68
|
+
|
69
|
+
traverse<T = any>(cb: (item: Item, level: number) => T | void): T | undefined;
|
70
|
+
traverse<T = any>(item: Item, cb: (item: Item, level: number) => T | void): T | undefined;
|
71
|
+
traverse<T = any>(
|
72
|
+
itemOrCb: Item | ((item: Item, level: number) => T | void),
|
73
|
+
maybeCb?: (item: Item, level: number) => T | void,
|
74
|
+
): T | undefined {
|
75
|
+
if (typeof itemOrCb === 'function') {
|
76
|
+
return traverse<T>(this, itemOrCb);
|
77
|
+
} else {
|
78
|
+
return traverse<T>(itemOrCb, maybeCb!);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Return the closest item.
|
84
|
+
*/
|
85
|
+
find(pos: number): Item | undefined {
|
86
|
+
return this.traverse((item) => (item.lineRange.from <= pos && item.lineRange.to >= pos ? item : undefined));
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Return the first child, next sibling, or parent's next sibling.
|
91
|
+
*/
|
92
|
+
next(item: Item, enter = true): Item | undefined {
|
93
|
+
if (enter && item.children.length > 0) {
|
94
|
+
return item.children[0];
|
95
|
+
}
|
96
|
+
|
97
|
+
if (item.nextSibling) {
|
98
|
+
return item.nextSibling;
|
99
|
+
}
|
100
|
+
|
101
|
+
if (item.parent) {
|
102
|
+
return this.next(item.parent, false);
|
103
|
+
}
|
104
|
+
|
105
|
+
return undefined;
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Return the previous sibling, or parent.
|
110
|
+
*/
|
111
|
+
prev(item: Item): Item | undefined {
|
112
|
+
if (item.prevSibling) {
|
113
|
+
return this.lastDescendant(item.prevSibling);
|
114
|
+
}
|
115
|
+
|
116
|
+
return item.parent?.type === 'root' ? undefined : item.parent;
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Return the last descendant of the item, or the item itself if it has no children.
|
121
|
+
*/
|
122
|
+
lastDescendant(item: Item): Item {
|
123
|
+
return item.children.length > 0 ? this.lastDescendant(item.children.at(-1)!) : item;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
export const getRange = (tree: Tree, item: Item): [number, number] => {
|
128
|
+
const lastDescendant = tree.lastDescendant(item);
|
129
|
+
return [item.lineRange.from, lastDescendant.lineRange.to];
|
130
|
+
};
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Traverse the tree, calling the callback for each item.
|
134
|
+
* If the callback returns a value, the traversal is stopped and the value is returned.
|
135
|
+
*/
|
136
|
+
export const traverse = <T = any>(root: Item, cb: (item: Item, level: number) => T | void): T | undefined => {
|
137
|
+
const t = (item: Item, level: number): T | undefined => {
|
138
|
+
if (item.type !== 'root') {
|
139
|
+
const value = cb(item, level);
|
140
|
+
if (value != null) {
|
141
|
+
return value;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
for (const child of item.children) {
|
146
|
+
const value = t(child, level + 1);
|
147
|
+
if (value != null) {
|
148
|
+
return value;
|
149
|
+
}
|
150
|
+
}
|
151
|
+
};
|
152
|
+
|
153
|
+
return t(root, root.type === 'root' ? -1 : 0);
|
154
|
+
};
|
155
|
+
|
156
|
+
export const getListItemContent = (state: EditorState, item: Item): string => {
|
157
|
+
return state.doc.sliceString(item.contentRange.from, item.contentRange.to);
|
158
|
+
};
|
159
|
+
|
160
|
+
export const listItemToString = (item: Item, level = 0) => {
|
161
|
+
const indent = ' '.repeat(level);
|
162
|
+
const data = {
|
163
|
+
i: item.index,
|
164
|
+
n: item.nextSibling?.index ?? '∅',
|
165
|
+
p: item.prevSibling?.index ?? '∅',
|
166
|
+
level: item.level,
|
167
|
+
node: format([item.node.from, item.node.to]),
|
168
|
+
line: format([item.lineRange.from, item.lineRange.to]),
|
169
|
+
content: format([item.contentRange.from, item.contentRange.to]),
|
170
|
+
};
|
171
|
+
|
172
|
+
return `${indent}${item.type[0].toUpperCase()}(${Object.entries(data)
|
173
|
+
.map(([k, v]) => `${k}=${v}`)
|
174
|
+
.join(', ')})`;
|
175
|
+
};
|
176
|
+
|
177
|
+
const format = (value: any) =>
|
178
|
+
JSON.stringify(value, (key: string, value: any) => {
|
179
|
+
if (typeof value === 'number') {
|
180
|
+
return value.toString().padStart(3, ' ');
|
181
|
+
}
|
182
|
+
return value;
|
183
|
+
}).replaceAll('"', '');
|
184
|
+
|
185
|
+
export const treeFacet = Facet.define<Tree, Tree>({
|
186
|
+
combine: (values) => values[0],
|
187
|
+
});
|
188
|
+
|
189
|
+
export type TreeOptions = {};
|
190
|
+
|
191
|
+
/**
|
192
|
+
* Creates a shadow tree of `ListItem` nodes whenever the document changes.
|
193
|
+
* This adds overhead relative to the markdown AST, but allows for efficient traversal of the list items.
|
194
|
+
* NOTE: Requires markdown parser to be enabled.
|
195
|
+
*/
|
196
|
+
export const outlinerTree = (options: TreeOptions = {}): Extension => {
|
197
|
+
const buildTree = (state: EditorState): Tree => {
|
198
|
+
let tree: Tree | undefined;
|
199
|
+
let parent: Item | undefined;
|
200
|
+
let current: Item | undefined;
|
201
|
+
let prev: Item | undefined;
|
202
|
+
let level = -1;
|
203
|
+
let index = -1;
|
204
|
+
|
205
|
+
// Array to track previous siblings at each level.
|
206
|
+
const prevSiblings: (Item | undefined)[] = [];
|
207
|
+
|
208
|
+
syntaxTree(state).iterate({
|
209
|
+
enter: (node) => {
|
210
|
+
switch (node.name) {
|
211
|
+
case 'Document': {
|
212
|
+
tree = new Tree(node.node);
|
213
|
+
current = tree;
|
214
|
+
break;
|
215
|
+
}
|
216
|
+
case 'BulletList': {
|
217
|
+
invariant(current);
|
218
|
+
parent = current;
|
219
|
+
if (current) {
|
220
|
+
current.lineRange.to = current.node.from;
|
221
|
+
}
|
222
|
+
prevSiblings[++level] = undefined;
|
223
|
+
break;
|
224
|
+
}
|
225
|
+
case 'ListItem': {
|
226
|
+
invariant(parent);
|
227
|
+
|
228
|
+
// Include all content up to the next sibling or the end of the document.
|
229
|
+
const nextSibling = node.node.nextSibling ?? node.node.parent?.nextSibling;
|
230
|
+
const docRange: Range = {
|
231
|
+
from: state.doc.lineAt(node.from).from,
|
232
|
+
to: nextSibling ? nextSibling.from - 1 : state.doc.length,
|
233
|
+
};
|
234
|
+
|
235
|
+
current = {
|
236
|
+
type: 'unknown',
|
237
|
+
index: ++index,
|
238
|
+
level,
|
239
|
+
node: node.node,
|
240
|
+
lineRange: docRange,
|
241
|
+
contentRange: { ...docRange },
|
242
|
+
parent,
|
243
|
+
prevSibling: prevSiblings[level],
|
244
|
+
children: [],
|
245
|
+
};
|
246
|
+
|
247
|
+
// Update sibling refs.
|
248
|
+
if (current.prevSibling) {
|
249
|
+
current.prevSibling.nextSibling = current;
|
250
|
+
}
|
251
|
+
|
252
|
+
// Update previous siblings array at current level.
|
253
|
+
prevSiblings[level] = current;
|
254
|
+
|
255
|
+
// Update previous item (not sibling).
|
256
|
+
if (prev) {
|
257
|
+
prev.lineRange.to = prev.contentRange.to = current.lineRange.from - 1;
|
258
|
+
}
|
259
|
+
prev = current;
|
260
|
+
|
261
|
+
// Update parent.
|
262
|
+
parent.children.push(current);
|
263
|
+
if (parent.lineRange.to === parent.node.from) {
|
264
|
+
parent.lineRange.to = parent.contentRange.to = current.lineRange.from - 1;
|
265
|
+
}
|
266
|
+
|
267
|
+
break;
|
268
|
+
}
|
269
|
+
case 'ListMark': {
|
270
|
+
invariant(current);
|
271
|
+
current.type = 'bullet';
|
272
|
+
current.contentRange.from = node.from + '- '.length;
|
273
|
+
break;
|
274
|
+
}
|
275
|
+
case 'Task': {
|
276
|
+
invariant(current);
|
277
|
+
current.type = 'task';
|
278
|
+
break;
|
279
|
+
}
|
280
|
+
case 'TaskMarker': {
|
281
|
+
invariant(current);
|
282
|
+
current.contentRange.from = node.from + '[ ] '.length;
|
283
|
+
break;
|
284
|
+
}
|
285
|
+
}
|
286
|
+
},
|
287
|
+
leave: (node) => {
|
288
|
+
if (node.name === 'BulletList') {
|
289
|
+
invariant(parent);
|
290
|
+
prevSiblings[level--] = undefined;
|
291
|
+
parent = parent.parent;
|
292
|
+
}
|
293
|
+
},
|
294
|
+
});
|
295
|
+
|
296
|
+
invariant(tree);
|
297
|
+
return tree;
|
298
|
+
};
|
299
|
+
|
300
|
+
return [
|
301
|
+
StateField.define<Tree | undefined>({
|
302
|
+
create: (state) => {
|
303
|
+
return buildTree(state);
|
304
|
+
},
|
305
|
+
update: (value: Tree | undefined, tr: Transaction) => {
|
306
|
+
if (!tr.docChanged) {
|
307
|
+
return value;
|
308
|
+
}
|
309
|
+
|
310
|
+
return buildTree(tr.state);
|
311
|
+
},
|
312
|
+
provide: (field) => treeFacet.from(field),
|
313
|
+
}),
|
314
|
+
];
|
315
|
+
};
|
@@ -180,14 +180,14 @@ class PreviewInlineWidget extends WidgetType {
|
|
180
180
|
// return false;
|
181
181
|
// }
|
182
182
|
|
183
|
-
override eq(other: this) {
|
183
|
+
override eq(other: this): boolean {
|
184
184
|
return this._link.ref === other._link.ref && this._link.label === other._link.label;
|
185
185
|
}
|
186
186
|
|
187
|
-
override toDOM(view: EditorView) {
|
187
|
+
override toDOM(view: EditorView): HTMLElement {
|
188
188
|
const root = document.createElement('dx-ref-tag');
|
189
189
|
root.textContent = this._link.label;
|
190
|
-
root.setAttribute('
|
190
|
+
root.setAttribute('refId', this._link.ref);
|
191
191
|
return root;
|
192
192
|
}
|
193
193
|
}
|
@@ -208,11 +208,11 @@ class PreviewBlockWidget extends WidgetType {
|
|
208
208
|
// return true;
|
209
209
|
// }
|
210
210
|
|
211
|
-
override eq(other: this) {
|
211
|
+
override eq(other: this): boolean {
|
212
212
|
return this._link.ref === other._link.ref;
|
213
213
|
}
|
214
214
|
|
215
|
-
override toDOM(view: EditorView) {
|
215
|
+
override toDOM(view: EditorView): HTMLDivElement {
|
216
216
|
const root = document.createElement('div');
|
217
217
|
root.classList.add('cm-preview-block');
|
218
218
|
|
@@ -0,0 +1,97 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import '@dxos-theme';
|
6
|
+
|
7
|
+
import React, { useState, type KeyboardEvent } from 'react';
|
8
|
+
|
9
|
+
import { Button, Icon, Input, DropdownMenu } from '@dxos/react-ui';
|
10
|
+
import { mx } from '@dxos/react-ui-theme';
|
11
|
+
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
12
|
+
|
13
|
+
import { EditorStory } from './util';
|
14
|
+
import { RefDropdownMenu } from '../components';
|
15
|
+
import { editorWidth } from '../defaults';
|
16
|
+
import { command, type Action, floatingMenu } from '../extensions';
|
17
|
+
import { str } from '../testing';
|
18
|
+
import { createRenderer } from '../util';
|
19
|
+
|
20
|
+
const CommandDialog = ({ onAction }: { onAction: (action?: Action) => void }) => {
|
21
|
+
const [text, setText] = useState('');
|
22
|
+
|
23
|
+
const handleInsert = () => {
|
24
|
+
// TODO(burdon): Use queue ref.
|
25
|
+
const link = `[${text}](dxn:queue:data:123)`;
|
26
|
+
onAction(text.length ? { type: 'insert', text: link } : undefined);
|
27
|
+
};
|
28
|
+
|
29
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
30
|
+
switch (event.key) {
|
31
|
+
case 'Enter': {
|
32
|
+
handleInsert();
|
33
|
+
break;
|
34
|
+
}
|
35
|
+
case 'Escape': {
|
36
|
+
onAction();
|
37
|
+
break;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
};
|
41
|
+
|
42
|
+
return (
|
43
|
+
<div className='flex w-full justify-center'>
|
44
|
+
<div
|
45
|
+
className={mx(
|
46
|
+
'flex w-full p-2 gap-2 items-center bg-modalSurface border border-separator rounded-md',
|
47
|
+
editorWidth,
|
48
|
+
)}
|
49
|
+
>
|
50
|
+
<Input.Root>
|
51
|
+
<Input.TextInput
|
52
|
+
autoFocus={true}
|
53
|
+
placeholder='Ask a question...'
|
54
|
+
value={text}
|
55
|
+
onChange={(ev) => setText(ev.target.value)}
|
56
|
+
onKeyDown={handleKeyDown}
|
57
|
+
/>
|
58
|
+
</Input.Root>
|
59
|
+
<Button variant='ghost' classNames='pli-0' onClick={() => onAction({ type: 'cancel' })}>
|
60
|
+
<Icon icon='ph--x--regular' size={5} />
|
61
|
+
</Button>
|
62
|
+
</div>
|
63
|
+
</div>
|
64
|
+
);
|
65
|
+
};
|
66
|
+
|
67
|
+
const meta: Meta<typeof EditorStory> = {
|
68
|
+
title: 'ui/react-ui-editor/Command',
|
69
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
70
|
+
render: () => (
|
71
|
+
<RefDropdownMenu.Provider>
|
72
|
+
<EditorStory
|
73
|
+
text={str('# Command', '', '', '')}
|
74
|
+
extensions={[
|
75
|
+
floatingMenu(),
|
76
|
+
command({
|
77
|
+
renderDialog: createRenderer(CommandDialog),
|
78
|
+
onHint: () => 'Press / for commands.',
|
79
|
+
}),
|
80
|
+
]}
|
81
|
+
/>
|
82
|
+
<DropdownMenu.Portal>
|
83
|
+
<DropdownMenu.Content>
|
84
|
+
<DropdownMenu.Viewport>
|
85
|
+
<DropdownMenu.Item onClick={() => console.log('!')}>Test</DropdownMenu.Item>
|
86
|
+
</DropdownMenu.Viewport>
|
87
|
+
<DropdownMenu.Arrow />
|
88
|
+
</DropdownMenu.Content>
|
89
|
+
</DropdownMenu.Portal>
|
90
|
+
</RefDropdownMenu.Provider>
|
91
|
+
),
|
92
|
+
parameters: { layout: 'fullscreen' },
|
93
|
+
};
|
94
|
+
|
95
|
+
export default meta;
|
96
|
+
|
97
|
+
export const Default = {};
|
@@ -9,17 +9,19 @@ import React, { type FC } from 'react';
|
|
9
9
|
|
10
10
|
import { keySymbols, parseShortcut } from '@dxos/keyboard';
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
12
|
+
import { log } from '@dxos/log';
|
12
13
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
13
14
|
|
14
|
-
import {
|
15
|
+
import { EditorStory, content, longText } from './util';
|
15
16
|
import { annotations, comments, createExternalCommentSync } from '../extensions';
|
17
|
+
import { str } from '../testing';
|
16
18
|
import { type Comment } from '../types';
|
17
19
|
import { createRenderer } from '../util';
|
18
20
|
|
19
|
-
const meta: Meta<typeof
|
20
|
-
title: 'ui/react-ui-editor/
|
21
|
+
const meta: Meta<typeof EditorStory> = {
|
22
|
+
title: 'ui/react-ui-editor/Comments',
|
21
23
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
22
|
-
render:
|
24
|
+
render: EditorStory,
|
23
25
|
parameters: { layout: 'fullscreen' },
|
24
26
|
};
|
25
27
|
|
@@ -33,7 +35,7 @@ export const Comments = {
|
|
33
35
|
render: () => {
|
34
36
|
const _comments = useSignal<Comment[]>([]);
|
35
37
|
return (
|
36
|
-
<
|
38
|
+
<EditorStory
|
37
39
|
text={str('# Comments', '', content.paragraphs, content.footer)}
|
38
40
|
extensions={[
|
39
41
|
createExternalCommentSync(
|
@@ -52,14 +54,11 @@ export const Comments = {
|
|
52
54
|
onSelect: (state) => {
|
53
55
|
const debug = false;
|
54
56
|
if (debug) {
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
closest: state.selection.closest?.slice(0, 8),
|
61
|
-
}),
|
62
|
-
);
|
57
|
+
log.info('update', {
|
58
|
+
comments: state.comments.length,
|
59
|
+
active: state.selection.current?.slice(0, 8),
|
60
|
+
closest: state.selection.closest?.slice(0, 8),
|
61
|
+
});
|
63
62
|
}
|
64
63
|
},
|
65
64
|
}),
|
@@ -94,6 +93,6 @@ const CommentTooltip: FC<{ shortcut: string }> = ({ shortcut }) => {
|
|
94
93
|
|
95
94
|
export const Annotations = {
|
96
95
|
render: () => (
|
97
|
-
<
|
96
|
+
<EditorStory text={str('# Annotations', '', longText)} extensions={[annotations({ match: /volup/gi })]} />
|
98
97
|
),
|
99
98
|
};
|
@@ -4,27 +4,29 @@
|
|
4
4
|
|
5
5
|
import '@dxos-theme';
|
6
6
|
|
7
|
+
import { type StoryObj } from '@storybook/react';
|
7
8
|
import React, { useCallback, useState } from 'react';
|
8
9
|
|
9
10
|
import { invariant } from '@dxos/invariant';
|
10
11
|
import { useThemeContext } from '@dxos/react-ui';
|
11
12
|
import { attentionSurface, mx } from '@dxos/react-ui-theme';
|
12
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
13
|
+
import { type Meta, withLayout, withTheme } from '@dxos/storybook-utils';
|
13
14
|
|
14
|
-
import { EditorToolbar, useEditorToolbarState } from '
|
15
|
+
import { EditorToolbar, useEditorToolbarState } from '../components';
|
16
|
+
import { editorWidth } from '../defaults';
|
15
17
|
import {
|
16
18
|
type EditorInputMode,
|
17
|
-
|
19
|
+
type EditorViewMode,
|
20
|
+
InputModeExtensions,
|
18
21
|
createMarkdownExtensions,
|
19
|
-
formattingKeymap,
|
20
|
-
useFormattingState,
|
21
22
|
createBasicExtensions,
|
22
23
|
createThemeExtensions,
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
import
|
24
|
+
decorateMarkdown,
|
25
|
+
formattingKeymap,
|
26
|
+
useFormattingState,
|
27
|
+
} from '../extensions';
|
28
|
+
import { useTextEditor, type UseTextEditorProps } from '../hooks';
|
29
|
+
import translations from '../translations';
|
28
30
|
|
29
31
|
type StoryProps = { placeholder?: string } & UseTextEditorProps;
|
30
32
|
|
@@ -67,24 +69,28 @@ const DefaultStory = ({ autoFocus, initialValue, placeholder }: StoryProps) => {
|
|
67
69
|
return (
|
68
70
|
<div role='none' className={mx('fixed inset-0 flex flex-col')}>
|
69
71
|
{toolbarState && <EditorToolbar state={toolbarState} getView={getView} viewMode={handleViewModeChange} />}
|
70
|
-
<div role='none' className='grow overflow-hidden'>
|
71
|
-
<div className={
|
72
|
+
<div role='none' className={mx('grow overflow-hidden', attentionSurface)}>
|
73
|
+
<div className={mx(editorWidth)} ref={parentRef} />
|
72
74
|
</div>
|
73
75
|
</div>
|
74
76
|
);
|
75
77
|
};
|
76
78
|
|
77
|
-
|
79
|
+
const meta: Meta<StoryProps> = {
|
80
|
+
title: 'ui/react-ui-editor/EditorToolbar',
|
81
|
+
render: DefaultStory,
|
82
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
83
|
+
parameters: { translations, layout: 'fullscreen' },
|
84
|
+
};
|
85
|
+
|
86
|
+
export default meta;
|
87
|
+
|
88
|
+
type Story = StoryObj<typeof meta>;
|
89
|
+
|
90
|
+
export const Default: Story = {
|
78
91
|
args: {
|
79
92
|
autoFocus: true,
|
80
93
|
placeholder: 'Text...',
|
81
94
|
initialValue: '# Demo\n\nThis is a document.\n\n',
|
82
95
|
},
|
83
96
|
};
|
84
|
-
|
85
|
-
export default {
|
86
|
-
title: 'ui/react-ui-editor/EditorToolbar',
|
87
|
-
render: DefaultStory,
|
88
|
-
decorators: [withTheme, withLayout({ fullscreen: true })],
|
89
|
-
parameters: { translations, layout: 'fullscreen' },
|
90
|
-
};
|
@@ -11,40 +11,19 @@ import { log } from '@dxos/log';
|
|
11
11
|
import { faker } from '@dxos/random';
|
12
12
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
13
13
|
|
14
|
-
import {
|
15
|
-
import { typewriter, blast, defaultOptions, dropFile
|
14
|
+
import { EditorStory, content } from './util';
|
15
|
+
import { typewriter, blast, defaultOptions, dropFile } from '../extensions';
|
16
|
+
import { str } from '../testing';
|
16
17
|
|
17
|
-
const meta: Meta<typeof
|
18
|
-
title: 'ui/react-ui-editor/
|
18
|
+
const meta: Meta<typeof EditorStory> = {
|
19
|
+
title: 'ui/react-ui-editor/Experimental',
|
19
20
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
20
|
-
render:
|
21
|
+
render: EditorStory,
|
21
22
|
parameters: { layout: 'fullscreen' },
|
22
23
|
};
|
23
24
|
|
24
25
|
export default meta;
|
25
26
|
|
26
|
-
//
|
27
|
-
// Listener
|
28
|
-
//
|
29
|
-
|
30
|
-
export const Listener = {
|
31
|
-
render: () => (
|
32
|
-
<DefaultStory
|
33
|
-
text={str('# Listener', '', content.footer)}
|
34
|
-
extensions={[
|
35
|
-
listener({
|
36
|
-
onFocus: (focusing) => {
|
37
|
-
console.log({ focusing });
|
38
|
-
},
|
39
|
-
onChange: (text) => {
|
40
|
-
console.log({ text });
|
41
|
-
},
|
42
|
-
}),
|
43
|
-
]}
|
44
|
-
/>
|
45
|
-
),
|
46
|
-
};
|
47
|
-
|
48
27
|
//
|
49
28
|
// Typewriter
|
50
29
|
//
|
@@ -53,7 +32,7 @@ const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewrite
|
|
53
32
|
|
54
33
|
export const Typewriter = {
|
55
34
|
render: () => (
|
56
|
-
<
|
35
|
+
<EditorStory
|
57
36
|
text={str('# Typewriter', '', content.paragraphs, content.footer)}
|
58
37
|
extensions={[typewriter({ items: typewriterItems })]}
|
59
38
|
/>
|
@@ -66,7 +45,7 @@ export const Typewriter = {
|
|
66
45
|
|
67
46
|
export const Blast = {
|
68
47
|
render: () => (
|
69
|
-
<
|
48
|
+
<EditorStory
|
70
49
|
text={str('# Blast', '', content.paragraphs, content.codeblocks, content.paragraphs)}
|
71
50
|
extensions={[
|
72
51
|
typewriter({ items: typewriterItems }),
|
@@ -93,7 +72,7 @@ export const Blast = {
|
|
93
72
|
|
94
73
|
export const DND = {
|
95
74
|
render: () => (
|
96
|
-
<
|
75
|
+
<EditorStory
|
97
76
|
text={str('# DND', '')}
|
98
77
|
extensions={[
|
99
78
|
dropFile({
|