@dxos/ui-editor 0.8.4-main.fcfe5033a5 → 0.9.0
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 +1258 -1004
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types/index.mjs +26 -6
- package/dist/lib/browser/types/index.mjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +1258 -1003
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types/index.mjs +27 -6
- package/dist/lib/node-esm/types/index.mjs.map +4 -4
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/annotations.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete/placeholder.d.ts +5 -2
- package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-codemirror.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/blast.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +19 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/debug.d.ts.map +1 -1
- package/dist/types/src/extensions/dnd.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +3 -2
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- 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 +1 -1
- package/dist/types/src/extensions/index.d.ts +3 -4
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/json.d.ts +1 -1
- package/dist/types/src/extensions/json.d.ts.map +1 -1
- package/dist/types/src/extensions/listener.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/image.d.ts +13 -2
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/image.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/image.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
- package/dist/types/src/extensions/mention.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
- package/dist/types/src/extensions/preview/preview.d.ts +2 -2
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
- package/dist/types/src/extensions/replacer.d.ts.map +1 -1
- 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 +83 -0
- package/dist/types/src/extensions/scrolling/crawler.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/index.d.ts +6 -0
- package/dist/types/src/extensions/scrolling/index.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts.map +1 -0
- package/dist/types/src/extensions/scrolling/scrollbar-autohide.d.ts +15 -0
- package/dist/types/src/extensions/scrolling/scrollbar-autohide.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.map +1 -1
- 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/spacing.d.ts +3 -0
- package/dist/types/src/extensions/spacing.d.ts.map +1 -0
- package/dist/types/src/extensions/submit.d.ts.map +1 -1
- package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -1
- package/dist/types/src/extensions/tags/fader.d.ts.map +1 -1
- package/dist/types/src/extensions/tags/index.d.ts +3 -1
- package/dist/types/src/extensions/tags/index.d.ts.map +1 -1
- 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 +1 -8
- package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
- package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +2 -2
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util/cursor.d.ts.map +1 -1
- package/dist/types/src/util/debug.d.ts.map +1 -1
- package/dist/types/src/util/decorations.d.ts.map +1 -1
- package/dist/types/src/util/dom.d.ts.map +1 -1
- package/dist/types/src/util/facet.d.ts.map +1 -1
- package/dist/types/src/util/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +55 -57
- package/src/defaults.ts +6 -4
- package/src/extensions/autocomplete/placeholder.ts +37 -18
- package/src/extensions/automerge/automerge.test.tsx +35 -9
- package/src/extensions/automerge/automerge.ts +1 -1
- package/src/extensions/automerge/cursor.ts +1 -1
- package/src/extensions/automerge/sync.ts +1 -1
- package/src/extensions/automerge/update-automerge.ts +1 -1
- package/src/extensions/comments.ts +54 -31
- package/src/extensions/factories.test.ts +88 -0
- package/src/extensions/factories.ts +22 -4
- package/src/extensions/index.ts +3 -4
- package/src/extensions/json.ts +1 -1
- package/src/extensions/markdown/decorate.ts +1 -1
- package/src/extensions/markdown/image.test.ts +54 -0
- package/src/extensions/markdown/image.ts +70 -9
- package/src/extensions/markdown/link.ts +7 -2
- package/src/extensions/outliner/outliner.ts +1 -1
- package/src/extensions/preview/preview.ts +14 -12
- package/src/extensions/scrolling/auto-scroll.ts +261 -0
- package/src/extensions/{scroller.ts → scrolling/crawler.ts} +89 -48
- package/src/extensions/scrolling/index.ts +9 -0
- package/src/extensions/{scroll-past-end.ts → scrolling/scroll-past-end.ts} +6 -6
- package/src/extensions/scrolling/scrollbar-autohide.ts +61 -0
- package/src/extensions/scrolling/scroller.ts +27 -0
- package/src/extensions/snippets.ts +67 -0
- package/src/extensions/spacing.ts +15 -0
- package/src/extensions/tags/index.ts +3 -1
- package/src/extensions/tags/testing/text.md +36 -0
- package/src/extensions/tags/testing/text.txt +35 -0
- package/src/extensions/tags/{wire.test.ts → typewriter.test.ts} +2 -2
- 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 +6 -32
- package/src/extensions/tags/xml-util.test.ts +90 -3
- package/src/extensions/tags/xml-util.ts +62 -5
- package/src/index.ts +0 -1
- package/src/styles/theme.ts +23 -13
- package/src/typings.d.ts +8 -0
- package/dist/lib/browser/chunk-D724USEC.mjs +0 -34
- package/dist/lib/browser/chunk-D724USEC.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-JRVJWKQF.mjs +0 -36
- package/dist/lib/node-esm/chunk-JRVJWKQF.mjs.map +0 -7
- package/dist/types/src/extensions/auto-scroll.d.ts +0 -8
- package/dist/types/src/extensions/auto-scroll.d.ts.map +0 -1
- package/dist/types/src/extensions/scroll-past-end.d.ts.map +0 -1
- package/dist/types/src/extensions/scroller.d.ts +0 -63
- package/dist/types/src/extensions/scroller.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/wire.d.ts +0 -23
- package/dist/types/src/extensions/tags/wire.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/wire.test.d.ts +0 -2
- package/dist/types/src/extensions/tags/wire.test.d.ts.map +0 -1
- package/dist/types/src/extensions/typewriter.d.ts +0 -10
- package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
- package/src/extensions/auto-scroll.ts +0 -179
- package/src/extensions/tags/wire.ts +0 -459
- package/src/extensions/typewriter.ts +0 -68
- /package/dist/types/src/extensions/{scroll-past-end.d.ts → scrolling/scroll-past-end.d.ts} +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { xmlLanguage } from '@codemirror/lang-xml';
|
|
6
|
+
import { type Extension, type Range } from '@codemirror/state';
|
|
7
|
+
import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
export type XmlBlockDecorationOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* Tag name to match (e.g. `'prompt'`).
|
|
12
|
+
*/
|
|
13
|
+
tag: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Class added via `Decoration.line` to each line that intersects the element's content
|
|
17
|
+
* range. Use to style the bubble container (e.g. flex alignment, vertical margin).
|
|
18
|
+
*/
|
|
19
|
+
lineClass?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Class added via `Decoration.mark` covering the inner content (between the open and
|
|
23
|
+
* close tags). Use to style the bubble surface (background, padding, rounding).
|
|
24
|
+
*/
|
|
25
|
+
contentClass?: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* When true, the open and close tag delimiters are hidden via `Decoration.replace`
|
|
29
|
+
* with no widget, so the rendered text is the inner content only.
|
|
30
|
+
*/
|
|
31
|
+
hideTags?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Walks the doc with the Lezer XML parser and decorates `<tag>…</tag>` elements without
|
|
36
|
+
* replacing them with a widget — the source text remains in the document and can still
|
|
37
|
+
* be matched by other extensions (e.g. `xmlFormatting`). Use this for "bubble"-style
|
|
38
|
+
* styling of XML blocks (chat prompts, callouts, etc.) where the inner content should
|
|
39
|
+
* stay editable/copyable rather than living inside a CodeMirror widget.
|
|
40
|
+
*/
|
|
41
|
+
export const xmlBlockDecoration = ({
|
|
42
|
+
tag,
|
|
43
|
+
lineClass,
|
|
44
|
+
contentClass,
|
|
45
|
+
hideTags,
|
|
46
|
+
}: XmlBlockDecorationOptions): Extension => {
|
|
47
|
+
const lineDecoration = lineClass ? Decoration.line({ class: lineClass }) : undefined;
|
|
48
|
+
const contentDecoration = contentClass ? Decoration.mark({ class: contentClass }) : undefined;
|
|
49
|
+
const hideDecoration = hideTags ? Decoration.replace({}) : undefined;
|
|
50
|
+
|
|
51
|
+
const buildDecorations = (view: EditorView): DecorationSet => {
|
|
52
|
+
const text = view.state.sliceDoc(0, view.state.doc.length);
|
|
53
|
+
if (!text.includes(`<${tag}`)) {
|
|
54
|
+
return Decoration.none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const tree = xmlLanguage.parser.parse(text);
|
|
58
|
+
const ranges: Range<Decoration>[] = [];
|
|
59
|
+
tree.iterate({
|
|
60
|
+
enter: (node) => {
|
|
61
|
+
if (node.type.name !== 'Element') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const openTag = node.node.getChild('OpenTag');
|
|
65
|
+
const closeTag = node.node.getChild('CloseTag') ?? node.node.getChild('MismatchedCloseTag');
|
|
66
|
+
const tagNameNode = openTag?.getChild('TagName');
|
|
67
|
+
if (!openTag || !tagNameNode) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (text.slice(tagNameNode.from, tagNameNode.to) !== tag) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const contentFrom = openTag.to;
|
|
75
|
+
const contentTo = closeTag?.from ?? node.node.to;
|
|
76
|
+
|
|
77
|
+
if (hideDecoration) {
|
|
78
|
+
ranges.push(hideDecoration.range(openTag.from, openTag.to));
|
|
79
|
+
if (closeTag) {
|
|
80
|
+
ranges.push(hideDecoration.range(closeTag.from, closeTag.to));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (contentDecoration && contentFrom < contentTo) {
|
|
85
|
+
ranges.push(contentDecoration.range(contentFrom, contentTo));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (lineDecoration && contentFrom <= view.state.doc.length) {
|
|
89
|
+
// Apply line decoration to every line that intersects the content range.
|
|
90
|
+
let pos = contentFrom;
|
|
91
|
+
while (pos <= contentTo && pos <= view.state.doc.length) {
|
|
92
|
+
const line = view.state.doc.lineAt(pos);
|
|
93
|
+
ranges.push(lineDecoration.range(line.from));
|
|
94
|
+
if (line.to >= contentTo) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
pos = line.to + 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
return Decoration.set(ranges, true);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return ViewPlugin.fromClass(
|
|
106
|
+
class {
|
|
107
|
+
decorations: DecorationSet;
|
|
108
|
+
|
|
109
|
+
constructor(view: EditorView) {
|
|
110
|
+
this.decorations = buildDecorations(view);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
update(update: ViewUpdate) {
|
|
114
|
+
if (update.docChanged) {
|
|
115
|
+
this.decorations = buildDecorations(update.view);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
decorations: (instance) => instance.decorations,
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { xmlLanguage } from '@codemirror/lang-xml';
|
|
6
|
+
import { type Extension, type Range } from '@codemirror/state';
|
|
7
|
+
import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lezer XML node names that represent the angle-bracket delimited portions of an element.
|
|
11
|
+
*/
|
|
12
|
+
const XML_TAG_NODES = new Set(['OpenTag', 'CloseTag', 'SelfClosingTag', 'MismatchedCloseTag']);
|
|
13
|
+
|
|
14
|
+
const xmlElementMark = Decoration.mark({ class: 'cm-xml-element' });
|
|
15
|
+
const xmlTagMark = Decoration.mark({ class: 'cm-xml-tag' });
|
|
16
|
+
const xmlContentMark = Decoration.mark({ class: 'cm-xml-content' });
|
|
17
|
+
|
|
18
|
+
export type XmlFormattingOptions = {
|
|
19
|
+
/**
|
|
20
|
+
* Tag names whose elements should NOT receive xmlFormatting decorations. Use to
|
|
21
|
+
* opt-out tags rendered by another extension (e.g. `xmlBlockDecoration` for `<prompt>`)
|
|
22
|
+
* so the two don't double-wrap the same content with stacked styling.
|
|
23
|
+
*
|
|
24
|
+
* Skipping is recursive: descendants of a skipped element are also untouched, so a
|
|
25
|
+
* `<foo>` inside a skipped `<prompt>` still appears literally without xmlFormatting
|
|
26
|
+
* styling.
|
|
27
|
+
*/
|
|
28
|
+
skip?: string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mark decoration extension that highlights XML tag delimiters
|
|
33
|
+
* (e.g., `<tag>`, `</tag>`, `<tag attr="x"/>`) with the `cm-xml-tag` class.
|
|
34
|
+
*
|
|
35
|
+
* Uses `@codemirror/lang-xml`'s Lezer parser standalone — only to compute
|
|
36
|
+
* decoration ranges — without changing the editor's primary language. This
|
|
37
|
+
* keeps the document plain text while still handling nesting and attributes
|
|
38
|
+
* correctly.
|
|
39
|
+
*/
|
|
40
|
+
export const xmlFormatting = ({ skip }: XmlFormattingOptions = {}): Extension => {
|
|
41
|
+
const skipSet = skip && skip.length > 0 ? new Set(skip) : undefined;
|
|
42
|
+
|
|
43
|
+
const buildDecorations = (view: EditorView): DecorationSet => {
|
|
44
|
+
const text = view.state.sliceDoc(0, view.state.doc.length);
|
|
45
|
+
if (!text.includes('<')) {
|
|
46
|
+
return Decoration.none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const tagNameAt = (node: { from: number; to: number }) => text.slice(node.from, node.to);
|
|
50
|
+
|
|
51
|
+
const tree = xmlLanguage.parser.parse(text);
|
|
52
|
+
const ranges: Range<Decoration>[] = [];
|
|
53
|
+
tree.iterate({
|
|
54
|
+
enter: (node) => {
|
|
55
|
+
const name = node.type.name;
|
|
56
|
+
if (name === 'SelfClosingTag' && node.from < node.to) {
|
|
57
|
+
if (skipSet) {
|
|
58
|
+
const tagNameNode = node.node.getChild('TagName');
|
|
59
|
+
if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Self-closing tag is its own outer block and tag delimiter.
|
|
64
|
+
ranges.push(xmlElementMark.range(node.from, node.to));
|
|
65
|
+
ranges.push(xmlTagMark.range(node.from, node.to));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (XML_TAG_NODES.has(name) && node.from < node.to) {
|
|
69
|
+
ranges.push(xmlTagMark.range(node.from, node.to));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (name === 'Element' && node.from < node.to) {
|
|
73
|
+
const openTag = node.node.getChild('OpenTag');
|
|
74
|
+
if (openTag && skipSet) {
|
|
75
|
+
const tagNameNode = openTag.getChild('TagName');
|
|
76
|
+
if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
|
|
77
|
+
// Skip this element AND its descendants — another extension owns rendering.
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const closeTag = node.node.getChild('CloseTag') ?? node.node.getChild('MismatchedCloseTag');
|
|
82
|
+
ranges.push(xmlElementMark.range(node.from, node.to));
|
|
83
|
+
if (openTag && closeTag && openTag.to < closeTag.from) {
|
|
84
|
+
ranges.push(xmlContentMark.range(openTag.to, closeTag.from));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return Decoration.set(ranges, true);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return [
|
|
93
|
+
ViewPlugin.fromClass(
|
|
94
|
+
class {
|
|
95
|
+
decorations: DecorationSet;
|
|
96
|
+
|
|
97
|
+
constructor(view: EditorView) {
|
|
98
|
+
this.decorations = buildDecorations(view);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
update(update: ViewUpdate) {
|
|
102
|
+
if (update.docChanged) {
|
|
103
|
+
this.decorations = buildDecorations(update.view);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
decorations: (instance) => instance.decorations,
|
|
109
|
+
},
|
|
110
|
+
),
|
|
111
|
+
|
|
112
|
+
EditorView.baseTheme({
|
|
113
|
+
'.cm-xml-element': {
|
|
114
|
+
backgroundColor: 'var(--color-current-surface)',
|
|
115
|
+
borderRadius: '0.25rem',
|
|
116
|
+
padding: '0.25rem',
|
|
117
|
+
},
|
|
118
|
+
'.cm-xml-tag': {
|
|
119
|
+
color: 'var(--color-blue-500)',
|
|
120
|
+
fontFamily: 'var(--font-mono)',
|
|
121
|
+
},
|
|
122
|
+
'.cm-xml-content': {},
|
|
123
|
+
}),
|
|
124
|
+
];
|
|
125
|
+
};
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
WidgetType,
|
|
14
14
|
keymap,
|
|
15
15
|
} from '@codemirror/view';
|
|
16
|
-
// TODO(burdon): Factor out agnostic types (React/solid).
|
|
17
16
|
import { type FunctionComponent } from 'react';
|
|
18
17
|
|
|
19
18
|
import { invariant } from '@dxos/invariant';
|
|
@@ -22,7 +21,7 @@ import { Domino } from '@dxos/ui';
|
|
|
22
21
|
|
|
23
22
|
import { type Range } from '../../types';
|
|
24
23
|
import { decorationSetToArray } from '../../util';
|
|
25
|
-
import {
|
|
24
|
+
import { crawlerLineEffect } from '../scrolling';
|
|
26
25
|
import { nodeToJson } from './xml-util';
|
|
27
26
|
|
|
28
27
|
/**
|
|
@@ -200,17 +199,8 @@ export type XmlTagsOptions = {
|
|
|
200
199
|
|
|
201
200
|
/** Called when widgets are mounted or unmounted. */
|
|
202
201
|
setWidgets?: (widgets: XmlWidgetState[]) => void;
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* When set, adds top margin on block widget lines that immediately follow a line without
|
|
206
|
-
* a `data-xml-widget` host (typically narrative text before portaled or status widgets).
|
|
207
|
-
*/
|
|
208
|
-
paragraphToWidgetGapRem?: number;
|
|
209
202
|
};
|
|
210
203
|
|
|
211
|
-
/** Marks widget roots used by `paragraphToWidgetGapRem` line spacing in the editor theme. */
|
|
212
|
-
export const XML_WIDGET_DATA_ATTR = 'data-xml-widget';
|
|
213
|
-
|
|
214
204
|
/**
|
|
215
205
|
* Implements custom XML tags via CodeMirror-native Widgets and portaled React/Solid components.
|
|
216
206
|
*
|
|
@@ -221,22 +211,9 @@ export const XML_WIDGET_DATA_ATTR = 'data-xml-widget';
|
|
|
221
211
|
* - Widget state can be update via effects.
|
|
222
212
|
* - NOTE: Widget state may be updated BEFORE the widget is mounted.
|
|
223
213
|
*/
|
|
224
|
-
export const xmlTags = ({
|
|
225
|
-
registry,
|
|
226
|
-
setWidgets,
|
|
227
|
-
bookmarks,
|
|
228
|
-
paragraphToWidgetGapRem,
|
|
229
|
-
}: XmlTagsOptions = {}): Extension => {
|
|
214
|
+
export const xmlTags = ({ registry, setWidgets, bookmarks }: XmlTagsOptions = {}): Extension => {
|
|
230
215
|
const notifier = createWidgetMap(setWidgets);
|
|
231
216
|
const widgetDecorationsField = createWidgetDecorationsField(registry, notifier);
|
|
232
|
-
const paragraphGapTheme =
|
|
233
|
-
paragraphToWidgetGapRem != null && paragraphToWidgetGapRem > 0
|
|
234
|
-
? EditorView.baseTheme({
|
|
235
|
-
[`& .cm-content > .cm-line:not(:has([${XML_WIDGET_DATA_ATTR}])) + .cm-line:has([${XML_WIDGET_DATA_ATTR}])`]: {
|
|
236
|
-
marginTop: `${paragraphToWidgetGapRem}rem`,
|
|
237
|
-
},
|
|
238
|
-
})
|
|
239
|
-
: null;
|
|
240
217
|
|
|
241
218
|
return [
|
|
242
219
|
widgetContextStateField,
|
|
@@ -245,7 +222,6 @@ export const xmlTags = ({
|
|
|
245
222
|
createWidgetUpdatePlugin(widgetDecorationsField, notifier),
|
|
246
223
|
createNavigationEffectPlugin(widgetDecorationsField, bookmarks),
|
|
247
224
|
bookmarks?.length ? Prec.highest(keyHandlers) : [],
|
|
248
|
-
...(paragraphGapTheme ? [paragraphGapTheme] : []),
|
|
249
225
|
];
|
|
250
226
|
};
|
|
251
227
|
|
|
@@ -323,7 +299,7 @@ const createNavigationEffectPlugin = (
|
|
|
323
299
|
const line = view.state.doc.lineAt(widget?.from ?? 0);
|
|
324
300
|
view.dispatch({
|
|
325
301
|
selection: { anchor: line.from, head: line.from },
|
|
326
|
-
effects:
|
|
302
|
+
effects: crawlerLineEffect.of({ line: line.number - 1, offset: -16 }),
|
|
327
303
|
});
|
|
328
304
|
|
|
329
305
|
continue;
|
|
@@ -349,13 +325,13 @@ const createNavigationEffectPlugin = (
|
|
|
349
325
|
const line = view.state.doc.lineAt(widget?.from);
|
|
350
326
|
view.dispatch({
|
|
351
327
|
selection: { anchor: line.to, head: line.to },
|
|
352
|
-
effects:
|
|
328
|
+
effects: crawlerLineEffect.of({ line: line.number - 1, offset: -16 }),
|
|
353
329
|
});
|
|
354
330
|
} else {
|
|
355
331
|
const line = view.state.doc.lineAt(view.state.doc.length);
|
|
356
332
|
view.dispatch({
|
|
357
333
|
selection: { anchor: line.to, head: line.to },
|
|
358
|
-
effects:
|
|
334
|
+
effects: crawlerLineEffect.of({ line: line.number - 1, position: 'end' }),
|
|
359
335
|
});
|
|
360
336
|
}
|
|
361
337
|
|
|
@@ -661,9 +637,7 @@ class PlaceholderWidget<TProps extends XmlWidgetProps> extends WidgetType {
|
|
|
661
637
|
override toDOM(view: EditorView) {
|
|
662
638
|
this.#view = view;
|
|
663
639
|
// NOTE: Set min-height to avoid jumps while scrolling.
|
|
664
|
-
this.#root = Domino.of('div')
|
|
665
|
-
.classNames('min-h-[24px]')
|
|
666
|
-
.attributes({ [XML_WIDGET_DATA_ATTR]: '' }).root;
|
|
640
|
+
this.#root = Domino.of('div').classNames('min-h-[24px]').root;
|
|
667
641
|
const props = Object.assign({}, this.props, { view }) as TProps;
|
|
668
642
|
this.notifier.mounted({ id: this.id, root: this.#root, props, Component: this.Component });
|
|
669
643
|
return this.#root;
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { syntaxTree } from '@codemirror/language';
|
|
5
|
+
import { ensureSyntaxTree, syntaxTree } from '@codemirror/language';
|
|
6
6
|
import { EditorState } from '@codemirror/state';
|
|
7
7
|
import { describe, test } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import { trim } from '@dxos/util';
|
|
10
10
|
|
|
11
11
|
import { extendedMarkdown } from './extended-markdown';
|
|
12
|
+
import TEXT from './testing/text.md?raw';
|
|
12
13
|
import { xmlTags } from './xml-tags';
|
|
13
14
|
import { nodeToJson, type Tag } from './xml-util';
|
|
14
15
|
|
|
@@ -27,16 +28,18 @@ const parseElements = (doc: string, registry: Record<string, any> = {}): ParsedE
|
|
|
27
28
|
extensions: [extendedMarkdown({ registry }), xmlTags({ registry })],
|
|
28
29
|
});
|
|
29
30
|
|
|
31
|
+
const tree = ensureSyntaxTree(state, doc.length, 5000) ?? syntaxTree(state);
|
|
30
32
|
const elements: ParsedElement[] = [];
|
|
31
|
-
|
|
33
|
+
tree.iterate({
|
|
32
34
|
enter: (node) => {
|
|
33
35
|
if (node.type.name === 'Element') {
|
|
34
36
|
const tag = nodeToJson(state, node.node);
|
|
35
37
|
if (tag) {
|
|
36
38
|
const hasCloseTag = !!node.node.getChild('CloseTag');
|
|
39
|
+
const hasSelfClose = !!node.node.getChild('SelfClosingTag');
|
|
37
40
|
elements.push({
|
|
38
41
|
...tag,
|
|
39
|
-
complete: hasCloseTag,
|
|
42
|
+
complete: hasCloseTag || hasSelfClose,
|
|
40
43
|
});
|
|
41
44
|
}
|
|
42
45
|
return false;
|
|
@@ -117,6 +120,34 @@ describe('nodeToJson', () => {
|
|
|
117
120
|
expect(elements[0].complete).toBe(true);
|
|
118
121
|
});
|
|
119
122
|
|
|
123
|
+
// Regression: when reasoning text contains escaped XML (e.g. `<foo>`), Lezer XML
|
|
124
|
+
// splits the inline content into Text + EntityReference + Text + EntityReference + Text
|
|
125
|
+
// siblings. The walker used to skip entity references and push each text segment as a
|
|
126
|
+
// separate child, so `getXmlTextChild(children)` only saw the prefix before the first
|
|
127
|
+
// `<` (e.g. ``"The user sent `"``).
|
|
128
|
+
test('should preserve text containing entity references as a single child', ({ expect }) => {
|
|
129
|
+
const xml = trim`
|
|
130
|
+
<reasoning>The user sent \`<foo>\`, which appears to be an XML-like tag.</reasoning>
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
const registry = { reasoning: { block: true } };
|
|
134
|
+
const elements = parseElements(xml, registry);
|
|
135
|
+
expect(elements).toHaveLength(1);
|
|
136
|
+
expect(elements[0]._tag).toBe('reasoning');
|
|
137
|
+
expect(elements[0].children).toEqual(['The user sent `<foo>`, which appears to be an XML-like tag.']);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('should decode numeric character references', ({ expect }) => {
|
|
141
|
+
const xml = trim`
|
|
142
|
+
<reasoning>arrow → and hex →</reasoning>
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const registry = { reasoning: { block: true } };
|
|
146
|
+
const elements = parseElements(xml, registry);
|
|
147
|
+
expect(elements).toHaveLength(1);
|
|
148
|
+
expect(elements[0].children).toEqual(['arrow → and hex →']);
|
|
149
|
+
});
|
|
150
|
+
|
|
120
151
|
test('should parse tag with multiple blank lines in content', ({ expect }) => {
|
|
121
152
|
const xml = trim`
|
|
122
153
|
<reasoning>
|
|
@@ -133,4 +164,60 @@ describe('nodeToJson', () => {
|
|
|
133
164
|
expect(elements[0]._tag).toBe('reasoning');
|
|
134
165
|
expect(elements[0].complete).toBe(true);
|
|
135
166
|
});
|
|
167
|
+
|
|
168
|
+
// Regression: when an unregistered XML tag like `<prompt>` appears earlier in the doc,
|
|
169
|
+
// the markdown parser treats it as a paragraph that lazy-continues into subsequent lines,
|
|
170
|
+
// and a later multi-line tag (e.g. `<reasoning>` whose body contains an ordered list) gets
|
|
171
|
+
// its HTMLBlock truncated to its first line — losing the closing tag and breaking widget
|
|
172
|
+
// rendering. Including the surrounding tag in the registry as a block-only entry (no
|
|
173
|
+
// factory/Component) lets `xmlBlockParsers` keep each tag as its own block.
|
|
174
|
+
test('multi-line reasoning is complete when preceded by a registered prompt block', ({ expect }) => {
|
|
175
|
+
const xml = [
|
|
176
|
+
'<prompt>summarize the posts</prompt>',
|
|
177
|
+
'<toolCall id="x" />',
|
|
178
|
+
'<reasoning>multi line content',
|
|
179
|
+
'1. "First" - desc',
|
|
180
|
+
'5. "Last" - desc</reasoning>',
|
|
181
|
+
].join('\n');
|
|
182
|
+
const registry = {
|
|
183
|
+
prompt: { block: true },
|
|
184
|
+
reasoning: { block: true },
|
|
185
|
+
toolCall: { block: true },
|
|
186
|
+
};
|
|
187
|
+
const elements = parseElements(xml, registry);
|
|
188
|
+
expect(elements.map((e) => `${e._tag}${e.complete ? '' : '!'}`)).toEqual(['prompt', 'toolCall', 'reasoning']);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('should parse text.md', ({ expect }) => {
|
|
192
|
+
// Mirror the live ChatThread registry, including `prompt` as a block-only entry so
|
|
193
|
+
// unregistered-paragraph lazy-continuation does not break later multi-line tags.
|
|
194
|
+
const registry = {
|
|
195
|
+
prompt: { block: true },
|
|
196
|
+
reasoning: { block: true },
|
|
197
|
+
status: { block: true },
|
|
198
|
+
toolCall: { block: true },
|
|
199
|
+
name: { block: true },
|
|
200
|
+
};
|
|
201
|
+
const elements = parseElements(TEXT, registry);
|
|
202
|
+
const tags = elements.map((element) => element._tag);
|
|
203
|
+
expect(tags).toEqual([
|
|
204
|
+
'prompt',
|
|
205
|
+
'reasoning',
|
|
206
|
+
'status',
|
|
207
|
+
'toolCall',
|
|
208
|
+
'toolCall',
|
|
209
|
+
'reasoning',
|
|
210
|
+
'status',
|
|
211
|
+
'toolCall',
|
|
212
|
+
'reasoning',
|
|
213
|
+
'prompt',
|
|
214
|
+
'reasoning',
|
|
215
|
+
'prompt',
|
|
216
|
+
'name',
|
|
217
|
+
]);
|
|
218
|
+
// Every element must be `complete` so the widget renderer wraps it. The bug we fixed:
|
|
219
|
+
// third multi-line `<reasoning>` (lines 9-14) used to be truncated by markdown's
|
|
220
|
+
// OrderedList parser breaking the HTMLBlock, leaving an Element node with no CloseTag.
|
|
221
|
+
expect(elements.filter((e) => !e.complete)).toEqual([]);
|
|
222
|
+
});
|
|
136
223
|
});
|
|
@@ -65,14 +65,25 @@ export const nodeToJson = (state: EditorState, node: SyntaxNode): Tag | undefine
|
|
|
65
65
|
const children: any[] = [];
|
|
66
66
|
let child = node.node.firstChild;
|
|
67
67
|
|
|
68
|
+
const appendText = (raw: string) => {
|
|
69
|
+
if (raw.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const last = children[children.length - 1];
|
|
73
|
+
if (typeof last === 'string') {
|
|
74
|
+
children[children.length - 1] = last + raw;
|
|
75
|
+
} else {
|
|
76
|
+
children.push(raw);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
68
80
|
while (child) {
|
|
69
81
|
// Skip the opening and closing tags.
|
|
70
82
|
if (child.type.name !== 'OpenTag' && child.type.name !== 'CloseTag') {
|
|
71
83
|
if (child.type.name === 'Text') {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
84
|
+
appendText(state.doc.sliceString(child.from, child.to));
|
|
85
|
+
} else if (child.type.name === 'EntityReference' || child.type.name === 'CharacterReference') {
|
|
86
|
+
appendText(decodeXmlEntity(state.doc.sliceString(child.from, child.to)));
|
|
76
87
|
} else if (child.type.name === 'Element') {
|
|
77
88
|
const data = nodeToJson(state, child);
|
|
78
89
|
if (data) {
|
|
@@ -83,11 +94,57 @@ export const nodeToJson = (state: EditorState, node: SyntaxNode): Tag | undefine
|
|
|
83
94
|
child = child.nextSibling;
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
// Trim only leading/trailing whitespace on the outer-boundary string segments —
|
|
98
|
+
// interior strings are preserved verbatim so meaningful whitespace around inline
|
|
99
|
+
// child elements (e.g. `<reasoning>foo <ref/> bar</reasoning>`) is not collapsed.
|
|
100
|
+
if (children.length > 0 && typeof children[0] === 'string') {
|
|
101
|
+
children[0] = children[0].trimStart();
|
|
102
|
+
}
|
|
86
103
|
if (children.length > 0) {
|
|
87
|
-
|
|
104
|
+
const lastIndex = children.length - 1;
|
|
105
|
+
const last = children[lastIndex];
|
|
106
|
+
if (typeof last === 'string') {
|
|
107
|
+
children[lastIndex] = last.trimEnd();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const trimmed = children.filter((value) => typeof value !== 'string' || value.length > 0);
|
|
111
|
+
|
|
112
|
+
if (trimmed.length > 0) {
|
|
113
|
+
tag.children = trimmed;
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
116
|
|
|
91
117
|
return tag;
|
|
92
118
|
}
|
|
93
119
|
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Decode the common XML named entities and numeric character references that
|
|
123
|
+
* Lezer XML produces as `EntityReference` / `CharacterReference` nodes.
|
|
124
|
+
*/
|
|
125
|
+
const XML_NAMED_ENTITIES: Record<string, string> = {
|
|
126
|
+
'<': '<',
|
|
127
|
+
'>': '>',
|
|
128
|
+
'&': '&',
|
|
129
|
+
'"': '"',
|
|
130
|
+
''': "'",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const decodeXmlEntity = (raw: string): string => {
|
|
134
|
+
const named = XML_NAMED_ENTITIES[raw];
|
|
135
|
+
if (named !== undefined) {
|
|
136
|
+
return named;
|
|
137
|
+
}
|
|
138
|
+
const numeric = /^&#(x?)([0-9a-fA-F]+);$/.exec(raw);
|
|
139
|
+
if (numeric) {
|
|
140
|
+
const code = parseInt(numeric[2], numeric[1] ? 16 : 10);
|
|
141
|
+
if (Number.isFinite(code)) {
|
|
142
|
+
try {
|
|
143
|
+
return String.fromCodePoint(code);
|
|
144
|
+
} catch {
|
|
145
|
+
// Fall through and return the raw text on out-of-range code points.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return raw;
|
|
150
|
+
};
|
package/src/index.ts
CHANGED
package/src/styles/theme.ts
CHANGED
|
@@ -108,12 +108,21 @@ export const baseTheme = EditorView.baseTheme({
|
|
|
108
108
|
* Scroller
|
|
109
109
|
*/
|
|
110
110
|
'.cm-scroller': {
|
|
111
|
-
|
|
111
|
+
// Browser scroll-anchoring: see comment in `scrolling/crawler.ts`. `auto` lets the browser pin a
|
|
112
|
+
// stable element near the viewport top so widget resizes (e.g. tool-block TogglePanel
|
|
113
|
+
// open/close) don't jump the user's view.
|
|
114
|
+
overflowAnchor: 'auto',
|
|
112
115
|
},
|
|
113
116
|
'.cm-scroller::-webkit-scrollbar': {
|
|
114
|
-
width: '8px',
|
|
117
|
+
width: 'var(--scrollbar-size,8px)',
|
|
118
|
+
height: 'var(--scrollbar-size,8px)',
|
|
119
|
+
},
|
|
120
|
+
'.cm-scroller::-webkit-scrollbar-corner': {
|
|
121
|
+
background: 'transparent',
|
|
122
|
+
},
|
|
123
|
+
'.cm-scroller::-webkit-scrollbar-track': {
|
|
124
|
+
background: 'transparent',
|
|
115
125
|
},
|
|
116
|
-
'.cm-scroller::-webkit-scrollbar-track': {},
|
|
117
126
|
'.cm-scroller::-webkit-scrollbar-thumb': {
|
|
118
127
|
background: 'transparent',
|
|
119
128
|
transition: 'background 0.15s',
|
|
@@ -152,7 +161,7 @@ export const baseTheme = EditorView.baseTheme({
|
|
|
152
161
|
* Height is set to match the corresponding line (which may have wrapped).
|
|
153
162
|
*/
|
|
154
163
|
'.cm-gutterElement': {
|
|
155
|
-
lineHeight:
|
|
164
|
+
lineHeight: '24px',
|
|
156
165
|
fontSize: '12px',
|
|
157
166
|
},
|
|
158
167
|
|
|
@@ -218,6 +227,7 @@ export const baseTheme = EditorView.baseTheme({
|
|
|
218
227
|
textDecorationColor: 'var(--color-separator)',
|
|
219
228
|
textUnderlineOffset: '2px',
|
|
220
229
|
borderRadius: '.125rem',
|
|
230
|
+
cursor: 'pointer',
|
|
221
231
|
},
|
|
222
232
|
'.cm-link > span': {
|
|
223
233
|
color: 'var(--color-accent-text)',
|
|
@@ -257,12 +267,12 @@ export const baseTheme = EditorView.baseTheme({
|
|
|
257
267
|
padding: '4px',
|
|
258
268
|
},
|
|
259
269
|
'.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]': {
|
|
260
|
-
background: 'var(--color-
|
|
261
|
-
color: 'var(--color-base-
|
|
270
|
+
background: 'var(--color-current-surface)',
|
|
271
|
+
color: 'var(--color-base-fg)',
|
|
262
272
|
},
|
|
263
273
|
'.cm-tooltip.cm-tooltip-autocomplete > ul > completion-section': {
|
|
264
274
|
paddingLeft: '4px !important',
|
|
265
|
-
color: 'var(--color-base-
|
|
275
|
+
color: 'var(--color-base-fg)',
|
|
266
276
|
},
|
|
267
277
|
|
|
268
278
|
/**
|
|
@@ -282,7 +292,7 @@ export const baseTheme = EditorView.baseTheme({
|
|
|
282
292
|
padding: '0 4px',
|
|
283
293
|
},
|
|
284
294
|
'.cm-completionMatchedText': {
|
|
285
|
-
color: 'var(--color-base-
|
|
295
|
+
color: 'var(--color-base-fg)',
|
|
286
296
|
textDecoration: 'none !important',
|
|
287
297
|
},
|
|
288
298
|
|
|
@@ -318,7 +328,7 @@ export const baseTheme = EditorView.baseTheme({
|
|
|
318
328
|
backgroundColor: 'var(--color-input-surface)',
|
|
319
329
|
},
|
|
320
330
|
'.cm-panel input:focus, .cm-panel button:focus': {
|
|
321
|
-
outline: '1px solid var(--color-
|
|
331
|
+
outline: '1px solid var(--color-focus-ring-subtle)',
|
|
322
332
|
},
|
|
323
333
|
'.cm-panel label': {
|
|
324
334
|
display: 'inline-flex',
|
|
@@ -331,15 +341,15 @@ export const baseTheme = EditorView.baseTheme({
|
|
|
331
341
|
height: '8px',
|
|
332
342
|
marginRight: '6px !important',
|
|
333
343
|
padding: '2px !important',
|
|
334
|
-
color: 'var(--color-
|
|
344
|
+
color: 'var(--color-focus-ring-subtle)',
|
|
335
345
|
},
|
|
336
346
|
'.cm-panel button': {
|
|
337
347
|
'&:hover': {
|
|
338
|
-
// TODO(burdon): Replace with layer and @apply bg-accent-
|
|
339
|
-
backgroundColor: 'var(--color-accent-
|
|
348
|
+
// TODO(burdon): Replace with layer and @apply bg-accent-bg-hover
|
|
349
|
+
backgroundColor: 'var(--color-accent-bg-hover) !important',
|
|
340
350
|
},
|
|
341
351
|
'&:active': {
|
|
342
|
-
backgroundColor: 'var(--color-accent-
|
|
352
|
+
backgroundColor: 'var(--color-accent-bg-hover)',
|
|
343
353
|
},
|
|
344
354
|
},
|
|
345
355
|
'.cm-panel.cm-search': {
|