@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.
- 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/{node-esm/chunk-2BGTVWHN.mjs → browser/chunk-4GGD6YJO.mjs} +5 -4
- 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-OUZCML5B.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 +116 -76
- 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-LPD4NOTH.cjs → DocumentCard-EHJDDSRY.cjs} +10 -17
- 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-HVDIBL5H.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 +4 -1
- 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 +96 -48
- 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 +74 -36
- package/src/components/index.ts +14 -2
- package/src/extensions.tsx +67 -124
- package/src/meta.tsx +5 -1
- package/src/types/document.ts +0 -12
- package/src/types/types.ts +7 -10
- package/dist/lib/browser/MarkdownContainer-OWVU5WMN.mjs +0 -467
- package/dist/lib/browser/MarkdownContainer-OWVU5WMN.mjs.map +0 -7
- package/dist/lib/browser/chunk-2SJN46PA.mjs +0 -16
- package/dist/lib/browser/chunk-2SJN46PA.mjs.map +0 -7
- package/dist/lib/browser/chunk-7WORDTCY.mjs +0 -50
- package/dist/lib/browser/chunk-7WORDTCY.mjs.map +0 -7
- package/dist/lib/browser/chunk-OUZCML5B.mjs.map +0 -7
- package/dist/lib/node/MarkdownContainer-IWMLWI6Z.cjs +0 -482
- package/dist/lib/node/MarkdownContainer-IWMLWI6Z.cjs.map +0 -7
- package/dist/lib/node/chunk-HVDIBL5H.cjs.map +0 -7
- package/dist/lib/node/chunk-L2FIDO4L.cjs +0 -72
- package/dist/lib/node/chunk-L2FIDO4L.cjs.map +0 -7
- package/dist/lib/node/chunk-LPD4NOTH.cjs.map +0 -7
- package/dist/lib/node-esm/MarkdownContainer-MLTDWWGB.mjs +0 -468
- package/dist/lib/node-esm/MarkdownContainer-MLTDWWGB.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-2BGTVWHN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-4AGP7IJE.mjs +0 -42
- package/dist/lib/node-esm/chunk-4AGP7IJE.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-YOIARYUO.mjs +0 -51
- package/dist/lib/node-esm/chunk-YOIARYUO.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/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,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
|
-
|
|
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
|
|
164
|
+
role !== 'section' && onFileUpload ? dropFile({ onDrop: handleDrop }) : [],
|
|
129
165
|
providerExtensions,
|
|
130
166
|
extensions,
|
|
131
|
-
].filter(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
!
|
|
220
|
+
!isDirectlyAttended && 'invisible',
|
|
188
221
|
sectionToolbarLayout,
|
|
189
222
|
]
|
|
190
|
-
: [
|
|
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
|
-
|
|
215
|
-
'
|
|
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;
|
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'));
|