@dxos/plugin-markdown 0.6.8-main.046e6cf
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/LICENSE +8 -0
- package/README.md +15 -0
- package/dist/lib/browser/DocumentCard-CDWDPILF.mjs +11 -0
- package/dist/lib/browser/DocumentCard-CDWDPILF.mjs.map +7 -0
- package/dist/lib/browser/DocumentEditor-VMHFHWOQ.mjs +11 -0
- package/dist/lib/browser/DocumentEditor-VMHFHWOQ.mjs.map +7 -0
- package/dist/lib/browser/MarkdownEditor-KYUQ45PC.mjs +10 -0
- package/dist/lib/browser/MarkdownEditor-KYUQ45PC.mjs.map +7 -0
- package/dist/lib/browser/chunk-4GGD6YJO.mjs +19 -0
- package/dist/lib/browser/chunk-4GGD6YJO.mjs.map +7 -0
- package/dist/lib/browser/chunk-6ZL2GJCQ.mjs +177 -0
- package/dist/lib/browser/chunk-6ZL2GJCQ.mjs.map +7 -0
- package/dist/lib/browser/chunk-DX3K37SM.mjs +86 -0
- package/dist/lib/browser/chunk-DX3K37SM.mjs.map +7 -0
- package/dist/lib/browser/chunk-MDX3MAMP.mjs +119 -0
- package/dist/lib/browser/chunk-MDX3MAMP.mjs.map +7 -0
- package/dist/lib/browser/chunk-PZDW7KVZ.mjs +172 -0
- package/dist/lib/browser/chunk-PZDW7KVZ.mjs.map +7 -0
- package/dist/lib/browser/chunk-RETREORA.mjs +39 -0
- package/dist/lib/browser/chunk-RETREORA.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +506 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/meta.mjs +9 -0
- package/dist/lib/browser/meta.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +12 -0
- package/dist/lib/browser/types/index.mjs.map +7 -0
- package/dist/lib/node/DocumentCard-SOTFILJY.cjs +32 -0
- package/dist/lib/node/DocumentCard-SOTFILJY.cjs.map +7 -0
- package/dist/lib/node/DocumentEditor-CUKHGS5R.cjs +29 -0
- package/dist/lib/node/DocumentEditor-CUKHGS5R.cjs.map +7 -0
- package/dist/lib/node/MarkdownEditor-GGFCD26C.cjs +31 -0
- package/dist/lib/node/MarkdownEditor-GGFCD26C.cjs.map +7 -0
- package/dist/lib/node/chunk-FW5O2I25.cjs +114 -0
- package/dist/lib/node/chunk-FW5O2I25.cjs.map +7 -0
- package/dist/lib/node/chunk-IUQ2SKGY.cjs +202 -0
- package/dist/lib/node/chunk-IUQ2SKGY.cjs.map +7 -0
- package/dist/lib/node/chunk-TGMR2CKU.cjs +52 -0
- package/dist/lib/node/chunk-TGMR2CKU.cjs.map +7 -0
- package/dist/lib/node/chunk-TO3FCKT7.cjs +202 -0
- package/dist/lib/node/chunk-TO3FCKT7.cjs.map +7 -0
- package/dist/lib/node/chunk-TZDYK4MV.cjs +151 -0
- package/dist/lib/node/chunk-TZDYK4MV.cjs.map +7 -0
- package/dist/lib/node/chunk-VYLYUDDI.cjs +58 -0
- package/dist/lib/node/chunk-VYLYUDDI.cjs.map +7 -0
- package/dist/lib/node/index.cjs +517 -0
- package/dist/lib/node/index.cjs.map +7 -0
- package/dist/lib/node/meta.cjs +30 -0
- package/dist/lib/node/meta.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -0
- package/dist/lib/node/types/index.cjs +34 -0
- package/dist/lib/node/types/index.cjs.map +7 -0
- package/dist/types/src/MarkdownPlugin.d.ts +4 -0
- package/dist/types/src/MarkdownPlugin.d.ts.map +1 -0
- package/dist/types/src/components/DocumentCard.d.ts +16 -0
- package/dist/types/src/components/DocumentCard.d.ts.map +1 -0
- package/dist/types/src/components/DocumentEditor.d.ts +14 -0
- package/dist/types/src/components/DocumentEditor.d.ts.map +1 -0
- package/dist/types/src/components/HeadingMenu.d.ts +13 -0
- package/dist/types/src/components/HeadingMenu.d.ts.map +1 -0
- package/dist/types/src/components/Layout.d.ts +6 -0
- package/dist/types/src/components/Layout.d.ts.map +1 -0
- package/dist/types/src/components/MarkdownEditor.d.ts +19 -0
- package/dist/types/src/components/MarkdownEditor.d.ts.map +1 -0
- package/dist/types/src/components/MarkdownEditor.stories.d.ts +27 -0
- package/dist/types/src/components/MarkdownEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/MarkdownSettings.d.ts +6 -0
- package/dist/types/src/components/MarkdownSettings.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar.stories.d.ts +9 -0
- package/dist/types/src/components/Toolbar.stories.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +13 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/extensions.d.ts +20 -0
- package/dist/types/src/extensions.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +6 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +15 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +29 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/document.d.ts +97 -0
- package/dist/types/src/types/document.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +3 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/types.d.ts +42 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +11 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/package.json +94 -0
- package/src/MarkdownPlugin.tsx +341 -0
- package/src/components/DocumentCard.tsx +107 -0
- package/src/components/DocumentEditor.tsx +140 -0
- package/src/components/HeadingMenu.tsx +46 -0
- package/src/components/Layout.tsx +27 -0
- package/src/components/MarkdownEditor.stories.tsx +55 -0
- package/src/components/MarkdownEditor.tsx +241 -0
- package/src/components/MarkdownSettings.tsx +105 -0
- package/src/components/Toolbar.stories.tsx +118 -0
- package/src/components/index.ts +21 -0
- package/src/extensions.tsx +175 -0
- package/src/index.ts +11 -0
- package/src/meta.tsx +19 -0
- package/src/translations.ts +36 -0
- package/src/types/document.ts +17 -0
- package/src/types/index.ts +6 -0
- package/src/types/types.ts +75 -0
- package/src/util.tsx +48 -0
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/plugin-markdown",
|
|
3
|
+
"version": "0.6.8-main.046e6cf",
|
|
4
|
+
"description": "DXOS Surface plugin for interacting with Markdown",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"browser": "./dist/lib/browser/index.mjs",
|
|
12
|
+
"node": {
|
|
13
|
+
"default": "./dist/lib/node/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"types": "./dist/types/src/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./meta": {
|
|
18
|
+
"browser": "./dist/lib/browser/meta.mjs",
|
|
19
|
+
"node": {
|
|
20
|
+
"default": "./dist/lib/node/meta.cjs"
|
|
21
|
+
},
|
|
22
|
+
"types": "./dist/types/src/meta.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./types": {
|
|
25
|
+
"browser": "./dist/lib/browser/types/index.mjs",
|
|
26
|
+
"node": {
|
|
27
|
+
"default": "./dist/lib/node/types/index.cjs"
|
|
28
|
+
},
|
|
29
|
+
"types": "./dist/types/src/types/index.d.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"types": "dist/types/src/index.d.ts",
|
|
33
|
+
"typesVersions": {
|
|
34
|
+
"*": {
|
|
35
|
+
"meta": [
|
|
36
|
+
"dist/types/src/meta.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"types": [
|
|
39
|
+
"dist/types/src/types/index.d.ts"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"src"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@codemirror/view": "^6.29.1",
|
|
49
|
+
"@preact/signals-core": "^1.6.0",
|
|
50
|
+
"@dxos/async": "0.6.8-main.046e6cf",
|
|
51
|
+
"@dxos/echo-schema": "0.6.8-main.046e6cf",
|
|
52
|
+
"@dxos/keys": "0.6.8-main.046e6cf",
|
|
53
|
+
"@dxos/invariant": "0.6.8-main.046e6cf",
|
|
54
|
+
"@dxos/local-storage": "0.6.8-main.046e6cf",
|
|
55
|
+
"@dxos/plugin-attention": "0.6.8-main.046e6cf",
|
|
56
|
+
"@dxos/log": "0.6.8-main.046e6cf",
|
|
57
|
+
"@dxos/plugin-client": "0.6.8-main.046e6cf",
|
|
58
|
+
"@dxos/plugin-graph": "0.6.8-main.046e6cf",
|
|
59
|
+
"@dxos/plugin-settings": "0.6.8-main.046e6cf",
|
|
60
|
+
"@dxos/plugin-theme": "0.6.8-main.046e6cf",
|
|
61
|
+
"@dxos/plugin-space": "0.6.8-main.046e6cf",
|
|
62
|
+
"@dxos/react-async": "0.6.8-main.046e6cf",
|
|
63
|
+
"@dxos/react-client": "0.6.8-main.046e6cf",
|
|
64
|
+
"@dxos/react-ui-card": "0.6.8-main.046e6cf",
|
|
65
|
+
"@dxos/app-framework": "0.6.8-main.046e6cf",
|
|
66
|
+
"@dxos/react-ui-deck": "0.6.8-main.046e6cf",
|
|
67
|
+
"@dxos/react-ui-editor": "0.6.8-main.046e6cf",
|
|
68
|
+
"@dxos/react-ui-mosaic": "0.6.8-main.046e6cf",
|
|
69
|
+
"@dxos/util": "0.6.8-main.046e6cf",
|
|
70
|
+
"@dxos/react-ui-stack": "0.6.8-main.046e6cf"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@phosphor-icons/react": "^2.1.5",
|
|
74
|
+
"@types/react": "~18.2.0",
|
|
75
|
+
"@types/react-dom": "~18.2.0",
|
|
76
|
+
"react": "~18.2.0",
|
|
77
|
+
"react-dom": "~18.2.0",
|
|
78
|
+
"vite": "^5.3.4",
|
|
79
|
+
"@dxos/random": "0.6.8-main.046e6cf",
|
|
80
|
+
"@dxos/react-ui": "0.6.8-main.046e6cf",
|
|
81
|
+
"@dxos/react-ui-theme": "0.6.8-main.046e6cf",
|
|
82
|
+
"@dxos/storybook-utils": "0.6.8-main.046e6cf"
|
|
83
|
+
},
|
|
84
|
+
"optionalDependencies": {
|
|
85
|
+
"@phosphor-icons/react": "^2.1.5",
|
|
86
|
+
"react": "^18.0.0",
|
|
87
|
+
"react-dom": "^18.0.0",
|
|
88
|
+
"@dxos/react-ui": "0.6.8-main.046e6cf",
|
|
89
|
+
"@dxos/react-ui-theme": "0.6.8-main.046e6cf"
|
|
90
|
+
},
|
|
91
|
+
"publishConfig": {
|
|
92
|
+
"access": "public"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type IconProps, TextAa } from '@phosphor-icons/react';
|
|
6
|
+
import React, { type Ref } from 'react';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isObject,
|
|
10
|
+
parseIntentPlugin,
|
|
11
|
+
resolvePlugin,
|
|
12
|
+
LayoutAction,
|
|
13
|
+
type LayoutCoordinate,
|
|
14
|
+
NavigationAction,
|
|
15
|
+
type PluginDefinition,
|
|
16
|
+
} from '@dxos/app-framework';
|
|
17
|
+
import { create } from '@dxos/echo-schema';
|
|
18
|
+
import { LocalStorageStore } from '@dxos/local-storage';
|
|
19
|
+
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
20
|
+
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
21
|
+
import { SpaceAction } from '@dxos/plugin-space';
|
|
22
|
+
import { CollectionType } from '@dxos/plugin-space/types';
|
|
23
|
+
import { fullyQualifiedId, isSpace, loadObjectReferences } from '@dxos/react-client/echo';
|
|
24
|
+
import {
|
|
25
|
+
type EditorInputMode,
|
|
26
|
+
type EditorViewMode,
|
|
27
|
+
EditorViewModes,
|
|
28
|
+
translations as editorTranslations,
|
|
29
|
+
} from '@dxos/react-ui-editor';
|
|
30
|
+
import { isTileComponentProps } from '@dxos/react-ui-mosaic';
|
|
31
|
+
|
|
32
|
+
import { type DocumentItemProps, DocumentCard, DocumentEditor, MarkdownEditor, MarkdownSettings } from './components';
|
|
33
|
+
import meta, { MARKDOWN_PLUGIN } from './meta';
|
|
34
|
+
import translations from './translations';
|
|
35
|
+
import { DocumentType, TextType } from './types';
|
|
36
|
+
import {
|
|
37
|
+
type MarkdownPluginProvides,
|
|
38
|
+
type MarkdownSettingsProps,
|
|
39
|
+
MarkdownAction,
|
|
40
|
+
type MarkdownPluginState,
|
|
41
|
+
} from './types';
|
|
42
|
+
import { markdownExtensionPlugins, serializer } from './util';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Checks if an object conforms to the interface needed to render an editor.
|
|
46
|
+
*/
|
|
47
|
+
const isEditorModel = (data: any): data is { id: string; text: string } => {
|
|
48
|
+
return (
|
|
49
|
+
data &&
|
|
50
|
+
typeof data === 'object' &&
|
|
51
|
+
'id' in data &&
|
|
52
|
+
typeof data.id === 'string' &&
|
|
53
|
+
'text' in data &&
|
|
54
|
+
typeof data.text === 'string'
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
|
|
59
|
+
const settings = new LocalStorageStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN, {
|
|
60
|
+
defaultViewMode: 'preview',
|
|
61
|
+
toolbar: true,
|
|
62
|
+
experimental: false,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const state = new LocalStorageStore<MarkdownPluginState>(MARKDOWN_PLUGIN, { extensionProviders: [], viewMode: {} });
|
|
66
|
+
|
|
67
|
+
const getViewMode = (id?: string) => {
|
|
68
|
+
return (id && state.values.viewMode[id]) || settings.values.defaultViewMode;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const setViewMode = (id: string, nextViewMode: EditorViewMode) => {
|
|
72
|
+
state.values.viewMode[id] = nextViewMode;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
meta,
|
|
77
|
+
ready: async (plugins) => {
|
|
78
|
+
settings
|
|
79
|
+
.prop({
|
|
80
|
+
key: 'defaultViewMode',
|
|
81
|
+
storageKey: 'default-view-mode',
|
|
82
|
+
type: LocalStorageStore.enum<EditorViewMode>(),
|
|
83
|
+
})
|
|
84
|
+
.prop({
|
|
85
|
+
key: 'editorInputMode',
|
|
86
|
+
storageKey: 'editor-mode',
|
|
87
|
+
type: LocalStorageStore.enum<EditorInputMode>({ allowUndefined: true }),
|
|
88
|
+
})
|
|
89
|
+
.prop({ key: 'toolbar', type: LocalStorageStore.bool({ allowUndefined: true }) })
|
|
90
|
+
.prop({ key: 'experimental', type: LocalStorageStore.bool({ allowUndefined: true }) })
|
|
91
|
+
.prop({ key: 'debug', type: LocalStorageStore.bool({ allowUndefined: true }) })
|
|
92
|
+
.prop({ key: 'typewriter', type: LocalStorageStore.string({ allowUndefined: true }) })
|
|
93
|
+
.prop({ key: 'numberedHeadings', type: LocalStorageStore.bool({ allowUndefined: true }) })
|
|
94
|
+
.prop({ key: 'folding', type: LocalStorageStore.bool({ allowUndefined: true }) });
|
|
95
|
+
|
|
96
|
+
state.prop({
|
|
97
|
+
key: 'viewMode',
|
|
98
|
+
storageKey: 'view-mode',
|
|
99
|
+
type: LocalStorageStore.json<{ [key: string]: EditorViewMode }>(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
markdownExtensionPlugins(plugins).forEach((plugin) => {
|
|
103
|
+
const { extensions } = plugin.provides.markdown;
|
|
104
|
+
state.values.extensionProviders.push(extensions);
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
provides: {
|
|
108
|
+
settings: settings.values,
|
|
109
|
+
metadata: {
|
|
110
|
+
records: {
|
|
111
|
+
[DocumentType.typename]: {
|
|
112
|
+
label: (object: any) => (object instanceof DocumentType ? object.name ?? object.fallbackName : undefined),
|
|
113
|
+
placeholder: ['document title placeholder', { ns: MARKDOWN_PLUGIN }],
|
|
114
|
+
icon: (props: IconProps) => <TextAa {...props} />,
|
|
115
|
+
iconSymbol: 'ph--text-aa--regular',
|
|
116
|
+
graphProps: {
|
|
117
|
+
managesAutofocus: true,
|
|
118
|
+
},
|
|
119
|
+
// TODO(wittjosiah): Move out of metadata.
|
|
120
|
+
loadReferences: (doc: DocumentType) => loadObjectReferences(doc, (doc) => [doc.content, ...doc.threads]),
|
|
121
|
+
serializer,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
translations: [...translations, ...editorTranslations],
|
|
126
|
+
echo: {
|
|
127
|
+
schema: [DocumentType, TextType],
|
|
128
|
+
},
|
|
129
|
+
graph: {
|
|
130
|
+
builder: (plugins) => {
|
|
131
|
+
const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
|
|
132
|
+
const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
|
|
133
|
+
if (!client || !dispatch) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return createExtension({
|
|
138
|
+
id: MarkdownAction.CREATE,
|
|
139
|
+
filter: (node): node is ActionGroup => isActionGroup(node) && node.id.startsWith(SpaceAction.ADD_OBJECT),
|
|
140
|
+
actions: ({ node }) => {
|
|
141
|
+
const id = node.id.split('/').at(-1);
|
|
142
|
+
const [spaceId, objectId] = id?.split(':') ?? [];
|
|
143
|
+
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
144
|
+
const object = objectId && space?.db.getObjectById(objectId);
|
|
145
|
+
const target = objectId ? object : space;
|
|
146
|
+
if (!target) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return [
|
|
151
|
+
{
|
|
152
|
+
id: `${MARKDOWN_PLUGIN}/create/${node.id}`,
|
|
153
|
+
data: async () => {
|
|
154
|
+
await dispatch([
|
|
155
|
+
{ plugin: MARKDOWN_PLUGIN, action: MarkdownAction.CREATE },
|
|
156
|
+
{ action: SpaceAction.ADD_OBJECT, data: { target } },
|
|
157
|
+
{ action: NavigationAction.OPEN },
|
|
158
|
+
]);
|
|
159
|
+
},
|
|
160
|
+
properties: {
|
|
161
|
+
label: ['create document label', { ns: MARKDOWN_PLUGIN }],
|
|
162
|
+
icon: (props: IconProps) => <TextAa {...props} />,
|
|
163
|
+
iconSymbol: 'ph--text-aa--regular',
|
|
164
|
+
testId: 'markdownPlugin.createObject',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
serializer: (plugins) => {
|
|
172
|
+
const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
|
|
173
|
+
if (!dispatch) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
inputType: DocumentType.typename,
|
|
179
|
+
outputType: 'text/markdown',
|
|
180
|
+
// Reconcile with metadata serializers.
|
|
181
|
+
serialize: async (node) => {
|
|
182
|
+
const doc = node.data;
|
|
183
|
+
const content = await loadObjectReferences(doc, (doc) => doc.content);
|
|
184
|
+
return {
|
|
185
|
+
name:
|
|
186
|
+
doc.name ||
|
|
187
|
+
doc.fallbackName ||
|
|
188
|
+
translations[0]['en-US'][MARKDOWN_PLUGIN]['document title placeholder'],
|
|
189
|
+
data: content.content,
|
|
190
|
+
type: 'text/markdown',
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
deserialize: async (data, ancestors) => {
|
|
194
|
+
const space = ancestors.find(isSpace);
|
|
195
|
+
const target =
|
|
196
|
+
ancestors.findLast((ancestor) => ancestor instanceof CollectionType) ??
|
|
197
|
+
space?.properties[CollectionType.typename];
|
|
198
|
+
if (!space || !target) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = await dispatch([
|
|
203
|
+
{
|
|
204
|
+
plugin: MARKDOWN_PLUGIN,
|
|
205
|
+
action: MarkdownAction.CREATE,
|
|
206
|
+
data: { name: data.name, content: data.data },
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
action: SpaceAction.ADD_OBJECT,
|
|
210
|
+
data: { target },
|
|
211
|
+
},
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
return result?.data.object;
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
stack: {
|
|
221
|
+
creators: [
|
|
222
|
+
{
|
|
223
|
+
id: 'create-stack-section-doc',
|
|
224
|
+
testId: 'markdownPlugin.createSection',
|
|
225
|
+
type: ['plugin name', { ns: MARKDOWN_PLUGIN }],
|
|
226
|
+
label: ['create stack section label', { ns: MARKDOWN_PLUGIN }],
|
|
227
|
+
icon: (props: any) => <TextAa {...props} />,
|
|
228
|
+
intent: {
|
|
229
|
+
plugin: MARKDOWN_PLUGIN,
|
|
230
|
+
action: MarkdownAction.CREATE,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
surface: {
|
|
236
|
+
component: ({ data, role, ...props }, forwardedRef) => {
|
|
237
|
+
const doc =
|
|
238
|
+
data.active instanceof DocumentType
|
|
239
|
+
? data.active
|
|
240
|
+
: data.object instanceof DocumentType
|
|
241
|
+
? data.object
|
|
242
|
+
: undefined;
|
|
243
|
+
|
|
244
|
+
switch (role) {
|
|
245
|
+
case 'section':
|
|
246
|
+
case 'article': {
|
|
247
|
+
if (doc && doc.content) {
|
|
248
|
+
return (
|
|
249
|
+
<DocumentEditor
|
|
250
|
+
role={role}
|
|
251
|
+
coordinate={data.coordinate as LayoutCoordinate}
|
|
252
|
+
document={doc}
|
|
253
|
+
extensionProviders={state.values.extensionProviders}
|
|
254
|
+
settings={settings.values}
|
|
255
|
+
viewMode={getViewMode(fullyQualifiedId(doc))}
|
|
256
|
+
onViewModeChange={setViewMode}
|
|
257
|
+
scrollPastEnd
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
} else if (isEditorModel(data.object)) {
|
|
261
|
+
return (
|
|
262
|
+
<MarkdownEditor
|
|
263
|
+
id={data.object.id}
|
|
264
|
+
role={role}
|
|
265
|
+
coordinate={data.coordinate as LayoutCoordinate}
|
|
266
|
+
initialValue={data.object.text}
|
|
267
|
+
extensionProviders={state.values.extensionProviders}
|
|
268
|
+
inputMode={settings.values.editorInputMode}
|
|
269
|
+
toolbar={settings.values.toolbar}
|
|
270
|
+
viewMode={getViewMode(data.object.id)}
|
|
271
|
+
onViewModeChange={setViewMode}
|
|
272
|
+
scrollPastEnd
|
|
273
|
+
/>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
case 'card': {
|
|
280
|
+
if (
|
|
281
|
+
isObject(data.content) &&
|
|
282
|
+
typeof data.content.id === 'string' &&
|
|
283
|
+
data.content.object instanceof DocumentType
|
|
284
|
+
) {
|
|
285
|
+
// isTileComponentProps is a type guard for these props.
|
|
286
|
+
// `props` will not pass this guard without transforming `data` into `item`.
|
|
287
|
+
const cardProps = {
|
|
288
|
+
...props,
|
|
289
|
+
item: {
|
|
290
|
+
id: data.content.id,
|
|
291
|
+
object: data.content.object,
|
|
292
|
+
color: typeof data.content.color === 'string' ? data.content.color : undefined,
|
|
293
|
+
} as DocumentItemProps,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
return isTileComponentProps(cardProps) ? (
|
|
297
|
+
<DocumentCard {...cardProps} settings={settings.values} ref={forwardedRef as Ref<HTMLDivElement>} />
|
|
298
|
+
) : null;
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
case 'settings': {
|
|
304
|
+
return data.plugin === meta.id ? <MarkdownSettings settings={settings.values} /> : null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return null;
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
intent: {
|
|
312
|
+
resolver: ({ action, data }) => {
|
|
313
|
+
switch (action) {
|
|
314
|
+
case MarkdownAction.CREATE: {
|
|
315
|
+
const doc = create(DocumentType, {
|
|
316
|
+
name: data?.name,
|
|
317
|
+
content: create(TextType, { content: data?.content ?? '' }),
|
|
318
|
+
threads: [],
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
data: doc,
|
|
323
|
+
intents: [[{ action: LayoutAction.SCROLL_INTO_VIEW, data: { id: doc.id } }]],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
case MarkdownAction.SET_VIEW_MODE: {
|
|
328
|
+
const { id, viewMode } = data ?? {};
|
|
329
|
+
if (typeof id === 'string' && EditorViewModes.includes(viewMode)) {
|
|
330
|
+
state.values.viewMode[id] = viewMode;
|
|
331
|
+
return { data: true };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { forwardRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { createDocAccessor } from '@dxos/react-client/echo';
|
|
8
|
+
import { DropdownMenu, Input, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Card } from '@dxos/react-ui-card';
|
|
10
|
+
import {
|
|
11
|
+
createBasicExtensions,
|
|
12
|
+
createDataExtensions,
|
|
13
|
+
createThemeExtensions,
|
|
14
|
+
useTextEditor,
|
|
15
|
+
} from '@dxos/react-ui-editor';
|
|
16
|
+
import type { MosaicTileComponent } from '@dxos/react-ui-mosaic';
|
|
17
|
+
import { focusRing, mx } from '@dxos/react-ui-theme';
|
|
18
|
+
|
|
19
|
+
import { createBaseExtensions } from '../extensions';
|
|
20
|
+
import { MARKDOWN_PLUGIN } from '../meta';
|
|
21
|
+
import { type DocumentType, type MarkdownSettingsProps } from '../types';
|
|
22
|
+
|
|
23
|
+
export type DocumentItemProps = {
|
|
24
|
+
id: string;
|
|
25
|
+
object: DocumentType;
|
|
26
|
+
color?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type DocumentCardProps = {
|
|
30
|
+
settings: MarkdownSettingsProps;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated
|
|
35
|
+
*/
|
|
36
|
+
// TODO(wittjosiah): Unify with DocumentEditor.
|
|
37
|
+
export const DocumentCard: MosaicTileComponent<DocumentItemProps, HTMLDivElement, DocumentCardProps> = forwardRef(
|
|
38
|
+
(
|
|
39
|
+
{
|
|
40
|
+
classNames,
|
|
41
|
+
isDragging,
|
|
42
|
+
draggableStyle,
|
|
43
|
+
draggableProps,
|
|
44
|
+
item: { id, object, color },
|
|
45
|
+
grow,
|
|
46
|
+
settings,
|
|
47
|
+
onSelect,
|
|
48
|
+
onAction,
|
|
49
|
+
},
|
|
50
|
+
forwardRef,
|
|
51
|
+
) => {
|
|
52
|
+
const { t } = useTranslation(MARKDOWN_PLUGIN);
|
|
53
|
+
const { themeMode } = useThemeContext();
|
|
54
|
+
const { parentRef, focusAttributes } = useTextEditor(
|
|
55
|
+
() => ({
|
|
56
|
+
initialValue: object.content?.content,
|
|
57
|
+
extensions: [
|
|
58
|
+
createBasicExtensions({ placeholder: t('editor placeholder') }),
|
|
59
|
+
createThemeExtensions({ themeMode }),
|
|
60
|
+
createDataExtensions({
|
|
61
|
+
id: object.id,
|
|
62
|
+
text: object.content && createDocAccessor(object.content, ['content']),
|
|
63
|
+
}),
|
|
64
|
+
createBaseExtensions({
|
|
65
|
+
document: object,
|
|
66
|
+
debug: settings.debug,
|
|
67
|
+
experimental: settings.experimental,
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
}),
|
|
71
|
+
[object, object.content, themeMode],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div role='none' ref={forwardRef} className='flex w-full' style={draggableStyle}>
|
|
76
|
+
<Card.Root classNames={mx('w-full snap-center', color, isDragging && 'opacity-20', classNames)} grow={grow}>
|
|
77
|
+
<Card.Header onDoubleClick={() => onSelect?.()}>
|
|
78
|
+
<Card.DragHandle {...draggableProps} />
|
|
79
|
+
<Input.Root>
|
|
80
|
+
<Input.TextInput
|
|
81
|
+
variant='subdued'
|
|
82
|
+
classNames='p-0'
|
|
83
|
+
placeholder={t('document title placeholder')}
|
|
84
|
+
value={object.name}
|
|
85
|
+
onChange={(event) => (object.name = event.target.value)}
|
|
86
|
+
/>
|
|
87
|
+
</Input.Root>
|
|
88
|
+
<Card.Menu>
|
|
89
|
+
{/* TODO(burdon): Handle events/intents? */}
|
|
90
|
+
<DropdownMenu.Item onClick={() => onAction?.({ id, action: 'delete' })}>
|
|
91
|
+
<span className='grow'>Delete</span>
|
|
92
|
+
</DropdownMenu.Item>
|
|
93
|
+
<DropdownMenu.Item onClick={() => onAction?.({ id, action: 'set-color' })}>
|
|
94
|
+
<span className='grow'>Change color</span>
|
|
95
|
+
</DropdownMenu.Item>
|
|
96
|
+
</Card.Menu>
|
|
97
|
+
</Card.Header>
|
|
98
|
+
<Card.Body>
|
|
99
|
+
<div {...focusAttributes} ref={parentRef} className={mx(focusRing, 'rounded-sm h-full p-1 text-sm')} />
|
|
100
|
+
</Card.Body>
|
|
101
|
+
</Card.Root>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
export default DocumentCard;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { EditorView } from '@codemirror/view';
|
|
6
|
+
import React, { useEffect, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { useResolvePlugin, parseFileManagerPlugin, useIntentDispatcher } from '@dxos/app-framework';
|
|
9
|
+
import { createDocAccessor, fullyQualifiedId, getSpace } from '@dxos/react-client/echo';
|
|
10
|
+
import { useIdentity } from '@dxos/react-client/halo';
|
|
11
|
+
import {
|
|
12
|
+
createDataExtensions,
|
|
13
|
+
listener,
|
|
14
|
+
localStorageStateStoreAdapter,
|
|
15
|
+
state,
|
|
16
|
+
type Extension,
|
|
17
|
+
} from '@dxos/react-ui-editor';
|
|
18
|
+
|
|
19
|
+
import MarkdownEditor, { type MarkdownEditorProps } from './MarkdownEditor';
|
|
20
|
+
import { createBaseExtensions } from '../extensions';
|
|
21
|
+
import { type DocumentType, type MarkdownPluginState, type MarkdownSettingsProps } from '../types';
|
|
22
|
+
import { getFallbackName, setFallbackName } from '../util';
|
|
23
|
+
|
|
24
|
+
type DocumentEditorProps = {
|
|
25
|
+
document: DocumentType;
|
|
26
|
+
settings: MarkdownSettingsProps;
|
|
27
|
+
} & Omit<MarkdownEditorProps, 'id' | 'inputMode' | 'toolbar' | 'extensions'> &
|
|
28
|
+
Pick<MarkdownPluginState, 'extensionProviders'>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Editor for a `DocumentType`.
|
|
32
|
+
*/
|
|
33
|
+
const DocumentEditor = ({
|
|
34
|
+
document: doc,
|
|
35
|
+
extensionProviders = [],
|
|
36
|
+
viewMode,
|
|
37
|
+
settings,
|
|
38
|
+
...props
|
|
39
|
+
}: DocumentEditorProps) => {
|
|
40
|
+
const space = getSpace(doc);
|
|
41
|
+
const identity = useIdentity();
|
|
42
|
+
const dispatch = useIntentDispatcher();
|
|
43
|
+
|
|
44
|
+
const baseExtensions = useMemo(() => {
|
|
45
|
+
// TODO(wittjosiah): Autocomplete is not working and this query is causing performance issues.
|
|
46
|
+
// const query = space?.db.query(Filter.schema(DocumentType));
|
|
47
|
+
// query?.subscribe();
|
|
48
|
+
return createBaseExtensions({
|
|
49
|
+
viewMode,
|
|
50
|
+
settings,
|
|
51
|
+
document: doc,
|
|
52
|
+
dispatch,
|
|
53
|
+
// query,
|
|
54
|
+
});
|
|
55
|
+
}, [doc, viewMode, dispatch, settings, settings.folding, settings.numberedHeadings]);
|
|
56
|
+
|
|
57
|
+
const providerExtensions = useMemo(
|
|
58
|
+
() =>
|
|
59
|
+
extensionProviders.reduce((acc: Extension[], provider) => {
|
|
60
|
+
const provided = typeof provider === 'function' ? provider({ document: doc }) : provider;
|
|
61
|
+
acc.push(...provided);
|
|
62
|
+
return acc;
|
|
63
|
+
}, []),
|
|
64
|
+
[extensionProviders],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const extensions = useMemo(
|
|
68
|
+
() => [
|
|
69
|
+
// NOTE: Data extensions must be first so that automerge is updated before other extensions compute their state.
|
|
70
|
+
createDataExtensions({
|
|
71
|
+
id: doc.id,
|
|
72
|
+
text: doc.content && createDocAccessor(doc.content, ['content']),
|
|
73
|
+
space,
|
|
74
|
+
identity,
|
|
75
|
+
}),
|
|
76
|
+
state(localStorageStateStoreAdapter),
|
|
77
|
+
listener({
|
|
78
|
+
onChange: (text) => {
|
|
79
|
+
setFallbackName(doc, text);
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
providerExtensions,
|
|
83
|
+
baseExtensions,
|
|
84
|
+
],
|
|
85
|
+
[doc, doc.content, space, baseExtensions, providerExtensions, identity],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const initialValue = useMemo(() => doc.content?.content, [doc.content]);
|
|
89
|
+
|
|
90
|
+
// Migrate gradually to `fallbackName`.
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!doc.fallbackName && doc.content?.content) {
|
|
93
|
+
doc.fallbackName = getFallbackName(doc.content.content);
|
|
94
|
+
}
|
|
95
|
+
}, [doc, doc.content]);
|
|
96
|
+
|
|
97
|
+
const { scrollTo /*, selection */ } = useMemo(() => {
|
|
98
|
+
const { scrollTo, selection } = localStorageStateStoreAdapter.getState(doc.id) ?? {};
|
|
99
|
+
return {
|
|
100
|
+
scrollTo: scrollTo?.from ? EditorView.scrollIntoView(scrollTo.from, { y: 'start', yMargin: 0 }) : undefined,
|
|
101
|
+
selection,
|
|
102
|
+
};
|
|
103
|
+
}, [doc]);
|
|
104
|
+
|
|
105
|
+
const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
|
|
106
|
+
|
|
107
|
+
const handleFileUpload = useMemo(() => {
|
|
108
|
+
if (space === undefined) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (fileManagerPlugin?.provides.file.upload === undefined) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return async (file: File) => {
|
|
117
|
+
return fileManagerPlugin?.provides?.file?.upload?.(file, space);
|
|
118
|
+
};
|
|
119
|
+
}, [fileManagerPlugin, space]);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<MarkdownEditor
|
|
123
|
+
id={fullyQualifiedId(doc)}
|
|
124
|
+
initialValue={initialValue}
|
|
125
|
+
extensions={extensions}
|
|
126
|
+
scrollTo={scrollTo}
|
|
127
|
+
// TODO(wittjosiah): Ensure selection is within the document.
|
|
128
|
+
// selection={selection}
|
|
129
|
+
onFileUpload={handleFileUpload}
|
|
130
|
+
inputMode={settings.editorInputMode}
|
|
131
|
+
toolbar={settings.toolbar}
|
|
132
|
+
viewMode={viewMode}
|
|
133
|
+
{...props}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export default DocumentEditor;
|
|
139
|
+
|
|
140
|
+
export type DocumentEditor = typeof DocumentEditor;
|