@dxos/plugin-presenter 0.9.0 → 0.9.1-staging.ee54ba693a

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 (166) hide show
  1. package/PLUGIN.mdl +12 -12
  2. package/dist/lib/neutral/{CollectionPresenterArticle-DFREOQTG.mjs → CollectionArticle-7R4KJWDW.mjs} +10 -9
  3. package/dist/lib/neutral/CollectionArticle-7R4KJWDW.mjs.map +7 -0
  4. package/dist/lib/neutral/DocumentArticle-YZRKVEN7.mjs +32 -0
  5. package/dist/lib/neutral/DocumentArticle-YZRKVEN7.mjs.map +7 -0
  6. package/dist/lib/neutral/PresenterPlugin.mjs +11 -4
  7. package/dist/lib/neutral/PresenterPlugin.mjs.map +4 -4
  8. package/dist/lib/neutral/SlideArticle-FE2I6KBO.mjs +20 -0
  9. package/dist/lib/neutral/SlideArticle-FE2I6KBO.mjs.map +7 -0
  10. package/dist/lib/neutral/{app-graph-builder-DIEDSRPX.mjs → app-graph-builder-FNDFY5L3.mjs} +8 -24
  11. package/dist/lib/neutral/app-graph-builder-FNDFY5L3.mjs.map +7 -0
  12. package/dist/lib/neutral/capabilities/index.mjs +7 -3
  13. package/dist/lib/neutral/capabilities/index.mjs.map +3 -3
  14. package/dist/lib/neutral/chunk-63IF7OXT.mjs +19 -0
  15. package/dist/lib/neutral/chunk-63IF7OXT.mjs.map +7 -0
  16. package/dist/lib/neutral/chunk-MSQDHOPV.mjs +47 -0
  17. package/dist/lib/neutral/chunk-MSQDHOPV.mjs.map +7 -0
  18. package/dist/lib/neutral/{chunk-VVALMI52.mjs → chunk-NDZ6IIAL.mjs} +8 -4
  19. package/dist/lib/neutral/chunk-NDZ6IIAL.mjs.map +7 -0
  20. package/dist/lib/neutral/components/index.mjs +90 -29
  21. package/dist/lib/neutral/components/index.mjs.map +4 -4
  22. package/dist/lib/neutral/containers/index.mjs +6 -6
  23. package/dist/lib/neutral/containers/index.mjs.map +3 -3
  24. package/dist/lib/neutral/index.mjs +2 -2
  25. package/dist/lib/neutral/markdown-extension-HGLRVZDF.mjs +37 -0
  26. package/dist/lib/neutral/markdown-extension-HGLRVZDF.mjs.map +7 -0
  27. package/dist/lib/neutral/meta.json +1 -1
  28. package/dist/lib/neutral/meta.mjs +1 -1
  29. package/dist/lib/neutral/operation-handler-3ZESW5AK.mjs +13 -0
  30. package/dist/lib/neutral/operation-handler-3ZESW5AK.mjs.map +7 -0
  31. package/dist/lib/neutral/operations/index.mjs +9 -0
  32. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  33. package/dist/lib/neutral/plugin.mjs +1 -1
  34. package/dist/lib/neutral/react-surface-VO4GPCLQ.mjs +42 -0
  35. package/dist/lib/neutral/react-surface-VO4GPCLQ.mjs.map +7 -0
  36. package/dist/lib/neutral/{settings-R6LRDAAK.mjs → settings-DBV7N5HT.mjs} +3 -3
  37. package/dist/lib/neutral/{settings-R6LRDAAK.mjs.map → settings-DBV7N5HT.mjs.map} +3 -3
  38. package/dist/lib/neutral/toggle-presentation-PH7ZJJDD.mjs +60 -0
  39. package/dist/lib/neutral/toggle-presentation-PH7ZJJDD.mjs.map +7 -0
  40. package/dist/lib/neutral/translations.mjs +1 -2
  41. package/dist/lib/neutral/translations.mjs.map +3 -3
  42. package/dist/lib/neutral/types/index.mjs +1 -1
  43. package/dist/types/dx.config.d.ts +28 -0
  44. package/dist/types/dx.config.d.ts.map +1 -0
  45. package/dist/types/src/PresenterPlugin.d.ts.map +1 -1
  46. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
  47. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  48. package/dist/types/src/capabilities/index.d.ts +6 -3
  49. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  50. package/dist/types/src/capabilities/markdown-extension.d.ts +9 -0
  51. package/dist/types/src/capabilities/markdown-extension.d.ts.map +1 -0
  52. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  53. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  54. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  55. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  56. package/dist/types/src/capabilities/settings.d.ts +1 -3
  57. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  58. package/dist/types/src/components/Presenter/PresentationShell.d.ts +20 -0
  59. package/dist/types/src/components/Presenter/PresentationShell.d.ts.map +1 -0
  60. package/dist/types/src/components/Presenter/index.d.ts +1 -0
  61. package/dist/types/src/components/Presenter/index.d.ts.map +1 -1
  62. package/dist/types/src/components/RevealPlayer/RevealPlayer.d.ts.map +1 -1
  63. package/dist/types/src/components/RevealPlayer/RevealPlayer.stories.d.ts +0 -1
  64. package/dist/types/src/components/RevealPlayer/RevealPlayer.stories.d.ts.map +1 -1
  65. package/dist/types/src/components/index.d.ts +0 -2
  66. package/dist/types/src/components/index.d.ts.map +1 -1
  67. package/dist/types/src/containers/CollectionArticle/CollectionArticle.d.ts +6 -0
  68. package/dist/types/src/containers/CollectionArticle/CollectionArticle.d.ts.map +1 -0
  69. package/dist/types/src/containers/CollectionArticle/index.d.ts +2 -0
  70. package/dist/types/src/{components/PresenterSettings → containers/CollectionArticle}/index.d.ts.map +1 -1
  71. package/dist/types/src/containers/DocumentArticle/DocumentArticle.d.ts +6 -0
  72. package/dist/types/src/containers/DocumentArticle/DocumentArticle.d.ts.map +1 -0
  73. package/dist/types/src/containers/DocumentArticle/index.d.ts +2 -0
  74. package/dist/types/src/containers/DocumentArticle/index.d.ts.map +1 -0
  75. package/dist/types/src/containers/SlideArticle/SlideArticle.d.ts +6 -0
  76. package/dist/types/src/containers/SlideArticle/SlideArticle.d.ts.map +1 -0
  77. package/dist/types/src/containers/SlideArticle/index.d.ts +2 -0
  78. package/dist/types/src/containers/SlideArticle/index.d.ts.map +1 -0
  79. package/dist/types/src/containers/index.d.ts +3 -3
  80. package/dist/types/src/containers/index.d.ts.map +1 -1
  81. package/dist/types/src/meta.d.ts +32 -2
  82. package/dist/types/src/meta.d.ts.map +1 -1
  83. package/dist/types/src/operations/index.d.ts +3 -0
  84. package/dist/types/src/operations/index.d.ts.map +1 -0
  85. package/dist/types/src/operations/toggle-presentation.d.ts +10 -0
  86. package/dist/types/src/operations/toggle-presentation.d.ts.map +1 -0
  87. package/dist/types/src/paths.d.ts +3 -0
  88. package/dist/types/src/paths.d.ts.map +1 -0
  89. package/dist/types/src/translations.d.ts +0 -1
  90. package/dist/types/src/translations.d.ts.map +1 -1
  91. package/dist/types/src/types/PresenterCapabilities.d.ts.map +1 -1
  92. package/dist/types/src/types/PresenterOperation.d.ts +5 -13
  93. package/dist/types/src/types/PresenterOperation.d.ts.map +1 -1
  94. package/dist/types/src/useExitPresenter.d.ts +6 -1
  95. package/dist/types/src/useExitPresenter.d.ts.map +1 -1
  96. package/dist/types/tsconfig.tsbuildinfo +1 -1
  97. package/dx.config.ts +37 -0
  98. package/package.json +39 -31
  99. package/src/PresenterPlugin.test.ts +1 -1
  100. package/src/PresenterPlugin.tsx +9 -2
  101. package/src/capabilities/app-graph-builder.ts +9 -24
  102. package/src/capabilities/index.ts +6 -0
  103. package/src/capabilities/markdown-extension.ts +45 -0
  104. package/src/capabilities/operation-handler.ts +16 -0
  105. package/src/capabilities/react-surface.tsx +14 -23
  106. package/src/capabilities/settings.ts +2 -2
  107. package/src/components/Markdown/Panel.stories.tsx +1 -1
  108. package/src/components/Presenter/PresentationShell.tsx +99 -0
  109. package/src/components/Presenter/index.ts +1 -0
  110. package/src/components/RevealPlayer/RevealPlayer.tsx +11 -12
  111. package/src/components/index.ts +0 -4
  112. package/src/containers/CollectionArticle/CollectionArticle.tsx +44 -0
  113. package/src/containers/CollectionArticle/index.ts +5 -0
  114. package/src/containers/DocumentArticle/DocumentArticle.tsx +34 -0
  115. package/src/containers/DocumentArticle/index.ts +5 -0
  116. package/src/containers/{MarkdownSlide/MarkdownSlide.tsx → SlideArticle/SlideArticle.tsx} +4 -5
  117. package/src/containers/SlideArticle/index.ts +5 -0
  118. package/src/containers/index.ts +3 -3
  119. package/src/meta.ts +2 -30
  120. package/src/operations/index.ts +7 -0
  121. package/src/operations/toggle-presentation.ts +59 -0
  122. package/src/paths.ts +8 -0
  123. package/src/translations.ts +1 -2
  124. package/src/types/PresenterCapabilities.ts +3 -1
  125. package/src/types/PresenterOperation.ts +4 -3
  126. package/src/useExitPresenter.ts +13 -26
  127. package/dist/lib/neutral/CollectionPresenterArticle-DFREOQTG.mjs.map +0 -7
  128. package/dist/lib/neutral/DocumentPresenterContainer-KCDZ2O2C.mjs +0 -24
  129. package/dist/lib/neutral/DocumentPresenterContainer-KCDZ2O2C.mjs.map +0 -7
  130. package/dist/lib/neutral/MarkdownSlide-WXILOIBE.mjs +0 -18
  131. package/dist/lib/neutral/MarkdownSlide-WXILOIBE.mjs.map +0 -7
  132. package/dist/lib/neutral/PresenterSettings-2G4XD4QY.mjs +0 -25
  133. package/dist/lib/neutral/PresenterSettings-2G4XD4QY.mjs.map +0 -7
  134. package/dist/lib/neutral/app-graph-builder-DIEDSRPX.mjs.map +0 -7
  135. package/dist/lib/neutral/chunk-PPL2FF6R.mjs +0 -38
  136. package/dist/lib/neutral/chunk-PPL2FF6R.mjs.map +0 -7
  137. package/dist/lib/neutral/chunk-V323QBC3.mjs +0 -41
  138. package/dist/lib/neutral/chunk-V323QBC3.mjs.map +0 -7
  139. package/dist/lib/neutral/chunk-VVALMI52.mjs.map +0 -7
  140. package/dist/lib/neutral/react-surface-SPJGAJIF.mjs +0 -54
  141. package/dist/lib/neutral/react-surface-SPJGAJIF.mjs.map +0 -7
  142. package/dist/types/src/components/PresenterSettings/PresenterSettings.d.ts +0 -6
  143. package/dist/types/src/components/PresenterSettings/PresenterSettings.d.ts.map +0 -1
  144. package/dist/types/src/components/PresenterSettings/PresenterSettings.stories.d.ts +0 -23
  145. package/dist/types/src/components/PresenterSettings/PresenterSettings.stories.d.ts.map +0 -1
  146. package/dist/types/src/components/PresenterSettings/index.d.ts +0 -2
  147. package/dist/types/src/containers/CollectionPresenterArticle/CollectionPresenterArticle.d.ts +0 -6
  148. package/dist/types/src/containers/CollectionPresenterArticle/CollectionPresenterArticle.d.ts.map +0 -1
  149. package/dist/types/src/containers/CollectionPresenterArticle/index.d.ts +0 -2
  150. package/dist/types/src/containers/CollectionPresenterArticle/index.d.ts.map +0 -1
  151. package/dist/types/src/containers/DocumentPresenterContainer/DocumentPresenterContainer.d.ts +0 -6
  152. package/dist/types/src/containers/DocumentPresenterContainer/DocumentPresenterContainer.d.ts.map +0 -1
  153. package/dist/types/src/containers/DocumentPresenterContainer/index.d.ts +0 -2
  154. package/dist/types/src/containers/DocumentPresenterContainer/index.d.ts.map +0 -1
  155. package/dist/types/src/containers/MarkdownSlide/MarkdownSlide.d.ts +0 -8
  156. package/dist/types/src/containers/MarkdownSlide/MarkdownSlide.d.ts.map +0 -1
  157. package/dist/types/src/containers/MarkdownSlide/index.d.ts +0 -2
  158. package/dist/types/src/containers/MarkdownSlide/index.d.ts.map +0 -1
  159. package/src/components/PresenterSettings/PresenterSettings.stories.tsx +0 -32
  160. package/src/components/PresenterSettings/PresenterSettings.tsx +0 -31
  161. package/src/components/PresenterSettings/index.ts +0 -5
  162. package/src/containers/CollectionPresenterArticle/CollectionPresenterArticle.tsx +0 -50
  163. package/src/containers/CollectionPresenterArticle/index.ts +0 -5
  164. package/src/containers/DocumentPresenterContainer/DocumentPresenterContainer.tsx +0 -24
  165. package/src/containers/DocumentPresenterContainer/index.ts +0 -5
  166. package/src/containers/MarkdownSlide/index.ts +0 -5
package/dx.config.ts ADDED
@@ -0,0 +1,37 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Config2 } from '@dxos/app-framework/config';
6
+ import { trim } from '@dxos/util';
7
+
8
+ export default Config2.make({
9
+ plugin: {
10
+ key: 'org.dxos.plugin.presenter',
11
+ name: 'Presenter',
12
+ author: 'DXOS',
13
+ description: trim`
14
+ Transform existing workspace objects into interactive presentation slideshows without
15
+ duplicating content. Markdown documents are split into slides on horizontal \`---\`
16
+ separators and rendered with Reveal.js, giving authors a familiar authoring surface
17
+ with full Markdown syntax. Collections can optionally be presented slide-by-slide,
18
+ with each member object rendered by its own plugin surface.
19
+
20
+ Activate presentation mode for any supported object via the action menu or the
21
+ Shift+Cmd+P (macOS) / Shift+Alt+P (Windows/Linux) keyboard shortcut. The presenter
22
+ opens in a solo fullscreen layout managed by the Deck plugin, keeping the rest of the
23
+ workspace undisturbed.
24
+
25
+ Navigation controls and a page-number indicator are shown during collection
26
+ slideshows. Pressing Escape exits fullscreen and returns to the originating document
27
+ or collection view. All navigation is keyboard-accessible.
28
+
29
+ Collaboration is inherent: because slides are backed by live ECHO objects, edits made
30
+ by any peer in the workspace are immediately reflected in an open presentation without
31
+ any manual refresh.
32
+ `,
33
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-presenter',
34
+ icon: { key: 'ph--presentation--regular', hue: 'indigo' },
35
+ spec: 'PLUGIN.mdl',
36
+ },
37
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-presenter",
3
- "version": "0.9.0",
3
+ "version": "0.9.1-staging.ee54ba693a",
4
4
  "description": "Braneframe presenter plugin",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -33,6 +33,11 @@
33
33
  "types": "./dist/types/src/meta.d.ts",
34
34
  "default": "./dist/lib/neutral/meta.mjs"
35
35
  },
36
+ "#operations": {
37
+ "source": "./src/operations/index.ts",
38
+ "types": "./dist/types/src/operations/index.d.ts",
39
+ "default": "./dist/lib/neutral/operations/index.mjs"
40
+ },
36
41
  "#plugin": {
37
42
  "source": {
38
43
  "workerd": "./src/PresenterPlugin.workerd.ts",
@@ -77,10 +82,12 @@
77
82
  "types": "dist/types/src/index.d.ts",
78
83
  "files": [
79
84
  "dist",
85
+ "dx.config.ts",
80
86
  "src",
81
87
  "PLUGIN.mdl"
82
88
  ],
83
89
  "dependencies": {
90
+ "@codemirror/state": "^6.6.0",
84
91
  "@effect-atom/atom-react": "^0.5.0",
85
92
  "hastscript": "^7.1.0",
86
93
  "highlight.js": "^11.9.0",
@@ -92,45 +99,46 @@
92
99
  "remark-frontmatter": "^5.0.0",
93
100
  "remark-parse-frontmatter": "^1.0.3",
94
101
  "reveal.js": "^5.1.0",
95
- "@dxos/app-framework": "0.9.0",
96
- "@dxos/app-toolkit": "0.9.0",
97
- "@dxos/echo": "0.9.0",
98
- "@dxos/effect": "0.9.0",
99
- "@dxos/compute": "0.9.0",
100
- "@dxos/plugin-client": "0.9.0",
101
- "@dxos/keys": "0.9.0",
102
- "@dxos/plugin-deck": "0.9.0",
103
- "@dxos/plugin-markdown": "0.9.0",
104
- "@dxos/react-ui-form": "0.9.0",
105
- "@dxos/plugin-graph": "0.9.0",
106
- "@dxos/util": "0.9.0",
107
- "@dxos/react-ui-attention": "0.9.0",
108
- "@dxos/log": "0.9.0"
102
+ "@dxos/app-framework": "0.9.1-staging.ee54ba693a",
103
+ "@dxos/app-toolkit": "0.9.1-staging.ee54ba693a",
104
+ "@dxos/keys": "0.9.1-staging.ee54ba693a",
105
+ "@dxos/log": "0.9.1-staging.ee54ba693a",
106
+ "@dxos/echo": "0.9.1-staging.ee54ba693a",
107
+ "@dxos/plugin-client": "0.9.1-staging.ee54ba693a",
108
+ "@dxos/plugin-deck": "0.9.1-staging.ee54ba693a",
109
+ "@dxos/plugin-graph": "0.9.1-staging.ee54ba693a",
110
+ "@dxos/compute": "0.9.1-staging.ee54ba693a",
111
+ "@dxos/plugin-markdown": "0.9.1-staging.ee54ba693a",
112
+ "@dxos/react-ui-attention": "0.9.1-staging.ee54ba693a",
113
+ "@dxos/ui-editor": "0.9.1-staging.ee54ba693a",
114
+ "@dxos/react-ui-form": "0.9.1-staging.ee54ba693a",
115
+ "@dxos/util": "0.9.1-staging.ee54ba693a",
116
+ "@dxos/effect": "0.9.1-staging.ee54ba693a"
109
117
  },
110
118
  "devDependencies": {
111
119
  "@effect-atom/atom-react": "^0.5.0",
112
- "@effect/platform": "0.96.1",
113
- "@types/react": "~19.2.7",
120
+ "@effect/platform": "0.96.2",
121
+ "@types/react": "~19.2.17",
114
122
  "@types/react-dom": "~19.2.3",
115
123
  "@types/reveal.js": "^5.0.3",
116
- "effect": "3.21.3",
117
- "react": "~19.2.3",
118
- "react-dom": "~19.2.3",
124
+ "effect": "3.21.4",
125
+ "react": "~19.2.7",
126
+ "react-dom": "~19.2.7",
119
127
  "vite": "^8.0.16",
120
- "@dxos/plugin-testing": "0.9.0",
121
- "@dxos/random": "0.9.0",
122
- "@dxos/react-ui": "0.9.0",
123
- "@dxos/storybook-utils": "0.9.0",
124
- "@dxos/ui-theme": "0.9.0"
128
+ "@dxos/plugin-testing": "0.9.1-staging.ee54ba693a",
129
+ "@dxos/react-ui": "0.9.1-staging.ee54ba693a",
130
+ "@dxos/storybook-utils": "0.9.1-staging.ee54ba693a",
131
+ "@dxos/random": "0.9.1-staging.ee54ba693a",
132
+ "@dxos/ui-theme": "0.9.1-staging.ee54ba693a"
125
133
  },
126
134
  "peerDependencies": {
127
135
  "@effect-atom/atom-react": "^0.5.0",
128
- "@effect/platform": "0.96.1",
129
- "effect": "3.21.3",
130
- "react": "~19.2.3",
131
- "react-dom": "~19.2.3",
132
- "@dxos/react-ui": "0.9.0",
133
- "@dxos/ui-theme": "0.9.0"
136
+ "@effect/platform": "0.96.2",
137
+ "effect": "3.21.4",
138
+ "react": "~19.2.7",
139
+ "react-dom": "~19.2.7",
140
+ "@dxos/react-ui": "0.9.1-staging.ee54ba693a",
141
+ "@dxos/ui-theme": "0.9.1-staging.ee54ba693a"
134
142
  },
135
143
  "publishConfig": {
136
144
  "access": "public"
@@ -10,7 +10,7 @@ import { PresenterPlugin } from '#plugin';
10
10
 
11
11
  import { meta } from './meta';
12
12
 
13
- const moduleId = (name: string) => `${meta.id}.module.${name}`;
13
+ const moduleId = (name: string) => `${meta.profile.key}.module.${name}`;
14
14
 
15
15
  describe('PresenterPlugin', () => {
16
16
  test('modules activate on the expected events', async ({ expect }) => {
@@ -4,8 +4,9 @@
4
4
 
5
5
  import { Plugin } from '@dxos/app-framework';
6
6
  import { AppPlugin } from '@dxos/app-toolkit';
7
+ import { MarkdownEvents } from '@dxos/plugin-markdown';
7
8
 
8
- import { AppGraphBuilder, PresenterSettings, ReactSurface } from '#capabilities';
9
+ import { AppGraphBuilder, MarkdownExtension, OperationHandler, PresenterSettings, ReactSurface } from '#capabilities';
9
10
  import { meta } from '#meta';
10
11
  import { translations } from '#translations';
11
12
 
@@ -17,11 +18,17 @@ import pluginSpec from '../PLUGIN.mdl?raw';
17
18
 
18
19
  export const PresenterPlugin = Plugin.define(meta).pipe(
19
20
  AppPlugin.addAppGraphModule({ activate: AppGraphBuilder }),
21
+ AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),
20
22
  AppPlugin.addSettingsModule({ activate: PresenterSettings }),
21
23
  AppPlugin.addSurfaceModule({ activate: ReactSurface }),
24
+ Plugin.addModule({
25
+ id: `${meta.profile.key}/markdown`,
26
+ activatesOn: MarkdownEvents.SetupExtensions,
27
+ activate: MarkdownExtension,
28
+ }),
22
29
  AppPlugin.addTranslationsModule({ translations }),
23
30
  AppPlugin.addPluginAssetModule({
24
- asset: { pluginId: meta.id, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
31
+ asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
25
32
  }),
26
33
  Plugin.make,
27
34
  );
@@ -2,14 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { type Atom } from '@effect-atom/atom-react';
5
6
  import * as Effect from 'effect/Effect';
6
7
  import * as Option from 'effect/Option';
7
8
 
8
- import { Capabilities, Capability } from '@dxos/app-framework';
9
- import { AppCapabilities, AppNode, LayoutOperation, getObjectPathFromObject, getSpacePath } from '@dxos/app-toolkit';
9
+ import { Capability } from '@dxos/app-framework';
10
+ import { AppCapabilities, AppNode } from '@dxos/app-toolkit';
10
11
  import { Operation } from '@dxos/compute';
11
12
  import { Collection, Obj } from '@dxos/echo';
12
- import { DeckCapabilities, DeckOperation } from '@dxos/plugin-deck';
13
13
  import { GraphBuilder, type Node, NodeMatcher } from '@dxos/plugin-graph';
14
14
  import { Markdown } from '@dxos/plugin-markdown';
15
15
  import { linkedSegment } from '@dxos/react-ui-attention';
@@ -19,9 +19,9 @@ import { PresenterOperation } from '#types';
19
19
  import { PresenterCapabilities } from '#types';
20
20
 
21
21
  /** Match nodes that can be presented (Collection or Document). */
22
- const whenPresentable = (node: Node.Node) =>
23
- Option.orElse(NodeMatcher.whenEchoType(Collection.Collection)(node), () =>
24
- NodeMatcher.whenEchoType(Markdown.Document)(node),
22
+ const whenPresentable = (node: Node.Node, get: Atom.Context) =>
23
+ Option.orElse(NodeMatcher.whenEchoType(Collection.Collection)(node, get), () =>
24
+ NodeMatcher.whenEchoType(Markdown.Document)(node, get),
25
25
  );
26
26
 
27
27
  export default Capability.makeModule(
@@ -47,7 +47,7 @@ export default Capability.makeModule(
47
47
  id: linkedSegment('presenter'),
48
48
  label: 'Presenter',
49
49
  icon: 'ph--presentation--regular',
50
- data: { type: meta.id, object },
50
+ data: { type: meta.profile.key, object },
51
51
  }),
52
52
  ]);
53
53
  },
@@ -61,30 +61,15 @@ export default Capability.makeModule(
61
61
  if (!isPresentable || !db) {
62
62
  return Effect.succeed([]);
63
63
  }
64
- const objectPath = getObjectPathFromObject(object);
65
64
 
66
65
  return Effect.succeed([
67
66
  {
68
67
  id: PresenterOperation.TogglePresentation.meta.key,
69
- // TODO(burdon): Allow function so can generate state when activated.
70
- // So can set explicit fullscreen state coordinated with current presenter state.
71
68
  data: Effect.fnUntraced(function* () {
72
- const deckState = yield* Capabilities.getAtomValue(DeckCapabilities.State);
73
- const deck = deckState.decks[deckState.activeDeck];
74
- const presenterId = `${objectPath}/${linkedSegment('presenter')}`;
75
- if (!deck?.fullscreen) {
76
- yield* Operation.invoke(DeckOperation.Adjust, {
77
- type: 'solo--fullscreen' as const,
78
- id: presenterId,
79
- });
80
- }
81
- yield* Operation.invoke(LayoutOperation.Open, {
82
- subject: [presenterId],
83
- workspace: getSpacePath(db.spaceId),
84
- });
69
+ yield* Operation.invoke(PresenterOperation.TogglePresentation, { object });
85
70
  }),
86
71
  properties: {
87
- label: ['toggle-presentation.label', { ns: meta.id }],
72
+ label: ['toggle-presentation.label', { ns: meta.profile.key }],
88
73
  icon: 'ph--presentation--regular',
89
74
  disposition: 'list-item',
90
75
  keyBinding: {
@@ -3,7 +3,13 @@
3
3
  //
4
4
 
5
5
  import { Capability } from '@dxos/app-framework';
6
+ import type { OperationHandlerSet } from '@dxos/compute';
6
7
 
7
8
  export const AppGraphBuilder = Capability.lazy('AppGraphBuilder', () => import('./app-graph-builder'));
9
+ export const MarkdownExtension = Capability.lazy('MarkdownExtension', () => import('./markdown-extension'));
10
+ export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
11
+ 'OperationHandler',
12
+ () => import('./operation-handler'),
13
+ );
8
14
  export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
9
15
  export const PresenterSettings = Capability.lazy('PresenterSettings', () => import('./settings'));
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Prec } from '@codemirror/state';
6
+ import * as Effect from 'effect/Effect';
7
+
8
+ import { Capabilities, Capability } from '@dxos/app-framework';
9
+ import { MarkdownCapabilities } from '@dxos/plugin-markdown/types';
10
+ import { keymap } from '@dxos/ui-editor';
11
+
12
+ import { PresenterOperation } from '#types';
13
+
14
+ /**
15
+ * Contributes the present shortcut (Shift+Cmd+P) to the markdown editor so presentation
16
+ * can be toggled while editing without relying on the global navtree keyboard context.
17
+ */
18
+ export default Capability.makeModule(
19
+ Effect.fnUntraced(function* () {
20
+ const capabilities = yield* Capability.Service;
21
+
22
+ return Capability.contributes(MarkdownCapabilities.ExtensionProvider, [
23
+ ({ document }) => {
24
+ if (!document) {
25
+ return undefined;
26
+ }
27
+
28
+ const { invokePromise } = capabilities.get(Capabilities.OperationInvoker);
29
+ return Prec.highest(
30
+ keymap.of([
31
+ {
32
+ key: 'Shift-Mod-p',
33
+ preventDefault: true,
34
+ stopPropagation: true,
35
+ run: () => {
36
+ void invokePromise(PresenterOperation.TogglePresentation, { object: document });
37
+ return true;
38
+ },
39
+ },
40
+ ]),
41
+ );
42
+ },
43
+ ]);
44
+ }),
45
+ );
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import type { OperationHandlerSet } from '@dxos/compute';
9
+
10
+ import { PresenterOperationHandlerSet } from '#operations';
11
+
12
+ export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
13
+ Effect.fnUntraced(function* () {
14
+ return Capability.contributes(Capabilities.OperationHandler, PresenterOperationHandlerSet);
15
+ }),
16
+ );
@@ -6,61 +6,52 @@ import * as Effect from 'effect/Effect';
6
6
  import React from 'react';
7
7
 
8
8
  import { Capabilities, Capability } from '@dxos/app-framework';
9
- import { Surface, useSettingsState } from '@dxos/app-framework/ui';
9
+ import { Surface } from '@dxos/app-framework/ui';
10
10
  import { AppSurface } from '@dxos/app-toolkit/ui';
11
11
  import { Collection, Obj } from '@dxos/echo';
12
12
  import { Markdown } from '@dxos/plugin-markdown';
13
+ import { Position } from '@dxos/util';
13
14
 
14
- import { PresenterSettings } from '#components';
15
- import { CollectionPresenterArticle, DocumentPresenterContainer, MarkdownSlide } from '#containers';
15
+ import { CollectionArticle, DocumentArticle, SlideArticle } from '#containers';
16
16
  import { meta } from '#meta';
17
- import { type Settings } from '#types';
18
17
 
19
18
  export default Capability.makeModule(() =>
20
19
  Effect.succeed(
21
20
  Capability.contributes(Capabilities.ReactSurface, [
22
21
  Surface.create({
23
22
  id: 'document',
24
- position: 'first',
25
- filter: AppSurface.predicate(
23
+ position: Position.first,
24
+ filter: Surface.makeFilter(
26
25
  AppSurface.Article,
27
- (data): data is AppSurface.ArticleData<{ type: typeof meta.id; object: Markdown.Document }> =>
26
+ (data): data is AppSurface.ArticleData<{ type: typeof meta.profile.key; object: Markdown.Document }> =>
28
27
  !!data.subject &&
29
28
  typeof data.subject === 'object' &&
30
29
  'type' in data.subject &&
31
30
  'object' in data.subject &&
32
- data.subject.type === meta.id &&
31
+ data.subject.type === meta.profile.key &&
33
32
  Obj.instanceOf(Markdown.Document, data.subject.object),
34
33
  ),
35
- component: ({ data }) => <DocumentPresenterContainer document={data.subject.object} />,
34
+ component: ({ role, data }) => <DocumentArticle role={role} subject={data.subject.object} />,
36
35
  }),
37
36
  Surface.create({
38
37
  id: 'collection',
39
- position: 'first',
40
- filter: AppSurface.predicate(
38
+ position: Position.first,
39
+ filter: Surface.makeFilter(
41
40
  AppSurface.Article,
42
- (data): data is AppSurface.ArticleData<{ type: typeof meta.id; object: Collection.Collection }> =>
41
+ (data): data is AppSurface.ArticleData<{ type: typeof meta.profile.key; object: Collection.Collection }> =>
43
42
  !!data.subject &&
44
43
  typeof data.subject === 'object' &&
45
44
  'type' in data.subject &&
46
45
  'object' in data.subject &&
47
- data.subject.type === meta.id &&
46
+ data.subject.type === meta.profile.key &&
48
47
  Obj.instanceOf(Collection.Collection, data.subject.object),
49
48
  ),
50
- component: ({ role, data }) => <CollectionPresenterArticle role={role} subject={data.subject.object} />,
49
+ component: ({ role, data }) => <CollectionArticle role={role} subject={data.subject.object} />,
51
50
  }),
52
51
  Surface.create({
53
52
  id: 'slide',
54
53
  filter: AppSurface.object(AppSurface.Slide, Markdown.Document),
55
- component: ({ data }) => <MarkdownSlide document={data.subject} />,
56
- }),
57
- Surface.create({
58
- id: 'pluginSettings',
59
- filter: AppSurface.settings(AppSurface.Article, meta.id),
60
- component: ({ data: { subject } }) => {
61
- const { settings, updateSettings } = useSettingsState<Settings.Settings>(subject.atom);
62
- return <PresenterSettings settings={settings} onSettingsChange={updateSettings} />;
63
- },
54
+ component: ({ data }) => <SlideArticle {...data} />,
64
55
  }),
65
56
  ]),
66
57
  ),
@@ -14,7 +14,7 @@ import { PresenterCapabilities, Settings } from '#types';
14
14
  export default Capability.makeModule(() =>
15
15
  Effect.sync(() => {
16
16
  const settingsAtom = createKvsStore({
17
- key: meta.id,
17
+ key: meta.profile.key,
18
18
  schema: Settings.Settings,
19
19
  defaultValue: () => ({}),
20
20
  });
@@ -22,7 +22,7 @@ export default Capability.makeModule(() =>
22
22
  return [
23
23
  Capability.contributes(PresenterCapabilities.Settings, settingsAtom),
24
24
  Capability.contributes(AppCapabilities.Settings, {
25
- prefix: meta.id,
25
+ prefix: meta.profile.key,
26
26
  schema: Settings.Settings,
27
27
  atom: settingsAtom,
28
28
  }),
@@ -14,7 +14,7 @@ import { Slide, type SlideProps } from './Slide';
14
14
 
15
15
  const DefaultStory = ({ content = '' }: SlideProps) => {
16
16
  return (
17
- <Panel classNames='bg-neutral-200'>
17
+ <Panel classNames='bg-scrim-surface'>
18
18
  <Slide content={content} />
19
19
  </Panel>
20
20
  );
@@ -0,0 +1,99 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
6
+
7
+ import { composable, composableProps } from '@dxos/react-ui';
8
+ import { mx } from '@dxos/ui-theme';
9
+
10
+ export type PresentationShellProps = PropsWithChildren<{
11
+ /** Fade-in/out duration (ms). */
12
+ fadeDuration?: number;
13
+ /** Duration the [ESC] hint remains visible (ms). */
14
+ hintDuration?: number;
15
+ /** Whether the presentation is fullscreen. */
16
+ fullscreen?: boolean;
17
+ /** Invoked once the exit fade-out has completed. */
18
+ onExit?: () => void;
19
+ }>;
20
+
21
+ /**
22
+ * Wraps presentation content with a fade-in/out transition, an ESC handler that exits in a
23
+ * single keypress (intercepting before the deck's fullscreen handler), and a transient [ESC]
24
+ * caption shown on enter.
25
+ */
26
+ export const PresentationShell = composable<HTMLDivElement, PresentationShellProps>(
27
+ ({ children, fadeDuration = 300, hintDuration = 3000, fullscreen = true, onExit, ...props }, forwardedRef) => {
28
+ const [visible, setVisible] = useState(false);
29
+ const [exiting, setExiting] = useState(false);
30
+ const [showHint, setShowHint] = useState(true);
31
+ const exitTimeout = useRef<ReturnType<typeof setTimeout>>(undefined);
32
+ // Guards against exiting twice; a state updater can't gate this since React double-invokes
33
+ // updaters in dev, which would schedule the exit (and its side effects) more than once.
34
+ const exitingRef = useRef(false);
35
+
36
+ // Fade in once mounted.
37
+ useEffect(() => {
38
+ const frame = requestAnimationFrame(() => setVisible(true));
39
+ return () => cancelAnimationFrame(frame);
40
+ }, []);
41
+
42
+ // Hide the hint after the configured duration.
43
+ useEffect(() => {
44
+ const timer = setTimeout(() => setShowHint(false), hintDuration);
45
+ return () => clearTimeout(timer);
46
+ }, [hintDuration]);
47
+
48
+ const handleExit = useCallback(() => {
49
+ if (exitingRef.current) {
50
+ return;
51
+ }
52
+ exitingRef.current = true;
53
+ setExiting(true);
54
+ setVisible(false);
55
+ exitTimeout.current = setTimeout(() => onExit?.(), fadeDuration);
56
+ }, [onExit, fadeDuration]);
57
+
58
+ useEffect(() => () => clearTimeout(exitTimeout.current), []);
59
+
60
+ // Capture ESC before the deck/reveal handlers so a single keypress exits directly.
61
+ useEffect(() => {
62
+ const handler = (event: KeyboardEvent) => {
63
+ if (event.key === 'Escape') {
64
+ event.preventDefault();
65
+ event.stopImmediatePropagation();
66
+ handleExit();
67
+ }
68
+ };
69
+
70
+ document.addEventListener('keydown', handler, { capture: true });
71
+ return () => document.removeEventListener('keydown', handler, { capture: true });
72
+ }, [handleExit]);
73
+
74
+ return (
75
+ <div
76
+ {...composableProps(props, {
77
+ classNames: [
78
+ 'relative grow overflow-hidden bg-black transition-opacity',
79
+ visible && !exiting ? 'opacity-100' : 'opacity-0',
80
+ ],
81
+ style: { transitionDuration: `${fadeDuration}ms` },
82
+ })}
83
+ ref={forwardedRef}
84
+ >
85
+ {children}
86
+ {fullscreen && (
87
+ <div
88
+ className={mx(
89
+ 'absolute top-4 left-4 z-[300] transition-opacity duration-500',
90
+ showHint && !exiting ? 'opacity-100' : 'opacity-0',
91
+ )}
92
+ >
93
+ <span className='rounded-sm bg-white/10 px-2 py-1 font-mono text-sm text-white/70'>[ESC]</span>
94
+ </div>
95
+ )}
96
+ </div>
97
+ );
98
+ },
99
+ );
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './Layout';
6
6
  export * from './Pager';
7
+ export * from './PresentationShell';
@@ -17,6 +17,7 @@ import RevealMarkdown from 'reveal.js/plugin/markdown/plugin.js';
17
17
 
18
18
  import { useAsyncEffect } from '@dxos/react-ui';
19
19
  import { composable, composableProps } from '@dxos/react-ui';
20
+ import { mx } from '@dxos/ui-theme';
20
21
 
21
22
  const styles = `
22
23
  <style type="text/css">
@@ -148,23 +149,21 @@ export const RevealPlayer = composable<HTMLDivElement, RevealProps>(
148
149
  return (
149
150
  <div
150
151
  {...composableProps(props, {
151
- classNames: ['dx-container grid place-items-center bg-black', fullscreen && 'absolute inset-0'],
152
+ classNames: ['dx-container grid place-items-center bg-scrim-surface', fullscreen && 'absolute inset-0'],
152
153
  })}
153
154
  ref={forwardedRef}
154
155
  >
155
156
  <div className='relative aspect-video h-full w-full h-auto max-h-full overflow-hidden'>
156
157
  <div ref={deckDivRef} className='absolute inset-0 reveal'>
157
- {/* NOTE: Must be in head. */}
158
- <style>
159
- <link rel='preconnect' href='https://fonts.googleapis.com' />
160
- <link rel='preconnect' href='https://fonts.gstatic.com' {...{ crossOrigin: '' }} />
161
- <link
162
- rel='stylesheet'
163
- href='https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap'
164
- />
165
- </style>
166
- <div className='slides'>
167
- <div className='text-center!' />
158
+ {/* React hoists these to <head>; they must not be wrapped in <style> (which only renders CSS text). */}
159
+ <link rel='preconnect' href='https://fonts.gstatic.com' {...{ crossOrigin: '' }} />
160
+ <link rel='preconnect' href='https://fonts.googleapis.com' />
161
+ <link
162
+ rel='stylesheet'
163
+ href='https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap'
164
+ />
165
+ <div className={mx('slides', !fullscreen && 'bg-base-surface p-8')}>
166
+ <div />
168
167
  <section {...{ 'data-markdown': [] }}>
169
168
  <textarea {...{ 'data-template': true }} defaultValue={[styles, content].join('\n')}></textarea>
170
169
  </section>
@@ -2,10 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type ComponentType, lazy } from 'react';
6
-
7
5
  export * from './Markdown';
8
6
  export * from './Presenter';
9
7
  export * from './RevealPlayer';
10
-
11
- export const PresenterSettings: ComponentType<any> = lazy(() => import('./PresenterSettings'));
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { useContext, useState } from 'react';
6
+
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { AppSurface } from '@dxos/app-toolkit/ui';
9
+ import { type Collection, Obj } from '@dxos/echo';
10
+ import { Panel } from '@dxos/react-ui';
11
+
12
+ import { PageNumber, Pager, PresentationShell, Layout as PresenterLayout } from '#components';
13
+ import { PresenterContext } from '#types';
14
+
15
+ import { useExitPresenter } from '../../useExitPresenter';
16
+
17
+ export type CollectionArticleProps = AppSurface.ObjectArticleProps<Collection.Collection>;
18
+
19
+ export const CollectionArticle = ({ role, subject: collection }: CollectionArticleProps) => {
20
+ const [slide, setSlide] = useState(0);
21
+ const { running } = useContext(PresenterContext);
22
+ const handleExit = useExitPresenter(collection);
23
+
24
+ return (
25
+ <Panel.Root role={role} classNames='relative'>
26
+ <Panel.Content asChild>
27
+ <PresentationShell onExit={handleExit}>
28
+ <PresenterLayout
29
+ bottomRight={<PageNumber index={slide} count={collection.objects.length} />}
30
+ bottomLeft={<Pager index={slide} count={collection.objects.length} keys={running} onChange={setSlide} />}
31
+ >
32
+ <Surface.Surface
33
+ type={AppSurface.Slide}
34
+ data={{
35
+ subject: collection.objects[slide],
36
+ attendableId: Obj.getURI(collection),
37
+ }}
38
+ />
39
+ </PresenterLayout>
40
+ </PresentationShell>
41
+ </Panel.Content>
42
+ </Panel.Root>
43
+ );
44
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export { CollectionArticle as default } from './CollectionArticle';