@dxos/react-ui-editor 0.3.3-main.1b7ca63

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 (81) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +21 -0
  3. package/dist/lib/browser/index.mjs +883 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/types/src/components/Composer/Composer.d.ts +15 -0
  7. package/dist/types/src/components/Composer/Composer.d.ts.map +1 -0
  8. package/dist/types/src/components/Composer/Composer.stories.d.ts +28 -0
  9. package/dist/types/src/components/Composer/Composer.stories.d.ts.map +1 -0
  10. package/dist/types/src/components/Composer/index.d.ts +2 -0
  11. package/dist/types/src/components/Composer/index.d.ts.map +1 -0
  12. package/dist/types/src/components/Markdown/Markdown.d.ts +19 -0
  13. package/dist/types/src/components/Markdown/Markdown.d.ts.map +1 -0
  14. package/dist/types/src/components/Markdown/Markdown.stories.d.ts +19 -0
  15. package/dist/types/src/components/Markdown/Markdown.stories.d.ts.map +1 -0
  16. package/dist/types/src/components/Markdown/index.d.ts +2 -0
  17. package/dist/types/src/components/Markdown/index.d.ts.map +1 -0
  18. package/dist/types/src/components/Markdown/markdownTags.d.ts +17 -0
  19. package/dist/types/src/components/Markdown/markdownTags.d.ts.map +1 -0
  20. package/dist/types/src/components/Markdown/markdownTheme.d.ts +93 -0
  21. package/dist/types/src/components/Markdown/markdownTheme.d.ts.map +1 -0
  22. package/dist/types/src/components/RichText/RichText.d.ts +20 -0
  23. package/dist/types/src/components/RichText/RichText.d.ts.map +1 -0
  24. package/dist/types/src/components/RichText/RichText.stories.d.ts +26 -0
  25. package/dist/types/src/components/RichText/RichText.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/RichText/index.d.ts +2 -0
  27. package/dist/types/src/components/RichText/index.d.ts.map +1 -0
  28. package/dist/types/src/components/index.d.ts +4 -0
  29. package/dist/types/src/components/index.d.ts.map +1 -0
  30. package/dist/types/src/index.d.ts +5 -0
  31. package/dist/types/src/index.d.ts.map +1 -0
  32. package/dist/types/src/model.d.ts +37 -0
  33. package/dist/types/src/model.d.ts.map +1 -0
  34. package/dist/types/src/styles/index.d.ts +3 -0
  35. package/dist/types/src/styles/index.d.ts.map +1 -0
  36. package/dist/types/src/styles/markdown.d.ts +17 -0
  37. package/dist/types/src/styles/markdown.d.ts.map +1 -0
  38. package/dist/types/src/styles/tokens.d.ts +3 -0
  39. package/dist/types/src/styles/tokens.d.ts.map +1 -0
  40. package/dist/types/src/testing/index.d.ts +3 -0
  41. package/dist/types/src/testing/index.d.ts.map +1 -0
  42. package/dist/types/src/testing/proto/gen/schema.d.ts +15 -0
  43. package/dist/types/src/testing/proto/gen/schema.d.ts.map +1 -0
  44. package/dist/types/src/testing/proto/index.d.ts +2 -0
  45. package/dist/types/src/testing/proto/index.d.ts.map +1 -0
  46. package/dist/types/src/testing/replicator.d.ts +45 -0
  47. package/dist/types/src/testing/replicator.d.ts.map +1 -0
  48. package/dist/types/src/yjs/cursors.d.ts +5 -0
  49. package/dist/types/src/yjs/cursors.d.ts.map +1 -0
  50. package/dist/types/src/yjs/index.d.ts +3 -0
  51. package/dist/types/src/yjs/index.d.ts.map +1 -0
  52. package/dist/types/src/yjs/space-provider.d.ts +27 -0
  53. package/dist/types/src/yjs/space-provider.d.ts.map +1 -0
  54. package/dist/types/stories/html/code-mirror.stories.d.ts +12 -0
  55. package/dist/types/stories/html/code-mirror.stories.d.ts.map +1 -0
  56. package/package.json +82 -0
  57. package/src/components/Composer/Composer.stories.tsx +80 -0
  58. package/src/components/Composer/Composer.tsx +33 -0
  59. package/src/components/Composer/index.ts +5 -0
  60. package/src/components/Markdown/Markdown.stories.tsx +56 -0
  61. package/src/components/Markdown/Markdown.tsx +240 -0
  62. package/src/components/Markdown/index.ts +5 -0
  63. package/src/components/Markdown/markdownTags.ts +38 -0
  64. package/src/components/Markdown/markdownTheme.ts +236 -0
  65. package/src/components/RichText/RichText.stories.tsx +68 -0
  66. package/src/components/RichText/RichText.tsx +181 -0
  67. package/src/components/RichText/index.ts +5 -0
  68. package/src/components/index.ts +7 -0
  69. package/src/index.ts +9 -0
  70. package/src/model.ts +72 -0
  71. package/src/styles/index.ts +6 -0
  72. package/src/styles/markdown.ts +46 -0
  73. package/src/styles/tokens.ts +8 -0
  74. package/src/testing/index.ts +6 -0
  75. package/src/testing/proto/gen/schema.ts +47 -0
  76. package/src/testing/proto/index.ts +5 -0
  77. package/src/testing/proto/schema.proto +15 -0
  78. package/src/testing/replicator.ts +191 -0
  79. package/src/yjs/cursors.ts +21 -0
  80. package/src/yjs/index.ts +6 -0
  81. package/src/yjs/space-provider.ts +126 -0
@@ -0,0 +1,3 @@
1
+ export * from './proto';
2
+ export * from './replicator';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/testing/index.ts"],"names":[],"mappings":"AAIA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @generated @dxos/echo-typegen src/testing/proto/schema.proto
3
+ **/
4
+ import * as dxos_echo_schema from "@dxos/echo-schema";
5
+ export declare const types: dxos_echo_schema.TypeCollection;
6
+ export type ComposerDocumentProps = {
7
+ content: dxos_echo_schema.Text;
8
+ };
9
+ export declare class ComposerDocument extends dxos_echo_schema.TypedObject<ComposerDocumentProps> {
10
+ static readonly schema: dxos_echo_schema.Schema;
11
+ static filter(opts?: Partial<ComposerDocumentProps>): dxos_echo_schema.TypeFilter<ComposerDocument>;
12
+ constructor(initValues?: Partial<ComposerDocumentProps>, opts?: dxos_echo_schema.TypedObjectOptions);
13
+ content: dxos_echo_schema.Text;
14
+ }
15
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../../../src/testing/proto/gen/schema.ts"],"names":[],"mappings":"AAAA;;IAEI;AAEJ,OAAO,KAAK,gBAAgB,MAAM,mBAAmB,CAAC;AAEtD,eAAO,MAAM,KAAK,iCAAwC,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC;CAChC,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,gBAAgB,CAAC,WAAW,CAAC,qBAAqB,CAAC;IACvF,gBAAwB,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC;IAExD,MAAM,CAAC,MAAM,CACX,IAAI,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACpC,gBAAgB,CAAC,UAAU,CAAC,gBAAgB,CAAC;gBAQ9C,UAAU,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,EAC3C,IAAI,CAAC,EAAE,gBAAgB,CAAC,kBAAkB;IAIpC,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC;CACxC"}
@@ -0,0 +1,2 @@
1
+ export * from './gen/schema';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/testing/proto/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { Observable } from 'lib0/observable';
2
+ import * as awarenessProtocol from 'y-protocols/awareness';
3
+ import { Doc } from 'yjs';
4
+ import { Event } from '@dxos/async';
5
+ import { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
6
+ import { type ComposerModel } from '../model';
7
+ type Awareness = awarenessProtocol.Awareness;
8
+ /**
9
+ * Yjs awareness provider in-memory.
10
+ */
11
+ export declare class AwarenessProvider extends Observable<any> {
12
+ private readonly _remoteUpdate;
13
+ private readonly _awareness;
14
+ private readonly _doc;
15
+ constructor({ update, doc, awareness }: {
16
+ update: Event<Uint8Array>;
17
+ doc: Doc;
18
+ awareness?: Awareness;
19
+ });
20
+ get awareness(): Awareness;
21
+ private _handleDocUpdate;
22
+ private _handleAwarenessUpdate;
23
+ private _handleRemoteUpdate;
24
+ private _readMessage;
25
+ private _handleBeforeUnload;
26
+ }
27
+ /**
28
+ * This mocks the replication across the swarm.
29
+ */
30
+ export declare class Replicator {
31
+ private readonly _kind;
32
+ private readonly _update;
33
+ private _peers;
34
+ constructor(_kind: TextKind);
35
+ setPeers(peers: ComposerModel[]): void;
36
+ createPeer: (id: string, doc?: Doc) => ComposerModel;
37
+ }
38
+ export type UseYjsModelOptions = {
39
+ replicator: Replicator;
40
+ id: string;
41
+ doc?: Doc;
42
+ };
43
+ export declare const useYjsModel: ({ replicator, id, doc }: UseYjsModelOptions) => ComposerModel;
44
+ export {};
45
+ //# sourceMappingURL=replicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../src/testing/replicator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,iBAAiB,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,4CAA4C,CAAC;AAEtE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,KAAK,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC;AAM7C;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,UAAU,CAAC,GAAG,CAAC;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAM;gBAEf,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;QAAE,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAAC,GAAG,EAAE,GAAG,CAAC;QAAC,SAAS,CAAC,EAAE,SAAS,CAAA;KAAE;IA+BtG,IAAI,SAAS,IAAI,SAAS,CAEzB;IAED,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,YAAY;IA0CpB,OAAO,CAAC,mBAAmB;CAG5B;AAED;;GAEG;AACH,qBAAa,UAAU;IAIT,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAuB;gBAER,KAAK,EAAE,QAAQ;IAE5C,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE;IAI/B,UAAU,OAAQ,MAAM,QAAO,GAAG,KAAe,aAAa,CAmB5D;CACH;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,GAAG,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,WAAW,4BAA6B,kBAAkB,KAAG,aAIzE,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const cursorColor: {
2
+ color: string;
3
+ light: string;
4
+ };
5
+ //# sourceMappingURL=cursors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursors.d.ts","sourceRoot":"","sources":["../../../../src/yjs/cursors.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,WAAW;;;CAAsD,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './cursors';
2
+ export * from './space-provider';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/yjs/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { Observable } from 'lib0/observable';
2
+ import * as awarenessProtocol from 'y-protocols/awareness';
3
+ import { type Doc } from 'yjs';
4
+ import { type Space } from '@dxos/react-client/echo';
5
+ type Awareness = awarenessProtocol.Awareness;
6
+ /**
7
+ * Yjs awareness provider on top of a DXOS space.
8
+ */
9
+ export declare class SpaceAwarenessProvider extends Observable<any> {
10
+ private readonly _space;
11
+ private readonly _awareness;
12
+ private readonly _clientId;
13
+ private readonly _channel;
14
+ constructor({ space, doc, channel, awareness }: {
15
+ space: Space;
16
+ doc: Doc;
17
+ channel: string;
18
+ awareness?: Awareness;
19
+ });
20
+ get awareness(): Awareness;
21
+ private _handleAwarenessUpdate;
22
+ private _handleSpaceMessage;
23
+ private _readMessage;
24
+ private _handleBeforeUnload;
25
+ }
26
+ export {};
27
+ //# sourceMappingURL=space-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-provider.d.ts","sourceRoot":"","sources":["../../../../src/yjs/space-provider.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,iBAAiB,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAGrD,KAAK,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC;AAO7C;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,UAAU,CAAC,GAAG,CAAC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,SAAS,CAAA;KAAE;IA+BlH,IAAI,SAAS,IAAI,SAAS,CAEzB;IAED,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,YAAY;IAoCpB,OAAO,CAAC,mBAAmB;CAG5B"}
@@ -0,0 +1,12 @@
1
+ import type { StoryObj } from '@storybook/html';
2
+ import { Client, PublicKey } from '@dxos/react-client';
3
+ declare const _default: {
4
+ title: string;
5
+ };
6
+ export default _default;
7
+ export declare const Default: StoryObj<{
8
+ id: number;
9
+ client: Client;
10
+ spaceKey: PublicKey;
11
+ }>;
12
+ //# sourceMappingURL=code-mirror.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-mirror.stories.d.ts","sourceRoot":"","sources":["../../../../stories/html/code-mirror.stories.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAMhD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;;;;AASvD,wBAEE;AAoEF,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,CAoBjF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@dxos/react-ui-editor",
3
+ "version": "0.3.3-main.1b7ca63",
4
+ "description": "Document editing experience within a DXOS shell.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "main": "dist/lib/browser/index.mjs",
10
+ "types": "dist/types/src/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "src"
14
+ ],
15
+ "dependencies": {
16
+ "@codemirror/autocomplete": "^6.4.2",
17
+ "@codemirror/commands": "^6.2.2",
18
+ "@codemirror/lang-markdown": "^6.1.0",
19
+ "@codemirror/language": "^6.6.0",
20
+ "@codemirror/language-data": "^6.1.0",
21
+ "@codemirror/lint": "^6.2.0",
22
+ "@codemirror/search": "^6.3.0",
23
+ "@codemirror/state": "^6.2.0",
24
+ "@codemirror/theme-one-dark": "^6.1.1",
25
+ "@codemirror/view": "^6.9.2",
26
+ "@fluentui/react-tabster": "^9.12.0",
27
+ "@lezer/common": "^1.0.2",
28
+ "@lezer/highlight": "^1.1.3",
29
+ "@lezer/markdown": "^1.0.2",
30
+ "@replit/codemirror-vim": "^6.0.14",
31
+ "@tiptap/core": "2.0.0-beta.220",
32
+ "@tiptap/extension-collaboration": "2.0.0-beta.220",
33
+ "@tiptap/extension-collaboration-cursor": "2.0.0-beta.220",
34
+ "@tiptap/extension-heading": "2.0.0-beta.220",
35
+ "@tiptap/extension-list-item": "2.0.0-beta.220",
36
+ "@tiptap/extension-placeholder": "2.0.0-beta.220",
37
+ "@tiptap/pm": "2.0.0-beta.220",
38
+ "@tiptap/react": "2.0.0-beta.220",
39
+ "@tiptap/starter-kit": "2.0.0-beta.220",
40
+ "codemirror": "^6.0.1",
41
+ "lib0": "^0.2.65",
42
+ "lodash.get": "^4.4.2",
43
+ "prosemirror-model": "^1.19.0",
44
+ "prosemirror-state": "^1.4.2",
45
+ "prosemirror-view": "^1.30.1",
46
+ "y-codemirror.next": "^0.3.2",
47
+ "y-prosemirror": "~1.0.20",
48
+ "y-protocols": "^1.0.5",
49
+ "yjs": "^13.6.7",
50
+ "@dxos/debug": "0.3.3-main.1b7ca63",
51
+ "@dxos/display-name": "0.3.3-main.1b7ca63",
52
+ "@dxos/log": "0.3.3-main.1b7ca63",
53
+ "@dxos/protocols": "0.3.3-main.1b7ca63",
54
+ "@dxos/react-async": "0.3.3-main.1b7ca63",
55
+ "@dxos/react-ui": "0.3.3-main.1b7ca63",
56
+ "@dxos/react-ui-theme": "0.3.3-main.1b7ca63",
57
+ "@dxos/text-model": "0.3.3-main.1b7ca63",
58
+ "@dxos/util": "0.3.3-main.1b7ca63"
59
+ },
60
+ "devDependencies": {
61
+ "@types/lodash.get": "^4.4.7",
62
+ "@types/react": "^18.0.21",
63
+ "@types/react-dom": "^18.0.6",
64
+ "react": "^18.2.0",
65
+ "react-dom": "^18.2.0",
66
+ "@dxos/config": "0.3.3-main.1b7ca63",
67
+ "@dxos/echo-schema": "0.3.3-main.1b7ca63",
68
+ "@dxos/echo-typegen": "0.3.3-main.1b7ca63",
69
+ "@dxos/react-client": "0.3.3-main.1b7ca63"
70
+ },
71
+ "peerDependencies": {
72
+ "react": "^18.2.0",
73
+ "react-dom": "^18.2.0",
74
+ "@dxos/react-client": "0.3.3-main.1b7ca63"
75
+ },
76
+ "publishConfig": {
77
+ "access": "public"
78
+ },
79
+ "scripts": {
80
+ "prebuild": "dxtype src/testing/proto/schema.proto src/testing/proto/gen/schema.ts"
81
+ }
82
+ }
@@ -0,0 +1,80 @@
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 { Text, 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
+
15
+ import { Composer, type ComposerProps } from './Composer';
16
+ import { ComposerDocument, types as schema } from '../../testing';
17
+
18
+ export default {
19
+ component: Composer,
20
+ };
21
+
22
+ const Story = ({ spaceKey, ...args }: Pick<ComposerProps, 'slots'> & { spaceKey: PublicKey }) => {
23
+ const [generate, setGenerate] = useState(false);
24
+ const generateId = useId('generate');
25
+
26
+ const identity = useIdentity();
27
+ const space = useSpace(spaceKey);
28
+ const [document] = useQuery(space, ComposerDocument.filter());
29
+
30
+ useDataGenerator({
31
+ generator: generate ? textGenerator : undefined,
32
+ options: { text: document?.content.content },
33
+ });
34
+
35
+ return (
36
+ <main className='flex-1 min-w-0 p-4'>
37
+ <div id={generateId} className='flex'>
38
+ <input type='checkbox' onChange={(event) => setGenerate(event.target.checked)} />
39
+ Generate Data
40
+ </div>
41
+ {document?.content.toString().length}
42
+ <Composer identity={identity} space={space} text={document?.content} {...args} />
43
+ </main>
44
+ );
45
+ };
46
+
47
+ const { spaceKey: markdownSpaceKey, clients: markdownClients } = await setupPeersInSpace({
48
+ count: 2,
49
+ schema,
50
+ onCreateSpace: async (space) => {
51
+ const document = new ComposerDocument({ content: new Text('Hello, Storybook!') });
52
+ await space?.db.add(document);
53
+ },
54
+ });
55
+
56
+ export const Markdown = {
57
+ render: (args: { id: number }) => <Story {...args} spaceKey={markdownSpaceKey} />,
58
+ decorators: [ClientDecorator({ clients: markdownClients })],
59
+ };
60
+
61
+ const { spaceKey: richSpaceKey, clients: richClients } = await setupPeersInSpace({
62
+ count: 2,
63
+ schema,
64
+ onCreateSpace: async (space) => {
65
+ const document = new ComposerDocument({ content: new Text('Hello, Storybook!', TextKind.RICH) });
66
+ await space?.db.add(document);
67
+ },
68
+ });
69
+
70
+ export const Rich = {
71
+ render: (args: { id: number }) => <Story {...args} spaceKey={richSpaceKey} />,
72
+ args: {
73
+ slots: {
74
+ editor: {
75
+ className: 'z-0 rounded bg-white text-neutral-900 w-full p-4 dark:bg-neutral-850 dark:text-white min-bs-[12em]',
76
+ },
77
+ },
78
+ },
79
+ decorators: [ClientDecorator({ clients: richClients })],
80
+ };
@@ -0,0 +1,33 @@
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 ComposerSlots, useTextModel, type UseTextModelOptions } from '../../model';
10
+ import { MarkdownComposer, type MarkdownComposerRef } from '../Markdown';
11
+ import { RichTextComposer, type TipTapEditor } from '../RichText';
12
+
13
+ export type ComposerProps = UseTextModelOptions & {
14
+ slots?: ComposerSlots;
15
+ };
16
+
17
+ /**
18
+ * Memoized composer which depends DXOS platform.
19
+ *
20
+ * Determines which composer to render based on the kind of text.
21
+ */
22
+ // NOTE: Without `memo`, if parent component uses `observer` the composer re-renders excessively.
23
+ // TODO(wittjosiah): Factor out?
24
+ export const Composer = memo(
25
+ forwardRef<TipTapEditor | MarkdownComposerRef, ComposerProps>(({ slots, ...options }, forwardedRef) => {
26
+ const model = useTextModel(options);
27
+ if (model?.content instanceof YXmlFragment) {
28
+ return <RichTextComposer ref={forwardedRef as Ref<TipTapEditor>} model={model} slots={slots} />;
29
+ } else {
30
+ return <MarkdownComposer ref={forwardedRef as Ref<MarkdownComposerRef>} model={model} slots={slots} />;
31
+ }
32
+ }),
33
+ );
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Composer';
@@ -0,0 +1,56 @@
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
+
14
+ import { MarkdownComposer } from './Markdown';
15
+ import { Replicator, useYjsModel } from '../../testing';
16
+
17
+ export default {
18
+ component: MarkdownComposer,
19
+ };
20
+
21
+ export const Default = {
22
+ args: {
23
+ model: {
24
+ id: 'editor',
25
+ content: 'Hello, Storybook!',
26
+ },
27
+ },
28
+ };
29
+
30
+ const replicator = new Replicator(TextKind.PLAIN);
31
+ export const WithYjs = {
32
+ render: () => {
33
+ const [generate, setGenerate] = useState(false);
34
+ const generateId = useId('generate');
35
+
36
+ const [id] = useState(PublicKey.random().toHex());
37
+ const model = useYjsModel({ id, replicator });
38
+
39
+ useDataGenerator({
40
+ generator: generate ? textGenerator : undefined,
41
+ options: { text: typeof model?.content !== 'string' ? model?.content : undefined },
42
+ });
43
+
44
+ return (
45
+ <main className='flex-1 min-w-0 p-4'>
46
+ <div id={generateId} className='flex'>
47
+ <input type='checkbox' onChange={(event) => setGenerate(event.target.checked)} />
48
+ Generate Data
49
+ </div>
50
+ <MarkdownComposer model={model} />
51
+ </main>
52
+ );
53
+ },
54
+ // TODO(wittjosiah): Decorator for doing this without clients being initialized?
55
+ decorators: [ClientDecorator({ count: 2 })],
56
+ };
@@ -0,0 +1,240 @@
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, 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 { vim } from '@replit/codemirror-vim';
34
+ import React, {
35
+ type KeyboardEvent,
36
+ forwardRef,
37
+ useEffect,
38
+ useImperativeHandle,
39
+ useState,
40
+ useMemo,
41
+ useCallback,
42
+ } from 'react';
43
+ import { yCollab } from 'y-codemirror.next';
44
+
45
+ import { generateName } from '@dxos/display-name';
46
+ import { useThemeContext } from '@dxos/react-ui';
47
+ import { configColors } from '@dxos/react-ui-theme';
48
+ import { YText } from '@dxos/text-model';
49
+
50
+ import { markdownTagsExtension } from './markdownTags';
51
+ import { markdownDarkHighlighting, markdownTheme } from './markdownTheme';
52
+ import { type ComposerModel, type ComposerSlots } from '../../model';
53
+
54
+ export const EditorModes = ['default', 'vim'] as const;
55
+ export type EditorMode = (typeof EditorModes)[number];
56
+
57
+ export type MarkdownComposerProps = {
58
+ model?: ComposerModel;
59
+ slots?: ComposerSlots;
60
+ editorMode?: EditorMode;
61
+ onChange?: (content: string | Text) => void;
62
+ };
63
+
64
+ export type MarkdownComposerRef = {
65
+ editor: HTMLDivElement | null;
66
+ state?: EditorState;
67
+ view?: EditorView;
68
+ };
69
+
70
+ const hexadecimalPaletteSeries: (keyof typeof configColors)[] = [
71
+ 'red' as const,
72
+ 'orange' as const,
73
+ 'amber' as const,
74
+ 'yellow' as const,
75
+ 'lime' as const,
76
+ 'green' as const,
77
+ 'emerald' as const,
78
+ 'teal' as const,
79
+ 'cyan' as const,
80
+ 'sky' as const,
81
+ 'indigo' as const,
82
+ 'violet' as const,
83
+ 'purple' as const,
84
+ 'fuchsia' as const,
85
+ 'pink' as const,
86
+ 'rose' as const,
87
+ ];
88
+
89
+ const shadeKeys = {
90
+ color: '450' as const,
91
+ highlightDark: '800' as const,
92
+ highlightLight: '100' as const,
93
+ };
94
+
95
+ export const MarkdownComposer = forwardRef<MarkdownComposerRef, MarkdownComposerProps>(
96
+ ({ model, slots = {}, onChange, editorMode }, forwardedRef) => {
97
+ const { id, content, provider, peer } = model ?? {};
98
+ const { themeMode } = useThemeContext();
99
+ const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
100
+
101
+ const [parent, setParent] = useState<HTMLDivElement | null>(null);
102
+ const [state, setState] = useState<EditorState>();
103
+ const [view, setView] = useState<EditorView>();
104
+
105
+ useImperativeHandle(forwardedRef, () => ({
106
+ editor: parent,
107
+ state,
108
+ view,
109
+ }));
110
+
111
+ const listenChangesExtension = useMemo(
112
+ () =>
113
+ StateField.define({
114
+ create: () => null,
115
+ update: (_value, transaction) => {
116
+ if (transaction.docChanged && onChange) {
117
+ onChange(transaction.newDoc);
118
+ }
119
+ return null;
120
+ },
121
+ }),
122
+ [onChange],
123
+ );
124
+
125
+ useEffect(() => {
126
+ if (provider && peer) {
127
+ let peerColorDigit = Math.floor(16 * Math.random());
128
+ try {
129
+ // TODO(wittjosiah): Factor out for use w/ html-only story and RichText component.
130
+ // `peer.id` is already a `string`, so we attempt `parseInt` within a `try` since we can’t be certain it is hexadecimal.
131
+ peerColorDigit = parseInt(peer.id.slice(-1), 16);
132
+ } catch (_) {}
133
+ provider.awareness.setLocalStateField('user', {
134
+ name: peer.name ?? generateName(peer.id),
135
+ color: configColors[hexadecimalPaletteSeries[peerColorDigit]][shadeKeys.color],
136
+ colorLight:
137
+ configColors[hexadecimalPaletteSeries[peerColorDigit]][
138
+ shadeKeys[themeMode === 'dark' ? 'highlightDark' : 'highlightLight']
139
+ ],
140
+ });
141
+ }
142
+ }, [provider, peer, themeMode]);
143
+
144
+ useEffect(() => {
145
+ if (!parent) {
146
+ return;
147
+ }
148
+
149
+ const state = EditorState.create({
150
+ doc: content?.toString(),
151
+ extensions: [
152
+ // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799.
153
+ listenChangesExtension,
154
+
155
+ ...(editorMode === 'vim' ? [vim()] : []),
156
+
157
+ // All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
158
+ highlightActiveLineGutter(),
159
+ highlightSpecialChars(),
160
+ history(),
161
+ drawSelection(),
162
+ dropCursor(),
163
+ EditorState.allowMultipleSelections.of(true),
164
+ indentOnInput(),
165
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
166
+ bracketMatching(),
167
+ closeBrackets(),
168
+ autocompletion(),
169
+ rectangularSelection(),
170
+ crosshairCursor(),
171
+ highlightActiveLine(),
172
+ highlightSelectionMatches(),
173
+ placeholder(slots.editor?.placeholder ?? ''), // TODO(burdon): Needs consistent styling.
174
+ keymap.of([
175
+ ...closeBracketsKeymap,
176
+ ...defaultKeymap,
177
+ ...searchKeymap,
178
+ ...historyKeymap,
179
+ ...foldKeymap,
180
+ ...completionKeymap,
181
+ ...lintKeymap,
182
+ indentWithTab,
183
+ ]),
184
+ EditorView.lineWrapping,
185
+ // Theme
186
+ markdown({ base: markdownLanguage, codeLanguages: languages, extensions: [markdownTagsExtension] }),
187
+ EditorView.theme({ ...markdownTheme, ...slots.editor?.markdownTheme }),
188
+ ...(themeMode === 'dark'
189
+ ? [syntaxHighlighting(oneDarkHighlightStyle)]
190
+ : [syntaxHighlighting(defaultHighlightStyle)]),
191
+ // TODO(thure): All but one rule here apply to both themes; rename or refactor.
192
+ syntaxHighlighting(markdownDarkHighlighting),
193
+
194
+ // Collaboration
195
+ ...(content instanceof YText ? [yCollab(content, provider?.awareness)] : []),
196
+ ],
197
+ });
198
+
199
+ setState(state);
200
+
201
+ // NOTE: This repaints the editor.
202
+ // If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
203
+ // Ideally this should not be hit except when changing between text objects.
204
+ view?.destroy();
205
+ setView(new EditorView({ state, parent }));
206
+
207
+ return () => {
208
+ view?.destroy();
209
+ setView(undefined);
210
+ setState(undefined);
211
+ };
212
+ }, [parent, content, provider?.awareness, themeMode, editorMode]);
213
+
214
+ const handleKeyUp = useCallback(
215
+ ({ key, altKey, shiftKey, metaKey, ctrlKey }: KeyboardEvent) => {
216
+ switch (key) {
217
+ case 'Enter':
218
+ view?.contentDOM.focus();
219
+ break;
220
+
221
+ case 'Escape':
222
+ editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
223
+ break;
224
+ }
225
+ },
226
+ [view, editorMode],
227
+ );
228
+
229
+ return (
230
+ <div
231
+ tabIndex={0}
232
+ key={id}
233
+ {...slots.root}
234
+ onKeyUp={handleKeyUp}
235
+ {...(editorMode !== 'vim' ? tabsterDOMAttribute : {})}
236
+ ref={setParent}
237
+ />
238
+ );
239
+ },
240
+ );
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Markdown';