@dxos/react-ui-editor 0.3.9-next.7522f7a → 0.3.9

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 (148) hide show
  1. package/dist/lib/browser/index.mjs +930 -811
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts +2 -2
  5. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +1 -1
  6. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +2 -2
  7. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +1 -1
  8. package/dist/types/src/automerge/automerge-plugin/handle.d.ts +9 -0
  9. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +1 -0
  10. package/dist/types/src/automerge/automerge-plugin/index.d.ts +2 -2
  11. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +1 -1
  12. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts +6 -5
  13. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +1 -1
  14. package/dist/types/src/automerge/automerge.stories.d.ts +2 -1
  15. package/dist/types/src/automerge/automerge.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/TextEditor/TextEditor.d.ts +24 -20
  17. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  18. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +7 -10
  19. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  20. package/dist/types/{stories/html/code-mirror.stories.d.ts → src/components/TextEditor/codemirror.stories.d.ts} +2 -2
  21. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts +8 -0
  23. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +3 -0
  25. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +1 -0
  26. package/dist/types/src/components/TextEditor/{theme.d.ts → extensions/basic/theme.d.ts} +2 -1
  27. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +1 -0
  28. package/dist/types/src/components/TextEditor/extensions/change.d.ts +7 -0
  29. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +1 -0
  30. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +1 -0
  31. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +1 -0
  32. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +1 -0
  33. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +4 -0
  34. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +1 -0
  35. package/dist/types/src/components/TextEditor/extensions/index.d.ts +4 -3
  36. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  37. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +8 -0
  38. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -0
  39. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +3 -0
  40. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -0
  41. package/dist/types/src/components/{Markdown/markdownTags.d.ts → TextEditor/extensions/markdown/tags.d.ts} +1 -1
  42. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +1 -0
  43. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +21 -0
  44. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +1 -0
  45. package/dist/types/src/components/TextEditor/index.d.ts +1 -3
  46. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  47. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +11 -0
  48. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  49. package/dist/types/src/components/index.d.ts +0 -3
  50. package/dist/types/src/components/index.d.ts.map +1 -1
  51. package/dist/types/src/hooks/index.d.ts +4 -0
  52. package/dist/types/src/hooks/index.d.ts.map +1 -0
  53. package/dist/types/src/hooks/useCollaboration.d.ts +9 -0
  54. package/dist/types/src/hooks/useCollaboration.d.ts.map +1 -0
  55. package/dist/types/src/{model.d.ts → hooks/useTextModel.d.ts} +3 -15
  56. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -0
  57. package/dist/types/src/hooks/yjs/cursors.d.ts.map +1 -0
  58. package/dist/types/src/hooks/yjs/index.d.ts.map +1 -0
  59. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +1 -0
  60. package/dist/types/src/index.d.ts +2 -2
  61. package/dist/types/src/index.d.ts.map +1 -1
  62. package/dist/types/src/styles/tokens.d.ts +4 -0
  63. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  64. package/dist/types/src/testing/replicator.d.ts +1 -1
  65. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  66. package/package.json +30 -27
  67. package/src/automerge/automerge-plugin/PatchSemaphore.ts +5 -5
  68. package/src/automerge/automerge-plugin/codeMirrorToAm.ts +10 -7
  69. package/src/automerge/automerge-plugin/handle.ts +14 -0
  70. package/src/automerge/automerge-plugin/index.ts +2 -2
  71. package/src/automerge/automerge-plugin/plugin.ts +39 -10
  72. package/src/automerge/automerge.stories.tsx +32 -47
  73. package/src/components/TextEditor/TextEditor.stories.tsx +10 -3
  74. package/src/components/TextEditor/TextEditor.tsx +113 -56
  75. package/src/components/TextEditor/codemirror.stories.ts +114 -0
  76. package/src/components/TextEditor/extensions/basic/bundle.ts +28 -0
  77. package/src/components/TextEditor/extensions/basic/index.ts +6 -0
  78. package/src/components/TextEditor/{theme.ts → extensions/basic/theme.ts} +22 -8
  79. package/src/components/TextEditor/extensions/change.ts +22 -0
  80. package/src/components/TextEditor/extensions/hyperlink/index.ts +7 -0
  81. package/src/components/TextEditor/extensions/index.ts +4 -3
  82. package/src/components/TextEditor/extensions/markdown/bundle.ts +99 -0
  83. package/src/components/TextEditor/extensions/markdown/index.ts +6 -0
  84. package/src/components/{Markdown/markdownTheme.ts → TextEditor/extensions/markdown/theme.ts} +16 -16
  85. package/src/components/TextEditor/index.ts +1 -4
  86. package/src/components/TextEditor/yjs.stories.tsx +55 -0
  87. package/src/components/index.ts +0 -3
  88. package/src/hooks/index.ts +7 -0
  89. package/src/hooks/useCollaboration.ts +45 -0
  90. package/src/{model.ts → hooks/useTextModel.ts} +42 -17
  91. package/src/index.ts +3 -3
  92. package/src/styles/tokens.ts +6 -0
  93. package/src/testing/replicator.ts +1 -2
  94. package/dist/types/src/automerge/demo.d.ts +0 -25
  95. package/dist/types/src/automerge/demo.d.ts.map +0 -1
  96. package/dist/types/src/components/Editor/Editor.d.ts +0 -14
  97. package/dist/types/src/components/Editor/Editor.d.ts.map +0 -1
  98. package/dist/types/src/components/Editor/Editor.stories.d.ts +0 -29
  99. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +0 -1
  100. package/dist/types/src/components/Editor/index.d.ts +0 -2
  101. package/dist/types/src/components/Editor/index.d.ts.map +0 -1
  102. package/dist/types/src/components/Markdown/Markdown.d.ts +0 -21
  103. package/dist/types/src/components/Markdown/Markdown.d.ts.map +0 -1
  104. package/dist/types/src/components/Markdown/Markdown.stories.d.ts +0 -20
  105. package/dist/types/src/components/Markdown/Markdown.stories.d.ts.map +0 -1
  106. package/dist/types/src/components/Markdown/index.d.ts +0 -2
  107. package/dist/types/src/components/Markdown/index.d.ts.map +0 -1
  108. package/dist/types/src/components/Markdown/markdownTags.d.ts.map +0 -1
  109. package/dist/types/src/components/Markdown/markdownTheme.d.ts +0 -100
  110. package/dist/types/src/components/Markdown/markdownTheme.d.ts.map +0 -1
  111. package/dist/types/src/components/RichText/RichText.d.ts +0 -20
  112. package/dist/types/src/components/RichText/RichText.d.ts.map +0 -1
  113. package/dist/types/src/components/RichText/RichText.stories.d.ts +0 -27
  114. package/dist/types/src/components/RichText/RichText.stories.d.ts.map +0 -1
  115. package/dist/types/src/components/RichText/index.d.ts +0 -2
  116. package/dist/types/src/components/RichText/index.d.ts.map +0 -1
  117. package/dist/types/src/components/TextEditor/extensions/hyperlinkDecoration.d.ts.map +0 -1
  118. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts.map +0 -1
  119. package/dist/types/src/components/TextEditor/extensions/hyperlinkWidget.d.ts.map +0 -1
  120. package/dist/types/src/components/TextEditor/theme.d.ts.map +0 -1
  121. package/dist/types/src/model.d.ts.map +0 -1
  122. package/dist/types/src/yjs/cursors.d.ts.map +0 -1
  123. package/dist/types/src/yjs/index.d.ts.map +0 -1
  124. package/dist/types/src/yjs/space-provider.d.ts.map +0 -1
  125. package/dist/types/stories/html/code-mirror.stories.d.ts.map +0 -1
  126. package/src/automerge/demo.ts +0 -106
  127. package/src/components/Editor/Editor.stories.tsx +0 -82
  128. package/src/components/Editor/Editor.tsx +0 -36
  129. package/src/components/Editor/index.ts +0 -5
  130. package/src/components/Markdown/Markdown.stories.tsx +0 -70
  131. package/src/components/Markdown/Markdown.tsx +0 -239
  132. package/src/components/Markdown/index.ts +0 -5
  133. package/src/components/RichText/RichText.stories.tsx +0 -70
  134. package/src/components/RichText/RichText.tsx +0 -181
  135. package/src/components/RichText/index.ts +0 -5
  136. /package/dist/types/src/components/TextEditor/extensions/{hyperlinkDecoration.d.ts → hyperlink/hyperlinkDecoration.d.ts} +0 -0
  137. /package/dist/types/src/components/TextEditor/extensions/{hyperlinkTooltip.d.ts → hyperlink/hyperlinkTooltip.d.ts} +0 -0
  138. /package/dist/types/src/components/TextEditor/extensions/{hyperlinkWidget.d.ts → hyperlink/hyperlinkWidget.d.ts} +0 -0
  139. /package/dist/types/src/{yjs → hooks/yjs}/cursors.d.ts +0 -0
  140. /package/dist/types/src/{yjs → hooks/yjs}/index.d.ts +0 -0
  141. /package/dist/types/src/{yjs → hooks/yjs}/space-provider.d.ts +0 -0
  142. /package/src/components/TextEditor/extensions/{hyperlinkDecoration.ts → hyperlink/hyperlinkDecoration.ts} +0 -0
  143. /package/src/components/TextEditor/extensions/{hyperlinkTooltip.tsx → hyperlink/hyperlinkTooltip.tsx} +0 -0
  144. /package/src/components/TextEditor/extensions/{hyperlinkWidget.tsx → hyperlink/hyperlinkWidget.tsx} +0 -0
  145. /package/src/components/{Markdown/markdownTags.ts → TextEditor/extensions/markdown/tags.ts} +0 -0
  146. /package/src/{yjs → hooks/yjs}/cursors.ts +0 -0
  147. /package/src/{yjs → hooks/yjs}/index.ts +0 -0
  148. /package/src/{yjs → hooks/yjs}/space-provider.ts +0 -0
@@ -1,106 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { Event } from '@dxos/async';
6
- import {
7
- type Doc,
8
- next as automerge,
9
- type ChangeOptions,
10
- type ChangeFn,
11
- type Heads,
12
- type SyncState,
13
- } from '@dxos/automerge/automerge';
14
-
15
- export class Peer {
16
- changeEvent = new Event();
17
-
18
- storage = new Map<string, Uint8Array>();
19
- stats = {
20
- messagesSent: 0,
21
- messagesReceived: 0,
22
- bytesSent: 0,
23
- bytesReceived: 0,
24
- snapshotBytes: 0,
25
- incrementalBytes: 0,
26
- };
27
-
28
- doc!: Doc<{ text: string }>;
29
-
30
- change(options: string | ChangeOptions<any> | ChangeFn<any>, callback?: ChangeFn<any>) {
31
- this.doc = automerge.change(this.doc, options, callback);
32
- this._handleChange();
33
- }
34
-
35
- changeAt(scope: Heads, options: string | ChangeOptions<any> | ChangeFn<any>, callback?: ChangeFn<any>) {
36
- const { newDoc, newHeads } = automerge.changeAt(this.doc, scope, options, callback);
37
- this.doc = newDoc;
38
- this._handleChange();
39
- return newHeads;
40
- }
41
-
42
- replicate(): { readable: ReadableStream<Uint8Array>; writable: WritableStream<Uint8Array> } {
43
- let syncState: SyncState = automerge.initSyncState();
44
-
45
- let unsub: (() => void) | undefined;
46
- const readable = new ReadableStream({
47
- start: (controller) => {
48
- unsub = this.changeEvent.on(() => {
49
- const [newState, msg] = automerge.generateSyncMessage(this.doc, syncState);
50
- syncState = newState;
51
-
52
- if (msg) {
53
- this.stats.messagesSent++;
54
- this.stats.bytesSent += msg.byteLength;
55
- controller.enqueue(msg);
56
- }
57
- });
58
- },
59
- pull: (controller) => {
60
- const [newState, msg] = automerge.generateSyncMessage(this.doc, syncState);
61
- syncState = newState;
62
-
63
- if (msg) {
64
- this.stats.messagesSent++;
65
- this.stats.bytesSent += msg.byteLength;
66
- controller.enqueue(msg);
67
- }
68
- },
69
- cancel: () => {
70
- unsub?.();
71
- },
72
- });
73
-
74
- const writable = new WritableStream({
75
- write: (chunk) => {
76
- this.stats.messagesReceived++;
77
- this.stats.bytesReceived += chunk.byteLength;
78
-
79
- const [newDoc, newState] = automerge.receiveSyncMessage(this.doc, syncState, chunk);
80
- this.doc = newDoc;
81
- syncState = newState;
82
-
83
- this._handleChange();
84
- },
85
- });
86
-
87
- return { readable, writable };
88
- }
89
-
90
- private _handleChange() {
91
- // TODO(dmaretskyi): Save since + debounce.
92
-
93
- if (this.stats.incrementalBytes > 1024 && this.stats.incrementalBytes > this.stats.snapshotBytes) {
94
- const snapshot = automerge.save(this.doc);
95
- this.stats.snapshotBytes = snapshot.byteLength;
96
- this.stats.incrementalBytes = 0;
97
- console.log('snapshot save', snapshot.byteLength);
98
- } else {
99
- const chunk = automerge.saveIncremental(this.doc);
100
- this.stats.incrementalBytes += chunk.length;
101
- console.log('incremental save', chunk.length);
102
- }
103
-
104
- this.changeEvent.emit();
105
- }
106
- }
@@ -1,82 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import '@dxosTheme';
6
-
7
- import React, { useState } from 'react';
8
-
9
- import { type PublicKey } from '@dxos/react-client';
10
- import { TextObject, TextKind, useQuery, useSpace } from '@dxos/react-client/echo';
11
- import { useIdentity } from '@dxos/react-client/halo';
12
- import { ClientDecorator, setupPeersInSpace, textGenerator, useDataGenerator } from '@dxos/react-client/testing';
13
- import { useId } from '@dxos/react-ui';
14
- import { withTheme } from '@dxos/storybook-utils';
15
-
16
- import { Editor, type EditorProps } from './Editor';
17
- import { EditorDocument, types as schema } from '../../testing';
18
-
19
- export default {
20
- component: Editor,
21
- decorators: [withTheme],
22
- };
23
-
24
- const Story = ({ spaceKey, ...params }: Pick<EditorProps, 'slots'> & { spaceKey: PublicKey }) => {
25
- const [generate, setGenerate] = useState(false);
26
- const generateId = useId('generate');
27
-
28
- const identity = useIdentity();
29
- const space = useSpace(spaceKey);
30
- const [document] = useQuery(space, EditorDocument.filter());
31
-
32
- useDataGenerator({
33
- generator: generate ? textGenerator : undefined,
34
- options: { text: document?.content.content },
35
- });
36
-
37
- return (
38
- <main className='flex-1 min-w-0 p-4'>
39
- <div id={generateId} className='flex'>
40
- <input type='checkbox' onChange={(event) => setGenerate(event.target.checked)} />
41
- Generate Data
42
- </div>
43
- {document?.content.toString().length}
44
- <Editor identity={identity} space={space} text={document?.content} {...params} />
45
- </main>
46
- );
47
- };
48
-
49
- const { spaceKey: markdownSpaceKey, clients: markdownClients } = await setupPeersInSpace({
50
- count: 2,
51
- schema,
52
- onCreateSpace: async (space) => {
53
- const document = new EditorDocument({ content: new TextObject('Hello, Storybook!') });
54
- await space?.db.add(document);
55
- },
56
- });
57
-
58
- export const Markdown = {
59
- render: (args: { id: number }) => <Story {...args} spaceKey={markdownSpaceKey} />,
60
- decorators: [ClientDecorator({ clients: markdownClients })],
61
- };
62
-
63
- const { spaceKey: richSpaceKey, clients: richClients } = await setupPeersInSpace({
64
- count: 2,
65
- schema,
66
- onCreateSpace: async (space) => {
67
- const document = new EditorDocument({ content: new TextObject('Hello, Storybook!', TextKind.RICH) });
68
- await space?.db.add(document);
69
- },
70
- });
71
-
72
- export const Rich = {
73
- render: (args: { id: number }) => <Story {...args} spaceKey={richSpaceKey} />,
74
- args: {
75
- slots: {
76
- editor: {
77
- className: 'z-0 rounded bg-white text-neutral-900 w-full p-4 dark:bg-neutral-850 dark:text-white min-bs-[12em]',
78
- },
79
- },
80
- },
81
- decorators: [ClientDecorator({ clients: richClients })],
82
- };
@@ -1,36 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import React, { forwardRef, memo, type Ref } from 'react';
6
-
7
- import { YXmlFragment } from '@dxos/text-model';
8
-
9
- import { type EditorSlots, useTextModel, type UseTextModelOptions } from '../../model';
10
- import { MarkdownEditor, type MarkdownEditorRef } from '../Markdown';
11
- import { RichTextEditor, type TipTapEditor } from '../RichText';
12
-
13
- export type EditorProps = UseTextModelOptions & {
14
- slots?: EditorSlots;
15
- };
16
-
17
- /**
18
- * Memoized editor which depends on DXOS platform.
19
- * Determines which editor to render based on the kind of text.
20
- */
21
- // NOTE: Without `memo`, if parent component uses `observer` the editor re-renders excessively.
22
- // TODO(wittjosiah): Factor out?
23
- export const Editor = memo(
24
- forwardRef<TipTapEditor | MarkdownEditorRef, EditorProps>(({ slots, ...params }, forwardedRef) => {
25
- const model = useTextModel(params);
26
- if (!model) {
27
- return null;
28
- }
29
-
30
- if (model?.content instanceof YXmlFragment) {
31
- return <RichTextEditor ref={forwardedRef as Ref<TipTapEditor>} model={model} slots={slots} />;
32
- } else {
33
- return <MarkdownEditor ref={forwardedRef as Ref<MarkdownEditorRef>} model={model} slots={slots} />;
34
- }
35
- }),
36
- );
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- export * from './Editor';
@@ -1,70 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import '@dxosTheme';
6
-
7
- import React, { useState } from 'react';
8
-
9
- import { PublicKey } from '@dxos/react-client';
10
- import { TextKind } from '@dxos/react-client/echo';
11
- import { ClientDecorator, textGenerator, useDataGenerator } from '@dxos/react-client/testing';
12
- import { useId } from '@dxos/react-ui';
13
- import { withTheme } from '@dxos/storybook-utils';
14
-
15
- import { MarkdownEditor } from './Markdown';
16
- import { Replicator, useYjsModel } from '../../testing';
17
-
18
- export default {
19
- component: MarkdownEditor,
20
- decorators: [withTheme],
21
- };
22
-
23
- const content = [
24
- '',
25
- '',
26
- 'This is all about [DXOS](https://dxos.org); read more [here](https://docs.dxos.org/guide/getting-started.html).',
27
- '',
28
- 'This is a link: https://www.dxos.org',
29
- '',
30
- '',
31
- ].join('\n');
32
-
33
- export const Default = {
34
- args: {
35
- model: {
36
- id: 'editor',
37
- content,
38
- },
39
- },
40
- };
41
-
42
- const replicator = new Replicator(TextKind.PLAIN);
43
-
44
- export const WithYjs = {
45
- render: () => {
46
- const [generate, setGenerate] = useState(false);
47
- const generateId = useId('generate');
48
-
49
- const [id] = useState(PublicKey.random().toHex());
50
- const model = useYjsModel({ id, replicator });
51
-
52
- useDataGenerator({
53
- generator: generate ? textGenerator : undefined,
54
- options: { text: typeof model?.content !== 'string' ? model?.content : undefined },
55
- });
56
-
57
- return (
58
- <main className='flex-1 min-w-0 p-4'>
59
- <div id={generateId} className='flex'>
60
- <input type='checkbox' onChange={(event) => setGenerate(event.target.checked)} />
61
- Generate Data
62
- </div>
63
- <MarkdownEditor model={model} />
64
- </main>
65
- );
66
- },
67
-
68
- // TODO(wittjosiah): Decorator for doing this without clients being initialized?
69
- decorators: [ClientDecorator({ count: 2 })],
70
- };
@@ -1,239 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
6
- import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
7
- import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
8
- import {
9
- bracketMatching,
10
- defaultHighlightStyle,
11
- foldKeymap,
12
- indentOnInput,
13
- syntaxHighlighting,
14
- } from '@codemirror/language';
15
- import { languages } from '@codemirror/language-data';
16
- import { lintKeymap } from '@codemirror/lint';
17
- import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
18
- import { EditorState, type Extension, StateField, type Text } from '@codemirror/state';
19
- import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
20
- import {
21
- keymap,
22
- crosshairCursor,
23
- drawSelection,
24
- dropCursor,
25
- highlightActiveLine,
26
- highlightActiveLineGutter,
27
- highlightSpecialChars,
28
- placeholder,
29
- rectangularSelection,
30
- EditorView,
31
- } from '@codemirror/view';
32
- import { useFocusableGroup } from '@fluentui/react-tabster';
33
- // import { GFM } from '@lezer/markdown';
34
- import { vim } from '@replit/codemirror-vim';
35
- import React, {
36
- type KeyboardEvent,
37
- forwardRef,
38
- useEffect,
39
- useImperativeHandle,
40
- useState,
41
- useMemo,
42
- useCallback,
43
- } from 'react';
44
- import { yCollab } from 'y-codemirror.next';
45
-
46
- import { generateName } from '@dxos/display-name';
47
- import { useThemeContext } from '@dxos/react-ui';
48
- import { getColorForValue } from '@dxos/react-ui-theme';
49
- import { YText } from '@dxos/text-model';
50
-
51
- import { markdownTagsExtension } from './markdownTags';
52
- import { markdownHighlightStyle, markdownTheme } from './markdownTheme';
53
- import { type EditorModel, type EditorSlots } from '../../model';
54
-
55
- export const EditorModes = ['default', 'vim'] as const;
56
- export type EditorMode = (typeof EditorModes)[number];
57
-
58
- export type MarkdownEditorProps = {
59
- model: EditorModel;
60
- slots?: EditorSlots;
61
- editorMode?: EditorMode;
62
- showWidgets?: boolean;
63
- extensions?: Extension[];
64
- onChange?: (content: string | Text) => void;
65
- };
66
-
67
- export type MarkdownEditorRef = {
68
- editor: HTMLDivElement | null;
69
- state?: EditorState;
70
- view?: EditorView;
71
- };
72
-
73
- export const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>(
74
- ({ model, slots = {}, editorMode, extensions = [], onChange }, forwardedRef) => {
75
- const { id, content, provider, peer } = model ?? {};
76
- const { themeMode } = useThemeContext();
77
- const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
78
-
79
- const [parent, setParent] = useState<HTMLDivElement | null>(null);
80
- const [state, setState] = useState<EditorState>();
81
- const [view, setView] = useState<EditorView>();
82
-
83
- useImperativeHandle(forwardedRef, () => ({
84
- editor: parent,
85
- state,
86
- view,
87
- }));
88
-
89
- const listenChangesExtension = useMemo(
90
- () =>
91
- StateField.define({
92
- create: () => null,
93
- update: (_value, transaction) => {
94
- if (transaction.docChanged && onChange) {
95
- onChange(transaction.newDoc);
96
- }
97
- return null;
98
- },
99
- }),
100
- [onChange],
101
- );
102
-
103
- useEffect(() => {
104
- if (provider && peer) {
105
- provider.awareness.setLocalStateField('user', {
106
- name: peer.name ?? generateName(peer.id),
107
- color: getColorForValue({ value: peer.id, type: 'color' }),
108
- colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
109
- });
110
- }
111
- }, [provider, peer, themeMode]);
112
-
113
- useEffect(() => {
114
- if (!parent) {
115
- return;
116
- }
117
-
118
- const state = EditorState.create({
119
- doc: content?.toString(),
120
- extensions: [
121
- // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799
122
- listenChangesExtension,
123
-
124
- // TODO(burdon): Create custom bundle to make this class more reusable and retire simpler TextEditor component.
125
- // All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
126
- // https://codemirror.net/docs/ref/#codemirror.basicSetup
127
- autocompletion(),
128
- bracketMatching(),
129
- closeBrackets(),
130
- crosshairCursor(),
131
- dropCursor(),
132
- drawSelection(),
133
- highlightActiveLine(),
134
- highlightActiveLineGutter(),
135
- highlightSelectionMatches(),
136
- highlightSpecialChars(),
137
- history(),
138
- indentOnInput(),
139
- placeholder(slots.editor?.placeholder ?? ''), // TODO(burdon): Needs consistent styling.
140
- rectangularSelection(),
141
- syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
142
- EditorState.allowMultipleSelections.of(true),
143
- EditorView.lineWrapping,
144
-
145
- keymap.of([
146
- ...closeBracketsKeymap,
147
- ...completionKeymap,
148
- ...defaultKeymap,
149
- ...foldKeymap,
150
- ...historyKeymap,
151
- ...lintKeymap,
152
- ...searchKeymap,
153
- indentWithTab,
154
- ]),
155
-
156
- // Main extension.
157
- // https://github.com/codemirror/lang-markdown
158
- markdown({
159
- base: markdownLanguage,
160
- codeLanguages: languages,
161
- extensions: [
162
- // TODO(burdon): This seems to upgrade the parser.
163
- // GitHub flavored markdown bundle: Table, TaskList, Strikethrough, and Autolink.
164
- // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
165
- // https://github.github.com/gfm
166
- // GFM,
167
-
168
- // Custom styling.
169
- markdownTagsExtension,
170
- ],
171
- }),
172
-
173
- // Theme.
174
- EditorView.theme({ ...markdownTheme, ...slots.editor?.markdownTheme }),
175
- ...(themeMode === 'dark'
176
- ? [syntaxHighlighting(oneDarkHighlightStyle)]
177
- : [syntaxHighlighting(defaultHighlightStyle)]),
178
-
179
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
180
- syntaxHighlighting(markdownHighlightStyle),
181
-
182
- // Settings.
183
- ...(editorMode === 'vim' ? [vim()] : []),
184
-
185
- // TODO(burdon): Move to extensions.
186
- // Replication and awareness (incl. remote selection).
187
- // https://codemirror.net/docs/ref/#collab
188
- ...(content instanceof YText ? [yCollab(content, provider?.awareness)] : []),
189
-
190
- // Custom.
191
- ...extensions,
192
- ],
193
- });
194
-
195
- setState(state);
196
-
197
- // NOTE: This repaints the editor.
198
- // If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
199
- // Ideally this should not be hit except when changing between text objects.
200
- view?.destroy();
201
- setView(new EditorView({ state, parent }));
202
-
203
- return () => {
204
- view?.destroy();
205
- setView(undefined);
206
- setState(undefined);
207
- };
208
- }, [parent, content, provider?.awareness, themeMode, editorMode]);
209
-
210
- const handleKeyUp = useCallback(
211
- (event: KeyboardEvent) => {
212
- const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
213
- switch (key) {
214
- case 'Enter': {
215
- view?.contentDOM.focus();
216
- break;
217
- }
218
-
219
- case 'Escape': {
220
- editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
221
- break;
222
- }
223
- }
224
- },
225
- [view, editorMode],
226
- );
227
-
228
- return (
229
- <div
230
- tabIndex={0}
231
- ref={setParent}
232
- key={id}
233
- {...slots.root}
234
- {...(editorMode !== 'vim' ? tabsterDOMAttribute : {})}
235
- onKeyUp={handleKeyUp}
236
- />
237
- );
238
- },
239
- );
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- export * from './Markdown';
@@ -1,70 +0,0 @@
1
- //
2
- // Copyright 2022 DXOS.org
3
- //
4
-
5
- import '@dxosTheme';
6
-
7
- import React, { useState } from 'react';
8
-
9
- import { PublicKey } from '@dxos/react-client';
10
- import { TextKind } from '@dxos/react-client/echo';
11
- import { ClientDecorator, textGenerator, useDataGenerator } from '@dxos/react-client/testing';
12
- import { useId } from '@dxos/react-ui';
13
- import { mx } from '@dxos/react-ui-theme';
14
- import { withTheme } from '@dxos/storybook-utils';
15
-
16
- import { RichTextEditor, type RichTextEditorProps } from './RichText';
17
- import { Replicator, useYjsModel } from '../../testing';
18
-
19
- export default {
20
- component: RichTextEditor,
21
- decorators: [withTheme],
22
- };
23
-
24
- export const Default = {
25
- args: {
26
- model: {
27
- id: 'editor',
28
- content: 'Hello, Storybook!',
29
- },
30
- },
31
- };
32
-
33
- const replicator = new Replicator(TextKind.RICH);
34
- export const WithYjs = {
35
- render: (args: Omit<RichTextEditorProps, 'model'>) => {
36
- const [generate, setGenerate] = useState(false);
37
- const generateId = useId('generate');
38
-
39
- const [id] = useState(PublicKey.random().toHex());
40
- const model = useYjsModel({ id, replicator });
41
-
42
- useDataGenerator({
43
- generator: generate ? textGenerator : undefined,
44
- options: { text: typeof model?.content !== 'string' ? model?.content : undefined },
45
- });
46
-
47
- return (
48
- <main className='flex-1 min-w-0 p-4'>
49
- <div id={generateId} className='flex'>
50
- <input type='checkbox' onChange={(event) => setGenerate(event.target.checked)} />
51
- Generate Data
52
- </div>
53
- <RichTextEditor
54
- {...args}
55
- model={model}
56
- slots={{
57
- editor: {
58
- className: mx(
59
- 'z-0 rounded bg-white text-neutral-900 w-full p-4 dark:bg-neutral-850 dark:text-white min-bs-[12em]',
60
- args.slots?.editor?.className,
61
- ),
62
- },
63
- }}
64
- />
65
- </main>
66
- );
67
- },
68
- // TODO(wittjosiah): Decorator for doing this without clients being initialized?
69
- decorators: [ClientDecorator({ count: 2 })],
70
- };