@dxos/ui-editor 0.0.0 → 0.8.4-main.1c7ec43d41
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 +8633 -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 +8635 -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/auto-scroll.d.ts +18 -0
- package/dist/types/src/extensions/auto-scroll.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 +32 -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/scroll-past-end.d.ts +3 -0
- package/dist/types/src/extensions/scroll-past-end.d.ts.map +1 -0
- package/dist/types/src/extensions/scroller.d.ts +68 -0
- package/dist/types/src/extensions/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 +42 -43
- package/src/defaults.ts +33 -20
- package/src/extensions/annotations.ts +1 -1
- package/src/extensions/auto-scroll.ts +234 -0
- 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 +4 -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/scroll-past-end.ts +32 -0
- package/src/extensions/scroller.ts +256 -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 +124 -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
|
@@ -18,7 +18,7 @@ export const formattingStyles = EditorView.theme({
|
|
|
18
18
|
width: '100%',
|
|
19
19
|
height: '0',
|
|
20
20
|
verticalAlign: 'middle',
|
|
21
|
-
borderTop: '1px solid var(--
|
|
21
|
+
borderTop: '1px solid var(--color-cm-separator)',
|
|
22
22
|
opacity: 0.5,
|
|
23
23
|
},
|
|
24
24
|
|
|
@@ -43,20 +43,45 @@ export const formattingStyles = EditorView.theme({
|
|
|
43
43
|
* Blockquote.
|
|
44
44
|
*/
|
|
45
45
|
'& .cm-blockquote': {
|
|
46
|
-
background: 'var(--
|
|
47
|
-
borderLeft: '2px solid var(--
|
|
46
|
+
background: 'var(--color-cm-codeblock)',
|
|
47
|
+
borderLeft: '2px solid var(--color-cm-separator)',
|
|
48
48
|
paddingLeft: '1rem',
|
|
49
|
-
margin:
|
|
49
|
+
margin: 0,
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Code and codeblocks.
|
|
54
54
|
*/
|
|
55
|
+
'& code': {
|
|
56
|
+
fontFamily: fontMono,
|
|
57
|
+
color: 'var(--color-cm-code)',
|
|
58
|
+
whiteSpace: 'nowrap',
|
|
59
|
+
},
|
|
55
60
|
'& .cm-code': {
|
|
56
61
|
fontFamily: fontMono,
|
|
62
|
+
color: 'var(--color-cm-code)',
|
|
63
|
+
},
|
|
64
|
+
// Inline code spans (triggered by backticks) use `cm-code-inline` + `font-mono`.
|
|
65
|
+
// Different monospace font metrics can slightly overflow the fixed CodeMirror line box,
|
|
66
|
+
// so constrain them to the target 24px height.
|
|
67
|
+
'& .cm-code-inline': {
|
|
68
|
+
fontFamily: fontMono,
|
|
69
|
+
height: '24px',
|
|
70
|
+
// display: 'inline-flex',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
overflow: 'hidden',
|
|
73
|
+
whiteSpace: 'nowrap',
|
|
74
|
+
color: 'var(--color-cm-code-inline)',
|
|
75
|
+
},
|
|
76
|
+
'& .cm-code-mark': {
|
|
77
|
+
fontFamily: fontMono,
|
|
78
|
+
height: '24px',
|
|
79
|
+
display: 'inline-flex',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
overflow: 'hidden',
|
|
57
82
|
},
|
|
58
83
|
'& .cm-codeblock-line': {
|
|
59
|
-
background: 'var(--
|
|
84
|
+
background: 'var(--color-cm-codeblock)',
|
|
60
85
|
paddingInline: '1rem !important',
|
|
61
86
|
},
|
|
62
87
|
'& .cm-codeblock-start': {
|
|
@@ -87,16 +112,24 @@ export const formattingStyles = EditorView.theme({
|
|
|
87
112
|
*/
|
|
88
113
|
'.cm-table *': {
|
|
89
114
|
fontFamily: fontMono,
|
|
115
|
+
lineHeight: 1.5,
|
|
90
116
|
textDecoration: 'none !important',
|
|
91
117
|
},
|
|
92
118
|
'.cm-table-head': {
|
|
93
119
|
padding: '2px 16px 2px 0px',
|
|
120
|
+
overflowWrap: 'break-word',
|
|
121
|
+
whiteSpace: 'pre-wrap',
|
|
122
|
+
wordBreak: 'keep-all',
|
|
94
123
|
textAlign: 'left',
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
color: 'var(--color-subdued)',
|
|
125
|
+
borderBottom: '1px solid var(--color-cm-separator)',
|
|
97
126
|
},
|
|
98
127
|
'.cm-table-cell': {
|
|
99
128
|
padding: '2px 16px 2px 0px',
|
|
129
|
+
overflowWrap: 'break-word',
|
|
130
|
+
whiteSpace: 'pre-wrap',
|
|
131
|
+
wordBreak: 'keep-all',
|
|
132
|
+
verticalAlign: 'top',
|
|
100
133
|
},
|
|
101
134
|
|
|
102
135
|
/**
|
|
@@ -113,12 +146,12 @@ export const formattingStyles = EditorView.theme({
|
|
|
113
146
|
},
|
|
114
147
|
'.cm-image-with-loader': {
|
|
115
148
|
display: 'block',
|
|
116
|
-
opacity:
|
|
149
|
+
opacity: 0,
|
|
117
150
|
transitionDuration: '350ms',
|
|
118
151
|
transitionProperty: 'opacity',
|
|
119
152
|
},
|
|
120
153
|
'.cm-image-with-loader.cm-loaded-image': {
|
|
121
|
-
opacity:
|
|
154
|
+
opacity: 1,
|
|
122
155
|
},
|
|
123
156
|
'.cm-image-wrapper': {
|
|
124
157
|
'grid-template-columns': '1fr',
|
|
@@ -107,6 +107,28 @@ const update = (state: EditorState, _options: TableOptions) => {
|
|
|
107
107
|
return builder.finish();
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
/** Renders cell text into el, processing inline markdown (bold, italic, code). */
|
|
111
|
+
const renderCellContent = (el: HTMLElement, text: string): void => {
|
|
112
|
+
const parts = text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*|__[^_\n]+__|\*[^*\n]+\*|_[^_\n]+_)/);
|
|
113
|
+
for (const part of parts) {
|
|
114
|
+
if (part.length > 2 && part.startsWith('`') && part.endsWith('`')) {
|
|
115
|
+
const code = document.createElement('code');
|
|
116
|
+
code.textContent = part.slice(1, -1);
|
|
117
|
+
el.appendChild(code);
|
|
118
|
+
} else if ((part.startsWith('**') && part.endsWith('**')) || (part.startsWith('__') && part.endsWith('__'))) {
|
|
119
|
+
const strong = document.createElement('strong');
|
|
120
|
+
strong.textContent = part.slice(2, -2);
|
|
121
|
+
el.appendChild(strong);
|
|
122
|
+
} else if ((part.startsWith('*') && part.endsWith('*')) || (part.startsWith('_') && part.endsWith('_'))) {
|
|
123
|
+
const em = document.createElement('em');
|
|
124
|
+
em.textContent = part.slice(1, -1);
|
|
125
|
+
el.appendChild(em);
|
|
126
|
+
} else {
|
|
127
|
+
el.appendChild(document.createTextNode(part));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
110
132
|
class TableWidget extends WidgetType {
|
|
111
133
|
constructor(readonly _table: Table) {
|
|
112
134
|
super();
|
|
@@ -132,7 +154,7 @@ class TableWidget extends WidgetType {
|
|
|
132
154
|
this._table.header?.forEach((cell) => {
|
|
133
155
|
const th = document.createElement('th');
|
|
134
156
|
th.setAttribute('class', 'cm-table-head');
|
|
135
|
-
tr.appendChild(th)
|
|
157
|
+
renderCellContent(tr.appendChild(th), cell);
|
|
136
158
|
});
|
|
137
159
|
|
|
138
160
|
const body = table.appendChild(document.createElement('tbody'));
|
|
@@ -141,7 +163,7 @@ class TableWidget extends WidgetType {
|
|
|
141
163
|
row.forEach((cell) => {
|
|
142
164
|
const td = document.createElement('td');
|
|
143
165
|
td.setAttribute('class', 'cm-table-cell');
|
|
144
|
-
tr.appendChild(td)
|
|
166
|
+
renderCellContent(tr.appendChild(td), cell);
|
|
145
167
|
});
|
|
146
168
|
});
|
|
147
169
|
|
|
@@ -8,7 +8,6 @@ import { describe, test } from 'vitest';
|
|
|
8
8
|
|
|
9
9
|
import { join } from '../../util';
|
|
10
10
|
import { createMarkdownExtensions } from '../markdown';
|
|
11
|
-
|
|
12
11
|
import { indentItemLess, indentItemMore, moveItemDown, moveItemUp } from './commands';
|
|
13
12
|
import { listItemToString, outlinerTree, treeFacet } from './tree';
|
|
14
13
|
|
|
@@ -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-neutral-focus-indicator)',
|
|
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,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Extension } from '@codemirror/state';
|
|
6
|
+
import { EditorView, ViewPlugin } from '@codemirror/view';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Custom scroll-past-end extension that accounts for the actual height of the last line.
|
|
10
|
+
* The built-in CodeMirror `scrollPastEnd` uses `defaultLineHeight` which doesn't account
|
|
11
|
+
* for taller elements like headings, block widgets, etc.
|
|
12
|
+
*/
|
|
13
|
+
const scrollPastEndPlugin = ViewPlugin.fromClass(
|
|
14
|
+
class {
|
|
15
|
+
height = 1000;
|
|
16
|
+
attrs: { style: string } | null = { style: 'padding-bottom: 1000px' };
|
|
17
|
+
|
|
18
|
+
update({ view }: { view: EditorView }) {
|
|
19
|
+
const lastLineBlock = view.lineBlockAt(view.state.doc.length);
|
|
20
|
+
const height = view.dom.clientHeight - lastLineBlock.height - view.documentPadding.top - 0.5;
|
|
21
|
+
if (height >= 0 && height !== this.height) {
|
|
22
|
+
this.height = height;
|
|
23
|
+
this.attrs = { style: `padding-bottom: ${height}px` };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export const scrollPastEnd = (): Extension => [
|
|
30
|
+
scrollPastEndPlugin,
|
|
31
|
+
EditorView.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?.attrs ?? null),
|
|
32
|
+
];
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { StateEffect } from '@codemirror/state';
|
|
6
|
+
import { EditorView, ViewPlugin } from '@codemirror/view';
|
|
7
|
+
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parameters for the scroll to line effect.
|
|
12
|
+
*/
|
|
13
|
+
export type ScrollToProps = {
|
|
14
|
+
/**
|
|
15
|
+
* Zero-based line number; -1 for end of document.
|
|
16
|
+
* NOTE: view.state.doc.lineAt() uses 1-based line numbers.
|
|
17
|
+
*/
|
|
18
|
+
line: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Additional offset from the target line in pixels.
|
|
22
|
+
* Positive values scroll past the line, negative values stop before it.
|
|
23
|
+
* @default 0
|
|
24
|
+
*/
|
|
25
|
+
offset?: number;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Position of the target line in the viewport.
|
|
29
|
+
* - 'start': Line appears at the start (top) of the screen
|
|
30
|
+
* - 'end': Line appears at the end (bottom) of the screen
|
|
31
|
+
* @default 'start'
|
|
32
|
+
*/
|
|
33
|
+
position?: 'start' | 'end';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether to use smooth scrolling.
|
|
37
|
+
* @default 'instant'
|
|
38
|
+
*/
|
|
39
|
+
behavior?: ScrollBehavior;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Scroll to a specific line. */
|
|
43
|
+
export const scrollerLineEffect = StateEffect.define<ScrollToProps>();
|
|
44
|
+
|
|
45
|
+
/** Start/stop crawling the end of the document. */
|
|
46
|
+
export const scrollerCrawlEffect = StateEffect.define<boolean>();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Helper function to scroll to a specific line.
|
|
50
|
+
* This is a convenience function that can be used directly with an EditorView.
|
|
51
|
+
*/
|
|
52
|
+
export const scrollToLine = (view: EditorView, options: ScrollToProps) => {
|
|
53
|
+
view.dispatch({
|
|
54
|
+
effects: scrollerLineEffect.of(options),
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ScrollerOptions = {
|
|
59
|
+
/** Threshold in px to trigger scroll from bottom. */
|
|
60
|
+
overScroll?: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extension that provides smooth scrolling to specific lines in the editor.
|
|
65
|
+
*/
|
|
66
|
+
export const scroller = ({ overScroll = 0 }: ScrollerOptions = {}) => {
|
|
67
|
+
// ViewPlugin to manage scroll animations.
|
|
68
|
+
const scrollPlugin = ViewPlugin.fromClass(
|
|
69
|
+
class ScrollerPlugin {
|
|
70
|
+
private readonly crawler: ReturnType<typeof createCrawler>;
|
|
71
|
+
constructor(private readonly view: EditorView) {
|
|
72
|
+
this.crawler = createCrawler(this.view);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// No-op.
|
|
76
|
+
destroy() {
|
|
77
|
+
this.crawler.cancel();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
cancel() {
|
|
81
|
+
this.crawler.cancel();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
crawl(start = false) {
|
|
85
|
+
if (start) {
|
|
86
|
+
this.crawler.scroll();
|
|
87
|
+
} else {
|
|
88
|
+
this.crawler.cancel();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
scroll({ line, offset = 0, position, behavior = 'instant' }: ScrollToProps) {
|
|
93
|
+
const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
|
|
94
|
+
const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
|
|
95
|
+
const doc = this.view.state.doc;
|
|
96
|
+
|
|
97
|
+
let targetScrollTop = scrollHeight - clientHeight + offset;
|
|
98
|
+
if (line >= 0 && line <= doc.lines - 1) {
|
|
99
|
+
const lineStart = doc.line(line + 1).from;
|
|
100
|
+
const coords = this.view.coordsAtPos(lineStart);
|
|
101
|
+
if (coords) {
|
|
102
|
+
// Calculate target scroll position based on position option.
|
|
103
|
+
const currentScrollTop = scrollTop;
|
|
104
|
+
const maxScrollTop = scrollHeight - clientHeight;
|
|
105
|
+
|
|
106
|
+
if (position === 'end') {
|
|
107
|
+
// Position line at end (bottom) of viewport.
|
|
108
|
+
// Calculate how far down we need to scroll so the line's bottom aligns with viewport bottom.
|
|
109
|
+
targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
|
|
110
|
+
} else {
|
|
111
|
+
// Default: position line at start (top) of viewport.
|
|
112
|
+
targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Clamp to valid scroll range.
|
|
116
|
+
targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// TODO(burdon): Smooth scrolling doesn't work when the document is being streamed into.
|
|
121
|
+
requestAnimationFrame(() => {
|
|
122
|
+
this.view.scrollDOM.scrollTo({ top: targetScrollTop }); //, behavior });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return [
|
|
129
|
+
scrollPlugin,
|
|
130
|
+
|
|
131
|
+
// Listen for effect.
|
|
132
|
+
EditorView.updateListener.of((update) => {
|
|
133
|
+
update.transactions.forEach((transaction) => {
|
|
134
|
+
try {
|
|
135
|
+
const plugin = update.view.plugin(scrollPlugin);
|
|
136
|
+
if (plugin) {
|
|
137
|
+
for (const effect of transaction.effects) {
|
|
138
|
+
if (effect.is(scrollerCrawlEffect)) {
|
|
139
|
+
plugin.crawl(effect.value);
|
|
140
|
+
} else if (effect.is(scrollerLineEffect)) {
|
|
141
|
+
plugin.scroll(effect.value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
log.catch(err);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}),
|
|
150
|
+
|
|
151
|
+
// Styles.
|
|
152
|
+
EditorView.theme({
|
|
153
|
+
'.cm-scroller': {
|
|
154
|
+
overflowY: 'scroll',
|
|
155
|
+
// Browser scroll-anchoring: when widgets above the viewport resize (e.g. tool blocks
|
|
156
|
+
// expanding their TogglePanel), the browser picks a stable element near the viewport
|
|
157
|
+
// top and adjusts `scrollTop` so the user's view doesn't jump. Auto-scroll's pinning
|
|
158
|
+
// logic still has the final word when pinned (forces scrollTop to scrollHeight).
|
|
159
|
+
overflowAnchor: 'auto',
|
|
160
|
+
},
|
|
161
|
+
'.cm-scroller.cm-hide-scrollbar::-webkit-scrollbar': {
|
|
162
|
+
display: 'none',
|
|
163
|
+
},
|
|
164
|
+
'.cm-scroller::-webkit-scrollbar-thumb': {
|
|
165
|
+
background: 'transparent',
|
|
166
|
+
transition: 'background 0.15s',
|
|
167
|
+
},
|
|
168
|
+
'&:hover .cm-scroller::-webkit-scrollbar-thumb': {
|
|
169
|
+
background: 'var(--color-scrollbar-thumb)',
|
|
170
|
+
},
|
|
171
|
+
// Spacer below the last text line. Implemented as a real block pseudo-element
|
|
172
|
+
// (rather than `padding-bottom` on `.cm-content`) so it materializes in the
|
|
173
|
+
// scroller's `scrollHeight` regardless of how `padding` is reset by the base
|
|
174
|
+
// theme or downstream classes — this is what gives auto-scroll its head-room
|
|
175
|
+
// so the last line stays `overScroll` px above the viewport bottom.
|
|
176
|
+
'.cm-content::after': {
|
|
177
|
+
content: '""',
|
|
178
|
+
display: 'block',
|
|
179
|
+
height: `${overScroll}px`,
|
|
180
|
+
},
|
|
181
|
+
'.cm-scroll-button': {
|
|
182
|
+
position: 'absolute',
|
|
183
|
+
bottom: '0.5rem',
|
|
184
|
+
right: '1rem',
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
];
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Creates a smooth crawler that follows the live bottom of a CodeMirror 6 EditorView.
|
|
192
|
+
*
|
|
193
|
+
* Uses a critically-damped spring: each frame applies a restoring force toward the
|
|
194
|
+
* target and a damping force opposing current velocity. With damping = 2·ω, the
|
|
195
|
+
* system is critically damped — fastest approach without overshoot. The spring
|
|
196
|
+
* naturally sprints when far behind and eases as it approaches, so streaming
|
|
197
|
+
* content is followed tightly without the jerk of explicit accel/decel state.
|
|
198
|
+
*
|
|
199
|
+
* Integration uses real elapsed wall-clock time so the perceived speed stays
|
|
200
|
+
* constant when requestAnimationFrame is throttled (e.g. low-power mode dropping
|
|
201
|
+
* from 60Hz to 30Hz).
|
|
202
|
+
*
|
|
203
|
+
* @param omega Spring stiffness in rad/s. Higher = snappier follow. ~12–18 feels good.
|
|
204
|
+
* @param snapThreshold Snap-to-target distance threshold in px.
|
|
205
|
+
* @param snapVelocity Snap-to-target velocity threshold in px/s.
|
|
206
|
+
*/
|
|
207
|
+
export function createCrawler(view: EditorView, omega = 5, snapThreshold = 5, snapVelocity = 50) {
|
|
208
|
+
const el = view.scrollDOM;
|
|
209
|
+
|
|
210
|
+
let currentTop = 0;
|
|
211
|
+
let velocity = 0;
|
|
212
|
+
let rafId: number | null = null;
|
|
213
|
+
let lastTime = 0;
|
|
214
|
+
|
|
215
|
+
function frame(now: number) {
|
|
216
|
+
// Clamp dt to handle long pauses (tab backgrounded) and the first frame.
|
|
217
|
+
const dt = lastTime === 0 ? 1 / 60 : Math.min(0.1, (now - lastTime) / 1000);
|
|
218
|
+
lastTime = now;
|
|
219
|
+
|
|
220
|
+
const targetTop = el.scrollHeight - el.clientHeight;
|
|
221
|
+
const delta = targetTop - currentTop;
|
|
222
|
+
if (Math.abs(delta) < snapThreshold && Math.abs(velocity) < snapVelocity) {
|
|
223
|
+
el.scrollTop = targetTop;
|
|
224
|
+
currentTop = targetTop;
|
|
225
|
+
velocity = 0;
|
|
226
|
+
rafId = null;
|
|
227
|
+
lastTime = 0;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Critically-damped spring: a = ω²·delta − 2ω·v.
|
|
232
|
+
const accel = omega * omega * delta - 2 * omega * velocity;
|
|
233
|
+
velocity += accel * dt;
|
|
234
|
+
currentTop += velocity * dt;
|
|
235
|
+
el.scrollTop = currentTop;
|
|
236
|
+
rafId = requestAnimationFrame(frame);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
scroll: () => {
|
|
241
|
+
if (rafId === null) {
|
|
242
|
+
currentTop = el.scrollTop;
|
|
243
|
+
lastTime = 0;
|
|
244
|
+
rafId = requestAnimationFrame(frame);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
cancel: () => {
|
|
248
|
+
if (rafId !== null) {
|
|
249
|
+
cancelAnimationFrame(rafId);
|
|
250
|
+
velocity = 0;
|
|
251
|
+
lastTime = 0;
|
|
252
|
+
rafId = null;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
@@ -31,7 +31,7 @@ export type EditorStateStore = {
|
|
|
31
31
|
getState: (id: string) => EditorSelectionState | undefined;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
const stateRestoreAnnotation = 'dxos.
|
|
34
|
+
const stateRestoreAnnotation = 'org.dxos.cm.state-restore';
|
|
35
35
|
|
|
36
36
|
export const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {
|
|
37
37
|
return {
|