@dxos/react-ui-editor 0.3.11-main.5a4849d → 0.3.11-main.5cbcf4e

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 (216) hide show
  1. package/dist/lib/browser/index.mjs +2506 -1566
  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 -14
  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 -6
  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/hooks.stories.d.ts +23 -0
  13. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.d.ts +25 -0
  15. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  16. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +23 -0
  17. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  18. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  19. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  20. package/dist/types/src/components/index.d.ts +1 -0
  21. package/dist/types/src/components/index.d.ts.map +1 -1
  22. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  24. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  26. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  28. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  30. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  31. package/dist/types/src/extensions/automerge/defs.d.ts +24 -0
  32. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  34. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  35. package/dist/types/src/extensions/automerge/semaphore.d.ts +15 -0
  36. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  37. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  38. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  39. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  40. package/dist/types/src/extensions/awareness.d.ts +46 -0
  41. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  42. package/dist/types/src/extensions/basic.d.ts +4 -5
  43. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  44. package/dist/types/src/extensions/comments.d.ts +41 -20
  45. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  46. package/dist/types/src/extensions/cursor.d.ts +25 -0
  47. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  48. package/dist/types/src/extensions/index.d.ts +4 -7
  49. package/dist/types/src/extensions/index.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  51. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/formatting.d.ts +13 -0
  54. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  56. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  58. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  59. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  60. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  61. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  62. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  63. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  64. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  65. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  66. package/dist/types/src/extensions/outliner.d.ts +4 -0
  67. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  68. package/dist/types/src/hooks/awareness-provider.d.ts +31 -0
  69. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  70. package/dist/types/src/hooks/defs.d.ts +25 -0
  71. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  72. package/dist/types/src/hooks/index.d.ts +5 -1
  73. package/dist/types/src/hooks/index.d.ts.map +1 -1
  74. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  75. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  76. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  77. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  78. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  79. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  80. package/dist/types/src/hooks/useTextModel.d.ts +14 -31
  81. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  82. package/dist/types/src/index.d.ts +3 -0
  83. package/dist/types/src/index.d.ts.map +1 -1
  84. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  85. package/dist/types/src/styles/index.d.ts +1 -0
  86. package/dist/types/src/styles/index.d.ts.map +1 -1
  87. package/dist/types/src/styles/markdown.d.ts +2 -2
  88. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  89. package/dist/types/src/styles/tokens.d.ts +1 -0
  90. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  91. package/dist/types/src/themes/default.d.ts.map +1 -1
  92. package/dist/types/src/util.d.ts +7 -0
  93. package/dist/types/src/util.d.ts.map +1 -0
  94. package/package.json +48 -36
  95. package/src/components/TextEditor/MarkdownEditor.stories.tsx +84 -91
  96. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  97. package/src/components/TextEditor/TextEditor.tsx +135 -95
  98. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +65 -6
  99. package/src/components/TextEditor/hooks.stories.tsx +106 -0
  100. package/src/components/Toolbar/Toolbar.stories.tsx +90 -0
  101. package/src/components/Toolbar/Toolbar.tsx +166 -0
  102. package/src/components/Toolbar/index.ts +5 -0
  103. package/src/components/index.ts +1 -0
  104. package/src/extensions/autocomplete.ts +23 -19
  105. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  106. package/src/extensions/automerge/automerge.test.ts +13 -0
  107. package/src/extensions/automerge/automerge.ts +103 -0
  108. package/src/extensions/automerge/cursor.ts +52 -0
  109. package/src/extensions/automerge/defs.ts +41 -0
  110. package/src/extensions/automerge/index.ts +6 -0
  111. package/src/extensions/automerge/semaphore.ts +81 -0
  112. package/src/extensions/automerge/update-automerge.ts +49 -0
  113. package/src/{hooks → extensions}/automerge/update-codemirror.ts +17 -9
  114. package/src/extensions/awareness.ts +313 -0
  115. package/src/extensions/basic.ts +17 -10
  116. package/src/extensions/blast.ts +4 -1
  117. package/src/extensions/comments.ts +336 -255
  118. package/src/extensions/cursor.ts +50 -0
  119. package/src/extensions/index.ts +4 -7
  120. package/src/extensions/markdown/bundle.ts +23 -28
  121. package/src/extensions/markdown/code.ts +150 -0
  122. package/src/extensions/markdown/formatting.ts +188 -0
  123. package/src/extensions/markdown/heading.ts +59 -0
  124. package/src/extensions/markdown/highlight.ts +11 -26
  125. package/src/extensions/markdown/hr.ts +71 -0
  126. package/src/extensions/markdown/image.ts +87 -0
  127. package/src/extensions/markdown/index.ts +8 -0
  128. package/src/extensions/{link.ts → markdown/link.ts} +90 -83
  129. package/src/extensions/{table.ts → markdown/table.ts} +10 -4
  130. package/src/extensions/markdown/tasklist.ts +110 -0
  131. package/src/extensions/mention.ts +9 -2
  132. package/src/extensions/outliner.ts +12 -0
  133. package/src/hooks/awareness-provider.ts +124 -0
  134. package/src/hooks/defs.ts +42 -0
  135. package/src/hooks/index.ts +6 -1
  136. package/src/hooks/useActionHandler.ts +62 -0
  137. package/src/hooks/useEditorView.ts +29 -0
  138. package/src/hooks/useTextEditor.ts +153 -0
  139. package/src/hooks/useTextModel.ts +48 -115
  140. package/src/index.ts +3 -0
  141. package/src/styles/index.ts +1 -0
  142. package/src/styles/markdown.ts +9 -12
  143. package/src/styles/tokens.ts +3 -0
  144. package/src/themes/default.ts +59 -83
  145. package/src/util.ts +41 -0
  146. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  147. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  148. package/dist/types/src/extensions/code.d.ts.map +0 -1
  149. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  150. package/dist/types/src/extensions/image.d.ts.map +0 -1
  151. package/dist/types/src/extensions/link.d.ts.map +0 -1
  152. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  153. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  154. package/dist/types/src/extensions/table.d.ts.map +0 -1
  155. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  156. package/dist/types/src/extensions/util.d.ts +0 -5
  157. package/dist/types/src/extensions/util.d.ts.map +0 -1
  158. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  159. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  160. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  161. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  162. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  163. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  164. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  165. package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
  166. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  167. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  168. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  169. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  170. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  171. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  172. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  173. package/dist/types/src/hooks/yjs/space-provider.d.ts +0 -27
  174. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  175. package/dist/types/src/hooks/yjs/yjs.stories.d.ts +0 -12
  176. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  177. package/dist/types/src/hooks/yjs/yjs.test.d.ts +0 -2
  178. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  179. package/dist/types/src/testing/index.d.ts +0 -3
  180. package/dist/types/src/testing/index.d.ts.map +0 -1
  181. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  182. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  183. package/dist/types/src/testing/proto/index.d.ts +0 -2
  184. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  185. package/dist/types/src/testing/replicator.d.ts +0 -45
  186. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  187. package/src/components/TextEditor/codemirror.stories.ts +0 -114
  188. package/src/extensions/code.ts +0 -99
  189. package/src/extensions/hr.ts +0 -74
  190. package/src/extensions/image.ts +0 -69
  191. package/src/extensions/mermaid.ts +0 -11
  192. package/src/extensions/tasklist.ts +0 -121
  193. package/src/extensions/util.ts +0 -18
  194. package/src/hooks/automerge/handle.ts +0 -14
  195. package/src/hooks/automerge/index.ts +0 -6
  196. package/src/hooks/automerge/plugin.ts +0 -132
  197. package/src/hooks/automerge/semaphore.ts +0 -78
  198. package/src/hooks/automerge/update-automerge.ts +0 -44
  199. package/src/hooks/yjs/index.ts +0 -6
  200. package/src/hooks/yjs/space-provider.ts +0 -126
  201. package/src/hooks/yjs/yjs.stories.tsx +0 -56
  202. package/src/hooks/yjs/yjs.test.ts +0 -59
  203. package/src/testing/index.ts +0 -6
  204. package/src/testing/proto/gen/schema.ts +0 -49
  205. package/src/testing/proto/index.ts +0 -5
  206. package/src/testing/proto/schema.proto +0 -15
  207. package/src/testing/replicator.ts +0 -189
  208. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  209. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  210. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  211. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  212. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  213. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  214. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
  215. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  216. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
@@ -1,114 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
6
- import { EditorState } from '@codemirror/state';
7
- import { EditorView } from '@codemirror/view';
8
- import { type StoryObj } from '@storybook/html';
9
- import { basicSetup } from 'codemirror';
10
- import { yCollab } from 'y-codemirror.next';
11
-
12
- import { EventSubscriptions } from '@dxos/async';
13
- import { generateName } from '@dxos/display-name';
14
- import { Client, type PublicKey } from '@dxos/react-client';
15
- import { type Space, TextObject } from '@dxos/react-client/echo';
16
- import { type Identity } from '@dxos/react-client/halo';
17
- import { joinCommonSpace, TestBuilder, textGenerator } from '@dxos/react-client/testing';
18
- import { YText } from '@dxos/text-model';
19
-
20
- import { cursorColor, SpaceAwarenessProvider } from '../../hooks';
21
- import { EditorDocument, types as schema } from '../../testing';
22
-
23
- export default {
24
- title: 'CodeMirror',
25
- };
26
-
27
- const testBuilder = new TestBuilder();
28
- const views: Record<number, EditorView> = {};
29
- const subscriptions = new EventSubscriptions();
30
-
31
- const updateEditor = async (id: number, editor: HTMLDivElement, identity: Identity, space: Space, text: TextObject) => {
32
- const doc = text.doc;
33
- const ytext = text.content;
34
- if (!(ytext instanceof YText) || !doc) {
35
- return;
36
- }
37
-
38
- const { awareness } = new SpaceAwarenessProvider({ space, doc, channel: `yjs.awareness.${text.id}` });
39
- awareness.setLocalStateField('user', {
40
- name: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
41
- // TODO(wittjosiah): Pick colours from theme based on identity key.
42
- color: cursorColor.color,
43
- colorLight: cursorColor.light,
44
- });
45
-
46
- const state = EditorState.create({
47
- doc: ytext.toString(),
48
- extensions: [basicSetup, markdown({ base: markdownLanguage }), yCollab(ytext, awareness)],
49
- });
50
-
51
- const view = views[id];
52
- if (view) {
53
- view.setState(state);
54
- } else {
55
- views[id] = new EditorView({ state, parent: editor });
56
- }
57
-
58
- if (id === 0) {
59
- subscriptions.clear();
60
- subscriptions.add(textGenerator({ text: ytext }));
61
- }
62
- };
63
-
64
- const setupEditor = async (id: number, client: Client, spaceKey: PublicKey, editor: HTMLDivElement) => {
65
- const space = await client.spaces.get(spaceKey)!;
66
- const query = space.db.query(EditorDocument.filter());
67
- query.subscribe(({ objects }) => {
68
- const text = objects[0]?.content;
69
- void (text && updateEditor(id, editor, client.halo.identity.get()!, space, text));
70
- });
71
-
72
- const text = query.objects[0]?.content;
73
- void (text && updateEditor(id, editor, client.halo.identity.get()!, space, text));
74
- };
75
-
76
- const setupSpace = async (count: number) => {
77
- const clients = [...Array(count)].map(() => new Client({ services: testBuilder.createLocal() }));
78
- await Promise.all(clients.map((client) => client.initialize()));
79
- await Promise.all(clients.map((client) => client.halo.createIdentity()));
80
- clients.map((client) => client.spaces.addSchema(schema));
81
-
82
- const space = await clients[0].spaces.create();
83
- const text = new TextObject('Hello, Storybook!');
84
- const document = new EditorDocument({ content: text });
85
- await space.db.add(document);
86
-
87
- await joinCommonSpace(clients, space.key);
88
-
89
- return { clients, spaceKey: space.key };
90
- };
91
-
92
- const count = 2;
93
-
94
- export const Default: StoryObj<{ id: number; client: Client; spaceKey: PublicKey }> = {
95
- render: ({ id, client, spaceKey }) => {
96
- const editor = document.createElement('div');
97
- editor.setAttribute('style', 'min-width: 0; flex: 1; padding: 1rem;');
98
- void setupEditor(id, client, spaceKey, editor);
99
- return editor;
100
- },
101
- decorators: [
102
- (story) => {
103
- const container = document.createElement('div');
104
- container.setAttribute('style', 'display: flex; justify-content: space-evenly');
105
- void setupSpace(count).then(({ clients, spaceKey }) => {
106
- clients.forEach((client, id) => {
107
- // TODO(wittjosiah): According to the docs these types should be compatible.
108
- container.appendChild(story({ args: { id, client, spaceKey } }) as HTMLElement);
109
- });
110
- });
111
- return container;
112
- },
113
- ],
114
- };
@@ -1,99 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { type Range } from '@codemirror/state';
6
- import {
7
- ViewPlugin,
8
- type DecorationSet,
9
- EditorView,
10
- type ViewUpdate,
11
- Decoration,
12
- type BlockInfo,
13
- } from '@codemirror/view';
14
- import get from 'lodash.get';
15
-
16
- import { mx } from '@dxos/react-ui-theme';
17
-
18
- import { tokens } from '../styles';
19
-
20
- const CODE_REGEX = /```[\s\S]*?```/gs;
21
-
22
- // TODO(burdon): Reconcile with theme.
23
- const styles = EditorView.baseTheme({
24
- '& .cm-codeblock': {
25
- fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
26
- },
27
- '&light [aria-readonly="true"] .cm-codeblock': {
28
- background: get(tokens, 'extend.colors.neutral.50'),
29
- },
30
- '&dark [aria-readonly="true"] .cm-codeblock': {
31
- background: get(tokens, 'extend.colors.neutral.850'),
32
- },
33
- '& [aria-readonly="true"] .cm-codeblock': {
34
- display: 'block',
35
- paddingInline: '4px !important',
36
- },
37
- '& [aria-readonly="true"] .cm-codeblock-first': {
38
- paddingTop: '4px !important',
39
- borderTopLeftRadius: '4px',
40
- borderTopRightRadius: '4px',
41
- },
42
- '& [aria-readonly="true"] .cm-codeblock-last': {
43
- paddingBottom: '4px !important',
44
- borderBottomLeftRadius: '4px',
45
- borderBottomRightRadius: '4px',
46
- },
47
- });
48
-
49
- // TODO(burdon): Start from EditorView.lineBlockAt(pos).
50
- const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
51
- const start = lines.findIndex((line) => line.from >= from);
52
- const end = lines.findIndex((line) => line.to >= to);
53
- return [start, end];
54
- };
55
-
56
- export const code = () => {
57
- const getDecorations = (view: EditorView) => {
58
- const decorations: Range<Decoration>[] = [];
59
- if (view.state.readOnly) {
60
- const text = view.state.doc.sliceString(0);
61
- const matches = text.matchAll(CODE_REGEX);
62
- const blocks = view.viewportLineBlocks;
63
- for (const match of matches) {
64
- const range = getLineRange(blocks, match.index!, match.index! + match[0].length);
65
- for (let i = range[0]; i <= range[1]; i++) {
66
- const block = blocks[i];
67
- decorations.push(
68
- Decoration.line({
69
- class: mx('cm-codeblock', i === range[0] && 'cm-codeblock-first', i === range[1] && 'cm-codeblock-last'),
70
- }).range(block.from),
71
- );
72
- }
73
- }
74
- }
75
-
76
- return Decoration.set(decorations);
77
- };
78
-
79
- return [
80
- ViewPlugin.fromClass(
81
- class {
82
- decorations: DecorationSet;
83
- constructor(view: EditorView) {
84
- this.decorations = getDecorations(view);
85
- }
86
-
87
- update(update: ViewUpdate) {
88
- if (update.docChanged || update.viewportChanged) {
89
- this.decorations = getDecorations(update.view);
90
- }
91
- }
92
- },
93
- {
94
- decorations: (value) => value.decorations,
95
- },
96
- ),
97
- styles,
98
- ];
99
- };
@@ -1,74 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import {
6
- Decoration,
7
- type DecorationSet,
8
- EditorView,
9
- MatchDecorator,
10
- ViewPlugin,
11
- type ViewUpdate,
12
- WidgetType,
13
- } from '@codemirror/view';
14
- import get from 'lodash.get';
15
-
16
- import { tokens } from '../styles';
17
-
18
- // TODO(burdon): Reconcile with theme.
19
- const styles = EditorView.baseTheme({
20
- '& .cm-hr': {
21
- // TODO(burdon): ???
22
- // Note that block-level decorations should not have vertical margins,
23
- borderBottom: `1px solid ${get(tokens, 'extend.colors.neutral.200')}`,
24
- },
25
- });
26
-
27
- class HorizontalRuleWidget extends WidgetType {
28
- constructor(readonly _pos: number) {
29
- super();
30
- }
31
-
32
- override eq(other: WidgetType) {
33
- return this._pos === (other as any as HorizontalRuleWidget)._pos;
34
- }
35
-
36
- toDOM(view: EditorView) {
37
- // TODO(burdon): Create <hr> element? Does this clash with markdown parser?
38
- const el = document.createElement('div');
39
- el.className = 'cm-hr';
40
- return el;
41
- }
42
- }
43
-
44
- const placeholderMatcher = new MatchDecorator({
45
- regexp: /^---$/g,
46
- decoration: (match, view, pos) =>
47
- Decoration.replace({
48
- widget: new HorizontalRuleWidget(pos),
49
- }),
50
- });
51
-
52
- export const hr = () => [
53
- styles,
54
- ViewPlugin.fromClass(
55
- class {
56
- rules: DecorationSet;
57
- constructor(view: EditorView) {
58
- this.rules = placeholderMatcher.createDeco(view);
59
- }
60
-
61
- update(update: ViewUpdate) {
62
- this.rules = placeholderMatcher.updateDeco(update, this.rules);
63
- }
64
- },
65
- {
66
- decorations: (instance) => instance.rules,
67
- // TODO(burdon): Is this really atomic (cursor can move into ---).
68
- provide: (plugin) =>
69
- EditorView.atomicRanges.of((view) => {
70
- return view.plugin(plugin)?.rules || Decoration.none;
71
- }),
72
- },
73
- ),
74
- ];
@@ -1,69 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import {
7
- type EditorState,
8
- type Extension,
9
- type RangeSet,
10
- RangeSetBuilder,
11
- StateField,
12
- type Transaction,
13
- } from '@codemirror/state';
14
- import { Decoration, EditorView, WidgetType } from '@codemirror/view';
15
-
16
- export type ImageOptions = {};
17
-
18
- export const image = (options: ImageOptions = {}): Extension => {
19
- return StateField.define<RangeSet<any>>({
20
- create: (state) => update(state, options),
21
- update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
22
- provide: (field) => EditorView.decorations.from(field),
23
- });
24
- };
25
-
26
- const update = (state: EditorState, options: ImageOptions) => {
27
- const builder = new RangeSetBuilder();
28
- const cursor = state.selection.main.head;
29
-
30
- syntaxTree(state).iterate({
31
- enter: (node) => {
32
- if (node.name === 'Image') {
33
- const urlNode = node.node.getChild('URL');
34
- if (urlNode) {
35
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
36
- const url = state.sliceDoc(urlNode.from, urlNode.to);
37
- builder.add(
38
- hide ? node.from : node.to,
39
- node.to,
40
- Decoration.replace({
41
- block: true, // Prevent cursor from entering.
42
- widget: new ImageWidget(url),
43
- }),
44
- );
45
- }
46
- }
47
- },
48
- });
49
-
50
- return builder.finish();
51
- };
52
-
53
- class ImageWidget extends WidgetType {
54
- constructor(readonly _url: string) {
55
- super();
56
- }
57
-
58
- // TODO(burdon): Does this need to be unique for each position?
59
- override eq(other: WidgetType) {
60
- return this._url === (other as any as ImageWidget)._url;
61
- }
62
-
63
- toDOM(view: EditorView) {
64
- const img = document.createElement('img');
65
- img.setAttribute('src', this._url);
66
- img.setAttribute('class', 'cm-image');
67
- return img;
68
- }
69
- }
@@ -1,11 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type Extension } from '@codemirror/state';
6
-
7
- // TODO(burdon): How will this interact with the code extension?
8
- // TODO(burdon): Move to mermaid-plugin (which should contribute this extension).
9
- export const mermaid = (): Extension => {
10
- return [];
11
- };
@@ -1,121 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import {
6
- EditorView,
7
- Decoration,
8
- WidgetType,
9
- MatchDecorator,
10
- ViewPlugin,
11
- type DecorationSet,
12
- type ViewUpdate,
13
- } from '@codemirror/view';
14
-
15
- // TODO(burdon): Reconcile with theme.
16
- const styles = EditorView.baseTheme({
17
- '& .cm-task-item': {
18
- paddingLeft: '4px',
19
- paddingRight: '8px',
20
- },
21
- });
22
-
23
- class CheckboxWidget extends WidgetType {
24
- constructor(
25
- private readonly _pos: number,
26
- private _checked: boolean,
27
- private readonly _indent: number,
28
- private readonly _onCheck?: (check: boolean) => void,
29
- ) {
30
- super();
31
- }
32
-
33
- override eq(other: CheckboxWidget) {
34
- return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
35
- }
36
-
37
- toDOM(view: EditorView) {
38
- const wrap = document.createElement('span');
39
- wrap.className = 'cm-task-item';
40
- wrap.setAttribute('aria-hidden', 'true');
41
- wrap.style.setProperty('margin-left', this._indent * 24 + 'px');
42
-
43
- const input = wrap.appendChild(document.createElement('input'));
44
- input.type = 'checkbox';
45
- input.checked = this._checked;
46
- if (!this._onCheck) {
47
- input.setAttribute('disabled', 'true');
48
- }
49
- if (this._onCheck) {
50
- input.onchange = (event: Event) => {
51
- this._onCheck?.((event.target as any).checked);
52
- return true;
53
- };
54
- }
55
-
56
- return wrap;
57
- }
58
-
59
- override ignoreEvent() {
60
- return false;
61
- }
62
- }
63
-
64
- export type TasklistOptions = {};
65
-
66
- export const tasklist = (options: TasklistOptions = {}) => {
67
- // TODO(burdon): Matcher isn't as precise as syntax tree.
68
- // Allows for indents to be greater than AST would allow.
69
- const taskMatcher = new MatchDecorator({
70
- regexp: /^(\s*)- \[([ xX])\]\s/g,
71
- decoration: (match, view, pos) => {
72
- const indent = Math.floor(match[1].length / 2);
73
- const checked = match[2] === 'x' || match[2] === 'X';
74
- return Decoration.replace({
75
- widget: new CheckboxWidget(
76
- pos,
77
- checked,
78
- indent,
79
- view.state.readOnly
80
- ? undefined
81
- : (checked) => {
82
- const idx = pos + match[0].indexOf('[') + 1;
83
- view.dispatch({
84
- changes: {
85
- from: idx,
86
- to: idx + 1,
87
- insert: checked ? 'x' : ' ',
88
- },
89
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
90
- selection: {
91
- anchor: idx + 3,
92
- },
93
- });
94
- },
95
- ),
96
- });
97
- },
98
- });
99
-
100
- const tasks = ViewPlugin.fromClass(
101
- class {
102
- tasks: DecorationSet;
103
- constructor(view: EditorView) {
104
- this.tasks = taskMatcher.createDeco(view);
105
- }
106
-
107
- update(update: ViewUpdate) {
108
- this.tasks = taskMatcher.updateDeco(update, this.tasks);
109
- }
110
- },
111
- {
112
- decorations: (value) => value.tasks,
113
- provide: (plugin) =>
114
- EditorView.atomicRanges.of((view) => {
115
- return view.plugin(plugin)?.tasks || Decoration.none;
116
- }),
117
- },
118
- );
119
-
120
- return [tasks, styles];
121
- };
@@ -1,18 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { log } from '@dxos/log';
6
-
7
- /**
8
- * CodeMirror callbacks swallow errors so wrap handlers.
9
- */
10
- // TODO(burdon): Figure out how to systematize this.
11
- export const callbackWrapper = <T extends Function>(fn: T): T =>
12
- ((...args: any[]) => {
13
- try {
14
- return fn(...args);
15
- } catch (error) {
16
- log.catch(error);
17
- }
18
- }) as unknown as T;
@@ -1,14 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import type * as A from '@dxos/automerge/automerge';
6
-
7
- export type IDocHandle<T = any> = {
8
- docSync(): A.Doc<T> | undefined;
9
- change(callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): void;
10
- changeAt(heads: A.Heads, callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): string[] | undefined;
11
-
12
- addListener(event: 'change', listener: () => void): void;
13
- removeListener(event: 'change', listener: () => void): void;
14
- };
@@ -1,6 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- export { type IDocHandle } from './handle';
6
- export { automergePlugin, type AutomergePlugin } from './plugin';
@@ -1,132 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import {
6
- Annotation,
7
- type EditorState,
8
- type Extension,
9
- Facet,
10
- StateEffect,
11
- StateField,
12
- type Transaction,
13
- type TransactionSpec,
14
- } from '@codemirror/state';
15
- import { ViewPlugin, type EditorView, type PluginValue, type ViewUpdate } from '@codemirror/view';
16
-
17
- import * as automerge from '@dxos/automerge/automerge';
18
- import { type Heads, type Prop } from '@dxos/automerge/automerge';
19
-
20
- import { type IDocHandle } from './handle';
21
- import { PatchSemaphore } from './semaphore';
22
-
23
- export type Value = {
24
- lastHeads: Heads;
25
- path: Prop[];
26
- unreconciledTransactions: Transaction[];
27
- };
28
-
29
- type UpdateHeads = {
30
- newHeads: Heads;
31
- };
32
-
33
- export const effectType = StateEffect.define<UpdateHeads>({});
34
-
35
- export const updateHeads = (newHeads: Heads): StateEffect<UpdateHeads> => effectType.of({ newHeads });
36
-
37
- export const getLastHeads = (state: EditorState, field: Field): Heads => state.field(field).lastHeads;
38
-
39
- export const getPath = (state: EditorState, field: Field): Prop[] => state.field(field).path;
40
-
41
- export type Field = StateField<Value>;
42
-
43
- const semaphoreFacet = Facet.define<PatchSemaphore, PatchSemaphore>({
44
- combine: (values) => values.at(-1)!, // Take last.
45
- });
46
-
47
- export type AutomergePlugin = {
48
- extension: Extension;
49
- };
50
-
51
- export const automergePlugin = (handle: IDocHandle, path: Prop[]): AutomergePlugin => {
52
- const stateField: StateField<Value> = StateField.define({
53
- create: () => ({
54
- lastHeads: automerge.getHeads(handle.docSync()!),
55
- unreconciledTransactions: [],
56
- path: path.slice(),
57
- }),
58
- update: (value: Value, tr: Transaction) => {
59
- const result: Value = {
60
- lastHeads: value.lastHeads,
61
- unreconciledTransactions: value.unreconciledTransactions.slice(),
62
- path: path.slice(),
63
- };
64
- let clearUnreconciled = false;
65
- for (const effect of tr.effects) {
66
- if (effect.is(effectType)) {
67
- result.lastHeads = effect.value.newHeads;
68
- clearUnreconciled = true;
69
- }
70
- }
71
- if (clearUnreconciled) {
72
- result.unreconciledTransactions = [];
73
- } else {
74
- if (!isReconcileTx(tr)) {
75
- result.unreconciledTransactions.push(tr);
76
- }
77
- }
78
- return result;
79
- },
80
- });
81
-
82
- const semaphore = new PatchSemaphore(stateField);
83
-
84
- const viewPlugin = ViewPlugin.fromClass(
85
- class AutomergeCodemirrorViewPlugin implements PluginValue {
86
- constructor(private readonly _view: EditorView) {
87
- handle.addListener('change', this._handleChange);
88
- }
89
-
90
- update(update: ViewUpdate) {
91
- if (update.transactions.length > 0 && update.transactions.some((tr) => !isReconcileTx(tr))) {
92
- // TODO(burdon): This is a hack to ensure that the view is updated after the transaction is applied.
93
- queueMicrotask(() => {
94
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
95
- });
96
- }
97
- }
98
-
99
- destroy() {
100
- handle.addListener('change', this._handleChange);
101
- }
102
-
103
- private _handleChange = () => {
104
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
105
- };
106
- },
107
- );
108
-
109
- return {
110
- extension: [stateField, semaphoreFacet.of(semaphore), viewPlugin],
111
- };
112
- };
113
-
114
- export const reconcileAnnotationType = Annotation.define<unknown>();
115
-
116
- export const isReconcileTx = (tr: Transaction): boolean => !!tr.annotation(reconcileAnnotationType);
117
-
118
- export const makeReconcile = (tr: TransactionSpec) => {
119
- if (tr.annotations != null) {
120
- if (tr.annotations instanceof Array) {
121
- tr.annotations = [...tr.annotations, reconcileAnnotationType.of({})];
122
- } else {
123
- tr.annotations = [tr.annotations, reconcileAnnotationType.of({})];
124
- }
125
- } else {
126
- tr.annotations = [reconcileAnnotationType.of({})];
127
- }
128
- // return {
129
- // ...tr,
130
- // annotations: reconcileAnnotationType.of({})
131
- // }
132
- };