@dxos/plugin-markdown 0.6.12-staging.e11e696 → 0.6.12

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 (118) hide show
  1. package/dist/lib/browser/DocumentCard-2P4EICBA.mjs +11 -0
  2. package/dist/lib/browser/DocumentEditor-GPWV3VN3.mjs +11 -0
  3. package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs +10 -0
  4. package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-354DCID5.mjs +117 -0
  6. package/dist/lib/browser/chunk-354DCID5.mjs.map +7 -0
  7. package/dist/lib/{node-esm/chunk-2BGTVWHN.mjs → browser/chunk-4GGD6YJO.mjs} +5 -4
  8. package/dist/lib/browser/chunk-4GGD6YJO.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-7AF2JLK4.mjs +164 -0
  10. package/dist/lib/browser/chunk-7AF2JLK4.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-OUZCML5B.mjs → chunk-CQJL4G4X.mjs} +2 -4
  12. package/dist/lib/browser/chunk-CQJL4G4X.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-RL7QY322.mjs +86 -0
  14. package/dist/lib/browser/chunk-RL7QY322.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-VUN4QKTT.mjs +208 -0
  16. package/dist/lib/browser/chunk-VUN4QKTT.mjs.map +7 -0
  17. package/dist/lib/browser/index.mjs +116 -76
  18. package/dist/lib/browser/index.mjs.map +4 -4
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/meta.mjs +1 -1
  21. package/dist/lib/browser/types/index.mjs +4 -6
  22. package/dist/lib/node/{chunk-LPD4NOTH.cjs → DocumentCard-EHJDDSRY.cjs} +10 -17
  23. package/dist/lib/node/DocumentCard-EHJDDSRY.cjs.map +7 -0
  24. package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs +29 -0
  25. package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs.map +7 -0
  26. package/dist/lib/node/MarkdownEditor-UE23H75V.cjs +31 -0
  27. package/dist/lib/node/MarkdownEditor-UE23H75V.cjs.map +7 -0
  28. package/dist/lib/node/chunk-7XIBNEI7.cjs +238 -0
  29. package/dist/lib/node/chunk-7XIBNEI7.cjs.map +7 -0
  30. package/dist/lib/node/chunk-KTYIOXL5.cjs +149 -0
  31. package/dist/lib/node/chunk-KTYIOXL5.cjs.map +7 -0
  32. package/dist/lib/node/chunk-Q4ZSCBQE.cjs +114 -0
  33. package/dist/lib/node/chunk-Q4ZSCBQE.cjs.map +7 -0
  34. package/dist/lib/node/chunk-RVGN72IX.cjs +189 -0
  35. package/dist/lib/node/chunk-RVGN72IX.cjs.map +7 -0
  36. package/dist/lib/node/chunk-TGMR2CKU.cjs +52 -0
  37. package/dist/lib/node/chunk-TGMR2CKU.cjs.map +7 -0
  38. package/dist/lib/node/{chunk-HVDIBL5H.cjs → chunk-VWQH4WC2.cjs} +8 -11
  39. package/dist/lib/node/chunk-VWQH4WC2.cjs.map +7 -0
  40. package/dist/lib/node/index.cjs +147 -111
  41. package/dist/lib/node/index.cjs.map +4 -4
  42. package/dist/lib/node/meta.cjs +3 -3
  43. package/dist/lib/node/meta.cjs.map +1 -1
  44. package/dist/lib/node/meta.json +1 -1
  45. package/dist/lib/node/types/index.cjs +6 -8
  46. package/dist/lib/node/types/index.cjs.map +2 -2
  47. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
  48. package/dist/types/src/components/DocumentCard.d.ts +16 -0
  49. package/dist/types/src/components/DocumentCard.d.ts.map +1 -0
  50. package/dist/types/src/components/DocumentEditor.d.ts +14 -0
  51. package/dist/types/src/components/DocumentEditor.d.ts.map +1 -0
  52. package/dist/types/src/components/HeadingMenu.d.ts +13 -0
  53. package/dist/types/src/components/HeadingMenu.d.ts.map +1 -0
  54. package/dist/types/src/components/Layout.d.ts +6 -0
  55. package/dist/types/src/components/Layout.d.ts.map +1 -0
  56. package/dist/types/src/components/MarkdownEditor.d.ts +3 -8
  57. package/dist/types/src/components/MarkdownEditor.d.ts.map +1 -1
  58. package/dist/types/src/components/MarkdownEditor.stories.d.ts +3 -3
  59. package/dist/types/src/components/MarkdownEditor.stories.d.ts.map +1 -1
  60. package/dist/types/src/components/index.d.ts +11 -2
  61. package/dist/types/src/components/index.d.ts.map +1 -1
  62. package/dist/types/src/extensions.d.ts +15 -11
  63. package/dist/types/src/extensions.d.ts.map +1 -1
  64. package/dist/types/src/meta.d.ts +4 -1
  65. package/dist/types/src/meta.d.ts.map +1 -1
  66. package/dist/types/src/types/document.d.ts +1 -10
  67. package/dist/types/src/types/document.d.ts.map +1 -1
  68. package/dist/types/src/types/types.d.ts +9 -8
  69. package/dist/types/src/types/types.d.ts.map +1 -1
  70. package/package.json +36 -41
  71. package/src/MarkdownPlugin.tsx +96 -48
  72. package/src/components/DocumentCard.tsx +107 -0
  73. package/src/components/DocumentEditor.tsx +137 -0
  74. package/src/components/HeadingMenu.tsx +46 -0
  75. package/src/components/Layout.tsx +27 -0
  76. package/src/components/MarkdownEditor.stories.tsx +5 -11
  77. package/src/components/MarkdownEditor.tsx +74 -36
  78. package/src/components/index.ts +14 -2
  79. package/src/extensions.tsx +67 -124
  80. package/src/meta.tsx +5 -1
  81. package/src/types/document.ts +0 -12
  82. package/src/types/types.ts +7 -10
  83. package/dist/lib/browser/MarkdownContainer-OWVU5WMN.mjs +0 -467
  84. package/dist/lib/browser/MarkdownContainer-OWVU5WMN.mjs.map +0 -7
  85. package/dist/lib/browser/chunk-2SJN46PA.mjs +0 -16
  86. package/dist/lib/browser/chunk-2SJN46PA.mjs.map +0 -7
  87. package/dist/lib/browser/chunk-7WORDTCY.mjs +0 -50
  88. package/dist/lib/browser/chunk-7WORDTCY.mjs.map +0 -7
  89. package/dist/lib/browser/chunk-OUZCML5B.mjs.map +0 -7
  90. package/dist/lib/node/MarkdownContainer-IWMLWI6Z.cjs +0 -482
  91. package/dist/lib/node/MarkdownContainer-IWMLWI6Z.cjs.map +0 -7
  92. package/dist/lib/node/chunk-HVDIBL5H.cjs.map +0 -7
  93. package/dist/lib/node/chunk-L2FIDO4L.cjs +0 -72
  94. package/dist/lib/node/chunk-L2FIDO4L.cjs.map +0 -7
  95. package/dist/lib/node/chunk-LPD4NOTH.cjs.map +0 -7
  96. package/dist/lib/node-esm/MarkdownContainer-MLTDWWGB.mjs +0 -468
  97. package/dist/lib/node-esm/MarkdownContainer-MLTDWWGB.mjs.map +0 -7
  98. package/dist/lib/node-esm/chunk-2BGTVWHN.mjs.map +0 -7
  99. package/dist/lib/node-esm/chunk-4AGP7IJE.mjs +0 -42
  100. package/dist/lib/node-esm/chunk-4AGP7IJE.mjs.map +0 -7
  101. package/dist/lib/node-esm/chunk-YOIARYUO.mjs +0 -51
  102. package/dist/lib/node-esm/chunk-YOIARYUO.mjs.map +0 -7
  103. package/dist/lib/node-esm/index.mjs +0 -493
  104. package/dist/lib/node-esm/index.mjs.map +0 -7
  105. package/dist/lib/node-esm/meta.json +0 -1
  106. package/dist/lib/node-esm/meta.mjs +0 -10
  107. package/dist/lib/node-esm/types/index.mjs +0 -15
  108. package/dist/types/src/components/MarkdownContainer.d.ts +0 -15
  109. package/dist/types/src/components/MarkdownContainer.d.ts.map +0 -1
  110. package/dist/types/src/hooks/index.d.ts +0 -2
  111. package/dist/types/src/hooks/index.d.ts.map +0 -1
  112. package/dist/types/src/hooks/useSelectCurrentThread.d.ts +0 -6
  113. package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +0 -1
  114. package/src/components/MarkdownContainer.tsx +0 -108
  115. package/src/hooks/index.ts +0 -5
  116. package/src/hooks/useSelectCurrentThread.tsx +0 -46
  117. /package/dist/lib/{node-esm/meta.mjs.map → browser/DocumentCard-2P4EICBA.mjs.map} +0 -0
  118. /package/dist/lib/{node-esm/types/index.mjs.map → browser/DocumentEditor-GPWV3VN3.mjs.map} +0 -0
@@ -0,0 +1,137 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useEffect, useMemo } from 'react';
6
+
7
+ import { useResolvePlugin, parseFileManagerPlugin, useIntentDispatcher } from '@dxos/app-framework';
8
+ import { createDocAccessor, fullyQualifiedId, getSpace } from '@dxos/react-client/echo';
9
+ import { useIdentity } from '@dxos/react-client/halo';
10
+ import {
11
+ createDataExtensions,
12
+ listener,
13
+ localStorageStateStoreAdapter,
14
+ state,
15
+ type Extension,
16
+ type EditorSelectionState,
17
+ } from '@dxos/react-ui-editor';
18
+
19
+ import MarkdownEditor, { type MarkdownEditorProps } from './MarkdownEditor';
20
+ import { createBaseExtensions } from '../extensions';
21
+ import { type DocumentType, type MarkdownPluginState, type MarkdownSettingsProps } from '../types';
22
+ import { getFallbackName, setFallbackName } from '../util';
23
+
24
+ type DocumentEditorProps = {
25
+ document: DocumentType;
26
+ settings: MarkdownSettingsProps;
27
+ } & Omit<MarkdownEditorProps, 'id' | 'inputMode' | 'toolbar' | 'extensions'> &
28
+ Pick<MarkdownPluginState, 'extensionProviders'>;
29
+
30
+ /**
31
+ * Editor for a `DocumentType`.
32
+ */
33
+ const DocumentEditor = ({
34
+ document: doc,
35
+ extensionProviders = [],
36
+ viewMode,
37
+ settings,
38
+ ...props
39
+ }: DocumentEditorProps) => {
40
+ const space = getSpace(doc);
41
+ const identity = useIdentity();
42
+ const dispatch = useIntentDispatcher();
43
+
44
+ const baseExtensions = useMemo(() => {
45
+ // TODO(wittjosiah): Autocomplete is not working and this query is causing performance issues.
46
+ // const query = space?.db.query(Filter.schema(DocumentType));
47
+ // query?.subscribe();
48
+ return createBaseExtensions({
49
+ viewMode,
50
+ settings,
51
+ document: doc,
52
+ dispatch,
53
+ // query,
54
+ });
55
+ }, [doc, viewMode, dispatch, settings, settings.folding, settings.numberedHeadings]);
56
+
57
+ const providerExtensions = useMemo(
58
+ () =>
59
+ extensionProviders.reduce((acc: Extension[], provider) => {
60
+ const provided = typeof provider === 'function' ? provider({ document: doc }) : provider;
61
+ acc.push(...provided);
62
+ return acc;
63
+ }, []),
64
+ [extensionProviders],
65
+ );
66
+
67
+ const extensions = useMemo(
68
+ () => [
69
+ // NOTE: Data extensions must be first so that automerge is updated before other extensions compute their state.
70
+ createDataExtensions({
71
+ id: doc.id,
72
+ text: doc.content && createDocAccessor(doc.content, ['content']),
73
+ space,
74
+ identity,
75
+ }),
76
+ state(localStorageStateStoreAdapter),
77
+ listener({
78
+ onChange: (text) => {
79
+ setFallbackName(doc, text);
80
+ },
81
+ }),
82
+ providerExtensions,
83
+ baseExtensions,
84
+ ],
85
+ [doc, doc.content, space, baseExtensions, providerExtensions, identity],
86
+ );
87
+
88
+ const initialValue = useMemo(() => doc.content?.content, [doc.content]);
89
+
90
+ // Migrate gradually to `fallbackName`.
91
+ useEffect(() => {
92
+ if (!doc.fallbackName && doc.content?.content) {
93
+ doc.fallbackName = getFallbackName(doc.content.content);
94
+ }
95
+ }, [doc, doc.content]);
96
+
97
+ // Restore last selection and scroll point.
98
+ const id = fullyQualifiedId(doc);
99
+ const { scrollTo, selection } = useMemo<EditorSelectionState>(
100
+ () => localStorageStateStoreAdapter.getState(id) ?? {},
101
+ [doc],
102
+ );
103
+
104
+ const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
105
+ const handleFileUpload = useMemo(() => {
106
+ if (space === undefined) {
107
+ return undefined;
108
+ }
109
+
110
+ if (fileManagerPlugin?.provides.file.upload === undefined) {
111
+ return undefined;
112
+ }
113
+
114
+ return async (file: File) => {
115
+ return fileManagerPlugin?.provides?.file?.upload?.(file, space);
116
+ };
117
+ }, [fileManagerPlugin, space]);
118
+
119
+ return (
120
+ <MarkdownEditor
121
+ id={id}
122
+ initialValue={initialValue}
123
+ extensions={extensions}
124
+ scrollTo={scrollTo}
125
+ selection={selection}
126
+ onFileUpload={handleFileUpload}
127
+ inputMode={settings.editorInputMode}
128
+ toolbar={settings.toolbar}
129
+ viewMode={viewMode}
130
+ {...props}
131
+ />
132
+ );
133
+ };
134
+
135
+ export default DocumentEditor;
136
+
137
+ export type DocumentEditor = typeof DocumentEditor;
@@ -0,0 +1,46 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { DotsThreeVertical } from '@phosphor-icons/react';
6
+ import React, { type PropsWithChildren, type FC } from 'react';
7
+
8
+ import { Surface } from '@dxos/app-framework';
9
+ import { Button, DropdownMenu } from '@dxos/react-ui';
10
+ import { fineButtonDimensions, getSize } from '@dxos/react-ui-theme';
11
+
12
+ import { type DocumentType, type MarkdownProperties } from '../types';
13
+
14
+ // TODO(thure): This needs to be refactored into a graph node action.
15
+ export const DocumentHeadingMenu: FC<{ document: DocumentType }> = ({ document }) => {
16
+ return <HeadingMenu properties={document} content={document.content?.content} />;
17
+ };
18
+
19
+ /**
20
+ * Menu for the layout heading.
21
+ */
22
+ export const HeadingMenu = ({
23
+ content,
24
+ properties,
25
+ }: PropsWithChildren<{
26
+ content: string | undefined;
27
+ properties: MarkdownProperties;
28
+ }>) => {
29
+ return (
30
+ <DropdownMenu.Root modal={false}>
31
+ <DropdownMenu.Trigger asChild>
32
+ <Button variant='ghost' classNames={fineButtonDimensions}>
33
+ <DotsThreeVertical className={getSize(4)} />
34
+ </Button>
35
+ </DropdownMenu.Trigger>
36
+ <DropdownMenu.Portal>
37
+ <DropdownMenu.Content sideOffset={8} classNames='z-10'>
38
+ <DropdownMenu.Viewport>
39
+ <Surface data={{ content, properties }} role='menuitem' />
40
+ </DropdownMenu.Viewport>
41
+ <DropdownMenu.Arrow />
42
+ </DropdownMenu.Content>
43
+ </DropdownMenu.Portal>
44
+ </DropdownMenu.Root>
45
+ );
46
+ };
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { type PropsWithChildren } from 'react';
6
+
7
+ import { Main } from '@dxos/react-ui';
8
+ import { editorWithToolbarLayout } from '@dxos/react-ui-editor';
9
+ import { topbarBlockPaddingStart } from '@dxos/react-ui-theme';
10
+
11
+ export const MainLayout = ({ children, toolbar }: PropsWithChildren<{ toolbar?: boolean }>) => {
12
+ return (
13
+ <Main.Content
14
+ bounce
15
+ data-toolbar={toolbar ? 'enabled' : 'disabled'}
16
+ classNames={[topbarBlockPaddingStart, editorWithToolbarLayout]}
17
+ >
18
+ {children}
19
+ </Main.Content>
20
+ );
21
+ };
22
+
23
+ // Used when the editor is embedded in another context (e.g., iframe) and has no topbar/sidebar/etc.
24
+ // TODO(wittjosiah): What's the difference between this and Section/Card?
25
+ export const EmbeddedLayout = ({ children }: PropsWithChildren) => {
26
+ return <Main.Content classNames='min-bs-[100dvh] grid p-0.5'>{children}</Main.Content>;
27
+ };
@@ -3,15 +3,13 @@
3
3
  //
4
4
 
5
5
  import '@dxos-theme';
6
-
7
6
  import React, { useMemo, type FC } from 'react';
8
7
 
9
8
  import { createDocAccessor, createEchoObject } from '@dxos/react-client/echo';
10
- import { Main } from '@dxos/react-ui';
11
- import { editorWithToolbarLayout, automerge } from '@dxos/react-ui-editor';
12
- import { topbarBlockPaddingStart } from '@dxos/react-ui-theme';
9
+ import { automerge } from '@dxos/react-ui-editor';
13
10
  import { withLayout, withTheme } from '@dxos/storybook-utils';
14
11
 
12
+ import { MainLayout } from './Layout';
15
13
  import { MarkdownEditor } from './MarkdownEditor';
16
14
 
17
15
  const Story: FC<{
@@ -22,13 +20,9 @@ const Story: FC<{
22
20
  const extensions = useMemo(() => [automerge(createDocAccessor(doc, ['content']))], [doc]);
23
21
 
24
22
  return (
25
- <Main.Content
26
- bounce
27
- data-toolbar={toolbar ? 'enabled' : 'disabled'}
28
- classNames={[topbarBlockPaddingStart, editorWithToolbarLayout]}
29
- >
23
+ <MainLayout toolbar={toolbar}>
30
24
  <MarkdownEditor id='test' initialValue={doc.content} extensions={extensions} toolbar={toolbar} />
31
- </Main.Content>
25
+ </MainLayout>
32
26
  );
33
27
  };
34
28
 
@@ -36,8 +30,8 @@ export default {
36
30
  title: 'plugin-markdown/EditorMain',
37
31
  component: MarkdownEditor,
38
32
  decorators: [withTheme, withLayout({ tooltips: true })],
39
- parameters: { layout: 'fullscreen' },
40
33
  render: Story,
34
+ parameters: { layout: 'fullscreen' },
41
35
  };
42
36
 
43
37
  const content = Array.from({ length: 100 })
@@ -3,47 +3,61 @@
3
3
  //
4
4
 
5
5
  import { openSearchPanel } from '@codemirror/search';
6
- import { type EditorView } from '@codemirror/view';
6
+ import { EditorView } from '@codemirror/view';
7
7
  import React, { useMemo, useEffect, useCallback } from 'react';
8
8
 
9
- import { type FileInfo, LayoutAction, type LayoutCoordinate, useIntentDispatcher } from '@dxos/app-framework';
9
+ import {
10
+ type FileInfo,
11
+ LayoutAction,
12
+ type LayoutCoordinate,
13
+ useResolvePlugin,
14
+ useIntentResolver,
15
+ parseLayoutPlugin,
16
+ useIntentDispatcher,
17
+ } from '@dxos/app-framework';
18
+ import { parseAttentionPlugin } from '@dxos/plugin-attention';
10
19
  import { useThemeContext, useTranslation } from '@dxos/react-ui';
11
- import { useAttendableAttributes, useAttention } from '@dxos/react-ui-attention';
12
20
  import {
13
21
  type Action,
14
22
  type DNDOptions,
15
23
  type EditorViewMode,
16
24
  type EditorInputMode,
17
- Toolbar,
18
25
  type UseTextEditorProps,
26
+ Toolbar,
19
27
  createBasicExtensions,
20
28
  createMarkdownExtensions,
21
29
  createThemeExtensions,
22
30
  dropFile,
23
- editorContent,
24
- editorGutter,
25
31
  processAction,
26
32
  useActionHandler,
27
33
  useCommentState,
28
34
  useCommentClickListener,
29
35
  useFormattingState,
30
36
  useTextEditor,
37
+ editorContent,
38
+ editorGutter,
39
+ Cursor,
40
+ setSelection,
31
41
  } from '@dxos/react-ui-editor';
32
42
  import { sectionToolbarLayout } from '@dxos/react-ui-stack';
33
43
  import { textBlockWidth, focusRing, mx } from '@dxos/react-ui-theme';
34
- import { isNotFalsy, nonNullable } from '@dxos/util';
44
+ import { nonNullable } from '@dxos/util';
35
45
 
36
- import { useSelectCurrentThread } from '../hooks';
37
46
  import { MARKDOWN_PLUGIN } from '../meta';
38
- import { type MarkdownPluginState } from '../types';
47
+ import type { MarkdownPluginState } from '../types';
48
+
49
+ const attentionFragment = mx(
50
+ 'group-focus-within/editor:attention-surface group-[[aria-current]]/editor:attention-surface',
51
+ 'group-focus-within/editor:border-separator',
52
+ );
39
53
 
40
54
  const DEFAULT_VIEW_MODE: EditorViewMode = 'preview';
41
55
 
42
56
  export type MarkdownEditorProps = {
43
57
  id: string;
44
- role?: string;
45
58
  coordinate?: LayoutCoordinate;
46
59
  inputMode?: EditorInputMode;
60
+ role?: string;
47
61
  scrollPastEnd?: boolean;
48
62
  toolbar?: boolean;
49
63
  viewMode?: EditorViewMode;
@@ -52,12 +66,6 @@ export type MarkdownEditorProps = {
52
66
  } & Pick<UseTextEditorProps, 'initialValue' | 'scrollTo' | 'selection' | 'extensions'> &
53
67
  Partial<Pick<MarkdownPluginState, 'extensionProviders'>>;
54
68
 
55
- /**
56
- * Base markdown editor component.
57
- *
58
- * This component provides all the features of the markdown editor that do no depend on ECHO.
59
- * This allows it to be used as a common editor for markdown content on arbitrary backends (e.g. files).
60
- */
61
69
  export const MarkdownEditor = ({
62
70
  id,
63
71
  role = 'article',
@@ -75,16 +83,14 @@ export const MarkdownEditor = ({
75
83
  const { t } = useTranslation(MARKDOWN_PLUGIN);
76
84
  const { themeMode } = useThemeContext();
77
85
  const dispatch = useIntentDispatcher();
86
+ const attentionPlugin = useResolvePlugin(parseAttentionPlugin);
87
+ const layoutPlugin = useResolvePlugin(parseLayoutPlugin);
88
+ const attended = Array.from(attentionPlugin?.provides.attention?.attended ?? []);
89
+ const isDirectlyAttended = attended.length === 1 && attended[0] === id;
78
90
  const [formattingState, formattingObserver] = useFormattingState();
79
- const attendableAttributes = useAttendableAttributes(id);
80
- const { hasAttention } = useAttention(id);
81
91
 
82
92
  // Extensions from other plugins.
83
- // TODO(burdon): Reconcile with DocumentEditor.useExtensions.
84
- const providerExtensions = useMemo(
85
- () => extensionProviders?.flatMap((provider) => provider({})).filter(nonNullable),
86
- [extensionProviders],
87
- );
93
+ const providerExtensions = useMemo(() => extensionProviders?.map((provider) => provider({})), [extensionProviders]);
88
94
 
89
95
  // TODO(Zan): Move these into thread plugin as well?
90
96
  const [commentsState, commentObserver] = useCommentState();
@@ -93,6 +99,36 @@ export const MarkdownEditor = ({
93
99
  }, [dispatch]);
94
100
  const commentClickObserver = useCommentClickListener(onCommentClick);
95
101
 
102
+ // Focus the space that references the comment.
103
+ useIntentResolver(MARKDOWN_PLUGIN, ({ action, data }) => {
104
+ switch (action) {
105
+ // TODO(burdon): Use fully qualified ids everywhere.
106
+ case LayoutAction.SCROLL_INTO_VIEW: {
107
+ if (editorView && data?.id === id && data?.cursor) {
108
+ // TODO(burdon): We need typed intents.
109
+ const range = Cursor.getRangeFromCursor(editorView.state, data.cursor);
110
+ if (range) {
111
+ const selection = editorView.state.selection.main.from !== range.from ? { anchor: range.from } : undefined;
112
+ const effects = [
113
+ // NOTE: This does not use the DOM scrollIntoView function.
114
+ EditorView.scrollIntoView(range.from, { y: 'start', yMargin: 96 }),
115
+ ];
116
+ if (selection) {
117
+ // Update the editor selection to get bi-directional highlighting.
118
+ effects.push(setSelection.of({ current: id }));
119
+ }
120
+
121
+ editorView.dispatch({
122
+ effects,
123
+ selection: selection ? { anchor: range.from } : undefined,
124
+ });
125
+ }
126
+ }
127
+ break;
128
+ }
129
+ }
130
+ });
131
+
96
132
  // Drag files.
97
133
  const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
98
134
  const file = files[0];
@@ -125,16 +161,16 @@ export const MarkdownEditor = ({
125
161
  slots: { content: { className: editorContent } },
126
162
  }),
127
163
  editorGutter,
128
- role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
164
+ role !== 'section' && onFileUpload ? dropFile({ onDrop: handleDrop }) : [],
129
165
  providerExtensions,
130
166
  extensions,
131
- ].filter(isNotFalsy),
167
+ ].filter(nonNullable),
132
168
  ...(role !== 'section' && {
133
169
  id,
134
170
  scrollTo,
135
171
  selection,
136
172
  // TODO(wittjosiah): Autofocus based on layout is racy.
137
- // autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
173
+ autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
138
174
  moveToEndOfLine: true,
139
175
  }),
140
176
  }),
@@ -142,7 +178,6 @@ export const MarkdownEditor = ({
142
178
  );
143
179
 
144
180
  useTest(editorView);
145
- useSelectCurrentThread(editorView, id);
146
181
 
147
182
  // Toolbar handler.
148
183
  const handleToolbarAction = useActionHandler(editorView);
@@ -170,24 +205,25 @@ export const MarkdownEditor = ({
170
205
  {...(role === 'section'
171
206
  ? { className: 'flex flex-col' }
172
207
  : {
173
- className: 'contents',
174
- // TODO(wittjosiah): Factor out to `useAttendableAttributes`?
175
- ...(hasAttention && { 'aria-current': 'location' }),
176
- ...attendableAttributes,
208
+ className: 'contents group/editor',
209
+ ...(isDirectlyAttended && { 'aria-current': 'location' }),
177
210
  })}
178
211
  >
179
212
  {toolbar && (
180
- <div role='none' className='flex shrink-0 justify-center overflow-x-auto attention-surface'>
213
+ <div role='none' className={mx('flex shrink-0 justify-center overflow-x-auto', attentionFragment)}>
181
214
  <Toolbar.Root
182
215
  classNames={
183
216
  role === 'section'
184
217
  ? [
185
218
  textBlockWidth,
186
219
  'z-[2] group-focus-within/section:visible',
187
- !hasAttention && 'invisible',
220
+ !isDirectlyAttended && 'invisible',
188
221
  sectionToolbarLayout,
189
222
  ]
190
- : [textBlockWidth]
223
+ : [
224
+ textBlockWidth,
225
+ 'group-focus-within/editor:border-separator group-[[aria-current]]/editor:border-separator',
226
+ ]
191
227
  }
192
228
  state={formattingState && { ...formattingState, ...commentsState }}
193
229
  onAction={handleAction}
@@ -211,8 +247,8 @@ export const MarkdownEditor = ({
211
247
  : mx(
212
248
  'flex is-full bs-full overflow-hidden',
213
249
  focusRing,
214
- 'focus-visible:ring-inset attention-surface',
215
- 'p-0.5', // TODO(burdon): Handle padding for focusRing consistently.
250
+ attentionFragment,
251
+ 'focus-visible:ring-inset',
216
252
  'data-[toolbar=disabled]:pbs-2 data-[toolbar=disabled]:row-span-2',
217
253
  )
218
254
  }
@@ -232,3 +268,5 @@ const useTest = (view?: EditorView) => {
232
268
  }
233
269
  }, [view]);
234
270
  };
271
+
272
+ export default MarkdownEditor;
@@ -2,8 +2,20 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { type LazyExoticComponent } 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';
7
16
  export * from './MarkdownSettings';
8
17
 
9
- export const MarkdownContainer = React.lazy(() => import('./MarkdownContainer'));
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'));