@dxos/plugin-markdown 0.9.1-main.c7dcc2e112 → 0.10.0

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.
Files changed (215) hide show
  1. package/dist/lib/neutral/{MarkdownArticle-I4UMD7AA.mjs → MarkdownArticle-ZHBXH7YF.mjs} +3 -5
  2. package/dist/lib/neutral/{MarkdownArticle-I4UMD7AA.mjs.map → MarkdownArticle-ZHBXH7YF.mjs.map} +2 -2
  3. package/dist/lib/neutral/{MarkdownCard-OYO2HYZF.mjs → MarkdownCard-2HUXCGZP.mjs} +2 -2
  4. package/dist/lib/neutral/{MarkdownCard-OYO2HYZF.mjs.map → MarkdownCard-2HUXCGZP.mjs.map} +1 -1
  5. package/dist/lib/neutral/MarkdownPlugin.mjs +3 -7
  6. package/dist/lib/neutral/MarkdownPlugin.mjs.map +3 -3
  7. package/dist/lib/neutral/MarkdownPlugin.node.mjs +4 -2
  8. package/dist/lib/neutral/MarkdownPlugin.node.mjs.map +3 -3
  9. package/dist/lib/neutral/MarkdownPlugin.workerd.mjs +6 -17
  10. package/dist/lib/neutral/MarkdownPlugin.workerd.mjs.map +4 -4
  11. package/dist/lib/neutral/MarkdownSettings-5VMOYMND.mjs +43 -0
  12. package/dist/lib/neutral/MarkdownSettings-5VMOYMND.mjs.map +7 -0
  13. package/dist/lib/neutral/{anchor-sort-UXN4RHFX.mjs → anchor-sort-QF7UDG2A.mjs} +4 -3
  14. package/dist/lib/neutral/anchor-sort-QF7UDG2A.mjs.map +7 -0
  15. package/dist/lib/neutral/capabilities/index.mjs +9 -13
  16. package/dist/lib/neutral/capabilities/index.mjs.map +3 -3
  17. package/dist/lib/neutral/capabilities/node.mjs +5 -3
  18. package/dist/lib/neutral/capabilities/node.mjs.map +3 -3
  19. package/dist/lib/neutral/chunk-2V6A5NDQ.mjs +8 -0
  20. package/dist/lib/neutral/{operation-handler-2ARMPBGZ.mjs → chunk-ANHHOTCD.mjs} +3 -4
  21. package/dist/lib/neutral/{operation-handler-2ARMPBGZ.mjs.map → chunk-ANHHOTCD.mjs.map} +1 -1
  22. package/dist/lib/neutral/chunk-CNYFGIDS.mjs +32 -0
  23. package/dist/lib/neutral/chunk-CNYFGIDS.mjs.map +7 -0
  24. package/dist/lib/neutral/{chunk-LXNSKCPD.mjs → chunk-EV4ZOL4P.mjs} +12 -11
  25. package/dist/lib/neutral/chunk-EV4ZOL4P.mjs.map +7 -0
  26. package/dist/lib/neutral/{chunk-GUXWFDD4.mjs → chunk-OC3JD73N.mjs} +2 -2
  27. package/dist/lib/neutral/{chunk-GUXWFDD4.mjs.map → chunk-OC3JD73N.mjs.map} +1 -1
  28. package/dist/lib/neutral/chunk-YJF6XOXX.mjs +175 -0
  29. package/dist/lib/neutral/chunk-YJF6XOXX.mjs.map +7 -0
  30. package/dist/lib/neutral/chunk-YMBC62S7.mjs +14 -0
  31. package/dist/lib/neutral/chunk-YMBC62S7.mjs.map +7 -0
  32. package/dist/lib/neutral/{chunk-CRKAIDRD.mjs → chunk-ZJ3GWRUJ.mjs} +15 -14
  33. package/dist/lib/neutral/chunk-ZJ3GWRUJ.mjs.map +7 -0
  34. package/dist/lib/neutral/{comment-config-EGY7RPPW.mjs → comment-config-M5OGFULS.mjs} +6 -4
  35. package/dist/lib/neutral/comment-config-M5OGFULS.mjs.map +7 -0
  36. package/dist/lib/neutral/components/index.mjs +13 -52
  37. package/dist/lib/neutral/components/index.mjs.map +4 -4
  38. package/dist/lib/neutral/containers/index.mjs +5 -3
  39. package/dist/lib/neutral/containers/index.mjs.map +3 -3
  40. package/dist/lib/neutral/{create-XW2FQNLK.mjs → create-OQUMKL5I.mjs} +2 -2
  41. package/dist/lib/neutral/{create-markdown-KLSVQCT7.mjs → create-markdown-4O5FDA77.mjs} +2 -2
  42. package/dist/lib/neutral/{create-object-3WMEQJ2C.mjs → create-object-RSEWTCMM.mjs} +2 -6
  43. package/dist/lib/neutral/create-object-RSEWTCMM.mjs.map +7 -0
  44. package/dist/lib/neutral/hooks/index.mjs +41 -15
  45. package/dist/lib/neutral/hooks/index.mjs.map +3 -3
  46. package/dist/lib/neutral/index.mjs +10 -16
  47. package/dist/lib/neutral/meta.json +1 -1
  48. package/dist/lib/neutral/meta.mjs +1 -1
  49. package/dist/lib/neutral/{open-F7KLNKVN.mjs → open-BAZIRXII.mjs} +2 -2
  50. package/dist/lib/neutral/operation-handler-SDCXGMGK.mjs +8 -0
  51. package/dist/lib/neutral/operations/index.mjs +1 -1
  52. package/dist/lib/neutral/plugin.mjs +2 -2
  53. package/dist/lib/neutral/plugin.workerd.mjs +12 -0
  54. package/dist/lib/neutral/plugin.workerd.mjs.map +7 -0
  55. package/dist/lib/neutral/{react-surface-6LX4ZQ2H.mjs → react-surface-37GL3UVG.mjs} +4 -4
  56. package/dist/lib/neutral/react-surface-37GL3UVG.mjs.map +7 -0
  57. package/dist/lib/neutral/{scroll-to-anchor-ACND3WJU.mjs → scroll-to-anchor-I6HFE5AH.mjs} +2 -2
  58. package/dist/lib/neutral/{set-view-mode-WDC7CAEC.mjs → set-view-mode-NY5OYYIM.mjs} +2 -2
  59. package/dist/lib/neutral/skill-definition-ET7BYY47.mjs +8 -0
  60. package/dist/lib/neutral/skill-definition-ET7BYY47.mjs.map +7 -0
  61. package/dist/lib/neutral/skills/index.mjs +8 -0
  62. package/dist/lib/neutral/skills/index.mjs.map +7 -0
  63. package/dist/lib/neutral/{state-ZYT3JRWT.mjs → state-3BR7L6CW.mjs} +1 -1
  64. package/dist/lib/neutral/{state-ZYT3JRWT.mjs.map → state-3BR7L6CW.mjs.map} +2 -2
  65. package/dist/lib/neutral/types/index.mjs +1 -1
  66. package/dist/lib/neutral/update-markdown-M4PTA6VS.mjs +24 -0
  67. package/dist/lib/neutral/update-markdown-M4PTA6VS.mjs.map +7 -0
  68. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
  69. package/dist/types/src/MarkdownPlugin.node.d.ts.map +1 -1
  70. package/dist/types/src/MarkdownPlugin.workerd.d.ts.map +1 -1
  71. package/dist/types/src/capabilities/anchor-sort.d.ts +1 -2
  72. package/dist/types/src/capabilities/anchor-sort.d.ts.map +1 -1
  73. package/dist/types/src/capabilities/comment-config.d.ts +1 -2
  74. package/dist/types/src/capabilities/comment-config.d.ts.map +1 -1
  75. package/dist/types/src/capabilities/create-object.d.ts +1 -7
  76. package/dist/types/src/capabilities/create-object.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/editor-view-state.d.ts.map +1 -1
  78. package/dist/types/src/capabilities/index.d.ts +1 -3
  79. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  80. package/dist/types/src/capabilities/node.d.ts +1 -0
  81. package/dist/types/src/capabilities/node.d.ts.map +1 -1
  82. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  83. package/dist/types/src/capabilities/settings.d.ts +1 -2
  84. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
  86. package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
  87. package/dist/types/src/capabilities/state.d.ts +1 -2
  88. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  89. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts +3 -4
  90. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
  91. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts +3 -2
  92. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts.map +1 -1
  93. package/dist/types/src/components/PreviewComponent/PreviewComponent.d.ts +17 -0
  94. package/dist/types/src/components/PreviewComponent/PreviewComponent.d.ts.map +1 -0
  95. package/dist/types/src/components/PreviewComponent/index.d.ts +2 -0
  96. package/dist/types/src/components/PreviewComponent/index.d.ts.map +1 -0
  97. package/dist/types/src/components/index.d.ts +1 -2
  98. package/dist/types/src/components/index.d.ts.map +1 -1
  99. package/dist/types/src/containers/MarkdownArticle/MarkdownArticle.d.ts +2 -13
  100. package/dist/types/src/containers/MarkdownArticle/MarkdownArticle.d.ts.map +1 -1
  101. package/dist/types/src/{components → containers}/MarkdownSettings/MarkdownSettings.d.ts +1 -1
  102. package/dist/types/src/{components → containers}/MarkdownSettings/MarkdownSettings.d.ts.map +1 -1
  103. package/dist/types/src/{components → containers}/MarkdownSettings/MarkdownSettings.stories.d.ts.map +1 -1
  104. package/dist/types/src/{components → containers}/MarkdownSettings/index.d.ts.map +1 -1
  105. package/dist/types/src/containers/index.d.ts +1 -0
  106. package/dist/types/src/containers/index.d.ts.map +1 -1
  107. package/dist/types/src/hooks/useExtensions.d.ts +3 -3
  108. package/dist/types/src/hooks/useExtensions.d.ts.map +1 -1
  109. package/dist/types/src/hooks/useLinkQuery.d.ts +2 -2
  110. package/dist/types/src/hooks/useLinkQuery.d.ts.map +1 -1
  111. package/dist/types/src/index.d.ts +1 -2
  112. package/dist/types/src/index.d.ts.map +1 -1
  113. package/dist/types/src/meta.d.ts +4 -0
  114. package/dist/types/src/meta.d.ts.map +1 -1
  115. package/dist/types/src/operations/update-markdown.d.ts.map +1 -1
  116. package/dist/types/src/plugin.workerd.d.ts +2 -0
  117. package/dist/types/src/plugin.workerd.d.ts.map +1 -0
  118. package/dist/types/src/skills/index.d.ts +2 -0
  119. package/dist/types/src/skills/index.d.ts.map +1 -0
  120. package/dist/types/src/skills/markdown-skill.d.ts +4 -0
  121. package/dist/types/src/skills/markdown-skill.d.ts.map +1 -0
  122. package/dist/types/src/types/Markdown.d.ts +12 -20
  123. package/dist/types/src/types/Markdown.d.ts.map +1 -1
  124. package/dist/types/src/types/MarkdownOperation.d.ts +5 -28
  125. package/dist/types/src/types/MarkdownOperation.d.ts.map +1 -1
  126. package/dist/types/src/types/types.d.ts +2 -0
  127. package/dist/types/src/types/types.d.ts.map +1 -1
  128. package/dist/types/tsconfig.tsbuildinfo +1 -1
  129. package/dx.config.ts +1 -1
  130. package/package.json +66 -59
  131. package/src/MarkdownPlugin.node.ts +2 -1
  132. package/src/MarkdownPlugin.tsx +2 -6
  133. package/src/MarkdownPlugin.workerd.ts +5 -2
  134. package/src/capabilities/anchor-sort.ts +3 -2
  135. package/src/capabilities/comment-config.ts +6 -9
  136. package/src/capabilities/create-object.ts +1 -4
  137. package/src/capabilities/editor-view-state.ts +1 -1
  138. package/src/capabilities/index.ts +1 -3
  139. package/src/capabilities/node.ts +1 -0
  140. package/src/capabilities/react-surface.tsx +9 -3
  141. package/src/capabilities/skill-definition.ts +14 -0
  142. package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +72 -30
  143. package/src/components/MarkdownEditor/MarkdownEditor.tsx +11 -37
  144. package/src/components/MarkdownEditor/MarkdownEditorContent.tsx +3 -3
  145. package/src/components/PreviewComponent/PreviewComponent.tsx +195 -0
  146. package/src/components/PreviewComponent/index.ts +5 -0
  147. package/src/components/index.ts +1 -4
  148. package/src/containers/MarkdownArticle/MarkdownArticle.tsx +3 -3
  149. package/src/containers/MarkdownCard/MarkdownCard.tsx +1 -1
  150. package/src/{components → containers}/MarkdownSettings/MarkdownSettings.stories.tsx +1 -1
  151. package/src/containers/MarkdownSettings/MarkdownSettings.tsx +54 -0
  152. package/src/containers/index.ts +1 -0
  153. package/src/hooks/useEditorMenuOptions.ts +1 -1
  154. package/src/hooks/useExtensions.tsx +32 -12
  155. package/src/hooks/useLinkQuery.ts +5 -3
  156. package/src/index.ts +1 -2
  157. package/src/operations/create.conversations.json +1 -1
  158. package/src/operations/create.test.ts +5 -12
  159. package/src/operations/update-markdown.ts +7 -27
  160. package/src/operations/update.conversations.json +1 -1
  161. package/src/operations/update.test.ts +86 -16
  162. package/src/plugin.workerd.ts +5 -0
  163. package/src/skills/index.ts +5 -0
  164. package/src/{blueprints/markdown-blueprint.ts → skills/markdown-skill.ts} +8 -7
  165. package/src/types/Markdown.ts +19 -18
  166. package/src/types/MarkdownOperation.ts +11 -5
  167. package/src/types/types.ts +2 -7
  168. package/dist/lib/neutral/MarkdownSettings-F5AWRDT3.mjs +0 -32
  169. package/dist/lib/neutral/MarkdownSettings-F5AWRDT3.mjs.map +0 -7
  170. package/dist/lib/neutral/anchor-sort-UXN4RHFX.mjs.map +0 -7
  171. package/dist/lib/neutral/app-graph-builder-6Y5NHTNR.mjs +0 -66
  172. package/dist/lib/neutral/app-graph-builder-6Y5NHTNR.mjs.map +0 -7
  173. package/dist/lib/neutral/blueprint-definition-DSPXK77L.mjs +0 -15
  174. package/dist/lib/neutral/blueprint-definition-DSPXK77L.mjs.map +0 -7
  175. package/dist/lib/neutral/blueprints/index.mjs +0 -8
  176. package/dist/lib/neutral/chunk-4ULSRZRL.mjs +0 -10
  177. package/dist/lib/neutral/chunk-4ULSRZRL.mjs.map +0 -7
  178. package/dist/lib/neutral/chunk-BL46CERY.mjs +0 -8
  179. package/dist/lib/neutral/chunk-CRKAIDRD.mjs.map +0 -7
  180. package/dist/lib/neutral/chunk-LXNSKCPD.mjs.map +0 -7
  181. package/dist/lib/neutral/comment-config-EGY7RPPW.mjs.map +0 -7
  182. package/dist/lib/neutral/create-object-3WMEQJ2C.mjs.map +0 -7
  183. package/dist/lib/neutral/navigation-resolver-3WEM7SSC.mjs +0 -14
  184. package/dist/lib/neutral/navigation-resolver-3WEM7SSC.mjs.map +0 -7
  185. package/dist/lib/neutral/react-surface-6LX4ZQ2H.mjs.map +0 -7
  186. package/dist/lib/neutral/update-markdown-FGVN3LD4.mjs +0 -44
  187. package/dist/lib/neutral/update-markdown-FGVN3LD4.mjs.map +0 -7
  188. package/dist/types/src/blueprints/index.d.ts +0 -2
  189. package/dist/types/src/blueprints/index.d.ts.map +0 -1
  190. package/dist/types/src/blueprints/markdown-blueprint.d.ts +0 -4
  191. package/dist/types/src/blueprints/markdown-blueprint.d.ts.map +0 -1
  192. package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -6
  193. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
  194. package/dist/types/src/capabilities/blueprint-definition.d.ts +0 -6
  195. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +0 -1
  196. package/dist/types/src/capabilities/navigation-resolver.d.ts +0 -6
  197. package/dist/types/src/capabilities/navigation-resolver.d.ts.map +0 -1
  198. package/dist/types/src/paths.d.ts +0 -3
  199. package/dist/types/src/paths.d.ts.map +0 -1
  200. package/src/blueprints/index.ts +0 -5
  201. package/src/capabilities/app-graph-builder.ts +0 -61
  202. package/src/capabilities/blueprint-definition.ts +0 -19
  203. package/src/capabilities/navigation-resolver.ts +0 -19
  204. package/src/components/MarkdownSettings/MarkdownSettings.tsx +0 -35
  205. package/src/paths.ts +0 -13
  206. /package/dist/lib/neutral/{chunk-BL46CERY.mjs.map → chunk-2V6A5NDQ.mjs.map} +0 -0
  207. /package/dist/lib/neutral/{create-XW2FQNLK.mjs.map → create-OQUMKL5I.mjs.map} +0 -0
  208. /package/dist/lib/neutral/{create-markdown-KLSVQCT7.mjs.map → create-markdown-4O5FDA77.mjs.map} +0 -0
  209. /package/dist/lib/neutral/{open-F7KLNKVN.mjs.map → open-BAZIRXII.mjs.map} +0 -0
  210. /package/dist/lib/neutral/{blueprints/index.mjs.map → operation-handler-SDCXGMGK.mjs.map} +0 -0
  211. /package/dist/lib/neutral/{scroll-to-anchor-ACND3WJU.mjs.map → scroll-to-anchor-I6HFE5AH.mjs.map} +0 -0
  212. /package/dist/lib/neutral/{set-view-mode-WDC7CAEC.mjs.map → set-view-mode-NY5OYYIM.mjs.map} +0 -0
  213. /package/dist/types/src/{components → containers}/MarkdownSettings/MarkdownSettings.stories.d.ts +0 -0
  214. /package/dist/types/src/{components → containers}/MarkdownSettings/index.d.ts +0 -0
  215. /package/src/{components → containers}/MarkdownSettings/index.ts +0 -0
@@ -9,11 +9,6 @@ import { createContext } from '@radix-ui/react-context';
9
9
  import React, { type ReactNode, useCallback, useMemo, useState } from 'react';
10
10
  import { createPortal } from 'react-dom';
11
11
 
12
- import { Surface } from '@dxos/app-framework/ui';
13
- import { AppSurface } from '@dxos/app-toolkit/ui';
14
- import { Obj } from '@dxos/echo';
15
- import { URI } from '@dxos/keys';
16
- import { useClient } from '@dxos/react-client';
17
12
  import { type ThemedClassName } from '@dxos/react-ui';
18
13
  import { composable, composableProps } from '@dxos/react-ui';
19
14
  import {
@@ -22,7 +17,7 @@ import {
22
17
  createEditorController,
23
18
  useEditorContext,
24
19
  } from '@dxos/react-ui-editor';
25
- import { type PreviewBlock, type PreviewOptions } from '@dxos/ui-editor';
20
+ import { type XmlWidgetState } from '@dxos/ui-editor';
26
21
  import { isNonNullable } from '@dxos/util';
27
22
 
28
23
  import {
@@ -49,7 +44,7 @@ import {
49
44
  type MarkdownEditorContextValue = {
50
45
  id: string;
51
46
  attendableId?: string;
52
- previewBlocks: PreviewBlock[];
47
+ widgets: XmlWidgetState[];
53
48
  } & Pick<ExtensionsOptions, 'compact' | 'viewMode'> &
54
49
  Pick<NaturalMarkdownToolbarProps, 'onAction' | 'onFileUpload' | 'onViewModeChange'>;
55
50
 
@@ -94,20 +89,8 @@ export const MarkdownEditorProvider = ({
94
89
  onFileUpload,
95
90
  onViewModeChange,
96
91
  }: MarkdownEditorProviderProps) => {
97
- // Preview blocks.
98
- const [previewBlocks, setPreviewBlocks] = useState<PreviewBlock[]>([]);
99
- const previewOptions = useMemo<PreviewOptions>(
100
- () => ({
101
- db: Obj.isObject(object) ? Obj.getDatabase(object) : undefined,
102
- addBlockContainer: (block) => {
103
- setPreviewBlocks((prev) => [...prev, block]);
104
- },
105
- removeBlockContainer: ({ link }) => {
106
- setPreviewBlocks((prev) => prev.filter(({ link: prevLink }) => prevLink.dxn !== link.dxn));
107
- },
108
- }),
109
- [object],
110
- );
92
+ // Widget portals driven by xmlTags.
93
+ const [widgets, setWidgets] = useState<XmlWidgetState[]>([]);
111
94
 
112
95
  // Context menu options (Editor.Root calls useEditorMenu with these props).
113
96
  const menuOptions = useEditorMenuOptions({ slashCommandGroups, onLinkQuery });
@@ -120,7 +103,7 @@ export const MarkdownEditorProvider = ({
120
103
  viewMode,
121
104
  viewState,
122
105
  editorStateStore,
123
- previewOptions,
106
+ setWidgets,
124
107
  settings,
125
108
  onSelectObject,
126
109
  });
@@ -149,12 +132,12 @@ export const MarkdownEditorProvider = ({
149
132
  attendableId,
150
133
  compact,
151
134
  viewMode,
152
- previewBlocks,
135
+ widgets,
153
136
  onAction,
154
137
  onFileUpload,
155
138
  onViewModeChange,
156
139
  }),
157
- [id, attendableId, compact, viewMode, previewBlocks, onAction, onFileUpload, onViewModeChange],
140
+ [id, attendableId, compact, viewMode, widgets, onAction, onFileUpload, onViewModeChange],
158
141
  );
159
142
 
160
143
  return (
@@ -251,12 +234,12 @@ const MARKDOWN_EDITOR_BLOCKS_NAME = 'MarkdownEditor.Blocks';
251
234
  type MarkdownEditorBlocksProps = {};
252
235
 
253
236
  const MarkdownEditorBlocks = (_props: MarkdownEditorBlocksProps) => {
254
- const { previewBlocks } = useMarkdownEditorContext(MARKDOWN_EDITOR_BLOCKS_NAME);
237
+ const { widgets } = useMarkdownEditorContext(MARKDOWN_EDITOR_BLOCKS_NAME);
255
238
 
256
239
  return (
257
240
  <>
258
- {previewBlocks.map(({ link, el }) => (
259
- <PreviewBlock key={link.dxn} link={link} el={el} />
241
+ {widgets.map(({ id, root, Component, props }) => (
242
+ <div key={id}>{createPortal(<Component {...props} />, root)}</div>
260
243
  ))}
261
244
  </>
262
245
  );
@@ -264,15 +247,6 @@ const MarkdownEditorBlocks = (_props: MarkdownEditorBlocksProps) => {
264
247
 
265
248
  MarkdownEditorBlocks.displayName = MARKDOWN_EDITOR_BLOCKS_NAME;
266
249
 
267
- const PreviewBlock = ({ el, link }: PreviewBlock) => {
268
- const client = useClient();
269
- const dxn = URI.make(link.dxn);
270
- const subject = client.graph.makeRef(dxn).target;
271
- const data = useMemo(() => ({ subject }), [subject]);
272
-
273
- return createPortal(<Surface.Surface type={AppSurface.CardContent} data={data} limit={1} />, el);
274
- };
275
-
276
250
  //
277
251
  // MarkdownEditor
278
252
  //
@@ -284,7 +258,7 @@ export const MarkdownEditor = {
284
258
  Blocks: MarkdownEditorBlocks,
285
259
  };
286
260
 
287
- export type { MarkdownEditorContentProps, MarkdownEditorToolbarProps, MarkdownEditorBlocksProps };
261
+ export type { MarkdownEditorBlocksProps, MarkdownEditorContentProps, MarkdownEditorToolbarProps };
288
262
 
289
263
  /** @deprecated Use `MarkdownEditorProviderProps`. */
290
264
  export type MarkdownEditorRootProps = MarkdownEditorProviderProps;
@@ -19,15 +19,15 @@ import {
19
19
  type EditorSelectionState,
20
20
  type EditorStateStore,
21
21
  type ThemeExtensionsOptions,
22
- mobileSlots,
23
22
  createBasicExtensions,
24
23
  createMarkdownExtensions,
25
24
  createThemeExtensions,
26
- dropFile,
27
25
  documentSlots,
26
+ dropFile,
27
+ editorClassNames,
28
28
  formattingListener,
29
+ mobileSlots,
29
30
  processEditorPayload,
30
- editorClassNames,
31
31
  scrollbarAutohide,
32
32
  } from '@dxos/ui-editor';
33
33
  import { type EditorViewMode } from '@dxos/ui-editor/types';
@@ -0,0 +1,195 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+
7
+ import { Surface, useOperationInvoker } from '@dxos/app-framework/ui';
8
+ import { LayoutOperation, Paths } from '@dxos/app-toolkit';
9
+ import { AppSurface } from '@dxos/app-toolkit/ui';
10
+ import { Obj } from '@dxos/echo';
11
+ import { URI } from '@dxos/keys';
12
+ import { useClient } from '@dxos/react-client';
13
+ import { Icon, IconButton } from '@dxos/react-ui';
14
+ import { ResizeHandle, type Size, resizeAttributes, sizeStyle } from '@dxos/react-ui-dnd';
15
+ import { type XmlWidgetProps } from '@dxos/ui-editor';
16
+
17
+ export type PreviewComponentProps = XmlWidgetProps<{
18
+ label: string;
19
+ dxn: string;
20
+ block?: boolean;
21
+ suggest?: boolean;
22
+ onOpen?: (dxn: URI.URI) => void;
23
+ }>;
24
+
25
+ // Persisted height (px) lives in the image alt text after the label, Obsidian-style: `![label|320](dxn)`.
26
+ const HEIGHT_PATTERN = /^(.*)\|(\d+)$/;
27
+
28
+ const parseEmbedLabel = (alt: string): { baseLabel: string; height?: number } => {
29
+ const match = HEIGHT_PATTERN.exec(alt ?? '');
30
+ if (match) {
31
+ const height = Number.parseInt(match[2], 10);
32
+ if (Number.isFinite(height) && height > 0) {
33
+ return { baseLabel: match[1], height };
34
+ }
35
+ }
36
+ return { baseLabel: alt ?? '' };
37
+ };
38
+
39
+ const formatEmbedLabel = (baseLabel: string, height?: number): string =>
40
+ height != null ? `${baseLabel}|${height}` : baseLabel;
41
+
42
+ const MIN_SIZE = 6;
43
+ const FALLBACK_SIZE = 320;
44
+
45
+ /** Scroll the element's top into view (honoring its `scroll-margin`) only if that top is not already visible. */
46
+ const scrollTopIntoViewIfNeeded = (element: HTMLElement): void => {
47
+ let parent = element.parentElement;
48
+ while (parent) {
49
+ const { overflowY } = getComputedStyle(parent);
50
+ if (
51
+ (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&
52
+ parent.scrollHeight > parent.clientHeight
53
+ ) {
54
+ break;
55
+ }
56
+ parent = parent.parentElement;
57
+ }
58
+ const elementTop = element.getBoundingClientRect().top;
59
+ const viewTop = parent ? parent.getBoundingClientRect().top : 0;
60
+ const viewBottom = parent ? parent.getBoundingClientRect().bottom : window.innerHeight;
61
+ if (elementTop < viewTop || elementTop > viewBottom) {
62
+ element.scrollIntoView({ block: 'start', inline: 'nearest' });
63
+ }
64
+ };
65
+
66
+ /**
67
+ * Registry-backed block widget for URL-scheme preview slots.
68
+ * Replaces the addBlockContainer callback pattern.
69
+ * Used as the Component entry in a urlSchemes XmlWidgetDef.
70
+ */
71
+ export const PreviewComponent = ({ dxn, label: alt, onOpen, view, range }: PreviewComponentProps) => {
72
+ const client = useClient();
73
+ const { invokePromise } = useOperationInvoker();
74
+ const containerRef = useRef<HTMLDivElement>(null);
75
+ const uri = dxn ? URI.make(dxn) : undefined;
76
+ const subject = uri ? client.graph.makeRef<Obj.Unknown>(uri).target : undefined;
77
+
78
+ // px per rem; ResizeHandle works in rem while the persisted height is in px.
79
+ const remSize = useMemo(() => parseFloat(getComputedStyle(document.documentElement).fontSize) || 16, []);
80
+ const { height } = parseEmbedLabel(alt);
81
+
82
+ // `min-content` keeps the embed at its intrinsic height until the user resizes it; the handle then
83
+ // measures the rendered box and switches to an explicit height.
84
+ const [size, setSize] = useState<Size>(height != null ? height / remSize : 'min-content');
85
+
86
+ // Tell the surface it is sized by its container (vs. intrinsic) so content (e.g. an image) can fit.
87
+ const extrinsic = size !== 'min-content';
88
+ const data = useMemo(
89
+ () => (subject ? { subject, attendableId: dxn, extrinsic } : undefined),
90
+ [subject, dxn, extrinsic],
91
+ );
92
+ useEffect(() => {
93
+ setSize(height != null ? height / remSize : 'min-content');
94
+ }, [height, remSize]);
95
+
96
+ // Persist the height (px) into the alt text on drop by rewriting the image node in the source. The
97
+ // node bounds and base label are re-derived from the live document (the captured `range`/`label`
98
+ // can lag a prior edit before React re-renders the widget). When the same object is transcluded
99
+ // more than once, the node whose start is NEAREST the widget's range is chosen so a duplicate
100
+ // embed isn't rewritten by mistake.
101
+ const handleResize = useCallback(
102
+ (next: Size, commit?: boolean) => {
103
+ setSize(next);
104
+ if (!commit || typeof next !== 'number' || !view) {
105
+ return;
106
+ }
107
+ const doc = view.state.doc.toString();
108
+ const marker = `](${dxn})`;
109
+ let open = -1;
110
+ let close = -1;
111
+ for (let at = doc.indexOf(marker); at >= 0; at = doc.indexOf(marker, at + marker.length)) {
112
+ const start = doc.lastIndexOf('![', at);
113
+ if (start >= 0 && (open < 0 || Math.abs(start - range.from) < Math.abs(open - range.from))) {
114
+ open = start;
115
+ close = at;
116
+ }
117
+ }
118
+ if (open < 0) {
119
+ return;
120
+ }
121
+ const { baseLabel: currentBaseLabel } = parseEmbedLabel(doc.slice(open + 2, close));
122
+ const insert = `![${formatEmbedLabel(currentBaseLabel, Math.round(next * remSize))}](${dxn})`;
123
+ view.dispatch({ changes: { from: open, to: close + marker.length, insert } });
124
+ // After the resized box has laid out, bring its top into frame — but only if that top scrolled
125
+ // out of view. Scroll the element itself (not the CM view) so it works whichever ancestor
126
+ // actually scrolls (cm-scroller or an outer panel); defer a frame so the new height applies first.
127
+ requestAnimationFrame(() => {
128
+ if (containerRef.current) {
129
+ scrollTopIntoViewIfNeeded(containerRef.current);
130
+ }
131
+ });
132
+ },
133
+ [view, range, dxn, remSize],
134
+ );
135
+
136
+ // Open the referenced object: defer to the caller's handler when provided, else navigate to it.
137
+ const handleOpen = useCallback(() => {
138
+ if (!uri || !subject) {
139
+ return;
140
+ }
141
+ if (onOpen) {
142
+ onOpen(uri);
143
+ } else {
144
+ void invokePromise?.(LayoutOperation.Open, {
145
+ subject: [Paths.getObjectPathFromObject(subject)],
146
+ navigation: 'immediate',
147
+ });
148
+ }
149
+ }, [uri, subject, onOpen, invokePromise]);
150
+
151
+ if (!uri || !subject || !data) {
152
+ return null;
153
+ }
154
+
155
+ const objectLabel = Obj.getLabel(subject);
156
+ const objectIcon = Obj.getIcon(subject);
157
+
158
+ // Frame transcluded block embeds so they read as a self-contained card within the document flow.
159
+ // The clip/border live on an inner element so they don't clip the resize handle (which straddles the
160
+ // bottom edge); the inner grid stretches the surface to fill the resizable box, overriding any
161
+ // intrinsic aspect.
162
+ return (
163
+ <div
164
+ className='relative grid scroll-mt-16'
165
+ style={sizeStyle(size, 'vertical')}
166
+ {...resizeAttributes}
167
+ ref={containerRef}
168
+ >
169
+ <div className='grid overflow-hidden border border-subdued-separator rounded-md'>
170
+ <Surface.Surface type={AppSurface.Section} data={data} limit={1} />
171
+ </div>
172
+ <div className='absolute top-1 right-1 flex items-center justify-end gap-1'>
173
+ <span className='dx-tag dx-tag--neutral flex items-center gap-1'>
174
+ {objectIcon && <Icon icon={objectIcon.icon} size={4} />}
175
+ {objectLabel}
176
+ </span>
177
+ <IconButton
178
+ density='sm'
179
+ icon='ph--arrow-square-out--regular'
180
+ iconOnly
181
+ label='Open'
182
+ variant='ghost'
183
+ onClick={handleOpen}
184
+ />
185
+ </div>
186
+ <ResizeHandle
187
+ side='block-end'
188
+ fallbackSize={FALLBACK_SIZE}
189
+ minSize={MIN_SIZE}
190
+ size={size}
191
+ onSizeChange={handleResize}
192
+ />
193
+ </div>
194
+ );
195
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './PreviewComponent';
@@ -2,8 +2,5 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type ComponentType, lazy } from 'react';
6
-
7
5
  export * from './MarkdownEditor';
8
-
9
- export const MarkdownSettings: ComponentType<any> = lazy(() => import('./MarkdownSettings'));
6
+ export * from './PreviewComponent';
@@ -19,8 +19,8 @@ import { Text } from '@dxos/schema';
19
19
 
20
20
  import {
21
21
  MarkdownEditor,
22
- MarkdownEditorProvider,
23
22
  type MarkdownEditorContentProps,
23
+ MarkdownEditorProvider,
24
24
  type MarkdownEditorProviderProps,
25
25
  } from '#components';
26
26
  import { useLinkQuery } from '#hooks';
@@ -90,7 +90,7 @@ export const MarkdownArticle = forwardRef<HTMLDivElement, MarkdownArticleProps>(
90
90
  }, [db, upload]);
91
91
 
92
92
  // Query for @ refs.
93
- const handleLinkQuery = useLinkQuery(db);
93
+ const handleLinkQuery = useLinkQuery(db, Obj.isObject(object) ? object : undefined);
94
94
 
95
95
  // Open linked objects.
96
96
  const { invokePromise } = useOperationInvoker();
@@ -129,7 +129,7 @@ export const MarkdownArticle = forwardRef<HTMLDivElement, MarkdownArticleProps>(
129
129
  <Editor.Root {...editorRootProps}>
130
130
  <Panel.Root role={role} ref={forwardedRef}>
131
131
  {settings.toolbar && (
132
- <Panel.Toolbar classNames='bg-toolbar-surface'>
132
+ <Panel.Toolbar>
133
133
  <MarkdownEditor.Toolbar classNames='dx-document' customActions={customActions} />
134
134
  </Panel.Toolbar>
135
135
  )}
@@ -29,7 +29,7 @@ export const MarkdownCard = ({ subject }: MarkdownCardProps) => {
29
29
  return (
30
30
  <Card.Body>
31
31
  {snippet && (
32
- <Card.Section className='relative'>
32
+ <Card.Section className='aspect-square relative'>
33
33
  <Card.Row fullWidth>
34
34
  <MarkdownEditorProvider id={subject.id} viewMode='readonly' extensions={extensions}>
35
35
  {(editorRootProps) => (
@@ -11,7 +11,7 @@ import { translations } from '#translations';
11
11
  import { MarkdownSettings } from './MarkdownSettings';
12
12
 
13
13
  const meta = {
14
- title: 'plugins/plugin-markdown/components/MarkdownSettings',
14
+ title: 'plugins/plugin-markdown/containers/MarkdownSettings',
15
15
  tags: ['settings'],
16
16
  component: MarkdownSettings,
17
17
  decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
@@ -0,0 +1,54 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type AppSurface } from '@dxos/app-toolkit/ui';
8
+ import { Input } from '@dxos/react-ui';
9
+ import { Form, type FormFieldRendererProps, FormRow } from '@dxos/react-ui-form';
10
+
11
+ import { meta } from '#meta';
12
+ import { Markdown } from '#types';
13
+
14
+ export type MarkdownSettingsProps = AppSurface.SettingsProps<Markdown.Settings>;
15
+
16
+ export const MarkdownSettings = ({ settings, onSettingsChange }: MarkdownSettingsProps) => {
17
+ return (
18
+ <Form.Root
19
+ variant='settings'
20
+ schema={Markdown.Settings}
21
+ readonly={!onSettingsChange}
22
+ values={settings}
23
+ onValuesChanged={(values) => onSettingsChange?.((current) => ({ ...current, ...values }))}
24
+ >
25
+ <Form.Viewport scroll>
26
+ <Form.Content>
27
+ <Form.Section title={meta.profile.name}>
28
+ <Form.FieldSet
29
+ fieldMap={{ snippets: SnippetsField }}
30
+ filter={(properties) =>
31
+ settings.debug ? properties : properties.filter((property) => property.name !== 'snippets')
32
+ }
33
+ />
34
+ </Form.Section>
35
+ </Form.Content>
36
+ </Form.Viewport>
37
+ </Form.Root>
38
+ );
39
+ };
40
+
41
+ /** Multi-line snippet editor; replaces the single-line text input the schema would otherwise render. */
42
+ const SnippetsField = ({ type, readonly, onValueChange, onBlur, ...props }: FormFieldRendererProps<string>) => (
43
+ <FormRow<string> readonly={readonly} {...props}>
44
+ {({ value }) => (
45
+ <Input.TextArea
46
+ disabled={!!readonly}
47
+ rows={5}
48
+ value={value ?? ''}
49
+ onBlur={onBlur}
50
+ onChange={(event) => onValueChange(type, event.target.value)}
51
+ />
52
+ )}
53
+ </FormRow>
54
+ );
@@ -9,3 +9,4 @@ export type { MarkdownArticleProps } from './MarkdownArticle';
9
9
  export const MarkdownCard: ComponentType<any> = lazy(() => import('./MarkdownCard'));
10
10
  export const EditableMarkdownCard: ComponentType<any> = lazy(() => import('./EditableMarkdownCard'));
11
11
  export const MarkdownArticle: ComponentType<any> = lazy(() => import('./MarkdownArticle'));
12
+ export const MarkdownSettings: ComponentType<any> = lazy(() => import('./MarkdownSettings'));
@@ -39,7 +39,7 @@ export const useEditorMenuOptions = ({
39
39
 
40
40
  case '/':
41
41
  default: {
42
- return filterMenuGroups([formattingCommands, linkSlashCommands, ...(slashCommandGroups ?? [])], (item) =>
42
+ return filterMenuGroups([linkSlashCommands, formattingCommands, ...(slashCommandGroups ?? [])], (item) =>
43
43
  text ? toLocalizedString(item.label, t).toLowerCase().includes(text.toLowerCase()) : true,
44
44
  );
45
45
  }
@@ -8,21 +8,23 @@ import { useMemo } from 'react';
8
8
  import { Paths } from '@dxos/app-toolkit';
9
9
  import { debounceAndThrottle } from '@dxos/async';
10
10
  import { Obj } from '@dxos/echo';
11
- import { createDocAccessor } from '@dxos/echo-client';
11
+ import { Doc } from '@dxos/echo-doc';
12
12
  import { invariant } from '@dxos/invariant';
13
13
  import { getSpace, useObject } from '@dxos/react-client/echo';
14
14
  import { useIdentity } from '@dxos/react-client/halo';
15
15
  import { useThemeContext } from '@dxos/react-ui';
16
- import { selectionAspect, type ViewStateManager } from '@dxos/react-ui-attention';
16
+ import { type ViewStateManager, selectionAspect } from '@dxos/react-ui-attention';
17
17
  import { Text } from '@dxos/schema';
18
18
  import { Domino } from '@dxos/ui';
19
19
  import {
20
+ AnchorWidget,
20
21
  Cursor,
21
22
  type EditorStateStore,
22
23
  EditorView,
23
24
  type Extension,
24
25
  InputModeExtensions,
25
- type PreviewOptions,
26
+ type XmlWidgetProps,
27
+ type XmlWidgetState,
26
28
  createDataExtensions,
27
29
  decorateMarkdown,
28
30
  documentId,
@@ -30,16 +32,17 @@ import {
30
32
  formattingKeymap,
31
33
  linkTooltip,
32
34
  listener,
33
- preview,
34
35
  replacer,
35
36
  selectionState,
36
37
  snippets,
38
+ xmlTags,
37
39
  } from '@dxos/ui-editor';
38
40
  import { type EditorViewMode, type RenderCallback } from '@dxos/ui-editor/types';
39
41
  import { isTruthy, safeUrl } from '@dxos/util';
40
42
 
41
43
  import { Markdown } from '#types';
42
44
 
45
+ import { PreviewComponent } from '../components/PreviewComponent/PreviewComponent';
43
46
  import { setFallbackName } from '../util';
44
47
 
45
48
  export type DocumentType = Markdown.Document | Text.Text | { id: string; text: string };
@@ -53,7 +56,7 @@ export type ExtensionsOptions = {
53
56
  editable?: boolean;
54
57
  viewState?: ViewStateManager;
55
58
  editorStateStore?: EditorStateStore;
56
- previewOptions?: PreviewOptions;
59
+ setWidgets?: (widgets: XmlWidgetState[]) => void;
57
60
  platform?: 'mobile' | 'desktop';
58
61
  /** Callback when an internal link is clicked. */
59
62
  onSelectObject?: (objectId: string) => void;
@@ -68,7 +71,7 @@ export const useExtensions = ({
68
71
  viewMode,
69
72
  viewState,
70
73
  editorStateStore,
71
- previewOptions,
74
+ setWidgets,
72
75
  onSelectObject,
73
76
  }: ExtensionsOptions): Extension[] => {
74
77
  const { platform } = useThemeContext();
@@ -79,7 +82,7 @@ export const useExtensions = ({
79
82
  const contentRef = Obj.instanceOf(Markdown.Document, object) ? (object as Markdown.Document).content : undefined;
80
83
  // Use useObject to trigger re-render when the reference loads (returns snapshot for reactivity).
81
84
  useObject(contentRef);
82
- // Get the actual live object target via .target (needed for createDocAccessor).
85
+ // Get the actual live object target via .target (needed for Doc.createAccessor).
83
86
  const target = contentRef?.target ?? (Obj.instanceOf(Text.Text, object) ? object : undefined);
84
87
 
85
88
  // TODO(wittjosiah): Autocomplete is not working and this query is causing performance issues.
@@ -96,7 +99,7 @@ export const useExtensions = ({
96
99
  compact,
97
100
  viewMode,
98
101
  viewState,
99
- previewOptions,
102
+ setWidgets,
100
103
  platform,
101
104
  onSelectObject,
102
105
  }),
@@ -106,7 +109,7 @@ export const useExtensions = ({
106
109
  compact,
107
110
  viewMode,
108
111
  viewState,
109
- previewOptions,
112
+ setWidgets,
110
113
  settings,
111
114
  settings?.debug,
112
115
  settings?.editorInputMode,
@@ -125,7 +128,7 @@ export const useExtensions = ({
125
128
  target &&
126
129
  createDataExtensions({
127
130
  id,
128
- text: createDocAccessor(target, ['content']),
131
+ text: Doc.createAccessor(target, ['content']),
129
132
  messenger: space,
130
133
  identity,
131
134
  }),
@@ -156,7 +159,7 @@ const createBaseExtensions = ({
156
159
  compact,
157
160
  viewMode,
158
161
  viewState,
159
- previewOptions,
162
+ setWidgets,
160
163
  platform,
161
164
  }: ExtensionsOptions): Extension[] => {
162
165
  const extensions: Extension[] = [
@@ -177,9 +180,26 @@ const createBaseExtensions = ({
177
180
  numberedHeadings: settings?.numberedHeadings ? { from: 2 } : undefined,
178
181
  // TODO(wittjosiah): For internal links render the label of the object.
179
182
  renderLinkButton: onSelectObject && createRenderLink(onSelectObject),
183
+ // xmlTags() handles dxn:/echo: links via url-scheme widgets; skip here to avoid double-processing.
184
+ skip: ({ url }) => url.startsWith('dxn:') || url.startsWith('echo:'),
180
185
  }),
181
186
  linkTooltip(renderLinkTooltip),
182
- preview(previewOptions),
187
+ xmlTags({
188
+ registry: {
189
+ 'dxn-preview': {
190
+ block: true,
191
+ urlSchemes: ['dxn:', 'echo:'],
192
+ Component: PreviewComponent,
193
+ },
194
+ 'link-preview': {
195
+ block: false,
196
+ urlSchemes: ['dxn:', 'echo:'],
197
+ factory: ({ label, dxn }: XmlWidgetProps<{ label: string; dxn: string }>) =>
198
+ label && dxn ? new AnchorWidget(label, dxn) : null,
199
+ },
200
+ },
201
+ setWidgets,
202
+ }),
183
203
  replacer(),
184
204
  ],
185
205
  );
@@ -14,7 +14,7 @@ import { insertAtCursor, insertAtLineStart } from '@dxos/ui-editor';
14
14
 
15
15
  import { Markdown } from '../types';
16
16
 
17
- export const useLinkQuery = (db: Database.Database | undefined) => {
17
+ export const useLinkQuery = (db: Database.Database | undefined, current?: Obj.Unknown) => {
18
18
  const { t } = useTranslation();
19
19
 
20
20
  const filter = useMemo(
@@ -41,7 +41,9 @@ export const useLinkQuery = (db: Database.Database | undefined) => {
41
41
 
42
42
  const items =
43
43
  results
44
- ?.filter((object) => toLocalizedString(getLabel(object), t).toLowerCase().includes(name))
44
+ // Exclude the current document; it cannot link to itself.
45
+ ?.filter((object) => object.id !== current?.id)
46
+ .filter((object) => toLocalizedString(getLabel(object), t).toLowerCase().includes(name))
45
47
  .map((object: Obj.Unknown): EditorMenuItem => {
46
48
  const type = Obj.getType(object);
47
49
  const icon = type
@@ -87,7 +89,7 @@ export const useLinkQuery = (db: Database.Database | undefined) => {
87
89
  { id: 'create', items: [createItem] },
88
90
  ];
89
91
  },
90
- [db, filter, t],
92
+ [db, filter, t, current],
91
93
  );
92
94
 
93
95
  return handleLinkQuery;
package/src/index.ts CHANGED
@@ -2,9 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './blueprints';
5
+ export * from './skills';
6
6
  export * from './meta';
7
7
  export * from './operations';
8
- export * from './paths';
9
8
  export * from './types';
10
9
  export * from './util';