@dxos/ui-editor 0.0.0 → 0.8.4-main.05e74ebcff
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/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/browser/index.mjs +8657 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/types/index.mjs +33 -0
- package/dist/lib/browser/types/index.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +8659 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/types/index.mjs +35 -0
- package/dist/lib/node-esm/types/index.mjs.map +7 -0
- package/dist/types/src/defaults.d.ts +6 -0
- package/dist/types/src/defaults.d.ts.map +1 -0
- package/dist/types/src/extensions/annotations.d.ts +9 -0
- package/dist/types/src/extensions/annotations.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete/autocomplete.d.ts +17 -0
- package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
- package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
- package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete/placeholder.d.ts +23 -0
- package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
- package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts +4 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
- package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts +4 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/defs.d.ts +17 -0
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/index.d.ts +2 -0
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/sync.d.ts +17 -0
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +6 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts +31 -0
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness/awareness.d.ts +46 -0
- package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness/index.d.ts +3 -0
- package/dist/types/src/extensions/awareness/index.d.ts.map +1 -0
- package/dist/types/src/extensions/blast.d.ts +25 -0
- package/dist/types/src/extensions/blast.d.ts.map +1 -0
- package/dist/types/src/extensions/blocks.d.ts +2 -0
- package/dist/types/src/extensions/blocks.d.ts.map +1 -0
- package/dist/types/src/extensions/bookmarks.d.ts +12 -0
- package/dist/types/src/extensions/bookmarks.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +90 -0
- package/dist/types/src/extensions/comments.d.ts.map +1 -0
- package/dist/types/src/extensions/debug.d.ts +3 -0
- package/dist/types/src/extensions/debug.d.ts.map +1 -0
- package/dist/types/src/extensions/dnd.d.ts +9 -0
- package/dist/types/src/extensions/dnd.d.ts.map +1 -0
- package/dist/types/src/extensions/factories.d.ts +88 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -0
- package/dist/types/src/extensions/factories.test.d.ts +2 -0
- package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
- package/dist/types/src/extensions/focus.d.ts +7 -0
- package/dist/types/src/extensions/focus.d.ts.map +1 -0
- package/dist/types/src/extensions/folding.d.ts +6 -0
- package/dist/types/src/extensions/folding.d.ts.map +1 -0
- package/dist/types/src/extensions/hashtag.d.ts +3 -0
- package/dist/types/src/extensions/hashtag.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +30 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- 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/listener.d.ts +13 -0
- package/dist/types/src/extensions/listener.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/action.d.ts +12 -0
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts +25 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/changes.d.ts +10 -0
- package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/changes.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/changes.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/debug.d.ts +11 -0
- package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/decorate.d.ts +25 -0
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/formatting.d.ts +63 -0
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
- package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts +37 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/image.d.ts +7 -0
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts +10 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/link.d.ts +7 -0
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/parser.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/parser.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/styles.d.ts +4 -0
- package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/table.d.ts +8 -0
- package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
- package/dist/types/src/extensions/mention.d.ts +7 -0
- package/dist/types/src/extensions/mention.d.ts.map +1 -0
- package/dist/types/src/extensions/modal.d.ts +7 -0
- package/dist/types/src/extensions/modal.d.ts.map +1 -0
- package/dist/types/src/extensions/modes.d.ts +10 -0
- package/dist/types/src/extensions/modes.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/commands.d.ts +10 -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 +4 -0
- package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
- package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts +11 -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/extensions/preview/index.d.ts +2 -0
- package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
- package/dist/types/src/extensions/preview/preview.d.ts +34 -0
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
- package/dist/types/src/extensions/replacer.d.ts +21 -0
- package/dist/types/src/extensions/replacer.d.ts.map +1 -0
- package/dist/types/src/extensions/replacer.test.d.ts +2 -0
- package/dist/types/src/extensions/replacer.test.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/auto-scroll.d.ts +18 -0
- package/dist/types/src/extensions/scrolling/auto-scroll.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/crawler.d.ts +75 -0
- package/dist/types/src/extensions/scrolling/crawler.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/index.d.ts +5 -0
- package/dist/types/src/extensions/scrolling/index.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts +3 -0
- package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/scroller.d.ts +16 -0
- package/dist/types/src/extensions/scrolling/scroller.d.ts.map +1 -0
- package/dist/types/src/extensions/selection.d.ts +24 -0
- package/dist/types/src/extensions/selection.d.ts.map +1 -0
- package/dist/types/src/extensions/snippets.d.ts +10 -0
- package/dist/types/src/extensions/snippets.d.ts.map +1 -0
- package/dist/types/src/extensions/state.d.ts +2 -0
- package/dist/types/src/extensions/state.d.ts.map +1 -0
- package/dist/types/src/extensions/submit.d.ts +10 -0
- package/dist/types/src/extensions/submit.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/extended-markdown.d.ts +10 -0
- package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/extended-markdown.test.d.ts +2 -0
- package/dist/types/src/extensions/tags/extended-markdown.test.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/fader.d.ts +12 -0
- package/dist/types/src/extensions/tags/fader.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/index.d.ts +7 -0
- package/dist/types/src/extensions/tags/index.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
- package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
- package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
- package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
- package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/xml-tags.d.ts +117 -0
- package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/xml-util.d.ts +10 -0
- package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -0
- package/dist/types/src/extensions/tags/xml-util.test.d.ts +2 -0
- package/dist/types/src/extensions/tags/xml-util.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +8 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/styles/index.d.ts +2 -0
- package/dist/types/src/styles/index.d.ts.map +1 -0
- package/dist/types/src/styles/theme.d.ts +58 -0
- package/dist/types/src/styles/theme.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/types.d.ts +21 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/dist/types/src/util/cursor.d.ts +31 -0
- package/dist/types/src/util/cursor.d.ts.map +1 -0
- package/dist/types/src/util/debug.d.ts +17 -0
- package/dist/types/src/util/debug.d.ts.map +1 -0
- package/dist/types/src/util/decorations.d.ts +4 -0
- package/dist/types/src/util/decorations.d.ts.map +1 -0
- package/dist/types/src/util/dom.d.ts +10 -0
- package/dist/types/src/util/dom.d.ts.map +1 -0
- package/dist/types/src/util/facet.d.ts +3 -0
- package/dist/types/src/util/facet.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +7 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/src/util/util.d.ts +8 -0
- package/dist/types/src/util/util.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +43 -44
- package/src/defaults.ts +33 -20
- package/src/extensions/annotations.ts +1 -1
- package/src/extensions/autocomplete/placeholder.ts +37 -18
- package/src/extensions/automerge/automerge.test.tsx +37 -11
- package/src/extensions/automerge/automerge.ts +5 -7
- package/src/extensions/blocks.ts +5 -5
- package/src/extensions/comments.ts +5 -6
- package/src/extensions/dnd.ts +2 -2
- package/src/extensions/factories.test.ts +88 -0
- package/src/extensions/factories.ts +32 -15
- package/src/extensions/folding.ts +5 -22
- package/src/extensions/index.ts +2 -3
- package/src/extensions/markdown/action.ts +0 -1
- package/src/extensions/markdown/bundle.ts +23 -9
- package/src/extensions/markdown/decorate.ts +15 -12
- package/src/extensions/markdown/formatting.ts +5 -10
- package/src/extensions/markdown/highlight.ts +15 -7
- package/src/extensions/markdown/link.ts +27 -33
- package/src/extensions/markdown/parser.test.ts +0 -1
- package/src/extensions/markdown/styles.ts +42 -9
- package/src/extensions/markdown/table.ts +24 -2
- package/src/extensions/outliner/outliner.test.ts +0 -1
- package/src/extensions/outliner/outliner.ts +3 -4
- package/src/extensions/outliner/tree.test.ts +0 -1
- package/src/extensions/preview/preview.ts +62 -15
- package/src/extensions/scrolling/auto-scroll.ts +244 -0
- package/src/extensions/scrolling/crawler.ts +263 -0
- package/src/extensions/scrolling/index.ts +8 -0
- package/src/extensions/scrolling/scroll-past-end.ts +32 -0
- package/src/extensions/scrolling/scroller.ts +27 -0
- package/src/extensions/selection.ts +1 -1
- package/src/extensions/snippets.ts +67 -0
- package/src/extensions/tags/extended-markdown.test.ts +120 -2
- package/src/extensions/tags/extended-markdown.ts +80 -1
- package/src/extensions/tags/fader.ts +195 -0
- package/src/extensions/tags/index.ts +4 -1
- package/src/extensions/tags/testing/text.md +36 -0
- package/src/extensions/tags/testing/text.txt +35 -0
- package/src/extensions/tags/typewriter.test.ts +65 -0
- package/src/extensions/tags/typewriter.ts +594 -0
- package/src/extensions/tags/xml-block-decoration.ts +123 -0
- package/src/extensions/tags/xml-formatting.ts +125 -0
- package/src/extensions/tags/xml-tags.ts +186 -35
- package/src/extensions/tags/xml-util.test.ts +199 -24
- package/src/extensions/tags/xml-util.ts +62 -5
- package/src/index.ts +0 -1
- package/src/styles/index.ts +0 -2
- package/src/styles/theme.ts +125 -33
- package/src/types/types.ts +10 -2
- package/src/typings.d.ts +8 -0
- package/src/util/cursor.ts +1 -2
- package/src/extensions/autoscroll.ts +0 -165
- package/src/extensions/scrolling.ts +0 -189
- package/src/extensions/tags/streamer.ts +0 -243
- package/src/extensions/typewriter.ts +0 -68
- package/src/styles/markdown.ts +0 -26
- package/src/styles/tokens.ts +0 -17
|
@@ -8,7 +8,6 @@ import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate
|
|
|
8
8
|
import { mx } from '@dxos/ui-theme';
|
|
9
9
|
|
|
10
10
|
import { decorateMarkdown } from '../markdown';
|
|
11
|
-
|
|
12
11
|
import { commands } from './commands';
|
|
13
12
|
import { editor } from './editor';
|
|
14
13
|
import { menu } from './menu';
|
|
@@ -61,7 +60,7 @@ export const outliner = (_options: OutlinerProps = {}): Extension => [
|
|
|
61
60
|
decorateMarkdown({ listPaddingLeft: 8 }),
|
|
62
61
|
|
|
63
62
|
// Researve space for menu.
|
|
64
|
-
EditorView.contentAttributes.of({ class: '
|
|
63
|
+
EditorView.contentAttributes.of({ class: 'w-full !mr-[3rem]' }),
|
|
65
64
|
];
|
|
66
65
|
|
|
67
66
|
/**
|
|
@@ -157,10 +156,10 @@ const decorations = () => [
|
|
|
157
156
|
},
|
|
158
157
|
|
|
159
158
|
'.cm-list-item-focused': {
|
|
160
|
-
borderColor: 'var(--
|
|
159
|
+
borderColor: 'var(--color-focus-ring-subtle)',
|
|
161
160
|
},
|
|
162
161
|
'&:focus-within .cm-list-item-selected': {
|
|
163
|
-
borderColor: 'var(--
|
|
162
|
+
borderColor: 'var(--color-separator)',
|
|
164
163
|
},
|
|
165
164
|
}),
|
|
166
165
|
),
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { syntaxTree } from '@codemirror/language';
|
|
6
|
-
import { type EditorState, type Extension, RangeSetBuilder, StateField } from '@codemirror/state';
|
|
7
|
-
import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
|
|
6
|
+
import { type EditorState, type Extension, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';
|
|
7
|
+
import { Decoration, type DecorationSet, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
|
|
8
8
|
import { type SyntaxNode } from '@lezer/common';
|
|
9
9
|
|
|
10
|
+
import { type Database, DXN, Entity } from '@dxos/echo';
|
|
11
|
+
|
|
10
12
|
export type PreviewBlock = {
|
|
11
13
|
link: PreviewLinkRef;
|
|
12
14
|
el: HTMLElement;
|
|
@@ -16,7 +18,7 @@ export type PreviewLinkRef = {
|
|
|
16
18
|
suggest?: boolean;
|
|
17
19
|
block?: boolean;
|
|
18
20
|
label: string;
|
|
19
|
-
|
|
21
|
+
dxn: string;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export type PreviewLinkTarget = {
|
|
@@ -26,22 +28,28 @@ export type PreviewLinkTarget = {
|
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
export type PreviewOptions = {
|
|
31
|
+
db?: Database.Database;
|
|
29
32
|
addBlockContainer?: (block: PreviewBlock) => void;
|
|
30
33
|
removeBlockContainer?: (block: PreviewBlock) => void;
|
|
31
34
|
};
|
|
32
35
|
|
|
36
|
+
const labelResolvedEffect = StateEffect.define<void>();
|
|
37
|
+
|
|
33
38
|
/**
|
|
34
39
|
* Create preview decorations.
|
|
35
40
|
*/
|
|
36
41
|
export const preview = (options: PreviewOptions = {}): Extension => {
|
|
42
|
+
// Mutable ref so the StateField's onLoad callback can dispatch into the view.
|
|
43
|
+
const viewRef: { current: EditorView | undefined } = { current: undefined };
|
|
44
|
+
|
|
37
45
|
return [
|
|
38
46
|
// NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
|
|
39
47
|
// "Block decorations may not be specified via plugins".
|
|
40
48
|
StateField.define<DecorationSet>({
|
|
41
|
-
create: (state) => buildDecorations(state, options),
|
|
49
|
+
create: (state) => buildDecorations(state, options, viewRef),
|
|
42
50
|
update: (decorations, tr) => {
|
|
43
|
-
if (tr.docChanged) {
|
|
44
|
-
return buildDecorations(tr.state, options);
|
|
51
|
+
if (tr.docChanged || tr.effects.some((effect) => effect.is(labelResolvedEffect))) {
|
|
52
|
+
return buildDecorations(tr.state, options, viewRef);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
return decorations.map(tr.changes);
|
|
@@ -51,14 +59,51 @@ export const preview = (options: PreviewOptions = {}): Extension => {
|
|
|
51
59
|
EditorView.atomicRanges.of((view) => view.state.field(field)),
|
|
52
60
|
],
|
|
53
61
|
}),
|
|
62
|
+
ViewPlugin.define((view) => {
|
|
63
|
+
viewRef.current = view;
|
|
64
|
+
return {
|
|
65
|
+
destroy() {
|
|
66
|
+
viewRef.current = undefined;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
54
70
|
];
|
|
55
71
|
};
|
|
56
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a DXN to a display label using the database.
|
|
75
|
+
*/
|
|
76
|
+
const resolveLabel = (
|
|
77
|
+
db: Database.Database,
|
|
78
|
+
dxnStr: string,
|
|
79
|
+
viewRef: { current: EditorView | undefined },
|
|
80
|
+
): string | undefined => {
|
|
81
|
+
const dxn = DXN.tryParse(dxnStr);
|
|
82
|
+
if (!dxn) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ref = db.makeRef(dxn);
|
|
87
|
+
const target = ref.target;
|
|
88
|
+
if (target) {
|
|
89
|
+
return Entity.getLabel(target);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Object not loaded yet — schedule a decoration rebuild when it resolves.
|
|
93
|
+
void ref.tryLoad().then(() => {
|
|
94
|
+
viewRef.current?.dispatch({ effects: labelResolvedEffect.of(undefined) });
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
57
98
|
/**
|
|
58
99
|
* Echo references are represented as markdown reference links.
|
|
59
100
|
* https://www.markdownguide.org/basic-syntax/#reference-style-links
|
|
60
101
|
*/
|
|
61
|
-
const buildDecorations = (
|
|
102
|
+
const buildDecorations = (
|
|
103
|
+
state: EditorState,
|
|
104
|
+
options: PreviewOptions,
|
|
105
|
+
viewRef: { current: EditorView | undefined },
|
|
106
|
+
): DecorationSet => {
|
|
62
107
|
const builder = new RangeSetBuilder<Decoration>();
|
|
63
108
|
|
|
64
109
|
syntaxTree(state).iterate({
|
|
@@ -71,11 +116,13 @@ const buildDecorations = (state: EditorState, options: PreviewOptions): Decorati
|
|
|
71
116
|
case 'Link': {
|
|
72
117
|
const link = getLinkRef(state, node.node);
|
|
73
118
|
if (link) {
|
|
119
|
+
const resolved = options.db ? resolveLabel(options.db, link.dxn, viewRef) : undefined;
|
|
120
|
+
const displayLink = resolved ? { ...link, label: resolved } : link;
|
|
74
121
|
builder.add(
|
|
75
122
|
node.from,
|
|
76
123
|
node.to,
|
|
77
124
|
Decoration.replace({
|
|
78
|
-
widget: new PreviewInlineWidget(options,
|
|
125
|
+
widget: new PreviewInlineWidget(options, displayLink),
|
|
79
126
|
side: 1,
|
|
80
127
|
}),
|
|
81
128
|
);
|
|
@@ -119,13 +166,13 @@ export const getLinkRef = (state: EditorState, node: SyntaxNode): PreviewLinkRef
|
|
|
119
166
|
const mark = node.getChildren('LinkMark');
|
|
120
167
|
const urlNode = node.getChild('URL');
|
|
121
168
|
if (mark && urlNode) {
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
169
|
+
const dxn = state.sliceDoc(urlNode.from, urlNode.to);
|
|
170
|
+
if (dxn.startsWith('dxn:')) {
|
|
124
171
|
const label = state.sliceDoc(mark[0].to, mark[1].from);
|
|
125
172
|
return {
|
|
126
173
|
block: state.sliceDoc(mark[0].from, mark[0].from + 1) === '!',
|
|
127
174
|
label,
|
|
128
|
-
|
|
175
|
+
dxn,
|
|
129
176
|
};
|
|
130
177
|
}
|
|
131
178
|
}
|
|
@@ -148,14 +195,14 @@ class PreviewInlineWidget extends WidgetType {
|
|
|
148
195
|
// }
|
|
149
196
|
|
|
150
197
|
override eq(other: this) {
|
|
151
|
-
return this._link.
|
|
198
|
+
return this._link.dxn === other._link.dxn && this._link.label === other._link.label;
|
|
152
199
|
}
|
|
153
200
|
|
|
154
201
|
override toDOM(_view: EditorView) {
|
|
155
202
|
const root = document.createElement('dx-anchor');
|
|
156
203
|
root.classList.add('dx-tag--anchor');
|
|
157
204
|
root.textContent = this._link.label;
|
|
158
|
-
root.setAttribute('
|
|
205
|
+
root.setAttribute('dxn', this._link.dxn);
|
|
159
206
|
return root;
|
|
160
207
|
}
|
|
161
208
|
}
|
|
@@ -177,12 +224,12 @@ class PreviewBlockWidget extends WidgetType {
|
|
|
177
224
|
// }
|
|
178
225
|
|
|
179
226
|
override eq(other: this) {
|
|
180
|
-
return this._link.
|
|
227
|
+
return this._link.dxn === other._link.dxn;
|
|
181
228
|
}
|
|
182
229
|
|
|
183
230
|
override toDOM(_view: EditorView) {
|
|
184
231
|
const root = document.createElement('div');
|
|
185
|
-
root.classList.add('cm-preview-block', 'density-fine');
|
|
232
|
+
root.classList.add('cm-preview-block', 'dx-density-fine');
|
|
186
233
|
this._options.addBlockContainer?.({ link: this._link, el: root });
|
|
187
234
|
return root;
|
|
188
235
|
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { StateEffect } from '@codemirror/state';
|
|
6
|
+
import { EditorView, ViewPlugin } from '@codemirror/view';
|
|
7
|
+
|
|
8
|
+
import { addEventListener, combine, throttle } from '@dxos/async';
|
|
9
|
+
import { Domino } from '@dxos/ui';
|
|
10
|
+
import { getSize } from '@dxos/ui-theme';
|
|
11
|
+
|
|
12
|
+
import { crawlerActiveEffect, crawlerLineEffect } from './crawler';
|
|
13
|
+
|
|
14
|
+
/** Enable or disable autoscroll. */
|
|
15
|
+
export const autoScrollEffect = StateEffect.define<boolean>();
|
|
16
|
+
|
|
17
|
+
export type AutoScrollProps = {
|
|
18
|
+
/**
|
|
19
|
+
* If true, immediately jump to the bottom and re-pin whenever the size of the
|
|
20
|
+
* document view (the scroll container) changes — e.g. when a sidebar toggles
|
|
21
|
+
* or the window is resized. This avoids the visible "stuck near bottom" gap
|
|
22
|
+
* that otherwise appears because the previous `scrollTop` no longer reaches
|
|
23
|
+
* the new content height.
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
scrollOnResize?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extension that supports pinning the scroll position and automatically scrolls to the bottom when content is added.
|
|
31
|
+
*/
|
|
32
|
+
export const autoScroll = ({ scrollOnResize = true }: AutoScrollProps = {}) => {
|
|
33
|
+
let buttonContainer: HTMLDivElement | undefined;
|
|
34
|
+
let isPinned = true;
|
|
35
|
+
let jumpPending = false;
|
|
36
|
+
let enabled = true;
|
|
37
|
+
let firstUpdate = true;
|
|
38
|
+
|
|
39
|
+
const setPinned = (pinned: boolean) => {
|
|
40
|
+
buttonContainer?.classList.toggle('opacity-0', pinned);
|
|
41
|
+
isPinned = pinned;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
// Update listener for scrolling when content changes.
|
|
46
|
+
EditorView.updateListener.of((update) => {
|
|
47
|
+
const { view, heightChanged, state, startState } = update;
|
|
48
|
+
|
|
49
|
+
// Handle enable/disable effect.
|
|
50
|
+
for (const tr of update.transactions) {
|
|
51
|
+
for (const effect of tr.effects) {
|
|
52
|
+
if (effect.is(autoScrollEffect)) {
|
|
53
|
+
enabled = effect.value;
|
|
54
|
+
if (enabled) {
|
|
55
|
+
setPinned(true);
|
|
56
|
+
view.dispatch({
|
|
57
|
+
effects: crawlerActiveEffect.of(true),
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
view.dispatch({
|
|
61
|
+
effects: crawlerActiveEffect.of(false),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!enabled) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Jump to bottom instantly when content first appears (either inserted into
|
|
73
|
+
// an empty doc, or present as initialValue when the editor is created).
|
|
74
|
+
if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
|
|
75
|
+
firstUpdate = false;
|
|
76
|
+
jumpPending = true;
|
|
77
|
+
requestAnimationFrame(() => {
|
|
78
|
+
view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
|
|
79
|
+
jumpPending = false;
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
firstUpdate = false;
|
|
84
|
+
|
|
85
|
+
// Suppress crawl while the initial jump is pending.
|
|
86
|
+
if (jumpPending) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Maybe scroll if doc changed and pinned.
|
|
91
|
+
// NOTE: Geometry changed is triggered when widgets change height (e.g., toggle tool block).
|
|
92
|
+
if (heightChanged) {
|
|
93
|
+
if (isPinned) {
|
|
94
|
+
// NOTE: Use scroll geometry instead of coordsAtPos to avoid forced layout/scroll side-effects.
|
|
95
|
+
const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
|
|
96
|
+
const delta = scrollHeight - scrollTop - clientHeight;
|
|
97
|
+
if (delta > 0) {
|
|
98
|
+
setPinned(true);
|
|
99
|
+
view.dispatch({ effects: crawlerActiveEffect.of(true) });
|
|
100
|
+
} else if (delta < -1) {
|
|
101
|
+
setPinned(false);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// TODO(burdon): Should re-pin if content shrinks.
|
|
105
|
+
if (state.doc.length === 0) {
|
|
106
|
+
setPinned(true);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}),
|
|
111
|
+
|
|
112
|
+
// Re-pin and jump to bottom when the scroll container itself resizes (e.g. sidebar toggle,
|
|
113
|
+
// window resize). Doc-driven height changes are handled by the updateListener above; this
|
|
114
|
+
// observer covers the case where the viewport changes while the doc length is unchanged.
|
|
115
|
+
scrollOnResize
|
|
116
|
+
? ViewPlugin.fromClass(
|
|
117
|
+
class {
|
|
118
|
+
private readonly observer: ResizeObserver;
|
|
119
|
+
private firstObservation = true;
|
|
120
|
+
private destroyed = false;
|
|
121
|
+
constructor(view: EditorView) {
|
|
122
|
+
// Throttle so a continuous drag-resize (or a flurry of layout changes) coalesces
|
|
123
|
+
// into a single re-pin per ~50ms instead of dispatching every frame.
|
|
124
|
+
const onResize = throttle(() => {
|
|
125
|
+
if (this.destroyed || !enabled) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
setPinned(true);
|
|
130
|
+
requestAnimationFrame(() => {
|
|
131
|
+
if (this.destroyed) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
view.scrollDOM.scrollTo({ top: view.scrollDOM.scrollHeight, behavior: 'instant' });
|
|
136
|
+
view.dispatch({ effects: crawlerActiveEffect.of(false) });
|
|
137
|
+
});
|
|
138
|
+
}, 50);
|
|
139
|
+
|
|
140
|
+
this.observer = new ResizeObserver(() => {
|
|
141
|
+
// Skip the initial fire that ResizeObserver emits on `observe()`.
|
|
142
|
+
if (this.firstObservation) {
|
|
143
|
+
this.firstObservation = false;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
onResize();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.observer.observe(view.scrollDOM);
|
|
150
|
+
}
|
|
151
|
+
destroy() {
|
|
152
|
+
this.destroyed = true;
|
|
153
|
+
this.observer.disconnect();
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
: [],
|
|
158
|
+
|
|
159
|
+
// Detect user scroll and unpin (or re-pin if scrolled to the bottom).
|
|
160
|
+
ViewPlugin.fromClass(
|
|
161
|
+
class {
|
|
162
|
+
private readonly cleanup: () => void;
|
|
163
|
+
constructor(view: EditorView) {
|
|
164
|
+
// Re-pin check is throttled so the listener doesn't thrash while scrolling, but
|
|
165
|
+
// unpinning must be immediate — otherwise content arriving during the throttle
|
|
166
|
+
// window re-applies the crawl effect and yanks the viewport back to the bottom.
|
|
167
|
+
const onUserScroll = throttle(() => {
|
|
168
|
+
requestAnimationFrame(() => {
|
|
169
|
+
const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
|
|
170
|
+
const delta = scrollHeight - scrollTop - clientHeight;
|
|
171
|
+
// Sub-pixel tolerance: fractional scroll positions can leave delta at e.g. 0.5
|
|
172
|
+
// even when the user is visually at the bottom.
|
|
173
|
+
const pinned = Math.abs(delta) <= 1;
|
|
174
|
+
setPinned(pinned);
|
|
175
|
+
if (!pinned) {
|
|
176
|
+
view.dispatch({ effects: crawlerActiveEffect.of(false) });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}, 500);
|
|
180
|
+
|
|
181
|
+
this.cleanup = createUserScrollDetector(view.scrollDOM, () => {
|
|
182
|
+
if (isPinned) {
|
|
183
|
+
setPinned(false);
|
|
184
|
+
view.dispatch({ effects: crawlerActiveEffect.of(false) });
|
|
185
|
+
}
|
|
186
|
+
onUserScroll();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
destroy() {
|
|
190
|
+
this.cleanup();
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
),
|
|
194
|
+
|
|
195
|
+
// Scroll button.
|
|
196
|
+
ViewPlugin.fromClass(
|
|
197
|
+
class {
|
|
198
|
+
constructor(view: EditorView) {
|
|
199
|
+
const icon = Domino.of('dx-icon' as any)
|
|
200
|
+
.classNames(getSize(4))
|
|
201
|
+
.attributes({ icon: 'ph--arrow-down--regular' });
|
|
202
|
+
const button = Domino.of('button')
|
|
203
|
+
.classNames('dx-button bg-accent-surface')
|
|
204
|
+
.attributes({ 'data-density': 'fine' })
|
|
205
|
+
.append(icon)
|
|
206
|
+
.on('click', () => {
|
|
207
|
+
setPinned(true);
|
|
208
|
+
view.dispatch({
|
|
209
|
+
effects: crawlerLineEffect.of({ line: -1, position: 'end', behavior: 'smooth' }),
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
buttonContainer = Domino.of('div')
|
|
214
|
+
.classNames('cm-scroll-button transition-opacity duration-300 opacity-0')
|
|
215
|
+
.append(button).root as HTMLDivElement;
|
|
216
|
+
|
|
217
|
+
view.scrollDOM.parentElement!.appendChild(buttonContainer);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
),
|
|
221
|
+
];
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Attaches listeners to detect genuine user-initiated scrolling on an element.
|
|
226
|
+
* Two sources are covered:
|
|
227
|
+
* - `wheel`: fires only from physical mouse wheel / trackpad gestures.
|
|
228
|
+
* - `pointerdown` on the scrollbar gutter: detected by comparing clientX to
|
|
229
|
+
* the element's clientWidth (the content area, excluding the scrollbar).
|
|
230
|
+
* Returns a cleanup function that removes the listeners.
|
|
231
|
+
*/
|
|
232
|
+
// TODO(burdon): Still jumps when widgets are rendered.
|
|
233
|
+
// - Track position of specific element/line in document and scroll relative to that.
|
|
234
|
+
function createUserScrollDetector(element: HTMLElement, onUserScroll: () => void): () => void {
|
|
235
|
+
return combine(
|
|
236
|
+
addEventListener(element, 'wheel', () => onUserScroll(), { passive: true }),
|
|
237
|
+
addEventListener(element, 'pointerdown', (event) => {
|
|
238
|
+
// If the pointer lands beyond the content width it hit the scrollbar gutter.
|
|
239
|
+
if (event.clientX > element.getBoundingClientRect().right - (element.offsetWidth - element.clientWidth)) {
|
|
240
|
+
onUserScroll();
|
|
241
|
+
}
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
}
|