@dxos/plugin-markdown 0.8.2 → 0.8.3-main.7f5a14c
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/{MarkdownContainer-5IEINNQB.mjs → MarkdownContainer-3KTC7Q4C.mjs} +132 -18
- package/dist/lib/browser/MarkdownContainer-3KTC7Q4C.mjs.map +7 -0
- package/dist/lib/browser/{MarkdownPreview-YW5CS3ID.mjs → MarkdownPreview-F4PYFW5L.mjs} +15 -22
- package/dist/lib/browser/MarkdownPreview-F4PYFW5L.mjs.map +7 -0
- package/dist/lib/browser/{anchor-sort-VS4OZVPP.mjs → anchor-sort-BMAN2ABT.mjs} +4 -4
- package/dist/lib/browser/anchor-sort-BMAN2ABT.mjs.map +7 -0
- package/dist/lib/browser/{app-graph-serializer-V6RLEHVY.mjs → app-graph-serializer-FLQI6GFL.mjs} +3 -3
- package/dist/lib/browser/{artifact-definition-5NAODQLG.mjs → artifact-definition-FQ2R6KPT.mjs} +6 -6
- package/dist/lib/browser/artifact-definition-FQ2R6KPT.mjs.map +7 -0
- package/dist/lib/browser/{chunk-C5RABVIX.mjs → chunk-CX5GYZYO.mjs} +2 -2
- package/dist/lib/browser/{chunk-ACAID3XF.mjs → chunk-LCMXUTQB.mjs} +7 -7
- package/dist/lib/browser/{chunk-77NGW7EO.mjs → chunk-LXSRQPEP.mjs} +9 -9
- package/dist/lib/browser/chunk-LXSRQPEP.mjs.map +7 -0
- package/dist/lib/browser/{chunk-ECSM56YC.mjs → chunk-N2D26K6W.mjs} +4 -5
- package/dist/lib/browser/chunk-N2D26K6W.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +7 -8
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/{intent-resolver-4GDYST4Y.mjs → intent-resolver-6ZOABX2J.mjs} +6 -7
- package/dist/lib/browser/intent-resolver-6ZOABX2J.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-QE4SKXBT.mjs → react-surface-RJQKYJJQ.mjs} +8 -8
- package/dist/lib/browser/react-surface-RJQKYJJQ.mjs.map +7 -0
- package/dist/lib/browser/{settings-W5CK4PXP.mjs → settings-PLH54VC7.mjs} +4 -4
- package/dist/lib/browser/settings-PLH54VC7.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node/{MarkdownContainer-LSNNPNRB.cjs → MarkdownContainer-NG4H6AZJ.cjs} +181 -67
- package/dist/lib/node/MarkdownContainer-NG4H6AZJ.cjs.map +7 -0
- package/dist/lib/node/{MarkdownPreview-G34HSQEB.cjs → MarkdownPreview-GCJJCXY6.cjs} +24 -31
- package/dist/lib/node/MarkdownPreview-GCJJCXY6.cjs.map +7 -0
- package/dist/lib/node/{anchor-sort-NHVF23EU.cjs → anchor-sort-V3T4SFFI.cjs} +12 -12
- package/dist/lib/node/anchor-sort-V3T4SFFI.cjs.map +7 -0
- package/dist/lib/node/{app-graph-serializer-CLALIYN3.cjs → app-graph-serializer-BZPM7HHJ.cjs} +9 -9
- package/dist/lib/node/{artifact-definition-VEAHK7BX.cjs → artifact-definition-U27MH5SC.cjs} +16 -16
- package/dist/lib/node/artifact-definition-U27MH5SC.cjs.map +7 -0
- package/dist/lib/node/{chunk-RQS4KBMG.cjs → chunk-3HHV4MM6.cjs} +6 -7
- package/dist/lib/node/chunk-3HHV4MM6.cjs.map +7 -0
- package/dist/lib/node/{chunk-C4HR7UXE.cjs → chunk-4DYNEQG3.cjs} +10 -10
- package/dist/lib/node/{chunk-G7RBJX22.cjs → chunk-CJLYFGPI.cjs} +12 -12
- package/dist/lib/node/chunk-CJLYFGPI.cjs.map +7 -0
- package/dist/lib/node/{chunk-ZDTL47I7.cjs → chunk-SYEFGLXN.cjs} +6 -6
- package/dist/lib/node/index.cjs +26 -27
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/{intent-resolver-AUZVK3NZ.cjs → intent-resolver-OEFLRNEJ.cjs} +14 -15
- package/dist/lib/node/intent-resolver-OEFLRNEJ.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/{react-surface-WJZTEBYO.cjs → react-surface-5RKEWAXS.cjs} +15 -15
- package/dist/lib/node/react-surface-5RKEWAXS.cjs.map +7 -0
- package/dist/lib/node/{settings-IRKU3WPM.cjs → settings-E3NUTXJ4.cjs} +7 -7
- package/dist/lib/node/settings-E3NUTXJ4.cjs.map +7 -0
- package/dist/lib/node/types/index.cjs +7 -7
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/{MarkdownContainer-UZSLXMWO.mjs → MarkdownContainer-DZPXCA6J.mjs} +132 -18
- package/dist/lib/node-esm/MarkdownContainer-DZPXCA6J.mjs.map +7 -0
- package/dist/lib/node-esm/{MarkdownPreview-TCV7BI32.mjs → MarkdownPreview-KFDRV4GC.mjs} +15 -22
- package/dist/lib/node-esm/MarkdownPreview-KFDRV4GC.mjs.map +7 -0
- package/dist/lib/node-esm/{anchor-sort-G2HLCYFK.mjs → anchor-sort-BXL7BE67.mjs} +4 -4
- package/dist/lib/node-esm/anchor-sort-BXL7BE67.mjs.map +7 -0
- package/dist/lib/node-esm/{app-graph-serializer-C3RNTQGM.mjs → app-graph-serializer-EBH54X6Z.mjs} +3 -3
- package/dist/lib/node-esm/{artifact-definition-7TIJW2CO.mjs → artifact-definition-NQOHB6S5.mjs} +6 -6
- package/dist/lib/node-esm/artifact-definition-NQOHB6S5.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-6RPARLIK.mjs → chunk-K26TX5V4.mjs} +9 -9
- package/dist/lib/node-esm/chunk-K26TX5V4.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-TCFJNUAE.mjs → chunk-Q7WUBLL3.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-NCMPVEXO.mjs → chunk-T2Y2BT53.mjs} +4 -5
- package/dist/lib/node-esm/chunk-T2Y2BT53.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-EIUTPXGL.mjs → chunk-WANCCPU7.mjs} +7 -7
- package/dist/lib/node-esm/index.mjs +7 -8
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/{intent-resolver-FTNXUNI2.mjs → intent-resolver-CLMSVF2K.mjs} +6 -7
- package/dist/lib/node-esm/intent-resolver-CLMSVF2K.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{react-surface-XNM3YDFB.mjs → react-surface-Z3DX37JV.mjs} +8 -8
- package/dist/lib/node-esm/react-surface-Z3DX37JV.mjs.map +7 -0
- package/dist/lib/node-esm/{settings-MK7D7LHQ.mjs → settings-SIY33P3F.mjs} +4 -4
- package/dist/lib/node-esm/settings-SIY33P3F.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/components/MarkdownContainer.d.ts +1 -1
- package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -1
- package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts +4 -2
- package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
- package/dist/types/src/components/MarkdownPreview/MarkdownPreview.d.ts +1 -1
- package/dist/types/src/components/MarkdownPreview/MarkdownPreview.d.ts.map +1 -1
- package/dist/types/src/components/MarkdownPreview/MarkdownPreview.stories.d.ts +2 -6
- package/dist/types/src/components/MarkdownPreview/MarkdownPreview.stories.d.ts.map +1 -1
- package/dist/types/src/components/Suggestions.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +38 -39
- package/src/MarkdownPlugin.tsx +3 -5
- package/src/capabilities/anchor-sort.ts +2 -2
- package/src/capabilities/artifact-definition.ts +3 -3
- package/src/capabilities/intent-resolver.ts +4 -5
- package/src/capabilities/react-surface.tsx +4 -4
- package/src/capabilities/settings.ts +2 -2
- package/src/components/MarkdownContainer.tsx +61 -7
- package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +3 -3
- package/src/components/MarkdownEditor/MarkdownEditor.tsx +196 -144
- package/src/components/MarkdownPreview/MarkdownPreview.stories.tsx +8 -7
- package/src/components/MarkdownPreview/MarkdownPreview.tsx +14 -28
- package/src/components/Suggestions.stories.tsx +8 -9
- package/src/components/Toolbar.stories.tsx +3 -3
- package/src/types/schema.ts +2 -3
- package/src/util.tsx +5 -7
- package/dist/lib/browser/MarkdownContainer-5IEINNQB.mjs.map +0 -7
- package/dist/lib/browser/MarkdownPreview-YW5CS3ID.mjs.map +0 -7
- package/dist/lib/browser/anchor-sort-VS4OZVPP.mjs.map +0 -7
- package/dist/lib/browser/artifact-definition-5NAODQLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-77NGW7EO.mjs.map +0 -7
- package/dist/lib/browser/chunk-ECSM56YC.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-4GDYST4Y.mjs.map +0 -7
- package/dist/lib/browser/react-surface-QE4SKXBT.mjs.map +0 -7
- package/dist/lib/browser/settings-W5CK4PXP.mjs.map +0 -7
- package/dist/lib/node/MarkdownContainer-LSNNPNRB.cjs.map +0 -7
- package/dist/lib/node/MarkdownPreview-G34HSQEB.cjs.map +0 -7
- package/dist/lib/node/anchor-sort-NHVF23EU.cjs.map +0 -7
- package/dist/lib/node/artifact-definition-VEAHK7BX.cjs.map +0 -7
- package/dist/lib/node/chunk-G7RBJX22.cjs.map +0 -7
- package/dist/lib/node/chunk-RQS4KBMG.cjs.map +0 -7
- package/dist/lib/node/intent-resolver-AUZVK3NZ.cjs.map +0 -7
- package/dist/lib/node/react-surface-WJZTEBYO.cjs.map +0 -7
- package/dist/lib/node/settings-IRKU3WPM.cjs.map +0 -7
- package/dist/lib/node-esm/MarkdownContainer-UZSLXMWO.mjs.map +0 -7
- package/dist/lib/node-esm/MarkdownPreview-TCV7BI32.mjs.map +0 -7
- package/dist/lib/node-esm/anchor-sort-G2HLCYFK.mjs.map +0 -7
- package/dist/lib/node-esm/artifact-definition-7TIJW2CO.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6RPARLIK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NCMPVEXO.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-FTNXUNI2.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-XNM3YDFB.mjs.map +0 -7
- package/dist/lib/node-esm/settings-MK7D7LHQ.mjs.map +0 -7
- /package/dist/lib/browser/{app-graph-serializer-V6RLEHVY.mjs.map → app-graph-serializer-FLQI6GFL.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-C5RABVIX.mjs.map → chunk-CX5GYZYO.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-ACAID3XF.mjs.map → chunk-LCMXUTQB.mjs.map} +0 -0
- /package/dist/lib/node/{app-graph-serializer-CLALIYN3.cjs.map → app-graph-serializer-BZPM7HHJ.cjs.map} +0 -0
- /package/dist/lib/node/{chunk-C4HR7UXE.cjs.map → chunk-4DYNEQG3.cjs.map} +0 -0
- /package/dist/lib/node/{chunk-ZDTL47I7.cjs.map → chunk-SYEFGLXN.cjs.map} +0 -0
- /package/dist/lib/node-esm/{app-graph-serializer-C3RNTQGM.mjs.map → app-graph-serializer-EBH54X6Z.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-TCFJNUAE.mjs.map → chunk-Q7WUBLL3.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-EIUTPXGL.mjs.map → chunk-WANCCPU7.mjs.map} +0 -0
|
@@ -3,33 +3,40 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type EditorView } from '@codemirror/view';
|
|
6
|
-
import React, { useMemo, useEffect, useCallback } from 'react';
|
|
6
|
+
import React, { useMemo, useEffect, useCallback, forwardRef, useImperativeHandle, useRef } from 'react';
|
|
7
7
|
import { useDropzone } from 'react-dropzone';
|
|
8
8
|
|
|
9
9
|
import { type FileInfo } from '@dxos/app-framework';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { toLocalizedString, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
type EditorViewMode,
|
|
15
|
-
type EditorInputMode,
|
|
16
|
-
type EditorSelectionState,
|
|
17
|
-
type EditorStateStore,
|
|
18
|
-
EditorToolbar,
|
|
19
|
-
type UseTextEditorProps,
|
|
13
|
+
addLink,
|
|
20
14
|
createBasicExtensions,
|
|
21
15
|
createMarkdownExtensions,
|
|
22
16
|
createThemeExtensions,
|
|
23
17
|
dropFile,
|
|
24
|
-
editorSlots,
|
|
25
18
|
editorGutter,
|
|
19
|
+
editorSlots,
|
|
20
|
+
EditorToolbar,
|
|
26
21
|
processEditorPayload,
|
|
22
|
+
RefPopover,
|
|
27
23
|
stackItemContentEditorClassNames,
|
|
24
|
+
type DNDOptions,
|
|
25
|
+
type EditorInputMode,
|
|
26
|
+
type EditorSelectionState,
|
|
27
|
+
type EditorStateStore,
|
|
28
|
+
type EditorToolbarActionGraphProps,
|
|
29
|
+
type EditorViewMode,
|
|
30
|
+
type CommandMenuGroup,
|
|
31
|
+
type UseTextEditorProps,
|
|
32
|
+
useEditorToolbarState,
|
|
28
33
|
useFormattingState,
|
|
34
|
+
useCommandMenu,
|
|
29
35
|
useTextEditor,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
filterItems,
|
|
37
|
+
coreSlashCommands,
|
|
38
|
+
CommandMenu,
|
|
39
|
+
linkSlashCommands,
|
|
33
40
|
} from '@dxos/react-ui-editor';
|
|
34
41
|
import { StackItem } from '@dxos/react-ui-stack';
|
|
35
42
|
import { isNotFalsy, isNonNullable } from '@dxos/util';
|
|
@@ -43,12 +50,14 @@ export type MarkdownEditorProps = {
|
|
|
43
50
|
role?: string;
|
|
44
51
|
inputMode?: EditorInputMode;
|
|
45
52
|
scrollPastEnd?: boolean;
|
|
53
|
+
slashCommandGroups?: CommandMenuGroup[];
|
|
46
54
|
toolbar?: boolean;
|
|
47
55
|
customActions?: EditorToolbarActionGraphProps['customActions'];
|
|
48
56
|
// TODO(wittjosiah): Generalize custom toolbar actions (e.g. comment, upload, etc.)
|
|
49
57
|
viewMode?: EditorViewMode;
|
|
50
58
|
editorStateStore?: EditorStateStore;
|
|
51
59
|
onViewModeChange?: (id: string, mode: EditorViewMode) => void;
|
|
60
|
+
onLinkQuery?: (query?: string) => Promise<CommandMenuGroup[]>;
|
|
52
61
|
onFileUpload?: (file: File) => Promise<FileInfo | undefined>;
|
|
53
62
|
} & Pick<UseTextEditorProps, 'initialValue' | 'extensions'> &
|
|
54
63
|
Partial<Pick<MarkdownPluginState, 'extensionProviders'>>;
|
|
@@ -60,151 +69,194 @@ export type MarkdownEditorProps = {
|
|
|
60
69
|
* This allows it to be used as a common editor for markdown content on arbitrary backends (e.g. files).
|
|
61
70
|
*/
|
|
62
71
|
export const MarkdownEditor = ({
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
extensionProviders,
|
|
68
|
-
scrollPastEnd,
|
|
69
|
-
toolbar,
|
|
70
|
-
customActions,
|
|
71
|
-
viewMode,
|
|
72
|
-
editorStateStore,
|
|
73
|
-
onFileUpload,
|
|
74
|
-
onViewModeChange,
|
|
72
|
+
extensions: _extensions,
|
|
73
|
+
slashCommandGroups,
|
|
74
|
+
onLinkQuery,
|
|
75
|
+
...props
|
|
75
76
|
}: MarkdownEditorProps) => {
|
|
76
|
-
const { t } = useTranslation(
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
const { t } = useTranslation();
|
|
78
|
+
const viewRef = useRef<EditorView>();
|
|
79
|
+
const getGroups = useCallback(
|
|
80
|
+
(trigger: string, query?: string) => {
|
|
81
|
+
switch (trigger) {
|
|
82
|
+
case '@':
|
|
83
|
+
return onLinkQuery?.(query) ?? [];
|
|
84
|
+
case '/':
|
|
85
|
+
default:
|
|
86
|
+
return filterItems([coreSlashCommands, linkSlashCommands, ...(slashCommandGroups ?? [])], (item) =>
|
|
87
|
+
query ? toLocalizedString(item.label, t).toLowerCase().includes(query.toLowerCase()) : true,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
[onLinkQuery, slashCommandGroups],
|
|
89
92
|
);
|
|
93
|
+
const { commandMenu, groupsRef, currentItem, onSelect, ...refPopoverProps } = useCommandMenu({
|
|
94
|
+
viewRef,
|
|
95
|
+
getGroups,
|
|
96
|
+
trigger: onLinkQuery ? ['/', '@'] : '/',
|
|
97
|
+
});
|
|
98
|
+
const extensions = useMemo(() => [_extensions, commandMenu].filter(isNotFalsy), [_extensions, commandMenu]);
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
focusAttributes,
|
|
105
|
-
} = useTextEditor(
|
|
106
|
-
() => ({
|
|
100
|
+
return (
|
|
101
|
+
<RefPopover modal={false} {...refPopoverProps}>
|
|
102
|
+
<MarkdownEditorImpl ref={viewRef} {...props} extensions={extensions} />
|
|
103
|
+
<CommandMenu groups={groupsRef.current} currentItem={currentItem} onSelect={onSelect} />
|
|
104
|
+
</RefPopover>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const MarkdownEditorImpl = forwardRef<EditorView | undefined, MarkdownEditorProps>(
|
|
109
|
+
(
|
|
110
|
+
{
|
|
111
|
+
id,
|
|
112
|
+
role = 'article',
|
|
107
113
|
initialValue,
|
|
108
|
-
extensions
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
extensions,
|
|
115
|
+
extensionProviders,
|
|
116
|
+
scrollPastEnd,
|
|
117
|
+
toolbar,
|
|
118
|
+
customActions,
|
|
119
|
+
viewMode,
|
|
120
|
+
editorStateStore,
|
|
121
|
+
onFileUpload,
|
|
122
|
+
onViewModeChange,
|
|
123
|
+
},
|
|
124
|
+
forwardedRef,
|
|
125
|
+
) => {
|
|
126
|
+
const { t } = useTranslation(MARKDOWN_PLUGIN);
|
|
127
|
+
const { themeMode } = useThemeContext();
|
|
128
|
+
const toolbarState = useEditorToolbarState({ viewMode });
|
|
129
|
+
const formattingObserver = useFormattingState(toolbarState);
|
|
130
|
+
|
|
131
|
+
// Restore last selection and scroll point.
|
|
132
|
+
const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
|
|
133
|
+
|
|
134
|
+
// Extensions from other plugins.
|
|
135
|
+
// TODO(burdon): Reconcile with DocumentEditor.useExtensions.
|
|
136
|
+
const providerExtensions = useMemo(
|
|
137
|
+
() => extensionProviders?.flatMap((provider) => provider({})).filter(isNonNullable),
|
|
138
|
+
[extensionProviders],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// TODO(wittjosiah): Factor out to file uploader plugin.
|
|
142
|
+
// Drag files.
|
|
143
|
+
const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
|
|
144
|
+
const file = files[0];
|
|
145
|
+
const info = file && onFileUpload ? await onFileUpload(file) : undefined;
|
|
146
|
+
if (info) {
|
|
147
|
+
processEditorPayload(view, { type: 'image', data: info.url });
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const {
|
|
152
|
+
parentRef,
|
|
153
|
+
view: editorView,
|
|
154
|
+
focusAttributes,
|
|
155
|
+
} = useTextEditor(
|
|
156
|
+
() => ({
|
|
157
|
+
initialValue,
|
|
158
|
+
extensions: [
|
|
159
|
+
formattingObserver,
|
|
160
|
+
createBasicExtensions({
|
|
161
|
+
readOnly: viewMode === 'readonly',
|
|
162
|
+
placeholder: t('editor placeholder'),
|
|
163
|
+
scrollPastEnd: role === 'section' ? false : scrollPastEnd,
|
|
164
|
+
}),
|
|
165
|
+
createMarkdownExtensions({ themeMode }),
|
|
166
|
+
createThemeExtensions({ themeMode, syntaxHighlighting: true, slots: editorSlots }),
|
|
167
|
+
editorGutter,
|
|
168
|
+
role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
|
|
169
|
+
providerExtensions,
|
|
170
|
+
extensions,
|
|
171
|
+
].filter(isNotFalsy),
|
|
172
|
+
...(role !== 'section' && {
|
|
173
|
+
id,
|
|
174
|
+
scrollTo,
|
|
175
|
+
selection,
|
|
176
|
+
// TODO(wittjosiah): Autofocus based on layout is racy.
|
|
177
|
+
// autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
|
|
178
|
+
moveToEndOfLine: true,
|
|
114
179
|
}),
|
|
115
|
-
createMarkdownExtensions({ themeMode }),
|
|
116
|
-
createThemeExtensions({ themeMode, syntaxHighlighting: true, slots: editorSlots }),
|
|
117
|
-
editorGutter,
|
|
118
|
-
role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
|
|
119
|
-
providerExtensions,
|
|
120
|
-
extensions,
|
|
121
|
-
].filter(isNotFalsy),
|
|
122
|
-
...(role !== 'section' && {
|
|
123
|
-
id,
|
|
124
|
-
scrollTo,
|
|
125
|
-
selection,
|
|
126
|
-
// TODO(wittjosiah): Autofocus based on layout is racy.
|
|
127
|
-
// autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
|
|
128
|
-
moveToEndOfLine: true,
|
|
129
180
|
}),
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
);
|
|
181
|
+
[id, formattingObserver, viewMode, themeMode, extensions, providerExtensions],
|
|
182
|
+
);
|
|
133
183
|
|
|
134
|
-
|
|
135
|
-
|
|
184
|
+
useImperativeHandle(forwardedRef, () => editorView, [editorView]);
|
|
185
|
+
useTest(editorView);
|
|
186
|
+
useSelectCurrentThread(editorView, id);
|
|
136
187
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
188
|
+
// https://react-dropzone.js.org/#src
|
|
189
|
+
const { acceptedFiles, getInputProps, open } = useDropzone({
|
|
190
|
+
multiple: false,
|
|
191
|
+
noDrag: true,
|
|
192
|
+
accept: {
|
|
193
|
+
'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
|
|
194
|
+
},
|
|
195
|
+
});
|
|
145
196
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (editorView && onFileUpload && acceptedFiles.length) {
|
|
199
|
+
requestAnimationFrame(async () => {
|
|
200
|
+
// NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
|
|
201
|
+
const f = acceptedFiles[0];
|
|
202
|
+
const file = new File([f], f.name, {
|
|
203
|
+
type: f.type,
|
|
204
|
+
lastModified: f.lastModified,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const info = await onFileUpload(file);
|
|
208
|
+
if (info) {
|
|
209
|
+
addLink({ url: info.url, image: true })(editorView);
|
|
210
|
+
}
|
|
154
211
|
});
|
|
212
|
+
}
|
|
213
|
+
}, [acceptedFiles, editorView, onFileUpload]);
|
|
155
214
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}, [acceptedFiles, editorView, onFileUpload]);
|
|
215
|
+
const getView = useCallback(() => {
|
|
216
|
+
invariant(editorView);
|
|
217
|
+
return editorView;
|
|
218
|
+
}, [editorView]);
|
|
163
219
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
220
|
+
const handleViewModeChange = useCallback(
|
|
221
|
+
(mode: EditorViewMode) => onViewModeChange?.(id, mode),
|
|
222
|
+
[id, onViewModeChange],
|
|
223
|
+
);
|
|
168
224
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
225
|
+
const handleImageUpload = useCallback(() => {
|
|
226
|
+
if (onFileUpload) {
|
|
227
|
+
open();
|
|
228
|
+
}
|
|
229
|
+
}, [onFileUpload]);
|
|
173
230
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
{...focusAttributes}
|
|
204
|
-
/>
|
|
205
|
-
</StackItem.Content>
|
|
206
|
-
);
|
|
207
|
-
};
|
|
231
|
+
return (
|
|
232
|
+
<StackItem.Content toolbar={!!toolbar}>
|
|
233
|
+
{toolbar && (
|
|
234
|
+
<>
|
|
235
|
+
<EditorToolbar
|
|
236
|
+
attendableId={id}
|
|
237
|
+
role={role}
|
|
238
|
+
state={toolbarState}
|
|
239
|
+
customActions={customActions}
|
|
240
|
+
getView={getView}
|
|
241
|
+
image={handleImageUpload}
|
|
242
|
+
viewMode={handleViewModeChange}
|
|
243
|
+
/>
|
|
244
|
+
<input {...getInputProps()} />
|
|
245
|
+
</>
|
|
246
|
+
)}
|
|
247
|
+
<div
|
|
248
|
+
role='none'
|
|
249
|
+
ref={parentRef}
|
|
250
|
+
data-testid='composer.markdownRoot'
|
|
251
|
+
data-toolbar={toolbar ? 'enabled' : 'disabled'}
|
|
252
|
+
className={stackItemContentEditorClassNames(role)}
|
|
253
|
+
data-popover-collision-boundary={true}
|
|
254
|
+
{...focusAttributes}
|
|
255
|
+
/>
|
|
256
|
+
</StackItem.Content>
|
|
257
|
+
);
|
|
258
|
+
},
|
|
259
|
+
);
|
|
208
260
|
|
|
209
261
|
// Expose editor view for playwright tests.
|
|
210
262
|
// TODO(wittjosiah): Find a better way to expose this or find a way to limit it to test runs.
|
|
@@ -9,10 +9,9 @@ import React from 'react';
|
|
|
9
9
|
|
|
10
10
|
import { IntentPlugin } from '@dxos/app-framework';
|
|
11
11
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
12
|
-
import {
|
|
12
|
+
import { Obj, Ref } from '@dxos/echo';
|
|
13
13
|
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
14
14
|
import { faker } from '@dxos/random';
|
|
15
|
-
import { makeRef } from '@dxos/react-client/echo';
|
|
16
15
|
import { Icon, Popover } from '@dxos/react-ui';
|
|
17
16
|
import { DataType } from '@dxos/schema';
|
|
18
17
|
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
@@ -29,7 +28,9 @@ const meta: Meta<typeof MarkdownPreview> = {
|
|
|
29
28
|
return (
|
|
30
29
|
<Popover.Root open>
|
|
31
30
|
<Popover.Content>
|
|
32
|
-
<
|
|
31
|
+
<Popover.Viewport>
|
|
32
|
+
<MarkdownPreview subject={subject} role='popover' />
|
|
33
|
+
</Popover.Viewport>
|
|
33
34
|
<Popover.Arrow />
|
|
34
35
|
</Popover.Content>
|
|
35
36
|
<Popover.Trigger>
|
|
@@ -54,10 +55,10 @@ const meta: Meta<typeof MarkdownPreview> = {
|
|
|
54
55
|
export default meta;
|
|
55
56
|
|
|
56
57
|
const data = (() => {
|
|
57
|
-
const document =
|
|
58
|
+
const document = Obj.make(DocumentType, {
|
|
58
59
|
name: faker.lorem.words(3),
|
|
59
|
-
content:
|
|
60
|
-
|
|
60
|
+
content: Ref.make(
|
|
61
|
+
Obj.make(DataType.Text, {
|
|
61
62
|
content: faker.lorem.paragraphs(3),
|
|
62
63
|
}),
|
|
63
64
|
),
|
|
@@ -68,6 +69,6 @@ const data = (() => {
|
|
|
68
69
|
|
|
69
70
|
export const Default = {
|
|
70
71
|
args: {
|
|
71
|
-
subject:
|
|
72
|
+
subject: Obj.make(DocumentType, data.document),
|
|
72
73
|
},
|
|
73
74
|
};
|
|
@@ -6,19 +6,11 @@ import { pipe } from 'effect';
|
|
|
6
6
|
import React, { useCallback } from 'react';
|
|
7
7
|
|
|
8
8
|
import { chain, createIntent, LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
type PreviewProps,
|
|
12
|
-
defaultCard,
|
|
13
|
-
kanbanCardWithoutPoster,
|
|
14
|
-
popoverCard,
|
|
15
|
-
previewTitle,
|
|
16
|
-
previewProse,
|
|
17
|
-
previewChrome,
|
|
18
|
-
} from '@dxos/plugin-preview';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
import { type PreviewProps } from '@dxos/plugin-preview';
|
|
19
11
|
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
20
12
|
import { Button, Icon, useTranslation } from '@dxos/react-ui';
|
|
21
|
-
import {
|
|
13
|
+
import { Card } from '@dxos/react-ui-stack';
|
|
22
14
|
import { DataType } from '@dxos/schema';
|
|
23
15
|
|
|
24
16
|
import { MARKDOWN_PLUGIN } from '../../meta';
|
|
@@ -27,23 +19,23 @@ import { getAbstract, getFallbackName } from '../../util';
|
|
|
27
19
|
|
|
28
20
|
// TODO(burdon): Factor out.
|
|
29
21
|
const getTitle = (subject: DocumentType | DataType.Text, fallback: string) => {
|
|
30
|
-
if (
|
|
22
|
+
if (Obj.instanceOf(DocumentType, subject)) {
|
|
31
23
|
return subject.name ?? subject.fallbackName ?? getFallbackName(subject.content?.target?.content ?? fallback);
|
|
32
|
-
} else if (
|
|
24
|
+
} else if (Obj.instanceOf(DataType.Text, subject)) {
|
|
33
25
|
return getFallbackName(subject.content);
|
|
34
26
|
}
|
|
35
27
|
};
|
|
36
28
|
|
|
37
29
|
// TODO(burdon): Factor out.
|
|
38
30
|
const getSnippet = (subject: DocumentType | DataType.Text, fallback: string) => {
|
|
39
|
-
if (
|
|
31
|
+
if (Obj.instanceOf(DocumentType, subject)) {
|
|
40
32
|
return getAbstract(subject.content?.target?.content ?? fallback);
|
|
41
|
-
} else if (
|
|
33
|
+
} else if (Obj.instanceOf(DataType.Text, subject)) {
|
|
42
34
|
return getAbstract(subject.content);
|
|
43
35
|
}
|
|
44
36
|
};
|
|
45
37
|
|
|
46
|
-
export const MarkdownPreview = ({
|
|
38
|
+
export const MarkdownPreview = ({ subject, role }: PreviewProps<DocumentType | DataType.Text>) => {
|
|
47
39
|
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
48
40
|
const { t } = useTranslation(MARKDOWN_PLUGIN);
|
|
49
41
|
const snippet = getSnippet(subject, t('fallback abstract'));
|
|
@@ -65,21 +57,15 @@ export const MarkdownPreview = ({ classNames, subject, role }: PreviewProps<Docu
|
|
|
65
57
|
);
|
|
66
58
|
|
|
67
59
|
return (
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
classNames,
|
|
73
|
-
)}
|
|
74
|
-
>
|
|
75
|
-
<h2 className={mx(previewTitle, previewProse)}>{getTitle(subject, t('fallback title'))}</h2>
|
|
76
|
-
{snippet && <p className={mx(previewProse, 'line-clamp-3 break-words col-span-2')}>{snippet}</p>}
|
|
77
|
-
<div role='none' className={previewChrome}>
|
|
60
|
+
<Card.Container role={role}>
|
|
61
|
+
<Card.Heading>{getTitle(subject, t('fallback title'))}</Card.Heading>
|
|
62
|
+
{snippet && <Card.Text classNames='line-clamp-3 break-words col-span-2'>{snippet}</Card.Text>}
|
|
63
|
+
<Card.Chrome>
|
|
78
64
|
<Button onClick={handleNavigate}>
|
|
79
65
|
<span className='grow'>{t('navigate to document label')}</span>
|
|
80
66
|
<Icon icon='ph--arrow-right--regular' />
|
|
81
67
|
</Button>
|
|
82
|
-
</
|
|
83
|
-
</
|
|
68
|
+
</Card.Chrome>
|
|
69
|
+
</Card.Container>
|
|
84
70
|
);
|
|
85
71
|
};
|
|
@@ -20,11 +20,9 @@ import {
|
|
|
20
20
|
useIntentDispatcher,
|
|
21
21
|
} from '@dxos/app-framework';
|
|
22
22
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
23
|
-
import { Type } from '@dxos/echo';
|
|
24
|
-
import { create, createQueueDxn, type Expando } from '@dxos/echo-schema';
|
|
23
|
+
import { Obj, Ref, Type } from '@dxos/echo';
|
|
25
24
|
import { invariant } from '@dxos/invariant';
|
|
26
25
|
import { DXN } from '@dxos/keys';
|
|
27
|
-
import { live, makeRef, refFromDXN } from '@dxos/live-object';
|
|
28
26
|
import { ClientPlugin } from '@dxos/plugin-client';
|
|
29
27
|
import { PreviewPlugin } from '@dxos/plugin-preview';
|
|
30
28
|
import { SpacePlugin } from '@dxos/plugin-space';
|
|
@@ -69,13 +67,13 @@ const TestChat: FC<{ doc: DocumentType; content: string }> = ({ doc, content })
|
|
|
69
67
|
const { editorState } = useCapability(MarkdownCapabilities.State);
|
|
70
68
|
|
|
71
69
|
const space = useSpace();
|
|
72
|
-
const queueDxn = useMemo(() => space &&
|
|
70
|
+
const queueDxn = useMemo(() => space && space.queues.create().dxn, [space]);
|
|
73
71
|
const queue = useQueue<Message>(queueDxn);
|
|
74
72
|
|
|
75
73
|
const handleInsert = async () => {
|
|
76
74
|
invariant(space);
|
|
77
75
|
invariant(queue);
|
|
78
|
-
queue.append([
|
|
76
|
+
queue.append([Obj.make(Message, { role: 'assistant', content: [{ type: 'text', text: 'Hello' }] })]);
|
|
79
77
|
const message = queue.objects.at(-1);
|
|
80
78
|
invariant(message);
|
|
81
79
|
|
|
@@ -96,8 +94,8 @@ const TestChat: FC<{ doc: DocumentType; content: string }> = ({ doc, content })
|
|
|
96
94
|
|
|
97
95
|
void dispatch(
|
|
98
96
|
createIntent(CollaborationActions.InsertContent, {
|
|
99
|
-
target: doc as any as Expando,
|
|
100
|
-
object:
|
|
97
|
+
target: doc as any as Type.Expando,
|
|
98
|
+
object: Ref.fromDXN(new DXN(DXN.kind.QUEUE, [...queue.dxn.parts, message.id])),
|
|
101
99
|
at: cursor,
|
|
102
100
|
label: 'Proposal',
|
|
103
101
|
}),
|
|
@@ -131,8 +129,8 @@ const DefaultStory = ({ document, chat }: { document: string; chat: string }) =>
|
|
|
131
129
|
|
|
132
130
|
// Create links.
|
|
133
131
|
content: document.replaceAll(/\[(\w+)\]/g, (_, label) => {
|
|
134
|
-
const obj = space.db.add(
|
|
135
|
-
const dxn =
|
|
132
|
+
const obj = space.db.add(Obj.make(TestItem, { title: label, description: faker.lorem.paragraph() }));
|
|
133
|
+
const dxn = Ref.make(obj).dxn.toString();
|
|
136
134
|
return `[${label}][${dxn}]`;
|
|
137
135
|
}),
|
|
138
136
|
}),
|
|
@@ -179,6 +177,7 @@ const meta: Meta<typeof DefaultStory> = {
|
|
|
179
177
|
],
|
|
180
178
|
parameters: {
|
|
181
179
|
translations,
|
|
180
|
+
controls: { disable: true },
|
|
182
181
|
},
|
|
183
182
|
};
|
|
184
183
|
|
|
@@ -7,11 +7,11 @@ import '@dxos-theme';
|
|
|
7
7
|
import { type Meta } from '@storybook/react';
|
|
8
8
|
import React, { type FC, useCallback, useState } from 'react';
|
|
9
9
|
|
|
10
|
+
import { Obj } from '@dxos/echo';
|
|
10
11
|
import { invariant } from '@dxos/invariant';
|
|
11
12
|
import { PublicKey } from '@dxos/keys';
|
|
12
|
-
import { live } from '@dxos/live-object';
|
|
13
13
|
import { faker } from '@dxos/random';
|
|
14
|
-
import { createDocAccessor
|
|
14
|
+
import { createDocAccessor } from '@dxos/react-client/echo';
|
|
15
15
|
import { useThemeContext } from '@dxos/react-ui';
|
|
16
16
|
import {
|
|
17
17
|
EditorToolbar,
|
|
@@ -38,7 +38,7 @@ faker.seed(101);
|
|
|
38
38
|
|
|
39
39
|
const DefaultStory: FC<{ content?: string }> = ({ content = '' }) => {
|
|
40
40
|
const { themeMode } = useThemeContext();
|
|
41
|
-
const [text] = useState(
|
|
41
|
+
const [text] = useState(Obj.make(DataType.Text, { content }));
|
|
42
42
|
const toolbarState = useEditorToolbarState({ viewMode: 'preview' });
|
|
43
43
|
const formattingObserver = useFormattingState(toolbarState);
|
|
44
44
|
const { parentRef, view } = useTextEditor(() => {
|
package/src/types/schema.ts
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Obj, Ref, Type } from '@dxos/echo';
|
|
8
8
|
import { LabelAnnotation } from '@dxos/echo-schema';
|
|
9
|
-
import { live } from '@dxos/live-object';
|
|
10
9
|
import { DataType } from '@dxos/schema';
|
|
11
10
|
|
|
12
11
|
export const DocumentSchema = Schema.Struct({
|
|
@@ -25,7 +24,7 @@ export type DocumentType = Schema.Schema.Type<typeof DocumentType>;
|
|
|
25
24
|
|
|
26
25
|
// TODO(burdon): Replace when defaults are supported.
|
|
27
26
|
export const createDocument = ({ name, content }: { name: string; content: string }) =>
|
|
28
|
-
|
|
27
|
+
Obj.make(DocumentType, { name, content: Ref.make(Obj.make(DataType.Text, { content })) });
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* Checks if an object conforms to the interface needed to render an editor.
|
package/src/util.tsx
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { debounce } from '@dxos/async';
|
|
6
|
+
import { Obj, Ref } from '@dxos/echo';
|
|
6
7
|
import { type TypedObjectSerializer } from '@dxos/plugin-space/types';
|
|
7
|
-
import { live, createObject, isEchoObject, loadObjectReferences, Ref } from '@dxos/react-client/echo';
|
|
8
8
|
import { DataType } from '@dxos/schema';
|
|
9
9
|
|
|
10
10
|
import { DocumentType, type MarkdownProperties } from './types';
|
|
11
11
|
|
|
12
12
|
export const isMarkdownProperties = (data: unknown): data is MarkdownProperties =>
|
|
13
|
-
(
|
|
13
|
+
(Obj.isObject(data) as boolean)
|
|
14
14
|
? true
|
|
15
15
|
: data && typeof data === 'object'
|
|
16
16
|
? 'title' in data && typeof data.title === 'string'
|
|
@@ -35,14 +35,12 @@ export const setFallbackName = debounce((doc: DocumentType, content: string) =>
|
|
|
35
35
|
|
|
36
36
|
export const serializer: TypedObjectSerializer<DocumentType> = {
|
|
37
37
|
serialize: async ({ object }): Promise<string> => {
|
|
38
|
-
const content = await
|
|
39
|
-
return JSON.stringify({ name: object.name, fallbackName: object.fallbackName, content
|
|
38
|
+
const { content } = await object.content.load();
|
|
39
|
+
return JSON.stringify({ name: object.name, fallbackName: object.fallbackName, content });
|
|
40
40
|
},
|
|
41
41
|
|
|
42
42
|
deserialize: async ({ content: serialized }) => {
|
|
43
43
|
const { name, fallbackName, content } = JSON.parse(serialized);
|
|
44
|
-
return
|
|
45
|
-
live(DocumentType, { name, fallbackName, content: Ref.make(live(DataType.Text, { content })) }),
|
|
46
|
-
);
|
|
44
|
+
return Obj.make(DocumentType, { name, fallbackName, content: Ref.make(Obj.make(DataType.Text, { content })) });
|
|
47
45
|
},
|
|
48
46
|
};
|