@dxos/react-ui-editor 0.4.5-main.f8d6c4e → 0.4.5

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.
Files changed (58) hide show
  1. package/dist/lib/browser/index.mjs +464 -572
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +7 -5
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -0
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/automerge.stories.d.ts +2 -0
  9. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/hooks.stories.d.ts +2 -0
  11. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  13. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +2 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  15. package/dist/types/src/extensions/cursor-line-margin.d.ts +8 -0
  16. package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -0
  17. package/dist/types/src/extensions/index.d.ts +1 -0
  18. package/dist/types/src/extensions/index.d.ts.map +1 -1
  19. package/dist/types/src/extensions/markdown/decorate.d.ts +5 -0
  20. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -0
  21. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  22. package/dist/types/src/extensions/markdown/index.d.ts +1 -4
  23. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  24. package/dist/types/src/extensions/markdown/link.d.ts +1 -11
  25. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  26. package/dist/types/src/index.d.ts +2 -0
  27. package/dist/types/src/index.d.ts.map +1 -1
  28. package/dist/types/src/styles/layout.d.ts +2 -2
  29. package/dist/types/src/styles/layout.d.ts.map +1 -1
  30. package/dist/types/src/translations.d.ts +2 -0
  31. package/dist/types/src/translations.d.ts.map +1 -1
  32. package/package.json +29 -26
  33. package/src/components/TextEditor/MarkdownEditor.stories.tsx +29 -37
  34. package/src/components/TextEditor/hooks.stories.tsx +5 -17
  35. package/src/components/Toolbar/Toolbar.stories.tsx +4 -7
  36. package/src/components/Toolbar/Toolbar.tsx +79 -47
  37. package/src/extensions/cursor-line-margin.ts +92 -0
  38. package/src/extensions/index.ts +1 -0
  39. package/src/extensions/markdown/decorate.ts +301 -0
  40. package/src/extensions/markdown/formatting.test.ts +17 -0
  41. package/src/extensions/markdown/formatting.ts +67 -76
  42. package/src/extensions/markdown/index.ts +1 -4
  43. package/src/extensions/markdown/link.ts +23 -185
  44. package/src/index.ts +2 -0
  45. package/src/styles/layout.ts +3 -2
  46. package/src/translations.ts +5 -3
  47. package/dist/types/src/extensions/markdown/code.d.ts +0 -2
  48. package/dist/types/src/extensions/markdown/code.d.ts.map +0 -1
  49. package/dist/types/src/extensions/markdown/heading.d.ts +0 -6
  50. package/dist/types/src/extensions/markdown/heading.d.ts.map +0 -1
  51. package/dist/types/src/extensions/markdown/hr.d.ts +0 -2
  52. package/dist/types/src/extensions/markdown/hr.d.ts.map +0 -1
  53. package/dist/types/src/extensions/markdown/tasklist.d.ts +0 -3
  54. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +0 -1
  55. package/src/extensions/markdown/code.ts +0 -162
  56. package/src/extensions/markdown/heading.ts +0 -59
  57. package/src/extensions/markdown/hr.ts +0 -71
  58. package/src/extensions/markdown/tasklist.ts +0 -110
@@ -4,196 +4,34 @@
4
4
  //
5
5
 
6
6
  import { syntaxTree } from '@codemirror/language';
7
- import { type Extension, RangeSetBuilder } from '@codemirror/state';
8
- import {
9
- hoverTooltip,
10
- Decoration,
11
- type DecorationSet,
12
- type EditorView,
13
- ViewPlugin,
14
- type ViewUpdate,
15
- WidgetType,
16
- } from '@codemirror/view';
7
+ import { hoverTooltip } from '@codemirror/view';
17
8
  import { type SyntaxNode } from '@lezer/common';
18
9
 
19
10
  import { tooltipContent } from '@dxos/react-ui-theme';
20
11
 
21
- // Adapted from:
22
- // https://codemirror.net/try/?c=aW1wb3J0IHsgYmFzaWNTZXR1cCwgRWRpdG9yVmlldyB9IGZyb20gImNvZGVtaXJyb3IiCmltcG9ydCB7IG1hcmtkb3duIH0gZnJvbSAiQGNvZGVtaXJyb3IvbGFuZy1tYXJrZG93biIKaW1wb3J0IHsgc3ludGF4VHJlZSB9IGZyb20gIkBjb2RlbWlycm9yL2xhbmd1YWdlIjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIFJhbmdlU2V0QnVpbGRlciwgU3RhdGVGaWVsZCB9IGZyb20gIkBjb2RlbWlycm9yL3N0YXRlIjsKaW1wb3J0IHsgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSB9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciOwoKY2xhc3MgTGluayBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKHRleHQsIHVybCkgewogICAgc3VwZXIoKQogICAgdGhpcy50ZXh0ID0gdGV4dAogICAgdGhpcy51cmwgPSB1cmwKICB9CiAgZXEob3RoZXIpIHsgcmV0dXJuIHRoaXMudGV4dCA9PSBvdGhlci50ZXh0ICYmIHRoaXMudXJsID09IG90aGVyLnVybCB9CiAgdG9ET00oKSB7CiAgICBsZXQgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKQogICAgbGluay50ZXh0Q29udGVudCA9IHRoaXMudGV4dAogICAgbGluay5ocmVmID0gdGhpcy51cmwKICAgIHJldHVybiBsaW5rOwogIH0KfQoKbGV0IGRlY29yYXRpb25zRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShfLCB0cikgewogICAgY29uc3QgYnVpbGRlciA9IG5ldyBSYW5nZVNldEJ1aWxkZXIoKTsKICAgIGxldCBjdXJzb3IgPSB0ci5zdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkOwogICAgc3ludGF4VHJlZSh0ci5zdGF0ZSkuaXRlcmF0ZSh7CiAgICAgIGVudGVyOiAobm9kZSkgPT4gewogICAgICAgIGlmICgoY3Vyc29yIDwgbm9kZS5mcm9tIHx8IGN1cnNvciA+IG5vZGUudG8pICYmIG5vZGUubmFtZSA9PSAiTGluayIpIHsKICAgICAgICAgIGxldCBtYXJrcyA9IG5vZGUubm9kZS5nZXRDaGlsZHJlbigiTGlua01hcmsiKTsKICAgICAgICAgIGxldCB0ZXh0ID0gbWFya3MubGVuZ3RoID49IDIgPyB0ci5zdGF0ZS5zbGljZURvYyhtYXJrc1swXS50bywgbWFya3NbMV0uZnJvbSkgOiAiIgoKICAgICAgICAgIGxldCB1cmxOb2RlID0gbm9kZS5ub2RlLmdldENoaWxkKCJVUkwiKTsKICAgICAgICAgIGxldCB1cmwgPSB1cmxOb2RlID8gdHIuc3RhdGUuc2xpY2VEb2ModXJsTm9kZS5mcm9tLCB1cmxOb2RlLnRvKSA6ICIiCiAgICAgICAgICBidWlsZGVyLmFkZChub2RlLmZyb20sIG5vZGUudG8sIERlY29yYXRpb24ucmVwbGFjZSh7CiAgICAgICAgICAgIHdpZGdldDogbmV3IExpbmsodGV4dCwgdXJsKSwKICAgICAgICAgIH0pLAogICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfSwKICAgIH0pOwogICAgcmV0dXJuIGJ1aWxkZXIuZmluaXNoKCk7CiAgfSwKICBwcm92aWRlOiAoZikgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGYpLAp9KTsKCmxldCB2aWV3ID0gbmV3IEVkaXRvclZpZXcoewogIGRvYzogIiMgSGVsbG8gV29ybGRcblxuW1Rlc3RdKGh0dHBzOi8vZXhhbXBsZS5jb20pXG5cblxuIiwKICBleHRlbnNpb25zOiBbZGVjb3JhdGlvbnNGaWVsZCwgbWFya2Rvd24oKV0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5LAp9KTsKCnZpZXcuZm9jdXMoKTs=
23
- // https://discuss.codemirror.net/t/link-widget-is-clicked-only-if-editor-is-out-of-focus/7433
24
-
25
- // TODO(burdon): Callback to dynamically lookup title of linked pages.
26
- export type LinkOptions = {
27
- link?: boolean;
28
- onHover?: (el: Element, url: string) => void;
29
- onRender?: (el: Element, url: string) => void;
30
- };
31
-
32
- /**
33
- * Creates a state field to replace AST elements with a hyperlink widget.
34
- * https://codemirror.net/docs/ref/#state.StateField
35
- */
36
- export const link = (options: LinkOptions = {}): Extension => {
37
- const extensions: Extension[] = [
38
- ViewPlugin.fromClass(
39
- class {
40
- decorations: DecorationSet;
41
- constructor(view: EditorView) {
42
- this.decorations = buildDecorations(view, options);
43
- }
44
-
45
- update(update: ViewUpdate) {
46
- if (update.docChanged || update.viewportChanged || update.selectionSet) {
47
- this.decorations = buildDecorations(update.view, options);
48
- }
49
- }
50
- },
51
- {
52
- decorations: (value) => value.decorations,
53
- },
54
- ),
55
- ];
56
-
57
- if (options.onHover) {
58
- extensions.push(
59
- // https://codemirror.net/examples/tooltip
60
- // https://codemirror.net/docs/ref/#view.hoverTooltip
61
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
62
- hoverTooltip((view, pos, side) => {
63
- const syntax = syntaxTree(view.state).resolveInner(pos, side);
64
- let link = null;
65
- for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
66
- link = node.name === 'Link' ? node : null;
67
- }
68
-
69
- const url = link && link.getChild('URL');
70
- if (!url || !link) {
71
- return null;
72
- }
73
-
74
- const urlText = view.state.sliceDoc(url.from, url.to);
75
- return {
76
- pos: link.from,
77
- end: link.to,
78
- above: true,
79
- create: () => {
80
- const el = document.createElement('div');
81
- el.className = tooltipContent({}, 'pli-2 plb-1');
82
- options.onHover!(el, urlText);
83
- return { dom: el, offset: { x: 0, y: 4 } };
84
- },
85
- };
86
- }),
87
- );
88
- }
89
-
90
- return extensions;
91
- };
92
-
93
- class LinkButton extends WidgetType {
94
- constructor(
95
- private readonly _url: string,
96
- private readonly _onAttach: LinkOptions['onRender'],
97
- ) {
98
- super();
99
- }
100
-
101
- override eq(other: this) {
102
- return this._url === other._url;
103
- }
104
-
105
- override toDOM(view: EditorView) {
106
- const el = document.createElement('span');
107
- this._onAttach!(el, this._url);
108
- return el;
109
- }
110
- }
111
-
112
- class LinkText extends WidgetType {
113
- constructor(
114
- private readonly _text: string,
115
- private readonly _url?: string,
116
- ) {
117
- super();
118
- }
119
-
120
- override eq(other: this) {
121
- return this._url === other._url;
122
- }
123
-
124
- override toDOM(view: EditorView) {
125
- const link = document.createElement('a');
126
- link.setAttribute('class', 'cm-link');
127
- link.textContent = this._text;
128
- if (this._url) {
129
- link.setAttribute('rel', 'noreferrer');
130
- link.setAttribute('target', '_blank');
131
- link.href = this._url;
132
- } else {
133
- link.onclick = () => {
134
- view.dispatch({
135
- selection: {
136
- anchor: view.posAtDOM(link),
137
- },
138
- });
139
- };
12
+ export const linkTooltip = (render: (el: Element, url: string) => void) =>
13
+ hoverTooltip((view, pos, side) => {
14
+ const syntax = syntaxTree(view.state).resolveInner(pos, side);
15
+ let link = null;
16
+ for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
17
+ link = node.name === 'Link' ? node : null;
140
18
  }
141
19
 
142
- return link;
143
- }
144
- }
145
-
146
- /**
147
- * Range sets provide a data structure that can hold a collection of tagged,
148
- * possibly overlapping ranges in such a way that they can efficiently be mapped though document changes.
149
- * https://codemirror.net/docs/ref/#state
150
- */
151
- const buildDecorations = (view: EditorView, options: LinkOptions): DecorationSet => {
152
- const builder = new RangeSetBuilder<Decoration>();
153
- const { state } = view;
154
- const cursor = state.selection.main.head;
155
-
156
- for (const { from, to } of view.visibleRanges) {
157
- syntaxTree(state).iterate({
158
- enter: (node) => {
159
- // Check if cursor is inside text.
160
- if (node.name === 'Link') {
161
- const marks = node.node.getChildren('LinkMark');
162
- const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
163
- const urlNode = node.node.getChild('URL');
164
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
165
- if (!url) {
166
- return false;
167
- }
168
-
169
- // TODO(burdon): Don't expand if moving cursor up/down.
170
- if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
171
- builder.add(
172
- node.from,
173
- node.to,
174
- Decoration.replace({
175
- widget: new LinkText(text, options.link ? url : undefined),
176
- }),
177
- );
178
- }
179
-
180
- if (options.onRender) {
181
- builder.add(
182
- node.to,
183
- node.to,
184
- Decoration.replace({
185
- widget: new LinkButton(url, options.onRender),
186
- }),
187
- );
188
- }
20
+ const url = link && link.getChild('URL');
21
+ if (!url || !link) {
22
+ return null;
23
+ }
189
24
 
190
- return false;
191
- }
25
+ const urlText = view.state.sliceDoc(url.from, url.to);
26
+ return {
27
+ pos: link.from,
28
+ end: link.to,
29
+ above: true,
30
+ create: () => {
31
+ const el = document.createElement('div');
32
+ el.className = tooltipContent({}, 'pli-2 plb-1');
33
+ render(el, urlText);
34
+ return { dom: el, offset: { x: 0, y: 4 } };
192
35
  },
193
- from,
194
- to,
195
- });
196
- }
197
-
198
- return builder.finish();
199
- };
36
+ };
37
+ });
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  //
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
+ import translations from './translations';
4
5
 
5
6
  export { type Extension, type EditorState } from '@codemirror/state';
6
7
  export { type EditorView, keymap } from '@codemirror/view';
@@ -20,3 +21,4 @@ export {
20
21
  } from './styles';
21
22
  export * from './themes';
22
23
  export * from './util';
24
+ export { translations };
@@ -5,7 +5,8 @@
5
5
  export const editorWithToolbarLayout =
6
6
  'grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden';
7
7
 
8
- export const editorFillLayoutRoot = 'min-bs-full grid';
9
- export const editorFillLayoutEditor = 'min-bs-full';
8
+ export const editorFillLayoutRoot = 'bs-full overflow-hidden grid';
9
+ export const editorFillLayoutEditor =
10
+ 'bs-full overflow-hidden [&>.cm-scroller]:bs-full [&>.cm-scroller]:overflow-y-auto';
10
11
 
11
12
  export const editorHalfViewportOverscrollContent = 'after:block after:min-bs-[50dvh] after:pointer-events-none';
@@ -8,10 +8,11 @@ export default [
8
8
  {
9
9
  'en-US': {
10
10
  [translationKey]: {
11
- 'strong label': 'Strong',
12
- 'emphasis label': 'Emphasis',
13
- 'strikethrough label': 'Strike-through',
11
+ 'strong label': 'Bold',
12
+ 'emphasis label': 'Italics',
13
+ 'strikethrough label': 'Strikethrough',
14
14
  'code label': 'Code',
15
+ 'link label': 'Link',
15
16
  'list-bullet label': 'Bullet list',
16
17
  'list-ordered label': 'Numbered list',
17
18
  'list-task label': 'Task list',
@@ -19,6 +20,7 @@ export default [
19
20
  'codeblock label': 'Code block',
20
21
  'comment label': 'Create comment',
21
22
  'heading label': 'Heading level',
23
+ 'table label': 'Create table',
22
24
  },
23
25
  },
24
26
  },
@@ -1,2 +0,0 @@
1
- export declare const code: () => import("@codemirror/state").Extension[];
2
- //# sourceMappingURL=code.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/markdown/code.ts"],"names":[],"mappings":"AA8IA,eAAO,MAAM,IAAI,+CAmBhB,CAAC"}
@@ -1,6 +0,0 @@
1
- import { type DecorationSet, ViewPlugin, type ViewUpdate } from '@codemirror/view';
2
- export declare const heading: () => ViewPlugin<{
3
- decorations: DecorationSet;
4
- update(update: ViewUpdate): void;
5
- }>[];
6
- //# sourceMappingURL=heading.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"heading.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/markdown/heading.ts"],"names":[],"mappings":"AAMA,OAAO,EAAc,KAAK,aAAa,EAAmB,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAkChH,eAAO,MAAM,OAAO;;mBASG,UAAU;IAShC,CAAC"}
@@ -1,2 +0,0 @@
1
- export declare const hr: () => import("@codemirror/state").Extension[];
2
- //# sourceMappingURL=hr.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hr.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/markdown/hr.ts"],"names":[],"mappings":"AAmDA,eAAO,MAAM,EAAE,+CAmBd,CAAC"}
@@ -1,3 +0,0 @@
1
- export type TasklistOptions = {};
2
- export declare const tasklist: (options?: TasklistOptions) => import("@codemirror/state").Extension[];
3
- //# sourceMappingURL=tasklist.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tasklist.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/markdown/tasklist.ts"],"names":[],"mappings":"AAsFA,MAAM,MAAM,eAAe,GAAG,EAAE,CAAC;AAEjC,eAAO,MAAM,QAAQ,aAAa,eAAe,4CAqBhD,CAAC"}
@@ -1,162 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import {
8
- ViewPlugin,
9
- type DecorationSet,
10
- EditorView,
11
- type ViewUpdate,
12
- Decoration,
13
- type BlockInfo,
14
- WidgetType,
15
- } from '@codemirror/view';
16
-
17
- import { mx } from '@dxos/react-ui-theme';
18
-
19
- import { getToken } from '../../styles';
20
-
21
- // TODO(burdon): Reconcile with theme.
22
- const styles = EditorView.baseTheme({
23
- '& .cm-code': {
24
- fontFamily: getToken('fontFamily.mono', []).join(','),
25
- },
26
- '& .cm-codeblock-line': {
27
- paddingInline: '1rem !important',
28
- },
29
-
30
- '& .cm-codeblock-end': {
31
- display: 'inline-block',
32
- width: '100%',
33
- position: 'relative',
34
- '&::after': {
35
- position: 'absolute',
36
- inset: 0,
37
- content: '""',
38
- },
39
- },
40
- '& .cm-codeblock-first': {
41
- '&::after': {
42
- borderTopLeftRadius: '.5rem',
43
- borderTopRightRadius: '.5rem',
44
- },
45
- },
46
- '& .cm-codeblock-last': {
47
- '&::after': {
48
- borderBottomLeftRadius: '.5rem',
49
- borderBottomRightRadius: '.5rem',
50
- },
51
- },
52
-
53
- '&light .cm-codeblock-line, &light .cm-activeLine.cm-codeblock-line': {
54
- background: getToken('extend.semanticColors.input.light'),
55
- mixBlendMode: 'darken',
56
- },
57
- '&dark .cm-codeblock-line, &dark .cm-activeLine.cm-codeblock-line': {
58
- background: getToken('extend.semanticColors.input.dark'),
59
- mixBlendMode: 'lighten',
60
- },
61
- '&light .cm-codeblock-first, &light .cm-codeblock-last': {
62
- mixBlendMode: 'darken',
63
- '&::after': {
64
- background: getToken('extend.semanticColors.input.light'),
65
- },
66
- },
67
- '&dark .cm-codeblock-first, &dark .cm-codeblock-last': {
68
- mixBlendMode: 'lighten',
69
- '&::after': {
70
- background: getToken('extend.semanticColors.input.dark'),
71
- },
72
- },
73
- });
74
-
75
- // TODO(burdon): Start from EditorView.lineBlockAt(pos).
76
- const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
77
- const start = lines.findIndex((line) => line.from >= from);
78
- const end = lines.findIndex((line) => line.to >= to);
79
- return [start, end];
80
- };
81
-
82
- /**
83
- * Widget for first/last line of code block (read-only).
84
- */
85
- // TODO(burdon): Add copy-to-clipboard button.
86
- class LineWidget extends WidgetType {
87
- constructor(private readonly _className: string) {
88
- super();
89
- }
90
-
91
- override toDOM() {
92
- const el = document.createElement('span');
93
- el.innerHTML = '&nbsp;';
94
- el.className = mx('cm-code cm-codeblock', this._className);
95
- return el;
96
- }
97
- }
98
-
99
- const top = new LineWidget('cm-codeblock-end cm-codeblock-first');
100
- const bottom = new LineWidget('cm-codeblock-end cm-codeblock-last');
101
-
102
- const buildDecorations = (view: EditorView): DecorationSet => {
103
- const builder = new RangeSetBuilder<Decoration>();
104
- const { state } = view;
105
- const blocks = view.viewportLineBlocks;
106
- const cursor = state.selection.main.head;
107
-
108
- for (const { from, to } of view.visibleRanges) {
109
- syntaxTree(state).iterate({
110
- enter: (node) => {
111
- if (node.name === 'FencedCode') {
112
- // FencedCode > CodeMark > [CodeInfo] > CodeText > CodeMark
113
- const editing = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
114
- const range = getLineRange(blocks, node.from, node.to);
115
- for (let i = range[0]; i <= range[1]; i++) {
116
- const block = blocks[i];
117
- if (!editing && (i === range[0] || i === range[1])) {
118
- builder.add(block.from, block.to, Decoration.replace({ widget: i === range[0] ? top : bottom }));
119
- } else {
120
- builder.add(
121
- block.from,
122
- block.from,
123
- Decoration.line({
124
- class: mx(
125
- 'cm-code cm-codeblock-line',
126
- i === range[0] && 'cm-codeblock-first',
127
- i === range[1] && 'cm-codeblock-last',
128
- ),
129
- }),
130
- );
131
- }
132
- }
133
- }
134
- },
135
- from,
136
- to,
137
- });
138
- }
139
-
140
- return builder.finish();
141
- };
142
-
143
- export const code = () => {
144
- return [
145
- ViewPlugin.fromClass(
146
- class {
147
- decorations: DecorationSet;
148
- constructor(view: EditorView) {
149
- this.decorations = buildDecorations(view);
150
- }
151
-
152
- update(update: ViewUpdate) {
153
- this.decorations = buildDecorations(update.view);
154
- }
155
- },
156
- {
157
- decorations: (value) => value.decorations,
158
- },
159
- ),
160
- styles,
161
- ];
162
- };
@@ -1,59 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import { Decoration, type DecorationSet, type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
8
-
9
- const buildDecorations = (view: EditorView): DecorationSet => {
10
- const builder = new RangeSetBuilder<Decoration>();
11
- const { state } = view;
12
- const cursor = state.selection.main.head;
13
-
14
- for (const { from, to } of view.visibleRanges) {
15
- syntaxTree(state).iterate({
16
- enter: (node) => {
17
- switch (node.name) {
18
- case 'ATXHeading1':
19
- case 'ATXHeading2':
20
- case 'ATXHeading3':
21
- case 'ATXHeading4':
22
- case 'ATXHeading5':
23
- case 'ATXHeading6': {
24
- const mark = node.node.getChild('HeaderMark');
25
- if (mark) {
26
- if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
27
- builder.add(mark.from, mark.to + 1, Decoration.replace({}));
28
- }
29
- }
30
- }
31
- }
32
- },
33
- from,
34
- to,
35
- });
36
- }
37
-
38
- return builder.finish();
39
- };
40
-
41
- export const heading = () => {
42
- return [
43
- ViewPlugin.fromClass(
44
- class {
45
- decorations: DecorationSet;
46
- constructor(view: EditorView) {
47
- this.decorations = buildDecorations(view);
48
- }
49
-
50
- update(update: ViewUpdate) {
51
- this.decorations = buildDecorations(update.view);
52
- }
53
- },
54
- {
55
- decorations: (value) => value.decorations,
56
- },
57
- ),
58
- ];
59
- };
@@ -1,71 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
8
-
9
- import { getToken } from '../../styles';
10
-
11
- // TODO(burdon): Reconcile with theme.
12
- const styles = EditorView.baseTheme({
13
- '& .cm-hr': {
14
- // Note that block-level decorations should not have vertical margins,
15
- borderTop: `1px solid ${getToken('extend.colors.neutral.200')}`,
16
- },
17
- });
18
-
19
- class HorizontalRuleWidget extends WidgetType {
20
- override toDOM() {
21
- const el = document.createElement('div');
22
- el.className = 'cm-hr';
23
- return el;
24
- }
25
- }
26
-
27
- const decoration = Decoration.replace({ widget: new HorizontalRuleWidget() });
28
-
29
- const buildDecorations = (view: EditorView): DecorationSet => {
30
- const builder = new RangeSetBuilder<Decoration>();
31
- const { state } = view;
32
- const cursor = state.selection.main.head;
33
-
34
- for (const { from, to } of view.visibleRanges) {
35
- syntaxTree(state).iterate({
36
- enter: (node) => {
37
- if (node.name === 'HorizontalRule') {
38
- // Check if cursor is inside text.
39
- if (cursor <= node.from || cursor >= node.to) {
40
- builder.add(node.from, node.to, decoration);
41
- }
42
- }
43
- },
44
- from,
45
- to,
46
- });
47
- }
48
-
49
- return builder.finish();
50
- };
51
-
52
- export const hr = () => {
53
- return [
54
- ViewPlugin.fromClass(
55
- class {
56
- decorations: DecorationSet;
57
- constructor(view: EditorView) {
58
- this.decorations = buildDecorations(view);
59
- }
60
-
61
- update(update: ViewUpdate) {
62
- this.decorations = buildDecorations(update.view);
63
- }
64
- },
65
- {
66
- decorations: (value) => value.decorations,
67
- },
68
- ),
69
- styles,
70
- ];
71
- };