@dxos/plugin-markdown 0.6.13 → 0.6.14-main.7bd9c89

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 (124) hide show
  1. package/dist/lib/browser/MarkdownContainer-X53JPAW5.mjs +469 -0
  2. package/dist/lib/browser/MarkdownContainer-X53JPAW5.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-45N5MEOV.mjs +50 -0
  4. package/dist/lib/browser/chunk-45N5MEOV.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-DRJ3FPYF.mjs +15 -0
  6. package/dist/lib/browser/chunk-DRJ3FPYF.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-CQJL4G4X.mjs → chunk-US5O2P3R.mjs} +4 -2
  8. package/dist/lib/browser/chunk-US5O2P3R.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +80 -119
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/meta.json +1 -1
  12. package/dist/lib/browser/meta.mjs +1 -1
  13. package/dist/lib/browser/types/index.mjs +6 -4
  14. package/dist/lib/node/MarkdownContainer-GZ6EAOPC.cjs +484 -0
  15. package/dist/lib/node/MarkdownContainer-GZ6EAOPC.cjs.map +7 -0
  16. package/dist/lib/node/{DocumentCard-EHJDDSRY.cjs → chunk-P7YU53RP.cjs} +16 -10
  17. package/dist/lib/node/chunk-P7YU53RP.cjs.map +7 -0
  18. package/dist/lib/node/{chunk-VWQH4WC2.cjs → chunk-UJMOZCIA.cjs} +11 -8
  19. package/dist/lib/node/chunk-UJMOZCIA.cjs.map +7 -0
  20. package/dist/lib/node/chunk-W2YJVZ3N.cjs +72 -0
  21. package/dist/lib/node/chunk-W2YJVZ3N.cjs.map +7 -0
  22. package/dist/lib/node/index.cjs +114 -149
  23. package/dist/lib/node/index.cjs.map +4 -4
  24. package/dist/lib/node/meta.cjs +3 -3
  25. package/dist/lib/node/meta.cjs.map +1 -1
  26. package/dist/lib/node/meta.json +1 -1
  27. package/dist/lib/node/types/index.cjs +8 -6
  28. package/dist/lib/node/types/index.cjs.map +2 -2
  29. package/dist/lib/node-esm/MarkdownContainer-FGCDNB4S.mjs +470 -0
  30. package/dist/lib/node-esm/MarkdownContainer-FGCDNB4S.mjs.map +7 -0
  31. package/dist/lib/node-esm/chunk-MIDCCMIX.mjs +42 -0
  32. package/dist/lib/node-esm/chunk-MIDCCMIX.mjs.map +7 -0
  33. package/dist/lib/node-esm/chunk-NEVN5WR6.mjs +17 -0
  34. package/dist/lib/node-esm/chunk-NEVN5WR6.mjs.map +7 -0
  35. package/dist/lib/node-esm/chunk-UCNOGIBC.mjs +51 -0
  36. package/dist/lib/node-esm/chunk-UCNOGIBC.mjs.map +7 -0
  37. package/dist/lib/node-esm/index.mjs +494 -0
  38. package/dist/lib/node-esm/index.mjs.map +7 -0
  39. package/dist/lib/node-esm/meta.json +1 -0
  40. package/dist/lib/node-esm/meta.mjs +10 -0
  41. package/dist/lib/node-esm/types/index.mjs +15 -0
  42. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
  43. package/dist/types/src/components/MarkdownContainer.d.ts +15 -0
  44. package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -0
  45. package/dist/types/src/components/MarkdownEditor.d.ts +8 -3
  46. package/dist/types/src/components/MarkdownEditor.d.ts.map +1 -1
  47. package/dist/types/src/components/MarkdownEditor.stories.d.ts +4 -14
  48. package/dist/types/src/components/MarkdownEditor.stories.d.ts.map +1 -1
  49. package/dist/types/src/components/Toolbar.stories.d.ts +4 -2
  50. package/dist/types/src/components/Toolbar.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/index.d.ts +1 -11
  52. package/dist/types/src/components/index.d.ts.map +1 -1
  53. package/dist/types/src/extensions.d.ts +11 -15
  54. package/dist/types/src/extensions.d.ts.map +1 -1
  55. package/dist/types/src/hooks/index.d.ts +2 -0
  56. package/dist/types/src/hooks/index.d.ts.map +1 -0
  57. package/dist/types/src/hooks/useSelectCurrentThread.d.ts +6 -0
  58. package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +1 -0
  59. package/dist/types/src/meta.d.ts +4 -9
  60. package/dist/types/src/meta.d.ts.map +1 -1
  61. package/dist/types/src/types/document.d.ts +10 -1
  62. package/dist/types/src/types/document.d.ts.map +1 -1
  63. package/dist/types/src/types/types.d.ts +8 -9
  64. package/dist/types/src/types/types.d.ts.map +1 -1
  65. package/package.json +41 -36
  66. package/src/MarkdownPlugin.tsx +51 -98
  67. package/src/components/MarkdownContainer.tsx +111 -0
  68. package/src/components/MarkdownEditor.stories.tsx +34 -23
  69. package/src/components/MarkdownEditor.tsx +40 -76
  70. package/src/components/Toolbar.stories.tsx +14 -11
  71. package/src/components/index.ts +2 -14
  72. package/src/extensions.tsx +124 -67
  73. package/src/hooks/index.ts +5 -0
  74. package/src/hooks/useSelectCurrentThread.tsx +46 -0
  75. package/src/meta.ts +15 -0
  76. package/src/types/document.ts +12 -0
  77. package/src/types/types.ts +10 -7
  78. package/src/util.tsx +2 -2
  79. package/dist/lib/browser/DocumentCard-2P4EICBA.mjs +0 -11
  80. package/dist/lib/browser/DocumentEditor-GPWV3VN3.mjs +0 -11
  81. package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs +0 -10
  82. package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs.map +0 -7
  83. package/dist/lib/browser/chunk-354DCID5.mjs +0 -117
  84. package/dist/lib/browser/chunk-354DCID5.mjs.map +0 -7
  85. package/dist/lib/browser/chunk-4GGD6YJO.mjs +0 -19
  86. package/dist/lib/browser/chunk-4GGD6YJO.mjs.map +0 -7
  87. package/dist/lib/browser/chunk-7AF2JLK4.mjs +0 -164
  88. package/dist/lib/browser/chunk-7AF2JLK4.mjs.map +0 -7
  89. package/dist/lib/browser/chunk-CQJL4G4X.mjs.map +0 -7
  90. package/dist/lib/browser/chunk-RL7QY322.mjs +0 -86
  91. package/dist/lib/browser/chunk-RL7QY322.mjs.map +0 -7
  92. package/dist/lib/browser/chunk-VUN4QKTT.mjs +0 -208
  93. package/dist/lib/browser/chunk-VUN4QKTT.mjs.map +0 -7
  94. package/dist/lib/node/DocumentCard-EHJDDSRY.cjs.map +0 -7
  95. package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs +0 -29
  96. package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs.map +0 -7
  97. package/dist/lib/node/MarkdownEditor-UE23H75V.cjs +0 -31
  98. package/dist/lib/node/MarkdownEditor-UE23H75V.cjs.map +0 -7
  99. package/dist/lib/node/chunk-7XIBNEI7.cjs +0 -238
  100. package/dist/lib/node/chunk-7XIBNEI7.cjs.map +0 -7
  101. package/dist/lib/node/chunk-KTYIOXL5.cjs +0 -149
  102. package/dist/lib/node/chunk-KTYIOXL5.cjs.map +0 -7
  103. package/dist/lib/node/chunk-Q4ZSCBQE.cjs +0 -114
  104. package/dist/lib/node/chunk-Q4ZSCBQE.cjs.map +0 -7
  105. package/dist/lib/node/chunk-RVGN72IX.cjs +0 -189
  106. package/dist/lib/node/chunk-RVGN72IX.cjs.map +0 -7
  107. package/dist/lib/node/chunk-TGMR2CKU.cjs +0 -52
  108. package/dist/lib/node/chunk-TGMR2CKU.cjs.map +0 -7
  109. package/dist/lib/node/chunk-VWQH4WC2.cjs.map +0 -7
  110. package/dist/types/src/components/DocumentCard.d.ts +0 -16
  111. package/dist/types/src/components/DocumentCard.d.ts.map +0 -1
  112. package/dist/types/src/components/DocumentEditor.d.ts +0 -14
  113. package/dist/types/src/components/DocumentEditor.d.ts.map +0 -1
  114. package/dist/types/src/components/HeadingMenu.d.ts +0 -13
  115. package/dist/types/src/components/HeadingMenu.d.ts.map +0 -1
  116. package/dist/types/src/components/Layout.d.ts +0 -6
  117. package/dist/types/src/components/Layout.d.ts.map +0 -1
  118. package/src/components/DocumentCard.tsx +0 -107
  119. package/src/components/DocumentEditor.tsx +0 -137
  120. package/src/components/HeadingMenu.tsx +0 -46
  121. package/src/components/Layout.tsx +0 -27
  122. package/src/meta.tsx +0 -19
  123. /package/dist/lib/{browser/DocumentCard-2P4EICBA.mjs.map → node-esm/meta.mjs.map} +0 -0
  124. /package/dist/lib/{browser/DocumentEditor-GPWV3VN3.mjs.map → node-esm/types/index.mjs.map} +0 -0
@@ -4,12 +4,13 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
+ import { type Meta } from '@storybook/react';
7
8
  import React, { type FC, useState } from 'react';
8
9
 
9
10
  import { create } from '@dxos/echo-schema';
10
11
  import { PublicKey } from '@dxos/keys';
11
12
  import { faker } from '@dxos/random';
12
- import { createDocAccessor, createEchoObject } from '@dxos/react-client/echo';
13
+ import { createDocAccessor, createObject } from '@dxos/react-client/echo';
13
14
  import { useThemeContext } from '@dxos/react-ui';
14
15
  import {
15
16
  type Action,
@@ -37,9 +38,9 @@ import { TextType } from '../types';
37
38
 
38
39
  faker.seed(101);
39
40
 
40
- const Story: FC<{ content: string }> = ({ content }) => {
41
+ const DefaultStory: FC<{ content?: string }> = ({ content = '' }) => {
41
42
  const { themeMode } = useThemeContext();
42
- const [text] = useState(createEchoObject(create(TextType, { content })));
43
+ const [text] = useState(createObject(create(TextType, { content })));
43
44
  const [formattingState, formattingObserver] = useFormattingState();
44
45
  const [viewMode, setViewMode] = useState<EditorViewMode>('preview');
45
46
  const { parentRef, view } = useTextEditor(() => {
@@ -91,14 +92,6 @@ const Story: FC<{ content: string }> = ({ content }) => {
91
92
  );
92
93
  };
93
94
 
94
- export default {
95
- title: 'react-ui-editor/Toolbar',
96
- component: Toolbar,
97
- decorators: [withTheme, withLayout({ tooltips: true })],
98
- parameters: { translations, layout: 'fullscreen' },
99
- render: (args: any) => <Story {...args} />,
100
- } as any;
101
-
102
95
  const content = [
103
96
  '# Demo',
104
97
  '',
@@ -114,3 +107,13 @@ export const Default = {
114
107
  content,
115
108
  },
116
109
  };
110
+
111
+ const meta: Meta<typeof Toolbar.Root> = {
112
+ title: 'plugins/plugin-markdown/Toolbar',
113
+ component: Toolbar.Root,
114
+ render: DefaultStory as any,
115
+ decorators: [withTheme, withLayout({ tooltips: true })],
116
+ parameters: { translations, layout: 'fullscreen' },
117
+ };
118
+
119
+ export default meta;
@@ -2,20 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React, { type LazyExoticComponent } from 'react';
5
+ import { lazy } from 'react';
6
6
 
7
- import { type DocumentEditor as DocumentEditorType } from './DocumentEditor';
8
-
9
- export { type DocumentCardProps, type DocumentItemProps } from './DocumentCard';
10
-
11
- export * from './DocumentCard';
12
- export * from './DocumentEditor';
13
- export * from './MarkdownEditor';
14
- export * from './HeadingMenu';
15
- export * from './Layout';
16
7
  export * from './MarkdownSettings';
17
8
 
18
- // Lazily load components for content surfaces.
19
- export const DocumentCard = React.lazy(() => import('./DocumentCard'));
20
- export const DocumentEditor: LazyExoticComponent<DocumentEditorType> = React.lazy(() => import('./DocumentEditor'));
21
- export const MarkdownEditor = React.lazy(() => import('./MarkdownEditor'));
9
+ export const MarkdownContainer = lazy(() => import('./MarkdownContainer'));
@@ -2,63 +2,117 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { ArrowSquareDown, ArrowSquareOut, type Icon } from '@phosphor-icons/react';
6
- import React, { type AnchorHTMLAttributes, StrictMode } from 'react';
5
+ import React, { type AnchorHTMLAttributes, type ReactNode, useMemo } from 'react';
7
6
  import { createRoot } from 'react-dom/client';
8
7
 
9
- import { type IntentDispatcher, NavigationAction } from '@dxos/app-framework';
8
+ import { type IntentDispatcher, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
10
9
  import { invariant } from '@dxos/invariant';
11
- import { fullyQualifiedId, type Query } from '@dxos/react-client/echo';
10
+ import { createDocAccessor, fullyQualifiedId, getSpace, type Query } from '@dxos/react-client/echo';
11
+ import { useIdentity } from '@dxos/react-client/halo';
12
+ import { Icon, ThemeProvider } from '@dxos/react-ui';
13
+ import { createDataExtensions, listener, localStorageStateStoreAdapter, state } from '@dxos/react-ui-editor';
12
14
  import {
13
15
  type AutocompleteResult,
14
- type Extension,
15
16
  type EditorViewMode,
17
+ type Extension,
18
+ InputModeExtensions,
16
19
  autocomplete,
17
20
  decorateMarkdown,
21
+ folding,
22
+ formattingKeymap,
18
23
  linkTooltip,
19
24
  typewriter,
20
- formattingKeymap,
21
- InputModeExtensions,
22
- folding,
23
25
  } from '@dxos/react-ui-editor';
24
- import { getSize, mx } from '@dxos/react-ui-theme';
25
- import { isNotFalsy, nonNullable } from '@dxos/util';
26
+ import { defaultTx } from '@dxos/react-ui-theme';
27
+ import { isNotFalsy } from '@dxos/util';
26
28
 
27
- import { type DocumentType, type MarkdownSettingsProps } from './types';
29
+ import { type DocumentType, type MarkdownPluginState, type MarkdownSettingsProps } from './types';
30
+ import { setFallbackName } from './util';
28
31
 
29
- export type ExtensionsOptions = {
30
- viewMode?: EditorViewMode;
31
- settings?: MarkdownSettingsProps;
32
- document?: DocumentType;
33
- debug?: boolean;
34
- experimental?: boolean;
35
- numberedHeadings?: boolean;
36
- folding?: boolean;
37
- query?: Query<DocumentType>;
32
+ type ExtensionsOptions = {
33
+ document: DocumentType;
38
34
  dispatch?: IntentDispatcher;
35
+ query?: Query<DocumentType>;
36
+ settings: MarkdownSettingsProps;
37
+ viewMode?: EditorViewMode;
39
38
  };
40
39
 
41
- /**
42
- * Create extension instances for editor.
43
- */
44
- export const createBaseExtensions = ({
45
- viewMode,
46
- settings,
40
+ // TODO(burdon): Merge with createBaseExtensions above.
41
+ export const useExtensions = ({
42
+ extensionProviders,
47
43
  document,
48
- query,
49
- dispatch,
50
- }: ExtensionsOptions): Extension[] => {
51
- const extensions: Extension[] = [];
44
+ settings,
45
+ viewMode,
46
+ }: Pick<ExtensionsOptions, 'document' | 'settings' | 'viewMode'> &
47
+ Pick<MarkdownPluginState, 'extensionProviders'>): Extension[] => {
48
+ const dispatch = useIntentDispatcher();
49
+ const identity = useIdentity();
50
+ const space = getSpace(document);
51
+
52
+ // TODO(wittjosiah): Autocomplete is not working and this query is causing performance issues.
53
+ // TODO(burdon): Unsubscribe.
54
+ // const query = space?.db.query(Filter.schema(DocumentType));
55
+ // query?.subscribe();
56
+ const baseExtensions = useMemo(
57
+ () =>
58
+ createBaseExtensions({
59
+ document,
60
+ settings,
61
+ viewMode,
62
+ dispatch,
63
+ // query,
64
+ }),
65
+ [document, viewMode, dispatch, settings, settings.folding, settings.numberedHeadings],
66
+ );
52
67
 
53
68
  //
54
- // Editor mode.
69
+ // External extensions from other plugins.
55
70
  //
56
- if (settings?.editorInputMode) {
57
- const extension = InputModeExtensions[settings.editorInputMode];
58
- if (extension) {
59
- extensions.push(extension);
60
- }
61
- }
71
+ const pluginExtensions = useMemo<Extension[] | undefined>(
72
+ () =>
73
+ extensionProviders?.reduce((acc: Extension[], provider) => {
74
+ const extension = typeof provider === 'function' ? provider({ document }) : provider;
75
+ if (extension) {
76
+ acc.push(extension);
77
+ }
78
+
79
+ return acc;
80
+ }, []),
81
+ [extensionProviders],
82
+ );
83
+
84
+ //
85
+ // Basic plugins.
86
+ //
87
+ return useMemo<Extension[]>(
88
+ () =>
89
+ [
90
+ // NOTE: Data extensions must be first so that automerge is updated before other extensions compute their state.
91
+ createDataExtensions({
92
+ id: document.id,
93
+ text: document.content && createDocAccessor(document.content, ['content']),
94
+ space,
95
+ identity,
96
+ }),
97
+ state(localStorageStateStoreAdapter),
98
+ listener({
99
+ onChange: (text) => setFallbackName(document, text),
100
+ }),
101
+ baseExtensions,
102
+ pluginExtensions,
103
+ ].filter(isNotFalsy),
104
+ [baseExtensions, pluginExtensions, document, document.content, space, identity],
105
+ );
106
+ };
107
+
108
+ /**
109
+ * Create extension instances for editor.
110
+ */
111
+ const createBaseExtensions = ({ document, dispatch, settings, query, viewMode }: ExtensionsOptions): Extension[] => {
112
+ const extensions: Extension[] = [
113
+ settings.editorInputMode && InputModeExtensions[settings.editorInputMode],
114
+ settings.folding && folding(),
115
+ ].filter(isNotFalsy);
62
116
 
63
117
  //
64
118
  // Markdown
@@ -69,7 +123,7 @@ export const createBaseExtensions = ({
69
123
  formattingKeymap(),
70
124
  decorateMarkdown({
71
125
  selectionChangeDelay: 100,
72
- numberedHeadings: settings?.numberedHeadings ? { from: 2 } : undefined,
126
+ numberedHeadings: settings.numberedHeadings ? { from: 2 } : undefined,
73
127
  // TODO(wittjosiah): For internal links, consider ignoring the link text and rendering the label of the object being linked to.
74
128
  renderLinkButton:
75
129
  dispatch && document
@@ -98,7 +152,6 @@ export const createBaseExtensions = ({
98
152
  extensions.push(
99
153
  autocomplete({
100
154
  onSearch: (text: string) => {
101
- // TODO query
102
155
  // TODO(burdon): Specify filter (e.g., stack).
103
156
  return query.objects
104
157
  .map<AutocompleteResult | undefined>((object) =>
@@ -110,29 +163,27 @@ export const createBaseExtensions = ({
110
163
  }
111
164
  : undefined,
112
165
  )
113
- .filter(nonNullable);
166
+ .filter(isNotFalsy);
114
167
  },
115
168
  }),
116
169
  );
117
170
  }
118
171
 
119
- extensions.push(
120
- ...[
121
- //
122
- settings?.folding && folding(),
123
- ].filter(isNotFalsy),
124
- );
125
-
126
- if (settings?.debug) {
127
- const items = settings.typewriter ?? '';
128
- extensions.push(...[items ? typewriter({ items: items.split(/[,\n]/) }) : undefined].filter(nonNullable));
172
+ if (settings.debug) {
173
+ const items = settings.typewriter?.split(/[,\n]/) ?? '';
174
+ if (items) {
175
+ extensions.push(typewriter({ items }));
176
+ }
129
177
  }
130
178
 
131
179
  return extensions;
132
180
  };
133
181
 
134
- // TODO(burdon): Factor out style.
135
- const hover = 'rounded-sm text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
182
+ // TODO(burdon): Factor out styles.
183
+ const style = {
184
+ hover: 'rounded-sm text-primary-500 hover:text-primary-600 dark:text-primary-500 hover:dark:text-primary-400',
185
+ icon: 'inline-block leading-none mis-1 cursor-pointer',
186
+ };
136
187
 
137
188
  const onRenderLink = (onSelectObject: (id: string) => void) => (el: Element, url: string) => {
138
189
  // TODO(burdon): Formalize/document internal link format.
@@ -155,25 +206,31 @@ const onRenderLink = (onSelectObject: (id: string) => void) => (el: Element, url
155
206
  target: '_blank',
156
207
  };
157
208
 
158
- const LinkIcon: Icon = isInternal ? ArrowSquareDown : ArrowSquareOut;
159
-
160
- createRoot(el).render(
161
- <StrictMode>
162
- <a {...options} className={hover}>
163
- <LinkIcon weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 cursor-pointer')} />
164
- </a>
165
- </StrictMode>,
209
+ renderRoot(
210
+ el,
211
+ <a {...options} className={style.hover}>
212
+ <Icon
213
+ icon={isInternal ? 'ph--arrow-square-down--bold' : 'ph--arrow-square-out--bold'}
214
+ size={4}
215
+ classNames={style.icon}
216
+ />
217
+ </a>,
166
218
  );
167
219
  };
168
220
 
169
221
  const renderLinkTooltip = (el: Element, url: string) => {
170
222
  const web = new URL(url);
171
- createRoot(el).render(
172
- <StrictMode>
173
- <a href={url} target='_blank' rel='noreferrer' className={hover}>
174
- {web.origin}
175
- <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 cursor-pointer')} />
176
- </a>
177
- </StrictMode>,
223
+ renderRoot(
224
+ el,
225
+ <a href={url} rel='noreferrer' target='_blank' className={style.hover}>
226
+ {web.origin}
227
+ <Icon icon='ph--arrow-square-out--bold' size={4} classNames={style.icon} />
228
+ </a>,
178
229
  );
179
230
  };
231
+
232
+ // TODO(burdon): Remove react rendering; use DOM directly.
233
+ export const renderRoot = <T extends Element>(root: T, node: ReactNode): T => {
234
+ createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
235
+ return root;
236
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './useSelectCurrentThread';
@@ -0,0 +1,46 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { EditorView } from '@codemirror/view';
6
+ import { useCallback } from 'react';
7
+
8
+ import { LayoutAction, useIntentResolver } from '@dxos/app-framework';
9
+ import { Cursor, setSelection } from '@dxos/react-ui-editor';
10
+
11
+ import { MARKDOWN_PLUGIN } from '../meta';
12
+
13
+ /**
14
+ * Handle scrolling and selection of the current thread in a markdown editor.
15
+ */
16
+ export const useSelectCurrentThread = (editorView: EditorView | undefined, documentId: string) => {
17
+ const handleScrollIntoView = useCallback(
18
+ ({ action, data }: { action: string; data?: any }) => {
19
+ if (action === LayoutAction.SCROLL_INTO_VIEW) {
20
+ if (editorView && data?.id === documentId && data?.cursor) {
21
+ // TODO(burdon): We need typed intents.
22
+ const range = Cursor.getRangeFromCursor(editorView.state, data.cursor);
23
+ if (range) {
24
+ const selection = editorView.state.selection.main.from !== range.from ? { anchor: range.from } : undefined;
25
+ const effects = [
26
+ // NOTE: This does not use the DOM scrollIntoView function.
27
+ EditorView.scrollIntoView(range.from, { y: 'start', yMargin: 96 }),
28
+ ];
29
+ if (selection) {
30
+ // Update the editor selection to get bi-directional highlighting.
31
+ effects.push(setSelection.of({ current: documentId }));
32
+ }
33
+
34
+ editorView.dispatch({
35
+ effects,
36
+ selection: selection ? { anchor: range.from } : undefined,
37
+ });
38
+ }
39
+ }
40
+ }
41
+ },
42
+ [documentId, editorView],
43
+ );
44
+
45
+ useIntentResolver(MARKDOWN_PLUGIN, handleScrollIntoView);
46
+ };
package/src/meta.ts ADDED
@@ -0,0 +1,15 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type PluginMeta } from '@dxos/app-framework';
6
+
7
+ export const MARKDOWN_PLUGIN = 'dxos.org/plugin/markdown';
8
+
9
+ export default {
10
+ id: MARKDOWN_PLUGIN,
11
+ name: 'Markdown Editor',
12
+ description: 'Text editor supporting extended Markdown.',
13
+ homePage: 'https://github.com/dxos/dxos/tree/main/packages/apps/plugins/plugin-markdown',
14
+ icon: 'ph--text-aa--regular',
15
+ } satisfies PluginMeta;
@@ -15,3 +15,15 @@ export class DocumentType extends TypedObject({ typename: 'dxos.org/type/Documen
15
15
  content: ref(TextType),
16
16
  threads: S.mutable(S.Array(ref(ThreadType))),
17
17
  }) {}
18
+
19
+ /**
20
+ * Checks if an object conforms to the interface needed to render an editor.
21
+ */
22
+ // TODO(burdon): Normalize types (from FilesPlugin).
23
+ export const isEditorModel = (data: any): data is { id: string; text: string } =>
24
+ data &&
25
+ typeof data === 'object' &&
26
+ 'id' in data &&
27
+ typeof data.id === 'string' &&
28
+ 'text' in data &&
29
+ typeof data.text === 'string';
@@ -12,6 +12,7 @@ import type {
12
12
  TranslationsProvides,
13
13
  } from '@dxos/app-framework';
14
14
  import { type SchemaProvides } from '@dxos/plugin-client';
15
+ import { type SpaceInitProvides } from '@dxos/plugin-space';
15
16
  import { type Extension, type EditorInputMode, type EditorViewMode } from '@dxos/react-ui-editor';
16
17
 
17
18
  import { type DocumentType } from './document';
@@ -26,13 +27,15 @@ export enum MarkdownAction {
26
27
 
27
28
  export type MarkdownProperties = Record<string, any>;
28
29
 
29
- export type ExtensionsProvider = (props: { document?: DocumentType }) => Extension[];
30
+ // TODO(burdon): Async.
31
+ export type MarkdownExtensionProvider = (props: { document?: DocumentType }) => Extension | undefined;
30
32
 
31
33
  export type OnChange = (text: string) => void;
32
34
 
33
35
  export type MarkdownExtensionProvides = {
36
+ // TODO(burdon): Rename.
34
37
  markdown: {
35
- extensions: ExtensionsProvider;
38
+ extensions: MarkdownExtensionProvider;
36
39
  };
37
40
  };
38
41
 
@@ -45,11 +48,11 @@ type StackProvides = {
45
48
 
46
49
  export type MarkdownPluginState = {
47
50
  // Codemirror extensions provided by other plugins.
48
- extensionProviders: NonNullable<ExtensionsProvider>[];
51
+ extensionProviders?: MarkdownExtensionProvider[];
49
52
 
50
53
  // TODO(burdon): Extend view mode per document to include scroll position, etc.
51
54
  // View mode per document.
52
- viewMode: { [key: string]: EditorViewMode };
55
+ viewMode: Record<string, EditorViewMode>;
53
56
  };
54
57
 
55
58
  export type MarkdownSettingsProps = {
@@ -64,12 +67,11 @@ export type MarkdownSettingsProps = {
64
67
  folding?: boolean;
65
68
  };
66
69
 
67
- // TODO(Zan): Move this to the plugin-space plugin or another common location
68
- // when we implement comments in sheets.
70
+ // TODO(Zan): Move this to the plugin-space plugin or another common location when we implement comments in sheets.
69
71
  type ThreadProvides<T> = {
70
72
  thread: {
71
73
  predicate: (obj: any) => obj is T;
72
- createSort: (obj: T) => (anchorA: string, anchorB: string) => number;
74
+ createSort: (obj: T) => (anchorA: string | undefined, anchorB: string | undefined) => number;
73
75
  };
74
76
  };
75
77
 
@@ -81,5 +83,6 @@ export type MarkdownPluginProvides = SurfaceProvides &
81
83
  SettingsProvides<MarkdownSettingsProps> &
82
84
  TranslationsProvides &
83
85
  SchemaProvides &
86
+ SpaceInitProvides &
84
87
  StackProvides &
85
88
  ThreadProvides<DocumentType>;
package/src/util.tsx CHANGED
@@ -5,7 +5,7 @@
5
5
  import { type Plugin } from '@dxos/app-framework';
6
6
  import { debounce } from '@dxos/async';
7
7
  import { type TypedObjectSerializer } from '@dxos/plugin-space/types';
8
- import { create, createEchoObject, isEchoObject, loadObjectReferences } from '@dxos/react-client/echo';
8
+ import { create, createObject, isEchoObject, loadObjectReferences } from '@dxos/react-client/echo';
9
9
 
10
10
  import { DocumentType, type MarkdownProperties, type MarkdownExtensionProvides, TextType } from './types';
11
11
 
@@ -43,6 +43,6 @@ export const serializer: TypedObjectSerializer<DocumentType> = {
43
43
 
44
44
  deserialize: async ({ content: serialized }) => {
45
45
  const { name, content } = JSON.parse(serialized);
46
- return createEchoObject(create(DocumentType, { name, content: create(TextType, { content }), threads: [] }));
46
+ return createObject(create(DocumentType, { name, content: create(TextType, { content }), threads: [] }));
47
47
  },
48
48
  };
@@ -1,11 +0,0 @@
1
- import {
2
- DocumentCard,
3
- DocumentCard_default
4
- } from "./chunk-RL7QY322.mjs";
5
- import "./chunk-354DCID5.mjs";
6
- import "./chunk-4GGD6YJO.mjs";
7
- export {
8
- DocumentCard,
9
- DocumentCard_default as default
10
- };
11
- //# sourceMappingURL=DocumentCard-2P4EICBA.mjs.map
@@ -1,11 +0,0 @@
1
- import {
2
- DocumentEditor_default
3
- } from "./chunk-7AF2JLK4.mjs";
4
- import "./chunk-CQJL4G4X.mjs";
5
- import "./chunk-354DCID5.mjs";
6
- import "./chunk-VUN4QKTT.mjs";
7
- import "./chunk-4GGD6YJO.mjs";
8
- export {
9
- DocumentEditor_default as default
10
- };
11
- //# sourceMappingURL=DocumentEditor-GPWV3VN3.mjs.map
@@ -1,10 +0,0 @@
1
- import {
2
- MarkdownEditor,
3
- MarkdownEditor_default
4
- } from "./chunk-VUN4QKTT.mjs";
5
- import "./chunk-4GGD6YJO.mjs";
6
- export {
7
- MarkdownEditor,
8
- MarkdownEditor_default as default
9
- };
10
- //# sourceMappingURL=MarkdownEditor-EKJJQEFL.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [],
5
- "mappings": "",
6
- "names": []
7
- }
@@ -1,117 +0,0 @@
1
- // packages/plugins/plugin-markdown/src/extensions.tsx
2
- import { ArrowSquareDown, ArrowSquareOut } from "@phosphor-icons/react";
3
- import React, { StrictMode } from "react";
4
- import { createRoot } from "react-dom/client";
5
- import { NavigationAction } from "@dxos/app-framework";
6
- import { invariant } from "@dxos/invariant";
7
- import { fullyQualifiedId } from "@dxos/react-client/echo";
8
- import { autocomplete, decorateMarkdown, linkTooltip, typewriter, formattingKeymap, InputModeExtensions, folding } from "@dxos/react-ui-editor";
9
- import { getSize, mx } from "@dxos/react-ui-theme";
10
- import { isNotFalsy, nonNullable } from "@dxos/util";
11
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/plugins/plugin-markdown/src/extensions.tsx";
12
- var createBaseExtensions = ({ viewMode, settings, document, query, dispatch }) => {
13
- const extensions = [];
14
- if (settings?.editorInputMode) {
15
- const extension = InputModeExtensions[settings.editorInputMode];
16
- if (extension) {
17
- extensions.push(extension);
18
- }
19
- }
20
- if (viewMode !== "source") {
21
- extensions.push(...[
22
- formattingKeymap(),
23
- decorateMarkdown({
24
- selectionChangeDelay: 100,
25
- numberedHeadings: settings?.numberedHeadings ? {
26
- from: 2
27
- } : void 0,
28
- // TODO(wittjosiah): For internal links, consider ignoring the link text and rendering the label of the object being linked to.
29
- renderLinkButton: dispatch && document ? onRenderLink((id) => {
30
- void dispatch({
31
- action: NavigationAction.ADD_TO_ACTIVE,
32
- data: {
33
- id,
34
- part: "main",
35
- pivotId: fullyQualifiedId(document),
36
- scrollIntoView: true
37
- }
38
- });
39
- }) : void 0
40
- }),
41
- linkTooltip(renderLinkTooltip)
42
- ]);
43
- }
44
- if (query) {
45
- extensions.push(autocomplete({
46
- onSearch: (text) => {
47
- return query.objects.map((object) => object.name?.length && object.id !== document?.id ? {
48
- label: object.name,
49
- // TODO(burdon): Factor out URL builder.
50
- apply: `[${object.name}](/${fullyQualifiedId(object)})`
51
- } : void 0).filter(nonNullable);
52
- }
53
- }));
54
- }
55
- extensions.push(...[
56
- //
57
- settings?.folding && folding()
58
- ].filter(isNotFalsy));
59
- if (settings?.debug) {
60
- const items = settings.typewriter ?? "";
61
- extensions.push(...[
62
- items ? typewriter({
63
- items: items.split(/[,\n]/)
64
- }) : void 0
65
- ].filter(nonNullable));
66
- }
67
- return extensions;
68
- };
69
- var hover = "rounded-sm text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200";
70
- var onRenderLink = (onSelectObject) => (el, url) => {
71
- const isInternal = url.startsWith("/") || // TODO(wittjosiah): This should probably be parsed out on paste?
72
- url.startsWith(window.location.origin);
73
- const options = isInternal ? {
74
- onClick: () => {
75
- const qualifiedId = url.split("/").at(-1);
76
- invariant(qualifiedId, "Invalid link format.", {
77
- F: __dxlog_file,
78
- L: 148,
79
- S: void 0,
80
- A: [
81
- "qualifiedId",
82
- "'Invalid link format.'"
83
- ]
84
- });
85
- onSelectObject(qualifiedId);
86
- }
87
- } : {
88
- href: url,
89
- rel: "noreferrer",
90
- target: "_blank"
91
- };
92
- const LinkIcon = isInternal ? ArrowSquareDown : ArrowSquareOut;
93
- createRoot(el).render(/* @__PURE__ */ React.createElement(StrictMode, null, /* @__PURE__ */ React.createElement("a", {
94
- ...options,
95
- className: hover
96
- }, /* @__PURE__ */ React.createElement(LinkIcon, {
97
- weight: "bold",
98
- className: mx(getSize(4), "inline-block leading-none mis-1 cursor-pointer")
99
- }))));
100
- };
101
- var renderLinkTooltip = (el, url) => {
102
- const web = new URL(url);
103
- createRoot(el).render(/* @__PURE__ */ React.createElement(StrictMode, null, /* @__PURE__ */ React.createElement("a", {
104
- href: url,
105
- target: "_blank",
106
- rel: "noreferrer",
107
- className: hover
108
- }, web.origin, /* @__PURE__ */ React.createElement(ArrowSquareOut, {
109
- weight: "bold",
110
- className: mx(getSize(4), "inline-block leading-none mis-1 cursor-pointer")
111
- }))));
112
- };
113
-
114
- export {
115
- createBaseExtensions
116
- };
117
- //# sourceMappingURL=chunk-354DCID5.mjs.map