@dxos/plugin-markdown 0.6.13-main.548ca8d → 0.6.13-staging.1e988a3
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.
- package/dist/lib/browser/DocumentCard-2P4EICBA.mjs +11 -0
- package/dist/lib/browser/DocumentEditor-GPWV3VN3.mjs +11 -0
- package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs +10 -0
- package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs.map +7 -0
- package/dist/lib/browser/chunk-354DCID5.mjs +117 -0
- package/dist/lib/browser/chunk-354DCID5.mjs.map +7 -0
- package/dist/lib/browser/chunk-4GGD6YJO.mjs +19 -0
- package/dist/lib/browser/chunk-4GGD6YJO.mjs.map +7 -0
- package/dist/lib/browser/chunk-7AF2JLK4.mjs +164 -0
- package/dist/lib/browser/chunk-7AF2JLK4.mjs.map +7 -0
- package/dist/lib/browser/{chunk-US5O2P3R.mjs → chunk-CQJL4G4X.mjs} +2 -4
- package/dist/lib/browser/chunk-CQJL4G4X.mjs.map +7 -0
- package/dist/lib/browser/chunk-RL7QY322.mjs +86 -0
- package/dist/lib/browser/chunk-RL7QY322.mjs.map +7 -0
- package/dist/lib/browser/chunk-VUN4QKTT.mjs +208 -0
- package/dist/lib/browser/chunk-VUN4QKTT.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +117 -77
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/lib/browser/types/index.mjs +4 -6
- package/dist/lib/node/{chunk-P7YU53RP.cjs → DocumentCard-EHJDDSRY.cjs} +10 -16
- package/dist/lib/node/DocumentCard-EHJDDSRY.cjs.map +7 -0
- package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs +29 -0
- package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs.map +7 -0
- package/dist/lib/node/MarkdownEditor-UE23H75V.cjs +31 -0
- package/dist/lib/node/MarkdownEditor-UE23H75V.cjs.map +7 -0
- package/dist/lib/node/chunk-7XIBNEI7.cjs +238 -0
- package/dist/lib/node/chunk-7XIBNEI7.cjs.map +7 -0
- package/dist/lib/node/chunk-KTYIOXL5.cjs +149 -0
- package/dist/lib/node/chunk-KTYIOXL5.cjs.map +7 -0
- package/dist/lib/node/chunk-Q4ZSCBQE.cjs +114 -0
- package/dist/lib/node/chunk-Q4ZSCBQE.cjs.map +7 -0
- package/dist/lib/node/chunk-RVGN72IX.cjs +189 -0
- package/dist/lib/node/chunk-RVGN72IX.cjs.map +7 -0
- package/dist/lib/node/chunk-TGMR2CKU.cjs +52 -0
- package/dist/lib/node/chunk-TGMR2CKU.cjs.map +7 -0
- package/dist/lib/node/{chunk-UJMOZCIA.cjs → chunk-VWQH4WC2.cjs} +8 -11
- package/dist/lib/node/chunk-VWQH4WC2.cjs.map +7 -0
- package/dist/lib/node/index.cjs +147 -111
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.cjs +3 -3
- package/dist/lib/node/meta.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +6 -8
- package/dist/lib/node/types/index.cjs.map +2 -2
- package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
- package/dist/types/src/components/DocumentCard.d.ts +16 -0
- package/dist/types/src/components/DocumentCard.d.ts.map +1 -0
- package/dist/types/src/components/DocumentEditor.d.ts +14 -0
- package/dist/types/src/components/DocumentEditor.d.ts.map +1 -0
- package/dist/types/src/components/HeadingMenu.d.ts +13 -0
- package/dist/types/src/components/HeadingMenu.d.ts.map +1 -0
- package/dist/types/src/components/Layout.d.ts +6 -0
- package/dist/types/src/components/Layout.d.ts.map +1 -0
- package/dist/types/src/components/MarkdownEditor.d.ts +3 -8
- package/dist/types/src/components/MarkdownEditor.d.ts.map +1 -1
- package/dist/types/src/components/MarkdownEditor.stories.d.ts +3 -3
- package/dist/types/src/components/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +11 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/extensions.d.ts +15 -11
- package/dist/types/src/extensions.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +9 -4
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/types/document.d.ts +1 -10
- package/dist/types/src/types/document.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +9 -8
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/package.json +36 -41
- package/src/MarkdownPlugin.tsx +97 -49
- package/src/components/DocumentCard.tsx +107 -0
- package/src/components/DocumentEditor.tsx +137 -0
- package/src/components/HeadingMenu.tsx +46 -0
- package/src/components/Layout.tsx +27 -0
- package/src/components/MarkdownEditor.stories.tsx +5 -11
- package/src/components/MarkdownEditor.tsx +75 -40
- package/src/components/index.ts +14 -2
- package/src/extensions.tsx +67 -124
- package/src/meta.tsx +19 -0
- package/src/types/document.ts +0 -12
- package/src/types/types.ts +7 -10
- package/dist/lib/browser/MarkdownContainer-52FJDCTV.mjs +0 -467
- package/dist/lib/browser/MarkdownContainer-52FJDCTV.mjs.map +0 -7
- package/dist/lib/browser/chunk-4MPY6KRJ.mjs +0 -50
- package/dist/lib/browser/chunk-4MPY6KRJ.mjs.map +0 -7
- package/dist/lib/browser/chunk-DRJ3FPYF.mjs +0 -15
- package/dist/lib/browser/chunk-DRJ3FPYF.mjs.map +0 -7
- package/dist/lib/browser/chunk-US5O2P3R.mjs.map +0 -7
- package/dist/lib/node/MarkdownContainer-5XPB5VP5.cjs +0 -482
- package/dist/lib/node/MarkdownContainer-5XPB5VP5.cjs.map +0 -7
- package/dist/lib/node/chunk-MOF6UCLA.cjs +0 -72
- package/dist/lib/node/chunk-MOF6UCLA.cjs.map +0 -7
- package/dist/lib/node/chunk-P7YU53RP.cjs.map +0 -7
- package/dist/lib/node/chunk-UJMOZCIA.cjs.map +0 -7
- package/dist/lib/node-esm/MarkdownContainer-ILCO3PDV.mjs +0 -468
- package/dist/lib/node-esm/MarkdownContainer-ILCO3PDV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CD634NG3.mjs +0 -51
- package/dist/lib/node-esm/chunk-CD634NG3.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-MIDCCMIX.mjs +0 -42
- package/dist/lib/node-esm/chunk-MIDCCMIX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NEVN5WR6.mjs +0 -17
- package/dist/lib/node-esm/chunk-NEVN5WR6.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -493
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/meta.mjs +0 -10
- package/dist/lib/node-esm/types/index.mjs +0 -15
- package/dist/types/src/components/MarkdownContainer.d.ts +0 -15
- package/dist/types/src/components/MarkdownContainer.d.ts.map +0 -1
- package/dist/types/src/hooks/index.d.ts +0 -2
- package/dist/types/src/hooks/index.d.ts.map +0 -1
- package/dist/types/src/hooks/useSelectCurrentThread.d.ts +0 -6
- package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +0 -1
- package/src/components/MarkdownContainer.tsx +0 -108
- package/src/hooks/index.ts +0 -5
- package/src/hooks/useSelectCurrentThread.tsx +0 -46
- package/src/meta.ts +0 -15
- /package/dist/lib/{node-esm/meta.mjs.map → browser/DocumentCard-2P4EICBA.mjs.map} +0 -0
- /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 {
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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 {
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
7
|
import React, { useMemo, useEffect, useCallback } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
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 {
|
|
44
|
+
import { nonNullable } from '@dxos/util';
|
|
35
45
|
|
|
36
|
-
import { useSelectCurrentThread } from '../hooks';
|
|
37
46
|
import { MARKDOWN_PLUGIN } from '../meta';
|
|
38
|
-
import {
|
|
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,27 +83,52 @@ 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
|
-
|
|
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();
|
|
91
97
|
const onCommentClick = useCallback(() => {
|
|
92
|
-
void dispatch({
|
|
93
|
-
action: LayoutAction.SET_LAYOUT,
|
|
94
|
-
data: { element: 'complementary', state: true },
|
|
95
|
-
});
|
|
98
|
+
void dispatch({ action: LayoutAction.SET_LAYOUT, data: { element: 'complementary', state: true } });
|
|
96
99
|
}, [dispatch]);
|
|
97
100
|
const commentClickObserver = useCommentClickListener(onCommentClick);
|
|
98
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
|
+
|
|
99
132
|
// Drag files.
|
|
100
133
|
const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
|
|
101
134
|
const file = files[0];
|
|
@@ -128,16 +161,16 @@ export const MarkdownEditor = ({
|
|
|
128
161
|
slots: { content: { className: editorContent } },
|
|
129
162
|
}),
|
|
130
163
|
editorGutter,
|
|
131
|
-
role !== 'section' && onFileUpload
|
|
164
|
+
role !== 'section' && onFileUpload ? dropFile({ onDrop: handleDrop }) : [],
|
|
132
165
|
providerExtensions,
|
|
133
166
|
extensions,
|
|
134
|
-
].filter(
|
|
167
|
+
].filter(nonNullable),
|
|
135
168
|
...(role !== 'section' && {
|
|
136
169
|
id,
|
|
137
170
|
scrollTo,
|
|
138
171
|
selection,
|
|
139
172
|
// TODO(wittjosiah): Autofocus based on layout is racy.
|
|
140
|
-
|
|
173
|
+
autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
|
|
141
174
|
moveToEndOfLine: true,
|
|
142
175
|
}),
|
|
143
176
|
}),
|
|
@@ -145,7 +178,6 @@ export const MarkdownEditor = ({
|
|
|
145
178
|
);
|
|
146
179
|
|
|
147
180
|
useTest(editorView);
|
|
148
|
-
useSelectCurrentThread(editorView, id);
|
|
149
181
|
|
|
150
182
|
// Toolbar handler.
|
|
151
183
|
const handleToolbarAction = useActionHandler(editorView);
|
|
@@ -173,24 +205,25 @@ export const MarkdownEditor = ({
|
|
|
173
205
|
{...(role === 'section'
|
|
174
206
|
? { className: 'flex flex-col' }
|
|
175
207
|
: {
|
|
176
|
-
className: 'contents',
|
|
177
|
-
|
|
178
|
-
...(hasAttention && { 'aria-current': 'location' }),
|
|
179
|
-
...attendableAttributes,
|
|
208
|
+
className: 'contents group/editor',
|
|
209
|
+
...(isDirectlyAttended && { 'aria-current': 'location' }),
|
|
180
210
|
})}
|
|
181
211
|
>
|
|
182
212
|
{toolbar && (
|
|
183
|
-
<div role='none' className='flex shrink-0 justify-center overflow-x-auto
|
|
213
|
+
<div role='none' className={mx('flex shrink-0 justify-center overflow-x-auto', attentionFragment)}>
|
|
184
214
|
<Toolbar.Root
|
|
185
215
|
classNames={
|
|
186
216
|
role === 'section'
|
|
187
217
|
? [
|
|
188
218
|
textBlockWidth,
|
|
189
219
|
'z-[2] group-focus-within/section:visible',
|
|
190
|
-
!
|
|
220
|
+
!isDirectlyAttended && 'invisible',
|
|
191
221
|
sectionToolbarLayout,
|
|
192
222
|
]
|
|
193
|
-
: [
|
|
223
|
+
: [
|
|
224
|
+
textBlockWidth,
|
|
225
|
+
'group-focus-within/editor:border-separator group-[[aria-current]]/editor:border-separator',
|
|
226
|
+
]
|
|
194
227
|
}
|
|
195
228
|
state={formattingState && { ...formattingState, ...commentsState }}
|
|
196
229
|
onAction={handleAction}
|
|
@@ -214,8 +247,8 @@ export const MarkdownEditor = ({
|
|
|
214
247
|
: mx(
|
|
215
248
|
'flex is-full bs-full overflow-hidden',
|
|
216
249
|
focusRing,
|
|
217
|
-
|
|
218
|
-
'
|
|
250
|
+
attentionFragment,
|
|
251
|
+
'focus-visible:ring-inset',
|
|
219
252
|
'data-[toolbar=disabled]:pbs-2 data-[toolbar=disabled]:row-span-2',
|
|
220
253
|
)
|
|
221
254
|
}
|
|
@@ -235,3 +268,5 @@ const useTest = (view?: EditorView) => {
|
|
|
235
268
|
}
|
|
236
269
|
}, [view]);
|
|
237
270
|
};
|
|
271
|
+
|
|
272
|
+
export default MarkdownEditor;
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
-
|
|
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'));
|