@dxos/react-ui-editor 0.3.11-main.a996413 → 0.3.11-main.afa73e0

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 (186) hide show
  1. package/dist/lib/browser/index.mjs +2014 -1648
  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 +16 -13
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -3
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  13. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  14. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  16. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  17. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  18. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  19. package/dist/types/src/extensions/automerge/defs.d.ts +24 -0
  20. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  21. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  22. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  23. package/dist/types/src/extensions/automerge/semaphore.d.ts +14 -0
  24. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  26. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  28. package/dist/types/src/extensions/awareness.d.ts +46 -0
  29. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  30. package/dist/types/src/extensions/basic.d.ts +4 -5
  31. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  32. package/dist/types/src/extensions/comments.d.ts +32 -19
  33. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  34. package/dist/types/src/extensions/cursor.d.ts +25 -0
  35. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  36. package/dist/types/src/extensions/index.d.ts +4 -7
  37. package/dist/types/src/extensions/index.d.ts.map +1 -1
  38. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  39. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  40. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  41. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  42. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  43. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  44. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  45. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  46. package/dist/types/src/extensions/markdown/index.d.ts +7 -0
  47. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  49. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  50. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  51. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  52. package/dist/types/src/extensions/outliner.d.ts +4 -0
  53. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  54. package/dist/types/src/hooks/awareness-provider.d.ts +31 -0
  55. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  56. package/dist/types/src/hooks/defs.d.ts +25 -0
  57. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  58. package/dist/types/src/hooks/index.d.ts +3 -1
  59. package/dist/types/src/hooks/index.d.ts.map +1 -1
  60. package/dist/types/src/hooks/useTextEditor.d.ts +17 -0
  61. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  62. package/dist/types/src/hooks/useTextModel.d.ts +2 -31
  63. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  64. package/dist/types/src/index.d.ts +3 -0
  65. package/dist/types/src/index.d.ts.map +1 -1
  66. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  67. package/dist/types/src/styles/index.d.ts +1 -0
  68. package/dist/types/src/styles/index.d.ts.map +1 -1
  69. package/dist/types/src/styles/markdown.d.ts +2 -2
  70. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  71. package/dist/types/src/styles/tokens.d.ts +1 -0
  72. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  73. package/dist/types/src/themes/default.d.ts.map +1 -1
  74. package/dist/types/src/util.d.ts +7 -0
  75. package/dist/types/src/util.d.ts.map +1 -0
  76. package/package.json +34 -35
  77. package/src/components/TextEditor/MarkdownEditor.stories.tsx +82 -83
  78. package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
  79. package/src/components/TextEditor/TextEditor.tsx +122 -93
  80. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
  81. package/src/components/TextEditor/comments.stories.tsx +357 -0
  82. package/src/extensions/autocomplete.ts +23 -19
  83. package/src/extensions/automerge/automerge.ts +88 -0
  84. package/src/extensions/automerge/cursor.ts +52 -0
  85. package/src/extensions/automerge/defs.ts +41 -0
  86. package/src/extensions/automerge/index.ts +6 -0
  87. package/src/{hooks → extensions}/automerge/semaphore.ts +34 -29
  88. package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
  89. package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
  90. package/src/extensions/awareness.ts +310 -0
  91. package/src/extensions/basic.ts +22 -17
  92. package/src/extensions/comments.ts +252 -229
  93. package/src/extensions/cursor.ts +50 -0
  94. package/src/extensions/index.ts +4 -7
  95. package/src/extensions/markdown/bundle.ts +20 -30
  96. package/src/extensions/markdown/code.ts +127 -0
  97. package/src/extensions/markdown/heading.ts +59 -0
  98. package/src/extensions/markdown/highlight.ts +11 -26
  99. package/src/extensions/markdown/hr.ts +71 -0
  100. package/src/extensions/markdown/image.ts +87 -0
  101. package/src/extensions/markdown/index.ts +7 -0
  102. package/src/extensions/{link.ts → markdown/link.ts} +85 -88
  103. package/src/extensions/{table.ts → markdown/table.ts} +10 -4
  104. package/src/extensions/markdown/tasklist.ts +110 -0
  105. package/src/extensions/mention.ts +9 -2
  106. package/src/extensions/outliner.ts +12 -0
  107. package/src/hooks/awareness-provider.ts +124 -0
  108. package/src/hooks/defs.ts +42 -0
  109. package/src/hooks/index.ts +4 -1
  110. package/src/hooks/useTextEditor.ts +29 -0
  111. package/src/hooks/useTextModel.ts +30 -116
  112. package/src/index.ts +3 -0
  113. package/src/styles/index.ts +1 -0
  114. package/src/styles/markdown.ts +9 -12
  115. package/src/styles/tokens.ts +3 -0
  116. package/src/themes/default.ts +49 -78
  117. package/src/util.ts +41 -0
  118. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  119. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  120. package/dist/types/src/extensions/code.d.ts.map +0 -1
  121. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  122. package/dist/types/src/extensions/image.d.ts.map +0 -1
  123. package/dist/types/src/extensions/link.d.ts.map +0 -1
  124. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  125. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  126. package/dist/types/src/extensions/table.d.ts.map +0 -1
  127. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  128. package/dist/types/src/extensions/util.d.ts +0 -5
  129. package/dist/types/src/extensions/util.d.ts.map +0 -1
  130. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  131. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  132. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  133. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  134. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  135. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  136. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  137. package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
  138. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  139. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  140. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  141. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  142. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  143. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  144. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  145. package/dist/types/src/hooks/yjs/space-provider.d.ts +0 -27
  146. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  147. package/dist/types/src/hooks/yjs/yjs.stories.d.ts +0 -12
  148. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  149. package/dist/types/src/hooks/yjs/yjs.test.d.ts +0 -2
  150. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  151. package/dist/types/src/testing/index.d.ts +0 -3
  152. package/dist/types/src/testing/index.d.ts.map +0 -1
  153. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  154. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  155. package/dist/types/src/testing/proto/index.d.ts +0 -2
  156. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  157. package/dist/types/src/testing/replicator.d.ts +0 -45
  158. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  159. package/src/components/TextEditor/codemirror.stories.ts +0 -114
  160. package/src/extensions/code.ts +0 -99
  161. package/src/extensions/hr.ts +0 -74
  162. package/src/extensions/image.ts +0 -69
  163. package/src/extensions/mermaid.ts +0 -11
  164. package/src/extensions/tasklist.ts +0 -121
  165. package/src/extensions/util.ts +0 -18
  166. package/src/hooks/automerge/handle.ts +0 -14
  167. package/src/hooks/automerge/index.ts +0 -6
  168. package/src/hooks/automerge/plugin.ts +0 -132
  169. package/src/hooks/yjs/index.ts +0 -6
  170. package/src/hooks/yjs/space-provider.ts +0 -126
  171. package/src/hooks/yjs/yjs.stories.tsx +0 -56
  172. package/src/hooks/yjs/yjs.test.ts +0 -59
  173. package/src/testing/index.ts +0 -6
  174. package/src/testing/proto/gen/schema.ts +0 -49
  175. package/src/testing/proto/index.ts +0 -5
  176. package/src/testing/proto/schema.proto +0 -15
  177. package/src/testing/replicator.ts +0 -189
  178. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  179. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  180. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  181. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  182. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  183. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  184. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
  185. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  186. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
@@ -3,17 +3,14 @@
3
3
  //
4
4
 
5
5
  export * from './autocomplete';
6
+ export * from './automerge';
7
+ export * from './awareness';
6
8
  export * from './basic';
7
9
  export * from './blast';
8
- export * from './code';
9
10
  export * from './comments';
10
- export * from './hr';
11
- export * from './image';
12
- export * from './link';
11
+ export * from './cursor';
13
12
  export * from './listener';
14
13
  export * from './markdown';
15
14
  export * from './mention';
16
- export * from './mermaid';
17
- export * from './table';
18
- export * from './tasklist';
15
+ export * from './outliner';
19
16
  export * from './typewriter';
@@ -23,34 +23,26 @@ import {
23
23
  import type { ThemeMode } from '@dxos/react-ui';
24
24
 
25
25
  import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
26
+ import { type TextEditorProps } from '../../components';
26
27
 
27
28
  export type MarkdownBundleOptions = {
28
- readonly?: boolean;
29
29
  themeMode?: ThemeMode;
30
- placeholder?: string;
31
- };
30
+ } & Pick<TextEditorProps, 'placeholder'>;
32
31
 
33
32
  /**
34
33
  * Markdown bundle.
35
34
  * Refs:
36
- * https://github.com/codemirror/basic-setup
37
- * https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts#L50
38
35
  * https://codemirror.net/docs/community
39
36
  * https://codemirror.net/docs/ref/#codemirror.basicSetup
40
37
  */
41
38
  // TODO(burdon): Add Composer here: https://codemirror.net/docs/community
42
- export const markdownBundle = ({
43
- readonly,
44
- themeMode,
45
- placeholder: _placeholder,
46
- }: MarkdownBundleOptions): Extension[] => {
39
+ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: MarkdownBundleOptions): Extension[] => {
47
40
  return [
48
41
  EditorState.allowMultipleSelections.of(true),
49
42
  EditorState.tabSize.of(2),
50
43
  EditorView.lineWrapping,
51
44
 
52
- customKeymap(),
53
-
45
+ // https://github.com/codemirror/basic-setup
54
46
  bracketMatching(),
55
47
  closeBrackets(),
56
48
  crosshairCursor(),
@@ -59,7 +51,7 @@ export const markdownBundle = ({
59
51
  highlightActiveLine(),
60
52
  history(),
61
53
  indentOnInput(),
62
- _placeholder && placeholder(_placeholder),
54
+ _placeholder ? placeholder(_placeholder) : [],
63
55
 
64
56
  // Main extension.
65
57
  // https://github.com/codemirror/lang-markdown
@@ -86,21 +78,19 @@ export const markdownBundle = ({
86
78
  themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
87
79
 
88
80
  // Custom styles.
89
- syntaxHighlighting(markdownHighlightStyle(readonly)),
90
- ].filter(Boolean) as Extension[];
91
- };
81
+ syntaxHighlighting(markdownHighlightStyle()),
92
82
 
93
- // https://codemirror.net/docs/ref/#view.keymap
94
- const customKeymap = () =>
95
- keymap.of([
96
- // https://codemirror.net/docs/ref/#commands.indentWithTab
97
- indentWithTab,
98
- // https://codemirror.net/docs/ref/#commands.standardKeymap
99
- ...standardKeymap,
100
- // https://codemirror.net/docs/ref/#commands.historyKeymap
101
- ...historyKeymap,
102
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
103
- ...closeBracketsKeymap,
104
- // https://codemirror.net/docs/ref/#search.searchKeymap
105
- ...searchKeymap,
106
- ]);
83
+ keymap.of([
84
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
85
+ indentWithTab,
86
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
87
+ ...closeBracketsKeymap,
88
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
89
+ ...historyKeymap,
90
+ // https://codemirror.net/docs/ref/#search.searchKeymap
91
+ ...searchKeymap,
92
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
93
+ ...standardKeymap,
94
+ ]),
95
+ ];
96
+ };
@@ -0,0 +1,127 @@
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
+ // [aria-readonly="true"]
23
+ const styles = EditorView.baseTheme({
24
+ '& .cm-code': {
25
+ paddingInline: '1rem !important',
26
+ fontFamily: getToken('fontFamily.mono', []).join(','),
27
+ },
28
+ '& .cm-codeblock': {
29
+ display: 'inline-block',
30
+ width: '100%',
31
+ },
32
+ '&light .cm-codeblock, &light .cm-codeblock.cm-activeLine': {
33
+ background: getToken('extend.colors.neutral.25'),
34
+ },
35
+ '&dark .cm-codeblock, &dark .cm-codeblock.cm-activeLine': {
36
+ background: getToken('extend.colors.neutral.850'),
37
+ },
38
+ '& .cm-codeblock-first': {
39
+ borderTopLeftRadius: '.5rem',
40
+ borderTopRightRadius: '.5rem',
41
+ },
42
+ '& .cm-codeblock-last': {
43
+ borderBottomLeftRadius: '.5rem',
44
+ borderBottomRightRadius: '.5rem',
45
+ },
46
+ });
47
+
48
+ // TODO(burdon): Start from EditorView.lineBlockAt(pos).
49
+ const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
50
+ const start = lines.findIndex((line) => line.from >= from);
51
+ const end = lines.findIndex((line) => line.to >= to);
52
+ return [start, end];
53
+ };
54
+
55
+ class LineWidget extends WidgetType {
56
+ constructor(private readonly _className: string) {
57
+ super();
58
+ }
59
+
60
+ override toDOM() {
61
+ const el = document.createElement('span');
62
+ el.innerHTML = '&nbsp;';
63
+ el.className = mx('cm-code cm-codeblock', this._className);
64
+ return el;
65
+ }
66
+ }
67
+
68
+ const top = new LineWidget('cm-codeblock-first');
69
+ const bottom = new LineWidget('cm-codeblock-last');
70
+
71
+ const buildDecorations = (view: EditorView): DecorationSet => {
72
+ const builder = new RangeSetBuilder<Decoration>();
73
+ const { state } = view;
74
+ const blocks = view.viewportLineBlocks;
75
+ const cursor = state.selection.main.head;
76
+
77
+ // TODO(burdon): Add copy to clipboard widget.
78
+ for (const { from, to } of view.visibleRanges) {
79
+ syntaxTree(state).iterate({
80
+ enter: (node) => {
81
+ if (node.name === 'FencedCode') {
82
+ const edit = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
83
+ const range = getLineRange(blocks, node.from, node.to);
84
+ for (let i = range[0]; i <= range[1]; i++) {
85
+ const block = blocks[i];
86
+ if (!edit && (i === range[0] || i === range[1])) {
87
+ builder.add(block.from, block.to, Decoration.replace({ widget: i === range[0] ? top : bottom }));
88
+ } else {
89
+ builder.add(
90
+ block.from,
91
+ block.from,
92
+ Decoration.line({
93
+ class: mx('cm-code cm-codeblock'),
94
+ }),
95
+ );
96
+ }
97
+ }
98
+ }
99
+ },
100
+ from,
101
+ to,
102
+ });
103
+ }
104
+
105
+ return builder.finish();
106
+ };
107
+
108
+ export const code = () => {
109
+ return [
110
+ ViewPlugin.fromClass(
111
+ class {
112
+ decorations: DecorationSet;
113
+ constructor(view: EditorView) {
114
+ this.decorations = buildDecorations(view);
115
+ }
116
+
117
+ update(update: ViewUpdate) {
118
+ this.decorations = buildDecorations(update.view);
119
+ }
120
+ },
121
+ {
122
+ decorations: (value) => value.decorations,
123
+ },
124
+ ),
125
+ styles,
126
+ ];
127
+ };
@@ -0,0 +1,59 @@
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.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
+ };
@@ -6,19 +6,18 @@ import { markdownLanguage } from '@codemirror/lang-markdown';
6
6
  import { HighlightStyle } from '@codemirror/language';
7
7
  import { tags, styleTags, Tag } from '@lezer/highlight';
8
8
  import { type MarkdownConfig, Table } from '@lezer/markdown';
9
- import get from 'lodash.get';
10
9
 
11
10
  import {
12
11
  blockquote,
13
12
  bold,
14
13
  code,
15
14
  codeMark,
15
+ getToken,
16
16
  heading,
17
17
  inlineUrl,
18
18
  italic,
19
19
  mark,
20
20
  strikethrough,
21
- tokens,
22
21
  } from '../../styles';
23
22
 
24
23
  /**
@@ -32,7 +31,6 @@ export const markdownTags = {
32
31
  CodeText: Tag.define(),
33
32
  EmphasisMark: Tag.define(),
34
33
  HeaderMark: Tag.define(),
35
- // HorizontalRule: Tag.define(),
36
34
  InlineCode: Tag.define(),
37
35
  LinkLabel: Tag.define(),
38
36
  LinkReference: Tag.define(),
@@ -130,13 +128,13 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
130
128
  // Markdown marks.
131
129
  {
132
130
  tag: [markdownTags.CodeMark, markdownTags.HeaderMark, markdownTags.QuoteMark, markdownTags.EmphasisMark],
133
- class: readonly ? 'hidden' : mark,
131
+ class: mark,
134
132
  },
135
133
 
136
134
  // E.g., code block language (after ```).
137
135
  {
138
136
  tag: [tags.function(tags.variableName), tags.labelName],
139
- class: readonly ? 'hidden' : codeMark,
137
+ class: codeMark,
140
138
  },
141
139
 
142
140
  {
@@ -145,12 +143,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
145
143
  },
146
144
 
147
145
  // Headings.
148
- { tag: tags.heading1, class: heading(1, readonly) },
149
- { tag: tags.heading2, class: heading(2, readonly) },
150
- { tag: tags.heading3, class: heading(3, readonly) },
151
- { tag: tags.heading4, class: heading(4, readonly) },
152
- { tag: tags.heading5, class: heading(5, readonly) },
153
- { tag: tags.heading6, class: heading(6, readonly) },
146
+ { tag: tags.heading1, class: heading(1) },
147
+ { tag: tags.heading2, class: heading(2) },
148
+ { tag: tags.heading3, class: heading(3) },
149
+ { tag: tags.heading4, class: heading(4) },
150
+ { tag: tags.heading5, class: heading(5) },
151
+ { tag: tags.heading6, class: heading(6) },
154
152
 
155
153
  // Emphasis.
156
154
  { tag: tags.emphasis, class: italic },
@@ -167,7 +165,7 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
167
165
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
168
166
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
169
167
  // the code, but all other CSS properties will be inherited.
170
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
168
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
171
169
  {
172
170
  tag: [markdownTags.CodeText, markdownTags.InlineCode],
173
171
  class: code,
@@ -178,27 +176,14 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
178
176
  class: blockquote,
179
177
  },
180
178
 
181
- // TODO(burdon): Replace with extension.
182
- // {
183
- // tag: [markdownTags.HorizontalRule],
184
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
185
- // },
186
-
187
- // TODO(burdon): Only if being edited.
188
179
  {
189
180
  tag: [markdownTags.TableCell],
190
181
  class: 'font-mono',
191
182
  },
192
-
193
- // Main content, paragraphs, etc.
194
- // {
195
- // tag: [tags.content],
196
- // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
197
- // },
198
183
  ],
199
184
  {
200
185
  scope: markdownLanguage,
201
- all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
186
+ all: { fontFamily: getToken('fontFamily.body', []).join(',') },
202
187
  },
203
188
  );
204
189
  };
@@ -0,0 +1,71 @@
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
+ };
@@ -0,0 +1,87 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { syntaxTree } from '@codemirror/language';
6
+ import { type EditorState, type Extension, StateField, type Transaction, type Range } from '@codemirror/state';
7
+ import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
8
+
9
+ export type ImageOptions = {};
10
+
11
+ export const image = (options: ImageOptions = {}): Extension => {
12
+ return StateField.define<DecorationSet>({
13
+ create: (state) => {
14
+ return Decoration.set(buildDecorations(0, state.doc.length, state));
15
+ },
16
+ update: (value: DecorationSet, tr: Transaction) => {
17
+ if (!tr.docChanged && !tr.selection) {
18
+ return value;
19
+ }
20
+ // Find range of changes and cursor changes.
21
+ const cursor = tr.state.selection.main.head;
22
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
23
+ let from = Math.min(cursor, oldCursor);
24
+ let to = Math.max(cursor, oldCursor);
25
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
26
+ from = Math.min(from, fromB);
27
+ to = Math.max(to, toB);
28
+ });
29
+ // Expand to cover lines
30
+ from = tr.state.doc.lineAt(from).from;
31
+ to = tr.state.doc.lineAt(to).to;
32
+ return value.map(tr.changes).update({
33
+ filterFrom: from,
34
+ filterTo: to,
35
+ filter: () => false,
36
+ add: buildDecorations(from, to, tr.state),
37
+ });
38
+ },
39
+ provide: (field) => EditorView.decorations.from(field),
40
+ });
41
+ };
42
+
43
+ const buildDecorations = (from: number, to: number, state: EditorState) => {
44
+ const decorations: Range<Decoration>[] = [];
45
+ const cursor = state.selection.main.head;
46
+
47
+ syntaxTree(state).iterate({
48
+ enter: (node) => {
49
+ if (node.name === 'Image') {
50
+ const urlNode = node.node.getChild('URL');
51
+ if (urlNode) {
52
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
53
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
54
+ decorations.push(
55
+ Decoration.replace({
56
+ block: true, // Prevent cursor from entering.
57
+ widget: new ImageWidget(url),
58
+ }).range(hide ? node.from : node.to, node.to),
59
+ );
60
+ }
61
+ }
62
+ },
63
+ from,
64
+ to,
65
+ });
66
+
67
+ return decorations;
68
+ };
69
+
70
+ class ImageWidget extends WidgetType {
71
+ constructor(readonly _url: string) {
72
+ super();
73
+ }
74
+
75
+ override eq(other: this) {
76
+ return this._url === (other as any as ImageWidget)._url;
77
+ }
78
+
79
+ override toDOM(view: EditorView) {
80
+ const img = document.createElement('img');
81
+ img.setAttribute('src', this._url);
82
+ img.setAttribute('class', 'cm-image');
83
+ // Images are hidden until successfully loaded to avoid flickering effects.
84
+ img.onload = () => img.classList.add('cm-loaded-image');
85
+ return img;
86
+ }
87
+ }
@@ -3,4 +3,11 @@
3
3
  //
4
4
 
5
5
  export * from './bundle';
6
+ export * from './code';
7
+ export * from './heading';
6
8
  export * from './highlight';
9
+ export * from './hr';
10
+ export * from './image';
11
+ export * from './link';
12
+ export * from './table';
13
+ export * from './tasklist';