@dxos/plugin-markdown 0.6.13 → 0.6.14-main.69511f5
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-HRGXWEA4.mjs +465 -0
- package/dist/lib/browser/MarkdownContainer-HRGXWEA4.mjs.map +7 -0
- package/dist/lib/browser/chunk-DRJ3FPYF.mjs +15 -0
- package/dist/lib/browser/chunk-DRJ3FPYF.mjs.map +7 -0
- package/dist/lib/browser/{chunk-CQJL4G4X.mjs → chunk-US5O2P3R.mjs} +4 -2
- package/dist/lib/browser/chunk-US5O2P3R.mjs.map +7 -0
- package/dist/lib/browser/chunk-VGIHBUXB.mjs +52 -0
- package/dist/lib/browser/chunk-VGIHBUXB.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +85 -125
- 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 +6 -4
- package/dist/lib/node/MarkdownContainer-QZ4YLO7M.cjs +480 -0
- package/dist/lib/node/MarkdownContainer-QZ4YLO7M.cjs.map +7 -0
- package/dist/lib/node/chunk-6HPTH2F5.cjs +74 -0
- package/dist/lib/node/chunk-6HPTH2F5.cjs.map +7 -0
- package/dist/lib/node/{DocumentCard-EHJDDSRY.cjs → chunk-P7YU53RP.cjs} +16 -10
- package/dist/lib/node/chunk-P7YU53RP.cjs.map +7 -0
- package/dist/lib/node/{chunk-VWQH4WC2.cjs → chunk-UJMOZCIA.cjs} +11 -8
- package/dist/lib/node/chunk-UJMOZCIA.cjs.map +7 -0
- package/dist/lib/node/index.cjs +117 -153
- 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 +8 -6
- package/dist/lib/node/types/index.cjs.map +2 -2
- package/dist/lib/node-esm/MarkdownContainer-FSWQL76V.mjs +466 -0
- package/dist/lib/node-esm/MarkdownContainer-FSWQL76V.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HPOTHJC4.mjs +53 -0
- package/dist/lib/node-esm/chunk-HPOTHJC4.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MIDCCMIX.mjs +42 -0
- package/dist/lib/node-esm/chunk-MIDCCMIX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-NEVN5WR6.mjs +17 -0
- package/dist/lib/node-esm/chunk-NEVN5WR6.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +493 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/meta.mjs +10 -0
- package/dist/lib/node-esm/types/index.mjs +15 -0
- package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
- package/dist/types/src/components/MarkdownContainer.d.ts +15 -0
- package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -0
- package/dist/types/src/components/MarkdownEditor.d.ts +12 -6
- package/dist/types/src/components/MarkdownEditor.d.ts.map +1 -1
- package/dist/types/src/components/MarkdownEditor.stories.d.ts +4 -14
- package/dist/types/src/components/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar.stories.d.ts +4 -2
- package/dist/types/src/components/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -11
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/extensions.d.ts +12 -15
- package/dist/types/src/extensions.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useSelectCurrentThread.d.ts +6 -0
- package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +4 -9
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/types/document.d.ts +10 -1
- package/dist/types/src/types/document.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +8 -9
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +42 -45
- package/src/MarkdownPlugin.tsx +58 -113
- package/src/components/MarkdownContainer.tsx +103 -0
- package/src/components/MarkdownEditor.stories.tsx +34 -23
- package/src/components/MarkdownEditor.tsx +48 -79
- package/src/components/MarkdownSettings.tsx +15 -15
- package/src/components/Toolbar.stories.tsx +14 -11
- package/src/components/index.ts +2 -14
- package/src/extensions.tsx +128 -67
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useSelectCurrentThread.tsx +46 -0
- package/src/meta.ts +15 -0
- package/src/translations.ts +1 -1
- package/src/types/document.ts +12 -0
- package/src/types/types.ts +10 -7
- package/src/util.tsx +6 -4
- package/dist/lib/browser/DocumentCard-2P4EICBA.mjs +0 -11
- package/dist/lib/browser/DocumentEditor-GPWV3VN3.mjs +0 -11
- package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs +0 -10
- package/dist/lib/browser/MarkdownEditor-EKJJQEFL.mjs.map +0 -7
- package/dist/lib/browser/chunk-354DCID5.mjs +0 -117
- package/dist/lib/browser/chunk-354DCID5.mjs.map +0 -7
- package/dist/lib/browser/chunk-4GGD6YJO.mjs +0 -19
- package/dist/lib/browser/chunk-4GGD6YJO.mjs.map +0 -7
- package/dist/lib/browser/chunk-7AF2JLK4.mjs +0 -164
- package/dist/lib/browser/chunk-7AF2JLK4.mjs.map +0 -7
- package/dist/lib/browser/chunk-CQJL4G4X.mjs.map +0 -7
- package/dist/lib/browser/chunk-RL7QY322.mjs +0 -86
- package/dist/lib/browser/chunk-RL7QY322.mjs.map +0 -7
- package/dist/lib/browser/chunk-VUN4QKTT.mjs +0 -208
- package/dist/lib/browser/chunk-VUN4QKTT.mjs.map +0 -7
- package/dist/lib/node/DocumentCard-EHJDDSRY.cjs.map +0 -7
- package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs +0 -29
- package/dist/lib/node/DocumentEditor-I5GCRBKU.cjs.map +0 -7
- package/dist/lib/node/MarkdownEditor-UE23H75V.cjs +0 -31
- package/dist/lib/node/MarkdownEditor-UE23H75V.cjs.map +0 -7
- package/dist/lib/node/chunk-7XIBNEI7.cjs +0 -238
- package/dist/lib/node/chunk-7XIBNEI7.cjs.map +0 -7
- package/dist/lib/node/chunk-KTYIOXL5.cjs +0 -149
- package/dist/lib/node/chunk-KTYIOXL5.cjs.map +0 -7
- package/dist/lib/node/chunk-Q4ZSCBQE.cjs +0 -114
- package/dist/lib/node/chunk-Q4ZSCBQE.cjs.map +0 -7
- package/dist/lib/node/chunk-RVGN72IX.cjs +0 -189
- package/dist/lib/node/chunk-RVGN72IX.cjs.map +0 -7
- package/dist/lib/node/chunk-TGMR2CKU.cjs +0 -52
- package/dist/lib/node/chunk-TGMR2CKU.cjs.map +0 -7
- package/dist/lib/node/chunk-VWQH4WC2.cjs.map +0 -7
- package/dist/types/src/components/DocumentCard.d.ts +0 -16
- package/dist/types/src/components/DocumentCard.d.ts.map +0 -1
- package/dist/types/src/components/DocumentEditor.d.ts +0 -14
- package/dist/types/src/components/DocumentEditor.d.ts.map +0 -1
- package/dist/types/src/components/HeadingMenu.d.ts +0 -13
- package/dist/types/src/components/HeadingMenu.d.ts.map +0 -1
- package/dist/types/src/components/Layout.d.ts +0 -6
- package/dist/types/src/components/Layout.d.ts.map +0 -1
- package/src/components/DocumentCard.tsx +0 -107
- package/src/components/DocumentEditor.tsx +0 -137
- package/src/components/HeadingMenu.tsx +0 -46
- package/src/components/Layout.tsx +0 -27
- package/src/meta.tsx +0 -19
- /package/dist/lib/{browser/DocumentCard-2P4EICBA.mjs.map → node-esm/meta.mjs.map} +0 -0
- /package/dist/lib/{browser/DocumentEditor-GPWV3VN3.mjs.map → node-esm/types/index.mjs.map} +0 -0
|
@@ -3,69 +3,64 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { openSearchPanel } from '@codemirror/search';
|
|
6
|
-
import { EditorView } from '@codemirror/view';
|
|
6
|
+
import { type EditorView } from '@codemirror/view';
|
|
7
7
|
import React, { useMemo, useEffect, useCallback } from 'react';
|
|
8
8
|
|
|
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';
|
|
9
|
+
import { type FileInfo, LayoutAction, type LayoutCoordinate, useIntentDispatcher } from '@dxos/app-framework';
|
|
19
10
|
import { useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { useAttendableAttributes, useAttention } from '@dxos/react-ui-attention';
|
|
20
12
|
import {
|
|
21
13
|
type Action,
|
|
22
14
|
type DNDOptions,
|
|
23
15
|
type EditorViewMode,
|
|
24
16
|
type EditorInputMode,
|
|
25
|
-
type
|
|
17
|
+
type EditorSelectionState,
|
|
18
|
+
type EditorStateStore,
|
|
26
19
|
Toolbar,
|
|
20
|
+
type UseTextEditorProps,
|
|
27
21
|
createBasicExtensions,
|
|
28
22
|
createMarkdownExtensions,
|
|
29
23
|
createThemeExtensions,
|
|
30
24
|
dropFile,
|
|
25
|
+
editorContent,
|
|
26
|
+
editorGutter,
|
|
31
27
|
processAction,
|
|
32
28
|
useActionHandler,
|
|
33
29
|
useCommentState,
|
|
34
30
|
useCommentClickListener,
|
|
35
31
|
useFormattingState,
|
|
36
32
|
useTextEditor,
|
|
37
|
-
editorContent,
|
|
38
|
-
editorGutter,
|
|
39
|
-
Cursor,
|
|
40
|
-
setSelection,
|
|
41
33
|
} from '@dxos/react-ui-editor';
|
|
42
34
|
import { sectionToolbarLayout } from '@dxos/react-ui-stack';
|
|
43
35
|
import { textBlockWidth, focusRing, mx } from '@dxos/react-ui-theme';
|
|
44
|
-
import { nonNullable } from '@dxos/util';
|
|
36
|
+
import { isNotFalsy, nonNullable } from '@dxos/util';
|
|
45
37
|
|
|
38
|
+
import { useSelectCurrentThread } from '../hooks';
|
|
46
39
|
import { MARKDOWN_PLUGIN } from '../meta';
|
|
47
|
-
import type
|
|
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
|
-
);
|
|
40
|
+
import { type MarkdownPluginState } from '../types';
|
|
53
41
|
|
|
54
42
|
const DEFAULT_VIEW_MODE: EditorViewMode = 'preview';
|
|
55
43
|
|
|
56
44
|
export type MarkdownEditorProps = {
|
|
57
45
|
id: string;
|
|
46
|
+
role?: string;
|
|
58
47
|
coordinate?: LayoutCoordinate;
|
|
59
48
|
inputMode?: EditorInputMode;
|
|
60
|
-
role?: string;
|
|
61
49
|
scrollPastEnd?: boolean;
|
|
62
50
|
toolbar?: boolean;
|
|
63
51
|
viewMode?: EditorViewMode;
|
|
52
|
+
editorStateStore?: EditorStateStore;
|
|
64
53
|
onViewModeChange?: (id: string, mode: EditorViewMode) => void;
|
|
65
54
|
onFileUpload?: (file: File) => Promise<FileInfo | undefined>;
|
|
66
|
-
} & Pick<UseTextEditorProps, 'initialValue' | '
|
|
55
|
+
} & Pick<UseTextEditorProps, 'initialValue' | 'extensions'> &
|
|
67
56
|
Partial<Pick<MarkdownPluginState, 'extensionProviders'>>;
|
|
68
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Base markdown editor component.
|
|
60
|
+
*
|
|
61
|
+
* This component provides all the features of the markdown editor that do no depend on ECHO.
|
|
62
|
+
* This allows it to be used as a common editor for markdown content on arbitrary backends (e.g. files).
|
|
63
|
+
*/
|
|
69
64
|
export const MarkdownEditor = ({
|
|
70
65
|
id,
|
|
71
66
|
role = 'article',
|
|
@@ -73,62 +68,39 @@ export const MarkdownEditor = ({
|
|
|
73
68
|
extensions,
|
|
74
69
|
extensionProviders,
|
|
75
70
|
scrollPastEnd,
|
|
76
|
-
scrollTo,
|
|
77
|
-
selection,
|
|
78
71
|
toolbar,
|
|
79
72
|
viewMode,
|
|
73
|
+
editorStateStore,
|
|
80
74
|
onFileUpload,
|
|
81
75
|
onViewModeChange,
|
|
82
76
|
}: MarkdownEditorProps) => {
|
|
83
77
|
const { t } = useTranslation(MARKDOWN_PLUGIN);
|
|
84
78
|
const { themeMode } = useThemeContext();
|
|
85
79
|
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;
|
|
90
80
|
const [formattingState, formattingObserver] = useFormattingState();
|
|
81
|
+
const attendableAttributes = useAttendableAttributes(id);
|
|
82
|
+
const { hasAttention } = useAttention(id);
|
|
83
|
+
|
|
84
|
+
// Restore last selection and scroll point.
|
|
85
|
+
const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
|
|
91
86
|
|
|
92
87
|
// Extensions from other plugins.
|
|
93
|
-
|
|
88
|
+
// TODO(burdon): Reconcile with DocumentEditor.useExtensions.
|
|
89
|
+
const providerExtensions = useMemo(
|
|
90
|
+
() => extensionProviders?.flatMap((provider) => provider({})).filter(nonNullable),
|
|
91
|
+
[extensionProviders],
|
|
92
|
+
);
|
|
94
93
|
|
|
95
94
|
// TODO(Zan): Move these into thread plugin as well?
|
|
96
95
|
const [commentsState, commentObserver] = useCommentState();
|
|
97
96
|
const onCommentClick = useCallback(() => {
|
|
98
|
-
void dispatch({
|
|
97
|
+
void dispatch({
|
|
98
|
+
action: LayoutAction.SET_LAYOUT,
|
|
99
|
+
data: { element: 'complementary', state: true },
|
|
100
|
+
});
|
|
99
101
|
}, [dispatch]);
|
|
100
102
|
const commentClickObserver = useCommentClickListener(onCommentClick);
|
|
101
103
|
|
|
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
|
-
|
|
132
104
|
// Drag files.
|
|
133
105
|
const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
|
|
134
106
|
const file = files[0];
|
|
@@ -161,16 +133,16 @@ export const MarkdownEditor = ({
|
|
|
161
133
|
slots: { content: { className: editorContent } },
|
|
162
134
|
}),
|
|
163
135
|
editorGutter,
|
|
164
|
-
role !== 'section' && onFileUpload
|
|
136
|
+
role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
|
|
165
137
|
providerExtensions,
|
|
166
138
|
extensions,
|
|
167
|
-
].filter(
|
|
139
|
+
].filter(isNotFalsy),
|
|
168
140
|
...(role !== 'section' && {
|
|
169
141
|
id,
|
|
170
142
|
scrollTo,
|
|
171
143
|
selection,
|
|
172
144
|
// TODO(wittjosiah): Autofocus based on layout is racy.
|
|
173
|
-
autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
|
|
145
|
+
// autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
|
|
174
146
|
moveToEndOfLine: true,
|
|
175
147
|
}),
|
|
176
148
|
}),
|
|
@@ -178,6 +150,7 @@ export const MarkdownEditor = ({
|
|
|
178
150
|
);
|
|
179
151
|
|
|
180
152
|
useTest(editorView);
|
|
153
|
+
useSelectCurrentThread(editorView, id);
|
|
181
154
|
|
|
182
155
|
// Toolbar handler.
|
|
183
156
|
const handleToolbarAction = useActionHandler(editorView);
|
|
@@ -201,29 +174,27 @@ export const MarkdownEditor = ({
|
|
|
201
174
|
return (
|
|
202
175
|
<div
|
|
203
176
|
role='none'
|
|
204
|
-
// TODO(burdon): Move role logic out of here (see sheet, table, sketch, etc.)
|
|
205
177
|
{...(role === 'section'
|
|
206
178
|
? { className: 'flex flex-col' }
|
|
207
179
|
: {
|
|
208
|
-
className: 'contents
|
|
209
|
-
|
|
180
|
+
className: 'contents',
|
|
181
|
+
// TODO(wittjosiah): Factor out to `useAttendableAttributes`?
|
|
182
|
+
...(hasAttention && { 'aria-current': 'location' }),
|
|
183
|
+
...attendableAttributes,
|
|
210
184
|
})}
|
|
211
185
|
>
|
|
212
186
|
{toolbar && (
|
|
213
|
-
<div role='none' className=
|
|
187
|
+
<div role='none' className='flex shrink-0 justify-center overflow-x-auto attention-surface'>
|
|
214
188
|
<Toolbar.Root
|
|
215
189
|
classNames={
|
|
216
190
|
role === 'section'
|
|
217
191
|
? [
|
|
218
192
|
textBlockWidth,
|
|
219
193
|
'z-[2] group-focus-within/section:visible',
|
|
220
|
-
!
|
|
194
|
+
!hasAttention && 'invisible',
|
|
221
195
|
sectionToolbarLayout,
|
|
222
196
|
]
|
|
223
|
-
: [
|
|
224
|
-
textBlockWidth,
|
|
225
|
-
'group-focus-within/editor:border-separator group-[[aria-current]]/editor:border-separator',
|
|
226
|
-
]
|
|
197
|
+
: [textBlockWidth]
|
|
227
198
|
}
|
|
228
199
|
state={formattingState && { ...formattingState, ...commentsState }}
|
|
229
200
|
onAction={handleAction}
|
|
@@ -247,8 +218,8 @@ export const MarkdownEditor = ({
|
|
|
247
218
|
: mx(
|
|
248
219
|
'flex is-full bs-full overflow-hidden',
|
|
249
220
|
focusRing,
|
|
250
|
-
|
|
251
|
-
'
|
|
221
|
+
'focus-visible:ring-inset attention-surface',
|
|
222
|
+
'p-0.5', // TODO(burdon): Handle padding for focusRing consistently.
|
|
252
223
|
'data-[toolbar=disabled]:pbs-2 data-[toolbar=disabled]:row-span-2',
|
|
253
224
|
)
|
|
254
225
|
}
|
|
@@ -268,5 +239,3 @@ const useTest = (view?: EditorView) => {
|
|
|
268
239
|
}
|
|
269
240
|
}, [view]);
|
|
270
241
|
};
|
|
271
|
-
|
|
272
|
-
export default MarkdownEditor;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { SettingsValue } from '@dxos/plugin-settings';
|
|
8
7
|
import { Input, Select, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
import { FormInput } from '@dxos/react-ui-data';
|
|
9
9
|
import { type EditorInputMode, EditorInputModes, type EditorViewMode, EditorViewModes } from '@dxos/react-ui-editor';
|
|
10
10
|
|
|
11
11
|
import { MARKDOWN_PLUGIN } from '../meta';
|
|
@@ -17,7 +17,7 @@ export const MarkdownSettings = ({ settings }: { settings: MarkdownSettingsProps
|
|
|
17
17
|
// TODO(wittjosiah): Add skill test confirmation for entering vim mode.
|
|
18
18
|
return (
|
|
19
19
|
<>
|
|
20
|
-
<
|
|
20
|
+
<FormInput label={t('default view mode label')}>
|
|
21
21
|
<Select.Root
|
|
22
22
|
value={settings.defaultViewMode}
|
|
23
23
|
onValueChange={(value) => {
|
|
@@ -37,9 +37,9 @@ export const MarkdownSettings = ({ settings }: { settings: MarkdownSettingsProps
|
|
|
37
37
|
</Select.Content>
|
|
38
38
|
</Select.Portal>
|
|
39
39
|
</Select.Root>
|
|
40
|
-
</
|
|
40
|
+
</FormInput>
|
|
41
41
|
|
|
42
|
-
<
|
|
42
|
+
<FormInput label={t('editor input mode label')}>
|
|
43
43
|
<Select.Root
|
|
44
44
|
value={settings.editorInputMode ?? 'default'}
|
|
45
45
|
onValueChange={(value) => {
|
|
@@ -59,31 +59,31 @@ export const MarkdownSettings = ({ settings }: { settings: MarkdownSettingsProps
|
|
|
59
59
|
</Select.Content>
|
|
60
60
|
</Select.Portal>
|
|
61
61
|
</Select.Root>
|
|
62
|
-
</
|
|
62
|
+
</FormInput>
|
|
63
63
|
|
|
64
|
-
<
|
|
64
|
+
<FormInput label={t('settings toolbar label')}>
|
|
65
65
|
<Input.Switch checked={settings.toolbar} onCheckedChange={(checked) => (settings.toolbar = !!checked)} />
|
|
66
|
-
</
|
|
66
|
+
</FormInput>
|
|
67
67
|
|
|
68
|
-
<
|
|
68
|
+
<FormInput label={t('settings numbered headings label')}>
|
|
69
69
|
<Input.Switch
|
|
70
70
|
checked={settings.numberedHeadings}
|
|
71
71
|
onCheckedChange={(checked) => (settings.numberedHeadings = !!checked)}
|
|
72
72
|
/>
|
|
73
|
-
</
|
|
73
|
+
</FormInput>
|
|
74
74
|
|
|
75
|
-
<
|
|
75
|
+
<FormInput label={t('settings folding label')}>
|
|
76
76
|
<Input.Switch checked={settings.folding} onCheckedChange={(checked) => (settings.folding = !!checked)} />
|
|
77
|
-
</
|
|
77
|
+
</FormInput>
|
|
78
78
|
|
|
79
|
-
<
|
|
79
|
+
<FormInput label={t('settings experimental label')}>
|
|
80
80
|
<Input.Switch
|
|
81
81
|
checked={settings.experimental}
|
|
82
82
|
onCheckedChange={(checked) => (settings.experimental = !!checked)}
|
|
83
83
|
/>
|
|
84
|
-
</
|
|
84
|
+
</FormInput>
|
|
85
85
|
|
|
86
|
-
<
|
|
86
|
+
<FormInput
|
|
87
87
|
label={t('settings debug label')}
|
|
88
88
|
secondary={
|
|
89
89
|
settings.debug ? (
|
|
@@ -99,7 +99,7 @@ export const MarkdownSettings = ({ settings }: { settings: MarkdownSettingsProps
|
|
|
99
99
|
}
|
|
100
100
|
>
|
|
101
101
|
<Input.Switch checked={settings.debug} onCheckedChange={(checked) => (settings.debug = !!checked)} />
|
|
102
|
-
</
|
|
102
|
+
</FormInput>
|
|
103
103
|
</>
|
|
104
104
|
);
|
|
105
105
|
};
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
+
import { type Meta } from '@storybook/react';
|
|
7
8
|
import React, { type FC, useState } from 'react';
|
|
8
9
|
|
|
9
10
|
import { create } from '@dxos/echo-schema';
|
|
10
11
|
import { PublicKey } from '@dxos/keys';
|
|
11
12
|
import { faker } from '@dxos/random';
|
|
12
|
-
import { createDocAccessor,
|
|
13
|
+
import { createDocAccessor, createObject } from '@dxos/react-client/echo';
|
|
13
14
|
import { useThemeContext } from '@dxos/react-ui';
|
|
14
15
|
import {
|
|
15
16
|
type Action,
|
|
@@ -37,9 +38,9 @@ import { TextType } from '../types';
|
|
|
37
38
|
|
|
38
39
|
faker.seed(101);
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
+
const DefaultStory: FC<{ content?: string }> = ({ content = '' }) => {
|
|
41
42
|
const { themeMode } = useThemeContext();
|
|
42
|
-
const [text] = useState(
|
|
43
|
+
const [text] = useState(createObject(create(TextType, { content })));
|
|
43
44
|
const [formattingState, formattingObserver] = useFormattingState();
|
|
44
45
|
const [viewMode, setViewMode] = useState<EditorViewMode>('preview');
|
|
45
46
|
const { parentRef, view } = useTextEditor(() => {
|
|
@@ -91,14 +92,6 @@ const Story: FC<{ content: string }> = ({ content }) => {
|
|
|
91
92
|
);
|
|
92
93
|
};
|
|
93
94
|
|
|
94
|
-
export default {
|
|
95
|
-
title: 'react-ui-editor/Toolbar',
|
|
96
|
-
component: Toolbar,
|
|
97
|
-
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
98
|
-
parameters: { translations, layout: 'fullscreen' },
|
|
99
|
-
render: (args: any) => <Story {...args} />,
|
|
100
|
-
} as any;
|
|
101
|
-
|
|
102
95
|
const content = [
|
|
103
96
|
'# Demo',
|
|
104
97
|
'',
|
|
@@ -114,3 +107,13 @@ export const Default = {
|
|
|
114
107
|
content,
|
|
115
108
|
},
|
|
116
109
|
};
|
|
110
|
+
|
|
111
|
+
const meta: Meta<typeof Toolbar.Root> = {
|
|
112
|
+
title: 'plugins/plugin-markdown/Toolbar',
|
|
113
|
+
component: Toolbar.Root,
|
|
114
|
+
render: DefaultStory as any,
|
|
115
|
+
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
116
|
+
parameters: { translations, layout: 'fullscreen' },
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export default meta;
|
package/src/components/index.ts
CHANGED
|
@@ -2,20 +2,8 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { lazy } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type DocumentEditor as DocumentEditorType } from './DocumentEditor';
|
|
8
|
-
|
|
9
|
-
export { type DocumentCardProps, type DocumentItemProps } from './DocumentCard';
|
|
10
|
-
|
|
11
|
-
export * from './DocumentCard';
|
|
12
|
-
export * from './DocumentEditor';
|
|
13
|
-
export * from './MarkdownEditor';
|
|
14
|
-
export * from './HeadingMenu';
|
|
15
|
-
export * from './Layout';
|
|
16
7
|
export * from './MarkdownSettings';
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
export const DocumentCard = React.lazy(() => import('./DocumentCard'));
|
|
20
|
-
export const DocumentEditor: LazyExoticComponent<DocumentEditorType> = React.lazy(() => import('./DocumentEditor'));
|
|
21
|
-
export const MarkdownEditor = React.lazy(() => import('./MarkdownEditor'));
|
|
9
|
+
export const MarkdownContainer = lazy(() => import('./MarkdownContainer'));
|
package/src/extensions.tsx
CHANGED
|
@@ -2,63 +2,121 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import React, { type AnchorHTMLAttributes, StrictMode } from 'react';
|
|
5
|
+
import React, { type AnchorHTMLAttributes, type ReactNode, useMemo } from 'react';
|
|
7
6
|
import { createRoot } from 'react-dom/client';
|
|
8
7
|
|
|
9
|
-
import { type IntentDispatcher, NavigationAction } from '@dxos/app-framework';
|
|
8
|
+
import { type IntentDispatcher, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
|
|
10
9
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { fullyQualifiedId, type Query } from '@dxos/react-client/echo';
|
|
10
|
+
import { createDocAccessor, fullyQualifiedId, getSpace, type Query } from '@dxos/react-client/echo';
|
|
11
|
+
import { useIdentity } from '@dxos/react-client/halo';
|
|
12
|
+
import { Icon, ThemeProvider } from '@dxos/react-ui';
|
|
12
13
|
import {
|
|
13
14
|
type AutocompleteResult,
|
|
14
|
-
type
|
|
15
|
+
type EditorStateStore,
|
|
15
16
|
type EditorViewMode,
|
|
17
|
+
type Extension,
|
|
18
|
+
InputModeExtensions,
|
|
19
|
+
createDataExtensions,
|
|
16
20
|
autocomplete,
|
|
17
21
|
decorateMarkdown,
|
|
22
|
+
folding,
|
|
23
|
+
formattingKeymap,
|
|
18
24
|
linkTooltip,
|
|
25
|
+
listener,
|
|
26
|
+
selectionState,
|
|
19
27
|
typewriter,
|
|
20
|
-
formattingKeymap,
|
|
21
|
-
InputModeExtensions,
|
|
22
|
-
folding,
|
|
23
28
|
} from '@dxos/react-ui-editor';
|
|
24
|
-
import {
|
|
25
|
-
import { isNotFalsy
|
|
29
|
+
import { defaultTx } from '@dxos/react-ui-theme';
|
|
30
|
+
import { isNotFalsy } from '@dxos/util';
|
|
26
31
|
|
|
27
|
-
import { type DocumentType, type MarkdownSettingsProps } from './types';
|
|
32
|
+
import { type DocumentType, type MarkdownPluginState, type MarkdownSettingsProps } from './types';
|
|
33
|
+
import { setFallbackName } from './util';
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
settings?: MarkdownSettingsProps;
|
|
32
|
-
document?: DocumentType;
|
|
33
|
-
debug?: boolean;
|
|
34
|
-
experimental?: boolean;
|
|
35
|
-
numberedHeadings?: boolean;
|
|
36
|
-
folding?: boolean;
|
|
37
|
-
query?: Query<DocumentType>;
|
|
35
|
+
type ExtensionsOptions = {
|
|
36
|
+
document: DocumentType;
|
|
38
37
|
dispatch?: IntentDispatcher;
|
|
38
|
+
query?: Query<DocumentType>;
|
|
39
|
+
settings: MarkdownSettingsProps;
|
|
40
|
+
viewMode?: EditorViewMode;
|
|
41
|
+
editorStateStore?: EditorStateStore;
|
|
39
42
|
};
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
export const createBaseExtensions = ({
|
|
45
|
-
viewMode,
|
|
46
|
-
settings,
|
|
44
|
+
// TODO(burdon): Merge with createBaseExtensions below.
|
|
45
|
+
export const useExtensions = ({
|
|
47
46
|
document,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
settings,
|
|
48
|
+
viewMode,
|
|
49
|
+
editorStateStore,
|
|
50
|
+
extensionProviders,
|
|
51
|
+
}: ExtensionsOptions & Pick<MarkdownPluginState, 'extensionProviders'>): Extension[] => {
|
|
52
|
+
const dispatch = useIntentDispatcher();
|
|
53
|
+
const identity = useIdentity();
|
|
54
|
+
const space = getSpace(document);
|
|
55
|
+
|
|
56
|
+
// TODO(wittjosiah): Autocomplete is not working and this query is causing performance issues.
|
|
57
|
+
// TODO(burdon): Unsubscribe.
|
|
58
|
+
// const query = space?.db.query(Filter.schema(DocumentType));
|
|
59
|
+
// query?.subscribe();
|
|
60
|
+
const baseExtensions = useMemo(
|
|
61
|
+
() =>
|
|
62
|
+
createBaseExtensions({
|
|
63
|
+
document,
|
|
64
|
+
settings,
|
|
65
|
+
viewMode,
|
|
66
|
+
dispatch,
|
|
67
|
+
// query,
|
|
68
|
+
}),
|
|
69
|
+
[document, viewMode, dispatch, settings, settings.folding, settings.numberedHeadings],
|
|
70
|
+
);
|
|
52
71
|
|
|
53
72
|
//
|
|
54
|
-
//
|
|
73
|
+
// External extensions from other plugins.
|
|
55
74
|
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
const pluginExtensions = useMemo<Extension[] | undefined>(
|
|
76
|
+
() =>
|
|
77
|
+
extensionProviders?.reduce((acc: Extension[], provider) => {
|
|
78
|
+
const extension = typeof provider === 'function' ? provider({ document }) : provider;
|
|
79
|
+
if (extension) {
|
|
80
|
+
acc.push(extension);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return acc;
|
|
84
|
+
}, []),
|
|
85
|
+
[extensionProviders],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
//
|
|
89
|
+
// Basic plugins.
|
|
90
|
+
//
|
|
91
|
+
return useMemo<Extension[]>(
|
|
92
|
+
() =>
|
|
93
|
+
[
|
|
94
|
+
// NOTE: Data extensions must be first so that automerge is updated before other extensions compute their state.
|
|
95
|
+
createDataExtensions({
|
|
96
|
+
id: document.id,
|
|
97
|
+
text: document.content && createDocAccessor(document.content, ['content']),
|
|
98
|
+
space,
|
|
99
|
+
identity,
|
|
100
|
+
}),
|
|
101
|
+
selectionState(editorStateStore),
|
|
102
|
+
listener({
|
|
103
|
+
onChange: (text) => setFallbackName(document, text),
|
|
104
|
+
}),
|
|
105
|
+
baseExtensions,
|
|
106
|
+
pluginExtensions,
|
|
107
|
+
].filter(isNotFalsy),
|
|
108
|
+
[baseExtensions, pluginExtensions, document, document.content, space, identity],
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create extension instances for editor.
|
|
114
|
+
*/
|
|
115
|
+
const createBaseExtensions = ({ document, dispatch, settings, query, viewMode }: ExtensionsOptions): Extension[] => {
|
|
116
|
+
const extensions: Extension[] = [
|
|
117
|
+
settings.editorInputMode && InputModeExtensions[settings.editorInputMode],
|
|
118
|
+
settings.folding && folding(),
|
|
119
|
+
].filter(isNotFalsy);
|
|
62
120
|
|
|
63
121
|
//
|
|
64
122
|
// Markdown
|
|
@@ -69,7 +127,7 @@ export const createBaseExtensions = ({
|
|
|
69
127
|
formattingKeymap(),
|
|
70
128
|
decorateMarkdown({
|
|
71
129
|
selectionChangeDelay: 100,
|
|
72
|
-
numberedHeadings: settings
|
|
130
|
+
numberedHeadings: settings.numberedHeadings ? { from: 2 } : undefined,
|
|
73
131
|
// TODO(wittjosiah): For internal links, consider ignoring the link text and rendering the label of the object being linked to.
|
|
74
132
|
renderLinkButton:
|
|
75
133
|
dispatch && document
|
|
@@ -98,7 +156,6 @@ export const createBaseExtensions = ({
|
|
|
98
156
|
extensions.push(
|
|
99
157
|
autocomplete({
|
|
100
158
|
onSearch: (text: string) => {
|
|
101
|
-
// TODO query
|
|
102
159
|
// TODO(burdon): Specify filter (e.g., stack).
|
|
103
160
|
return query.objects
|
|
104
161
|
.map<AutocompleteResult | undefined>((object) =>
|
|
@@ -110,29 +167,27 @@ export const createBaseExtensions = ({
|
|
|
110
167
|
}
|
|
111
168
|
: undefined,
|
|
112
169
|
)
|
|
113
|
-
.filter(
|
|
170
|
+
.filter(isNotFalsy);
|
|
114
171
|
},
|
|
115
172
|
}),
|
|
116
173
|
);
|
|
117
174
|
}
|
|
118
175
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
if (settings?.debug) {
|
|
127
|
-
const items = settings.typewriter ?? '';
|
|
128
|
-
extensions.push(...[items ? typewriter({ items: items.split(/[,\n]/) }) : undefined].filter(nonNullable));
|
|
176
|
+
if (settings.debug) {
|
|
177
|
+
const items = settings.typewriter?.split(/[,\n]/) ?? '';
|
|
178
|
+
if (items) {
|
|
179
|
+
extensions.push(typewriter({ items }));
|
|
180
|
+
}
|
|
129
181
|
}
|
|
130
182
|
|
|
131
183
|
return extensions;
|
|
132
184
|
};
|
|
133
185
|
|
|
134
|
-
// TODO(burdon): Factor out
|
|
135
|
-
const
|
|
186
|
+
// TODO(burdon): Factor out styles.
|
|
187
|
+
const style = {
|
|
188
|
+
hover: 'rounded-sm text-primary-500 hover:text-primary-600 dark:text-primary-500 hover:dark:text-primary-400',
|
|
189
|
+
icon: 'inline-block leading-none mis-1 cursor-pointer',
|
|
190
|
+
};
|
|
136
191
|
|
|
137
192
|
const onRenderLink = (onSelectObject: (id: string) => void) => (el: Element, url: string) => {
|
|
138
193
|
// TODO(burdon): Formalize/document internal link format.
|
|
@@ -155,25 +210,31 @@ const onRenderLink = (onSelectObject: (id: string) => void) => (el: Element, url
|
|
|
155
210
|
target: '_blank',
|
|
156
211
|
};
|
|
157
212
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
213
|
+
renderRoot(
|
|
214
|
+
el,
|
|
215
|
+
<a {...options} className={style.hover}>
|
|
216
|
+
<Icon
|
|
217
|
+
icon={isInternal ? 'ph--arrow-square-down--bold' : 'ph--arrow-square-out--bold'}
|
|
218
|
+
size={4}
|
|
219
|
+
classNames={style.icon}
|
|
220
|
+
/>
|
|
221
|
+
</a>,
|
|
166
222
|
);
|
|
167
223
|
};
|
|
168
224
|
|
|
169
225
|
const renderLinkTooltip = (el: Element, url: string) => {
|
|
170
226
|
const web = new URL(url);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
</StrictMode>,
|
|
227
|
+
renderRoot(
|
|
228
|
+
el,
|
|
229
|
+
<a href={url} rel='noreferrer' target='_blank' className={style.hover}>
|
|
230
|
+
{web.origin}
|
|
231
|
+
<Icon icon='ph--arrow-square-out--bold' size={4} classNames={style.icon} />
|
|
232
|
+
</a>,
|
|
178
233
|
);
|
|
179
234
|
};
|
|
235
|
+
|
|
236
|
+
// TODO(burdon): Remove react rendering; use DOM directly.
|
|
237
|
+
export const renderRoot = <T extends Element>(root: T, node: ReactNode): T => {
|
|
238
|
+
createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
|
|
239
|
+
return root;
|
|
240
|
+
};
|