@dxos/plugin-markdown 0.6.13 → 0.6.14-main.2b6a0f3
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-6VGH47F5.mjs +469 -0
- package/dist/lib/browser/MarkdownContainer-6VGH47F5.mjs.map +7 -0
- package/dist/lib/browser/chunk-45N5MEOV.mjs +50 -0
- package/dist/lib/browser/chunk-45N5MEOV.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/index.mjs +80 -119
- 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-OXS7TME6.cjs +484 -0
- package/dist/lib/node/MarkdownContainer-OXS7TME6.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/chunk-W2YJVZ3N.cjs +72 -0
- package/dist/lib/node/chunk-W2YJVZ3N.cjs.map +7 -0
- package/dist/lib/node/index.cjs +114 -149
- 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-IYX45C2U.mjs +470 -0
- package/dist/lib/node-esm/MarkdownContainer-IYX45C2U.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/chunk-UCNOGIBC.mjs +51 -0
- package/dist/lib/node-esm/chunk-UCNOGIBC.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +494 -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 +8 -3
- 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 +11 -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/package.json +42 -36
- package/src/MarkdownPlugin.tsx +51 -98
- package/src/components/MarkdownContainer.tsx +111 -0
- package/src/components/MarkdownEditor.stories.tsx +34 -23
- package/src/components/MarkdownEditor.tsx +40 -76
- package/src/components/Toolbar.stories.tsx +14 -11
- package/src/components/index.ts +2 -14
- package/src/extensions.tsx +124 -67
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useSelectCurrentThread.tsx +46 -0
- package/src/meta.ts +15 -0
- package/src/types/document.ts +12 -0
- package/src/types/types.ts +10 -7
- package/src/util.tsx +2 -2
- 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
package/src/MarkdownPlugin.tsx
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import React
|
|
5
|
+
import { TextAa } from '@phosphor-icons/react';
|
|
6
|
+
import React from 'react';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
isObject,
|
|
10
9
|
parseIntentPlugin,
|
|
11
10
|
resolvePlugin,
|
|
12
11
|
LayoutAction,
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
} from '@dxos/app-framework';
|
|
17
16
|
import { create } from '@dxos/echo-schema';
|
|
18
17
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
19
|
-
import { log } from '@dxos/log';
|
|
20
18
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
21
19
|
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
22
20
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
@@ -34,12 +32,11 @@ import {
|
|
|
34
32
|
EditorViewModes,
|
|
35
33
|
translations as editorTranslations,
|
|
36
34
|
} from '@dxos/react-ui-editor';
|
|
37
|
-
import { isTileComponentProps } from '@dxos/react-ui-mosaic';
|
|
38
35
|
|
|
39
|
-
import {
|
|
36
|
+
import { MarkdownContainer, MarkdownSettings } from './components';
|
|
40
37
|
import meta, { MARKDOWN_PLUGIN } from './meta';
|
|
41
38
|
import translations from './translations';
|
|
42
|
-
import { DocumentType, TextType } from './types';
|
|
39
|
+
import { DocumentType, isEditorModel, TextType } from './types';
|
|
43
40
|
import {
|
|
44
41
|
type MarkdownPluginProvides,
|
|
45
42
|
type MarkdownSettingsProps,
|
|
@@ -48,37 +45,22 @@ import {
|
|
|
48
45
|
} from './types';
|
|
49
46
|
import { markdownExtensionPlugins, serializer } from './util';
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*/
|
|
54
|
-
const isEditorModel = (data: any): data is { id: string; text: string } => {
|
|
55
|
-
return (
|
|
56
|
-
data &&
|
|
57
|
-
typeof data === 'object' &&
|
|
58
|
-
'id' in data &&
|
|
59
|
-
typeof data.id === 'string' &&
|
|
60
|
-
'text' in data &&
|
|
61
|
-
typeof data.text === 'string'
|
|
62
|
-
);
|
|
63
|
-
};
|
|
48
|
+
// TODO(burdon): Normalize active/object.
|
|
49
|
+
const getDoc = (object: any) => (object instanceof DocumentType ? object : undefined);
|
|
64
50
|
|
|
65
51
|
export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
66
52
|
const settings = new LocalStorageStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN, {
|
|
67
53
|
defaultViewMode: 'preview',
|
|
68
54
|
toolbar: true,
|
|
69
|
-
|
|
55
|
+
numberedHeadings: true,
|
|
56
|
+
folding: true,
|
|
70
57
|
experimental: false,
|
|
71
58
|
});
|
|
72
59
|
|
|
73
60
|
const state = new LocalStorageStore<MarkdownPluginState>(MARKDOWN_PLUGIN, { extensionProviders: [], viewMode: {} });
|
|
74
61
|
|
|
75
|
-
const getViewMode = (id
|
|
76
|
-
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const setViewMode = (id: string, nextViewMode: EditorViewMode) => {
|
|
80
|
-
state.values.viewMode[id] = nextViewMode;
|
|
81
|
-
};
|
|
62
|
+
const getViewMode = (id: string) => (id && state.values.viewMode[id]) || settings.values.defaultViewMode;
|
|
63
|
+
const setViewMode = (id: string, viewMode: EditorViewMode) => (state.values.viewMode[id] = viewMode);
|
|
82
64
|
|
|
83
65
|
return {
|
|
84
66
|
meta,
|
|
@@ -109,7 +91,7 @@ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
|
109
91
|
|
|
110
92
|
markdownExtensionPlugins(plugins).forEach((plugin) => {
|
|
111
93
|
const { extensions } = plugin.provides.markdown;
|
|
112
|
-
state.values.extensionProviders
|
|
94
|
+
state.values.extensionProviders?.push(extensions);
|
|
113
95
|
});
|
|
114
96
|
},
|
|
115
97
|
provides: {
|
|
@@ -117,10 +99,9 @@ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
|
117
99
|
metadata: {
|
|
118
100
|
records: {
|
|
119
101
|
[DocumentType.typename]: {
|
|
120
|
-
label: (object: any) => (object instanceof DocumentType ? object.name
|
|
102
|
+
label: (object: any) => (object instanceof DocumentType ? object.name || object.fallbackName : undefined),
|
|
121
103
|
placeholder: ['document title placeholder', { ns: MARKDOWN_PLUGIN }],
|
|
122
|
-
icon:
|
|
123
|
-
iconSymbol: 'ph--text-aa--regular',
|
|
104
|
+
icon: 'ph--text-aa--regular',
|
|
124
105
|
graphProps: {
|
|
125
106
|
managesAutofocus: true,
|
|
126
107
|
},
|
|
@@ -134,6 +115,12 @@ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
|
134
115
|
echo: {
|
|
135
116
|
schema: [DocumentType, TextType],
|
|
136
117
|
},
|
|
118
|
+
space: {
|
|
119
|
+
onSpaceCreate: {
|
|
120
|
+
label: ['create document label', { ns: MARKDOWN_PLUGIN }],
|
|
121
|
+
action: MarkdownAction.CREATE,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
137
124
|
graph: {
|
|
138
125
|
builder: (plugins) => {
|
|
139
126
|
const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
|
|
@@ -167,8 +154,7 @@ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
|
167
154
|
},
|
|
168
155
|
properties: {
|
|
169
156
|
label: ['create document label', { ns: MARKDOWN_PLUGIN }],
|
|
170
|
-
icon:
|
|
171
|
-
iconSymbol: 'ph--text-aa--regular',
|
|
157
|
+
icon: 'ph--text-aa--regular',
|
|
172
158
|
testId: 'markdownPlugin.createObject',
|
|
173
159
|
},
|
|
174
160
|
},
|
|
@@ -241,13 +227,10 @@ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
|
241
227
|
],
|
|
242
228
|
},
|
|
243
229
|
thread: {
|
|
244
|
-
// TODO(Zan): How to better handle the type predicate?
|
|
245
230
|
predicate: (obj) => obj instanceof DocumentType,
|
|
246
231
|
createSort: (doc: DocumentType) => {
|
|
247
232
|
const accessor = doc.content ? createDocAccessor(doc.content, ['content']) : undefined;
|
|
248
|
-
|
|
249
233
|
if (!accessor) {
|
|
250
|
-
log.warn('No accessor found for document content.');
|
|
251
234
|
return (_) => 0;
|
|
252
235
|
}
|
|
253
236
|
|
|
@@ -256,75 +239,45 @@ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
|
256
239
|
return range?.start ?? Number.MAX_SAFE_INTEGER;
|
|
257
240
|
};
|
|
258
241
|
|
|
259
|
-
return (anchorA: string, anchorB: string
|
|
242
|
+
return (anchorA: string | undefined, anchorB: string | undefined): number => {
|
|
243
|
+
if (anchorA === undefined || anchorB === undefined) {
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
const posA = getStartPosition(anchorA);
|
|
247
|
+
const posB = getStartPosition(anchorB);
|
|
248
|
+
return posA - posB;
|
|
249
|
+
};
|
|
260
250
|
},
|
|
261
251
|
},
|
|
262
252
|
surface: {
|
|
263
|
-
component: ({ data, role
|
|
264
|
-
const doc =
|
|
265
|
-
data.active instanceof DocumentType
|
|
266
|
-
? data.active
|
|
267
|
-
: data.object instanceof DocumentType
|
|
268
|
-
? data.object
|
|
269
|
-
: undefined;
|
|
270
|
-
|
|
253
|
+
component: ({ data, role }) => {
|
|
271
254
|
switch (role) {
|
|
272
255
|
case 'section':
|
|
273
256
|
case 'article': {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
settings={settings.values}
|
|
282
|
-
scrollPastEnd
|
|
283
|
-
viewMode={getViewMode(fullyQualifiedId(doc))}
|
|
284
|
-
onViewModeChange={setViewMode}
|
|
285
|
-
/>
|
|
286
|
-
);
|
|
287
|
-
} else if (isEditorModel(data.object)) {
|
|
288
|
-
return (
|
|
289
|
-
<MarkdownEditor
|
|
290
|
-
id={data.object.id}
|
|
291
|
-
role={role}
|
|
292
|
-
coordinate={data.coordinate as LayoutCoordinate}
|
|
293
|
-
initialValue={data.object.text}
|
|
294
|
-
extensionProviders={state.values.extensionProviders}
|
|
295
|
-
inputMode={settings.values.editorInputMode}
|
|
296
|
-
toolbar={settings.values.toolbar}
|
|
297
|
-
scrollPastEnd
|
|
298
|
-
viewMode={getViewMode(data.object.id)}
|
|
299
|
-
onViewModeChange={setViewMode}
|
|
300
|
-
/>
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
257
|
+
// TODO(burdon): Normalize types (from FilesPlugin).
|
|
258
|
+
const doc = getDoc(data.active) ?? getDoc(data.object);
|
|
259
|
+
const { id, object } = isEditorModel(data.object)
|
|
260
|
+
? { id: data.object.id, object: data.object }
|
|
261
|
+
: doc
|
|
262
|
+
? { id: fullyQualifiedId(doc), object: doc }
|
|
263
|
+
: {};
|
|
305
264
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
isObject(data.content) &&
|
|
309
|
-
typeof data.content.id === 'string' &&
|
|
310
|
-
data.content.object instanceof DocumentType
|
|
311
|
-
) {
|
|
312
|
-
// isTileComponentProps is a type guard for these props.
|
|
313
|
-
// `props` will not pass this guard without transforming `data` into `item`.
|
|
314
|
-
const cardProps = {
|
|
315
|
-
...props,
|
|
316
|
-
item: {
|
|
317
|
-
id: data.content.id,
|
|
318
|
-
object: data.content.object,
|
|
319
|
-
color: typeof data.content.color === 'string' ? data.content.color : undefined,
|
|
320
|
-
} as DocumentItemProps,
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
return isTileComponentProps(cardProps) ? (
|
|
324
|
-
<DocumentCard {...cardProps} settings={settings.values} ref={forwardedRef as Ref<HTMLDivElement>} />
|
|
325
|
-
) : null;
|
|
265
|
+
if (!id || !object) {
|
|
266
|
+
return null;
|
|
326
267
|
}
|
|
327
|
-
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<MarkdownContainer
|
|
271
|
+
id={id}
|
|
272
|
+
object={object}
|
|
273
|
+
role={role}
|
|
274
|
+
coordinate={data.coordinate as LayoutCoordinate}
|
|
275
|
+
settings={settings.values}
|
|
276
|
+
extensionProviders={state.values.extensionProviders}
|
|
277
|
+
viewMode={getViewMode(id)}
|
|
278
|
+
onViewModeChange={setViewMode}
|
|
279
|
+
/>
|
|
280
|
+
);
|
|
328
281
|
}
|
|
329
282
|
|
|
330
283
|
case 'settings': {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useResolvePlugin, parseFileManagerPlugin } from '@dxos/app-framework';
|
|
8
|
+
import { fullyQualifiedId, getSpace } from '@dxos/react-client/echo';
|
|
9
|
+
import { localStorageStateStoreAdapter, type EditorSelectionState } from '@dxos/react-ui-editor';
|
|
10
|
+
|
|
11
|
+
import { MarkdownEditor, type MarkdownEditorProps } from './MarkdownEditor';
|
|
12
|
+
import { useExtensions } from '../extensions';
|
|
13
|
+
import { DocumentType, type MarkdownSettingsProps } from '../types';
|
|
14
|
+
import { getFallbackName } from '../util';
|
|
15
|
+
|
|
16
|
+
export type MarkdownContainerProps = Pick<
|
|
17
|
+
MarkdownEditorProps,
|
|
18
|
+
'role' | 'coordinate' | 'extensionProviders' | 'viewMode' | 'onViewModeChange'
|
|
19
|
+
> & {
|
|
20
|
+
id: string;
|
|
21
|
+
object: DocumentType | any;
|
|
22
|
+
settings: MarkdownSettingsProps;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// TODO(burdon): Move toolbar here.
|
|
26
|
+
// TODO(burdon): Factor out difference for ECHO and non-ECHO objects; i.e., single component.
|
|
27
|
+
const MarkdownContainer = ({ id, role, object, settings, ...props }: MarkdownContainerProps) => {
|
|
28
|
+
const scrollPastEnd = role === 'article';
|
|
29
|
+
if (object instanceof DocumentType) {
|
|
30
|
+
return (
|
|
31
|
+
<DocumentEditor
|
|
32
|
+
id={fullyQualifiedId(object)}
|
|
33
|
+
role={role}
|
|
34
|
+
document={object}
|
|
35
|
+
settings={settings}
|
|
36
|
+
scrollPastEnd={scrollPastEnd}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
} else {
|
|
41
|
+
return (
|
|
42
|
+
<MarkdownEditor
|
|
43
|
+
id={id}
|
|
44
|
+
role={role}
|
|
45
|
+
initialValue={object.text}
|
|
46
|
+
toolbar={settings.toolbar}
|
|
47
|
+
scrollPastEnd={scrollPastEnd}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type DocumentEditorProps = Omit<MarkdownContainerProps, 'object'> & { document: DocumentType } & Pick<
|
|
55
|
+
MarkdownEditorProps,
|
|
56
|
+
'id' | 'scrollPastEnd'
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
export const DocumentEditor = ({
|
|
60
|
+
id,
|
|
61
|
+
document: doc,
|
|
62
|
+
extensionProviders,
|
|
63
|
+
settings,
|
|
64
|
+
viewMode,
|
|
65
|
+
...props
|
|
66
|
+
}: DocumentEditorProps) => {
|
|
67
|
+
const space = getSpace(doc);
|
|
68
|
+
const initialValue = useMemo(() => doc.content?.content, [doc.content]);
|
|
69
|
+
const extensions = useExtensions({ extensionProviders, document: doc, settings, viewMode });
|
|
70
|
+
|
|
71
|
+
// Migrate gradually to `fallbackName`.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!doc.fallbackName && doc.content?.content) {
|
|
74
|
+
doc.fallbackName = getFallbackName(doc.content.content);
|
|
75
|
+
}
|
|
76
|
+
}, [doc, doc.content]);
|
|
77
|
+
|
|
78
|
+
// Restore last selection and scroll point.
|
|
79
|
+
const { scrollTo, selection } = useMemo<EditorSelectionState>(
|
|
80
|
+
() => localStorageStateStoreAdapter.getState(id) ?? {},
|
|
81
|
+
[id, doc],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// File dragging.
|
|
85
|
+
const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
|
|
86
|
+
const handleFileUpload = useMemo(() => {
|
|
87
|
+
if (space === undefined || fileManagerPlugin?.provides.file.upload === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// TODO(burdon): Re-order props: space, file.
|
|
92
|
+
return async (file: File) => fileManagerPlugin?.provides?.file?.upload?.(file, space);
|
|
93
|
+
}, [space, fileManagerPlugin]);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<MarkdownEditor
|
|
97
|
+
id={id}
|
|
98
|
+
initialValue={initialValue}
|
|
99
|
+
extensions={extensions}
|
|
100
|
+
scrollTo={scrollTo}
|
|
101
|
+
selection={selection}
|
|
102
|
+
toolbar={settings.toolbar}
|
|
103
|
+
inputMode={settings.editorInputMode}
|
|
104
|
+
viewMode={viewMode}
|
|
105
|
+
onFileUpload={handleFileUpload}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default MarkdownContainer;
|
|
@@ -3,41 +3,42 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
|
-
import React, { useMemo, type FC } from 'react';
|
|
7
6
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { type Meta } from '@storybook/react';
|
|
8
|
+
import React, { useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
import { createDocAccessor, createObject } from '@dxos/react-client/echo';
|
|
11
|
+
import { Main } from '@dxos/react-ui';
|
|
12
|
+
import { editorWithToolbarLayout, automerge } from '@dxos/react-ui-editor';
|
|
13
|
+
import { topbarBlockPaddingStart } from '@dxos/react-ui-theme';
|
|
10
14
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
11
15
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
16
|
+
import { MarkdownEditor, type MarkdownEditorProps } from './MarkdownEditor';
|
|
17
|
+
|
|
18
|
+
const content = Array.from({ length: 100 })
|
|
19
|
+
.map((_, i) => `Line ${i + 1}`)
|
|
20
|
+
.join('\n');
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
content
|
|
22
|
+
type StoryProps = MarkdownEditorProps & {
|
|
23
|
+
content?: string;
|
|
17
24
|
toolbar?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const DefaultStory = ({ content = '# Test', toolbar }: StoryProps) => {
|
|
28
|
+
const doc = useMemo(() => createObject({ content }), [content]);
|
|
20
29
|
const extensions = useMemo(() => [automerge(createDocAccessor(doc, ['content']))], [doc]);
|
|
21
30
|
|
|
22
31
|
return (
|
|
23
|
-
<
|
|
32
|
+
<Main.Content
|
|
33
|
+
bounce
|
|
34
|
+
data-toolbar={toolbar ? 'enabled' : 'disabled'}
|
|
35
|
+
classNames={[topbarBlockPaddingStart, editorWithToolbarLayout]}
|
|
36
|
+
>
|
|
24
37
|
<MarkdownEditor id='test' initialValue={doc.content} extensions={extensions} toolbar={toolbar} />
|
|
25
|
-
</
|
|
38
|
+
</Main.Content>
|
|
26
39
|
);
|
|
27
40
|
};
|
|
28
41
|
|
|
29
|
-
export default {
|
|
30
|
-
title: 'plugin-markdown/EditorMain',
|
|
31
|
-
component: MarkdownEditor,
|
|
32
|
-
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
33
|
-
render: Story,
|
|
34
|
-
parameters: { layout: 'fullscreen' },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const content = Array.from({ length: 100 })
|
|
38
|
-
.map((_, i) => `Line ${i + 1}`)
|
|
39
|
-
.join('\n');
|
|
40
|
-
|
|
41
42
|
export const Default = {
|
|
42
43
|
args: {
|
|
43
44
|
content,
|
|
@@ -50,3 +51,13 @@ export const WithToolbar = {
|
|
|
50
51
|
toolbar: true,
|
|
51
52
|
},
|
|
52
53
|
};
|
|
54
|
+
|
|
55
|
+
const meta: Meta<typeof MarkdownEditor> = {
|
|
56
|
+
title: 'plugins/plugin-markdown/EditorMain',
|
|
57
|
+
component: MarkdownEditor,
|
|
58
|
+
render: DefaultStory,
|
|
59
|
+
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
60
|
+
parameters: { layout: 'fullscreen' },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default meta;
|
|
@@ -3,61 +3,47 @@
|
|
|
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 UseTextEditorProps,
|
|
26
17
|
Toolbar,
|
|
18
|
+
type UseTextEditorProps,
|
|
27
19
|
createBasicExtensions,
|
|
28
20
|
createMarkdownExtensions,
|
|
29
21
|
createThemeExtensions,
|
|
30
22
|
dropFile,
|
|
23
|
+
editorContent,
|
|
24
|
+
editorGutter,
|
|
31
25
|
processAction,
|
|
32
26
|
useActionHandler,
|
|
33
27
|
useCommentState,
|
|
34
28
|
useCommentClickListener,
|
|
35
29
|
useFormattingState,
|
|
36
30
|
useTextEditor,
|
|
37
|
-
editorContent,
|
|
38
|
-
editorGutter,
|
|
39
|
-
Cursor,
|
|
40
|
-
setSelection,
|
|
41
31
|
} from '@dxos/react-ui-editor';
|
|
42
32
|
import { sectionToolbarLayout } from '@dxos/react-ui-stack';
|
|
43
33
|
import { textBlockWidth, focusRing, mx } from '@dxos/react-ui-theme';
|
|
44
|
-
import { nonNullable } from '@dxos/util';
|
|
34
|
+
import { isNotFalsy, nonNullable } from '@dxos/util';
|
|
45
35
|
|
|
36
|
+
import { useSelectCurrentThread } from '../hooks';
|
|
46
37
|
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
|
-
);
|
|
38
|
+
import { type MarkdownPluginState } from '../types';
|
|
53
39
|
|
|
54
40
|
const DEFAULT_VIEW_MODE: EditorViewMode = 'preview';
|
|
55
41
|
|
|
56
42
|
export type MarkdownEditorProps = {
|
|
57
43
|
id: string;
|
|
44
|
+
role?: string;
|
|
58
45
|
coordinate?: LayoutCoordinate;
|
|
59
46
|
inputMode?: EditorInputMode;
|
|
60
|
-
role?: string;
|
|
61
47
|
scrollPastEnd?: boolean;
|
|
62
48
|
toolbar?: boolean;
|
|
63
49
|
viewMode?: EditorViewMode;
|
|
@@ -66,6 +52,12 @@ export type MarkdownEditorProps = {
|
|
|
66
52
|
} & Pick<UseTextEditorProps, 'initialValue' | 'scrollTo' | 'selection' | 'extensions'> &
|
|
67
53
|
Partial<Pick<MarkdownPluginState, 'extensionProviders'>>;
|
|
68
54
|
|
|
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
|
+
*/
|
|
69
61
|
export const MarkdownEditor = ({
|
|
70
62
|
id,
|
|
71
63
|
role = 'article',
|
|
@@ -83,52 +75,27 @@ export const MarkdownEditor = ({
|
|
|
83
75
|
const { t } = useTranslation(MARKDOWN_PLUGIN);
|
|
84
76
|
const { themeMode } = useThemeContext();
|
|
85
77
|
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
78
|
const [formattingState, formattingObserver] = useFormattingState();
|
|
79
|
+
const attendableAttributes = useAttendableAttributes(id);
|
|
80
|
+
const { hasAttention } = useAttention(id);
|
|
91
81
|
|
|
92
82
|
// Extensions from other plugins.
|
|
93
|
-
|
|
83
|
+
// TODO(burdon): Reconcile with DocumentEditor.useExtensions.
|
|
84
|
+
const providerExtensions = useMemo(
|
|
85
|
+
() => extensionProviders?.flatMap((provider) => provider({})).filter(nonNullable),
|
|
86
|
+
[extensionProviders],
|
|
87
|
+
);
|
|
94
88
|
|
|
95
89
|
// TODO(Zan): Move these into thread plugin as well?
|
|
96
90
|
const [commentsState, commentObserver] = useCommentState();
|
|
97
91
|
const onCommentClick = useCallback(() => {
|
|
98
|
-
void dispatch({
|
|
92
|
+
void dispatch({
|
|
93
|
+
action: LayoutAction.SET_LAYOUT,
|
|
94
|
+
data: { element: 'complementary', state: true },
|
|
95
|
+
});
|
|
99
96
|
}, [dispatch]);
|
|
100
97
|
const commentClickObserver = useCommentClickListener(onCommentClick);
|
|
101
98
|
|
|
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
99
|
// Drag files.
|
|
133
100
|
const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
|
|
134
101
|
const file = files[0];
|
|
@@ -161,16 +128,16 @@ export const MarkdownEditor = ({
|
|
|
161
128
|
slots: { content: { className: editorContent } },
|
|
162
129
|
}),
|
|
163
130
|
editorGutter,
|
|
164
|
-
role !== 'section' && onFileUpload
|
|
131
|
+
role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
|
|
165
132
|
providerExtensions,
|
|
166
133
|
extensions,
|
|
167
|
-
].filter(
|
|
134
|
+
].filter(isNotFalsy),
|
|
168
135
|
...(role !== 'section' && {
|
|
169
136
|
id,
|
|
170
137
|
scrollTo,
|
|
171
138
|
selection,
|
|
172
139
|
// TODO(wittjosiah): Autofocus based on layout is racy.
|
|
173
|
-
autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
|
|
140
|
+
// autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
|
|
174
141
|
moveToEndOfLine: true,
|
|
175
142
|
}),
|
|
176
143
|
}),
|
|
@@ -178,6 +145,7 @@ export const MarkdownEditor = ({
|
|
|
178
145
|
);
|
|
179
146
|
|
|
180
147
|
useTest(editorView);
|
|
148
|
+
useSelectCurrentThread(editorView, id);
|
|
181
149
|
|
|
182
150
|
// Toolbar handler.
|
|
183
151
|
const handleToolbarAction = useActionHandler(editorView);
|
|
@@ -201,29 +169,27 @@ export const MarkdownEditor = ({
|
|
|
201
169
|
return (
|
|
202
170
|
<div
|
|
203
171
|
role='none'
|
|
204
|
-
// TODO(burdon): Move role logic out of here (see sheet, table, sketch, etc.)
|
|
205
172
|
{...(role === 'section'
|
|
206
173
|
? { className: 'flex flex-col' }
|
|
207
174
|
: {
|
|
208
|
-
className: 'contents
|
|
209
|
-
|
|
175
|
+
className: 'contents',
|
|
176
|
+
// TODO(wittjosiah): Factor out to `useAttendableAttributes`?
|
|
177
|
+
...(hasAttention && { 'aria-current': 'location' }),
|
|
178
|
+
...attendableAttributes,
|
|
210
179
|
})}
|
|
211
180
|
>
|
|
212
181
|
{toolbar && (
|
|
213
|
-
<div role='none' className=
|
|
182
|
+
<div role='none' className='flex shrink-0 justify-center overflow-x-auto attention-surface'>
|
|
214
183
|
<Toolbar.Root
|
|
215
184
|
classNames={
|
|
216
185
|
role === 'section'
|
|
217
186
|
? [
|
|
218
187
|
textBlockWidth,
|
|
219
188
|
'z-[2] group-focus-within/section:visible',
|
|
220
|
-
!
|
|
189
|
+
!hasAttention && 'invisible',
|
|
221
190
|
sectionToolbarLayout,
|
|
222
191
|
]
|
|
223
|
-
: [
|
|
224
|
-
textBlockWidth,
|
|
225
|
-
'group-focus-within/editor:border-separator group-[[aria-current]]/editor:border-separator',
|
|
226
|
-
]
|
|
192
|
+
: [textBlockWidth]
|
|
227
193
|
}
|
|
228
194
|
state={formattingState && { ...formattingState, ...commentsState }}
|
|
229
195
|
onAction={handleAction}
|
|
@@ -247,8 +213,8 @@ export const MarkdownEditor = ({
|
|
|
247
213
|
: mx(
|
|
248
214
|
'flex is-full bs-full overflow-hidden',
|
|
249
215
|
focusRing,
|
|
250
|
-
|
|
251
|
-
'
|
|
216
|
+
'focus-visible:ring-inset attention-surface',
|
|
217
|
+
'p-0.5', // TODO(burdon): Handle padding for focusRing consistently.
|
|
252
218
|
'data-[toolbar=disabled]:pbs-2 data-[toolbar=disabled]:row-span-2',
|
|
253
219
|
)
|
|
254
220
|
}
|
|
@@ -268,5 +234,3 @@ const useTest = (view?: EditorView) => {
|
|
|
268
234
|
}
|
|
269
235
|
}, [view]);
|
|
270
236
|
};
|
|
271
|
-
|
|
272
|
-
export default MarkdownEditor;
|