@dxos/plugin-presenter 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112

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 (104) hide show
  1. package/dist/lib/neutral/{CollectionPresenterArticle-DFREOQTG.mjs → CollectionPresenterArticle-ZRYWBX2H.mjs} +7 -6
  2. package/dist/lib/neutral/CollectionPresenterArticle-ZRYWBX2H.mjs.map +7 -0
  3. package/dist/lib/neutral/{DocumentPresenterContainer-KCDZ2O2C.mjs → DocumentPresenterContainer-5QF4P736.mjs} +7 -6
  4. package/dist/lib/neutral/DocumentPresenterContainer-5QF4P736.mjs.map +7 -0
  5. package/dist/lib/neutral/PresenterPlugin.mjs +10 -3
  6. package/dist/lib/neutral/PresenterPlugin.mjs.map +4 -4
  7. package/dist/lib/neutral/{PresenterSettings-2G4XD4QY.mjs → PresenterSettings-4YFP4K5G.mjs} +3 -3
  8. package/dist/lib/neutral/{PresenterSettings-2G4XD4QY.mjs.map → PresenterSettings-4YFP4K5G.mjs.map} +3 -3
  9. package/dist/lib/neutral/{app-graph-builder-DIEDSRPX.mjs → app-graph-builder-JMQVBFG2.mjs} +7 -23
  10. package/dist/lib/neutral/app-graph-builder-JMQVBFG2.mjs.map +7 -0
  11. package/dist/lib/neutral/capabilities/index.mjs +7 -3
  12. package/dist/lib/neutral/capabilities/index.mjs.map +3 -3
  13. package/dist/lib/neutral/chunk-63IF7OXT.mjs +19 -0
  14. package/dist/lib/neutral/chunk-63IF7OXT.mjs.map +7 -0
  15. package/dist/lib/neutral/{chunk-VVALMI52.mjs → chunk-H5JLSLAO.mjs} +7 -3
  16. package/dist/lib/neutral/chunk-H5JLSLAO.mjs.map +7 -0
  17. package/dist/lib/neutral/chunk-MSQDHOPV.mjs +47 -0
  18. package/dist/lib/neutral/chunk-MSQDHOPV.mjs.map +7 -0
  19. package/dist/lib/neutral/components/index.mjs +87 -18
  20. package/dist/lib/neutral/components/index.mjs.map +4 -4
  21. package/dist/lib/neutral/containers/index.mjs +2 -2
  22. package/dist/lib/neutral/index.mjs +2 -2
  23. package/dist/lib/neutral/markdown-extension-HGLRVZDF.mjs +37 -0
  24. package/dist/lib/neutral/markdown-extension-HGLRVZDF.mjs.map +7 -0
  25. package/dist/lib/neutral/meta.json +1 -1
  26. package/dist/lib/neutral/meta.mjs +1 -1
  27. package/dist/lib/neutral/operation-handler-3ZESW5AK.mjs +13 -0
  28. package/dist/lib/neutral/operation-handler-3ZESW5AK.mjs.map +7 -0
  29. package/dist/lib/neutral/operations/index.mjs +9 -0
  30. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  31. package/dist/lib/neutral/plugin.mjs +1 -1
  32. package/dist/lib/neutral/{react-surface-SPJGAJIF.mjs → react-surface-YGBE3TDK.mjs} +4 -4
  33. package/dist/lib/neutral/react-surface-YGBE3TDK.mjs.map +7 -0
  34. package/dist/lib/neutral/{settings-R6LRDAAK.mjs → settings-DBV7N5HT.mjs} +3 -3
  35. package/dist/lib/neutral/{settings-R6LRDAAK.mjs.map → settings-DBV7N5HT.mjs.map} +3 -3
  36. package/dist/lib/neutral/toggle-presentation-PH7ZJJDD.mjs +60 -0
  37. package/dist/lib/neutral/toggle-presentation-PH7ZJJDD.mjs.map +7 -0
  38. package/dist/lib/neutral/translations.mjs +1 -1
  39. package/dist/lib/neutral/translations.mjs.map +3 -3
  40. package/dist/lib/neutral/types/index.mjs +1 -1
  41. package/dist/types/dx.config.d.ts +28 -0
  42. package/dist/types/dx.config.d.ts.map +1 -0
  43. package/dist/types/src/PresenterPlugin.d.ts.map +1 -1
  44. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  45. package/dist/types/src/capabilities/index.d.ts +6 -3
  46. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  47. package/dist/types/src/capabilities/markdown-extension.d.ts +9 -0
  48. package/dist/types/src/capabilities/markdown-extension.d.ts.map +1 -0
  49. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  50. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  51. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  52. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  53. package/dist/types/src/components/Presenter/PresentationShell.d.ts +18 -0
  54. package/dist/types/src/components/Presenter/PresentationShell.d.ts.map +1 -0
  55. package/dist/types/src/components/Presenter/index.d.ts +1 -0
  56. package/dist/types/src/components/Presenter/index.d.ts.map +1 -1
  57. package/dist/types/src/containers/CollectionPresenterArticle/CollectionPresenterArticle.d.ts.map +1 -1
  58. package/dist/types/src/containers/DocumentPresenterContainer/DocumentPresenterContainer.d.ts.map +1 -1
  59. package/dist/types/src/meta.d.ts +28 -2
  60. package/dist/types/src/meta.d.ts.map +1 -1
  61. package/dist/types/src/operations/index.d.ts +3 -0
  62. package/dist/types/src/operations/index.d.ts.map +1 -0
  63. package/dist/types/src/operations/toggle-presentation.d.ts +10 -0
  64. package/dist/types/src/operations/toggle-presentation.d.ts.map +1 -0
  65. package/dist/types/src/paths.d.ts +3 -0
  66. package/dist/types/src/paths.d.ts.map +1 -0
  67. package/dist/types/src/types/PresenterCapabilities.d.ts.map +1 -1
  68. package/dist/types/src/types/PresenterOperation.d.ts +2 -1
  69. package/dist/types/src/types/PresenterOperation.d.ts.map +1 -1
  70. package/dist/types/src/useExitPresenter.d.ts +6 -1
  71. package/dist/types/src/useExitPresenter.d.ts.map +1 -1
  72. package/dist/types/tsconfig.tsbuildinfo +1 -1
  73. package/dx.config.ts +37 -0
  74. package/package.json +30 -22
  75. package/src/PresenterPlugin.test.ts +1 -1
  76. package/src/PresenterPlugin.tsx +9 -2
  77. package/src/capabilities/app-graph-builder.ts +5 -21
  78. package/src/capabilities/index.ts +6 -0
  79. package/src/capabilities/markdown-extension.ts +45 -0
  80. package/src/capabilities/operation-handler.ts +16 -0
  81. package/src/capabilities/react-surface.tsx +7 -7
  82. package/src/capabilities/settings.ts +2 -2
  83. package/src/components/Presenter/PresentationShell.tsx +96 -0
  84. package/src/components/Presenter/index.ts +1 -0
  85. package/src/components/PresenterSettings/PresenterSettings.tsx +2 -2
  86. package/src/containers/CollectionPresenterArticle/CollectionPresenterArticle.tsx +14 -20
  87. package/src/containers/DocumentPresenterContainer/DocumentPresenterContainer.tsx +4 -2
  88. package/src/meta.ts +2 -30
  89. package/src/operations/index.ts +7 -0
  90. package/src/operations/toggle-presentation.ts +59 -0
  91. package/src/paths.ts +8 -0
  92. package/src/translations.ts +1 -1
  93. package/src/types/PresenterCapabilities.ts +3 -1
  94. package/src/types/PresenterOperation.ts +3 -2
  95. package/src/useExitPresenter.ts +13 -26
  96. package/dist/lib/neutral/CollectionPresenterArticle-DFREOQTG.mjs.map +0 -7
  97. package/dist/lib/neutral/DocumentPresenterContainer-KCDZ2O2C.mjs.map +0 -7
  98. package/dist/lib/neutral/app-graph-builder-DIEDSRPX.mjs.map +0 -7
  99. package/dist/lib/neutral/chunk-PPL2FF6R.mjs +0 -38
  100. package/dist/lib/neutral/chunk-PPL2FF6R.mjs.map +0 -7
  101. package/dist/lib/neutral/chunk-V323QBC3.mjs +0 -41
  102. package/dist/lib/neutral/chunk-V323QBC3.mjs.map +0 -7
  103. package/dist/lib/neutral/chunk-VVALMI52.mjs.map +0 -7
  104. package/dist/lib/neutral/react-surface-SPJGAJIF.mjs.map +0 -7
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.8.4-staging.60fe92afc8",
3
+ "version": "0.9.1-main.c7dcc2e112",
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,20 +99,21 @@
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.8.4-staging.60fe92afc8",
96
- "@dxos/app-toolkit": "0.8.4-staging.60fe92afc8",
97
- "@dxos/compute": "0.8.4-staging.60fe92afc8",
98
- "@dxos/keys": "0.8.4-staging.60fe92afc8",
99
- "@dxos/echo": "0.8.4-staging.60fe92afc8",
100
- "@dxos/effect": "0.8.4-staging.60fe92afc8",
101
- "@dxos/log": "0.8.4-staging.60fe92afc8",
102
- "@dxos/plugin-client": "0.8.4-staging.60fe92afc8",
103
- "@dxos/plugin-deck": "0.8.4-staging.60fe92afc8",
104
- "@dxos/plugin-graph": "0.8.4-staging.60fe92afc8",
105
- "@dxos/react-ui-attention": "0.8.4-staging.60fe92afc8",
106
- "@dxos/react-ui-form": "0.8.4-staging.60fe92afc8",
107
- "@dxos/util": "0.8.4-staging.60fe92afc8",
108
- "@dxos/plugin-markdown": "0.8.4-staging.60fe92afc8"
102
+ "@dxos/app-toolkit": "0.9.1-main.c7dcc2e112",
103
+ "@dxos/app-framework": "0.9.1-main.c7dcc2e112",
104
+ "@dxos/compute": "0.9.1-main.c7dcc2e112",
105
+ "@dxos/echo": "0.9.1-main.c7dcc2e112",
106
+ "@dxos/keys": "0.9.1-main.c7dcc2e112",
107
+ "@dxos/effect": "0.9.1-main.c7dcc2e112",
108
+ "@dxos/plugin-client": "0.9.1-main.c7dcc2e112",
109
+ "@dxos/log": "0.9.1-main.c7dcc2e112",
110
+ "@dxos/plugin-deck": "0.9.1-main.c7dcc2e112",
111
+ "@dxos/plugin-graph": "0.9.1-main.c7dcc2e112",
112
+ "@dxos/plugin-markdown": "0.9.1-main.c7dcc2e112",
113
+ "@dxos/react-ui-attention": "0.9.1-main.c7dcc2e112",
114
+ "@dxos/react-ui-form": "0.9.1-main.c7dcc2e112",
115
+ "@dxos/ui-editor": "0.9.1-main.c7dcc2e112",
116
+ "@dxos/util": "0.9.1-main.c7dcc2e112"
109
117
  },
110
118
  "devDependencies": {
111
119
  "@effect-atom/atom-react": "^0.5.0",
@@ -117,11 +125,11 @@
117
125
  "react": "~19.2.3",
118
126
  "react-dom": "~19.2.3",
119
127
  "vite": "^8.0.16",
120
- "@dxos/random": "0.8.4-staging.60fe92afc8",
121
- "@dxos/plugin-testing": "0.8.4-staging.60fe92afc8",
122
- "@dxos/react-ui": "0.8.4-staging.60fe92afc8",
123
- "@dxos/storybook-utils": "0.8.4-staging.60fe92afc8",
124
- "@dxos/ui-theme": "0.8.4-staging.60fe92afc8"
128
+ "@dxos/plugin-testing": "0.9.1-main.c7dcc2e112",
129
+ "@dxos/random": "0.9.1-main.c7dcc2e112",
130
+ "@dxos/react-ui": "0.9.1-main.c7dcc2e112",
131
+ "@dxos/ui-theme": "0.9.1-main.c7dcc2e112",
132
+ "@dxos/storybook-utils": "0.9.1-main.c7dcc2e112"
125
133
  },
126
134
  "peerDependencies": {
127
135
  "@effect-atom/atom-react": "^0.5.0",
@@ -129,8 +137,8 @@
129
137
  "effect": "3.21.3",
130
138
  "react": "~19.2.3",
131
139
  "react-dom": "~19.2.3",
132
- "@dxos/ui-theme": "0.8.4-staging.60fe92afc8",
133
- "@dxos/react-ui": "0.8.4-staging.60fe92afc8"
140
+ "@dxos/ui-theme": "0.9.1-main.c7dcc2e112",
141
+ "@dxos/react-ui": "0.9.1-main.c7dcc2e112"
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
  );
@@ -5,11 +5,10 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
  import * as Option from 'effect/Option';
7
7
 
8
- import { Capabilities, Capability } from '@dxos/app-framework';
9
- import { AppCapabilities, AppNode, LayoutOperation, getObjectPathFromObject, getSpacePath } from '@dxos/app-toolkit';
8
+ import { Capability } from '@dxos/app-framework';
9
+ import { AppCapabilities, AppNode } from '@dxos/app-toolkit';
10
10
  import { Operation } from '@dxos/compute';
11
11
  import { Collection, Obj } from '@dxos/echo';
12
- import { DeckCapabilities, DeckOperation } from '@dxos/plugin-deck';
13
12
  import { GraphBuilder, type Node, NodeMatcher } from '@dxos/plugin-graph';
14
13
  import { Markdown } from '@dxos/plugin-markdown';
15
14
  import { linkedSegment } from '@dxos/react-ui-attention';
@@ -47,7 +46,7 @@ export default Capability.makeModule(
47
46
  id: linkedSegment('presenter'),
48
47
  label: 'Presenter',
49
48
  icon: 'ph--presentation--regular',
50
- data: { type: meta.id, object },
49
+ data: { type: meta.profile.key, object },
51
50
  }),
52
51
  ]);
53
52
  },
@@ -61,30 +60,15 @@ export default Capability.makeModule(
61
60
  if (!isPresentable || !db) {
62
61
  return Effect.succeed([]);
63
62
  }
64
- const objectPath = getObjectPathFromObject(object);
65
63
 
66
64
  return Effect.succeed([
67
65
  {
68
66
  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
67
  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
- });
68
+ yield* Operation.invoke(PresenterOperation.TogglePresentation, { object });
85
69
  }),
86
70
  properties: {
87
- label: ['toggle-presentation.label', { ns: meta.id }],
71
+ label: ['toggle-presentation.label', { ns: meta.profile.key }],
88
72
  icon: 'ph--presentation--regular',
89
73
  disposition: 'list-item',
90
74
  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
+ );
@@ -22,14 +22,14 @@ export default Capability.makeModule(() =>
22
22
  Surface.create({
23
23
  id: 'document',
24
24
  position: 'first',
25
- filter: AppSurface.predicate(
25
+ filter: Surface.makeFilter(
26
26
  AppSurface.Article,
27
- (data): data is AppSurface.ArticleData<{ type: typeof meta.id; object: Markdown.Document }> =>
27
+ (data): data is AppSurface.ArticleData<{ type: typeof meta.profile.key; object: Markdown.Document }> =>
28
28
  !!data.subject &&
29
29
  typeof data.subject === 'object' &&
30
30
  'type' in data.subject &&
31
31
  'object' in data.subject &&
32
- data.subject.type === meta.id &&
32
+ data.subject.type === meta.profile.key &&
33
33
  Obj.instanceOf(Markdown.Document, data.subject.object),
34
34
  ),
35
35
  component: ({ data }) => <DocumentPresenterContainer document={data.subject.object} />,
@@ -37,14 +37,14 @@ export default Capability.makeModule(() =>
37
37
  Surface.create({
38
38
  id: 'collection',
39
39
  position: 'first',
40
- filter: AppSurface.predicate(
40
+ filter: Surface.makeFilter(
41
41
  AppSurface.Article,
42
- (data): data is AppSurface.ArticleData<{ type: typeof meta.id; object: Collection.Collection }> =>
42
+ (data): data is AppSurface.ArticleData<{ type: typeof meta.profile.key; object: Collection.Collection }> =>
43
43
  !!data.subject &&
44
44
  typeof data.subject === 'object' &&
45
45
  'type' in data.subject &&
46
46
  'object' in data.subject &&
47
- data.subject.type === meta.id &&
47
+ data.subject.type === meta.profile.key &&
48
48
  Obj.instanceOf(Collection.Collection, data.subject.object),
49
49
  ),
50
50
  component: ({ role, data }) => <CollectionPresenterArticle role={role} subject={data.subject.object} />,
@@ -56,7 +56,7 @@ export default Capability.makeModule(() =>
56
56
  }),
57
57
  Surface.create({
58
58
  id: 'pluginSettings',
59
- filter: AppSurface.settings(AppSurface.Article, meta.id),
59
+ filter: AppSurface.settings(AppSurface.Article, meta.profile.key),
60
60
  component: ({ data: { subject } }) => {
61
61
  const { settings, updateSettings } = useSettingsState<Settings.Settings>(subject.atom);
62
62
  return <PresenterSettings settings={settings} onSettingsChange={updateSettings} />;
@@ -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
  }),
@@ -0,0 +1,96 @@
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
+ /** Invoked once the exit fade-out has completed. */
12
+ onExit?: () => void;
13
+ /** Fade-in/out duration (ms). */
14
+ fadeDuration?: number;
15
+ /** Duration the [ESC] hint remains visible (ms). */
16
+ hintDuration?: number;
17
+ }>;
18
+
19
+ /**
20
+ * Wraps presentation content with a fade-in/out transition, an ESC handler that exits in a
21
+ * single keypress (intercepting before the deck's fullscreen handler), and a transient [ESC]
22
+ * caption shown on enter.
23
+ */
24
+ export const PresentationShell = composable<HTMLDivElement, PresentationShellProps>(
25
+ ({ children, onExit, fadeDuration = 300, hintDuration = 3000, ...props }, forwardedRef) => {
26
+ const [visible, setVisible] = useState(false);
27
+ const [exiting, setExiting] = useState(false);
28
+ const [showHint, setShowHint] = useState(true);
29
+ const exitTimeout = useRef<ReturnType<typeof setTimeout>>(undefined);
30
+ // Guards against exiting twice; a state updater can't gate this since React double-invokes
31
+ // updaters in dev, which would schedule the exit (and its side effects) more than once.
32
+ const exitingRef = useRef(false);
33
+
34
+ // Fade in once mounted.
35
+ useEffect(() => {
36
+ const frame = requestAnimationFrame(() => setVisible(true));
37
+ return () => cancelAnimationFrame(frame);
38
+ }, []);
39
+
40
+ // Hide the hint after the configured duration.
41
+ useEffect(() => {
42
+ const timer = setTimeout(() => setShowHint(false), hintDuration);
43
+ return () => clearTimeout(timer);
44
+ }, [hintDuration]);
45
+
46
+ const handleExit = useCallback(() => {
47
+ if (exitingRef.current) {
48
+ return;
49
+ }
50
+ exitingRef.current = true;
51
+ setExiting(true);
52
+ setVisible(false);
53
+ exitTimeout.current = setTimeout(() => onExit?.(), fadeDuration);
54
+ }, [onExit, fadeDuration]);
55
+
56
+ useEffect(() => () => clearTimeout(exitTimeout.current), []);
57
+
58
+ // Capture ESC before the deck/reveal handlers so a single keypress exits directly.
59
+ useEffect(() => {
60
+ const handler = (event: KeyboardEvent) => {
61
+ if (event.key === 'Escape') {
62
+ event.preventDefault();
63
+ event.stopImmediatePropagation();
64
+ handleExit();
65
+ }
66
+ };
67
+
68
+ document.addEventListener('keydown', handler, { capture: true });
69
+ return () => document.removeEventListener('keydown', handler, { capture: true });
70
+ }, [handleExit]);
71
+
72
+ return (
73
+ <div
74
+ {...composableProps(props, {
75
+ classNames: [
76
+ 'relative grow overflow-hidden bg-black transition-opacity',
77
+ visible && !exiting ? 'opacity-100' : 'opacity-0',
78
+ ],
79
+ style: { transitionDuration: `${fadeDuration}ms` },
80
+ })}
81
+ ref={forwardedRef}
82
+ >
83
+ {children}
84
+
85
+ <div
86
+ className={mx(
87
+ 'absolute top-4 left-4 z-[300] transition-opacity duration-500',
88
+ showHint && !exiting ? 'opacity-100' : 'opacity-0',
89
+ )}
90
+ >
91
+ <span className='rounded-sm bg-white/10 px-2 py-1 font-mono text-sm text-white/70'>[ESC]</span>
92
+ </div>
93
+ </div>
94
+ );
95
+ },
96
+ );
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './Layout';
6
6
  export * from './Pager';
7
+ export * from './PresentationShell';
@@ -14,11 +14,11 @@ import { Settings } from '#types';
14
14
  export type PresenterSettingsProps = AppSurface.SettingsArticleProps<Settings.Settings>;
15
15
 
16
16
  export const PresenterSettings = ({ settings, onSettingsChange }: PresenterSettingsProps) => {
17
- const { t } = useTranslation(meta.id);
17
+ const { t } = useTranslation(meta.profile.key);
18
18
 
19
19
  return (
20
20
  <SettingsForm.Viewport>
21
- <SettingsForm.Section title={t('settings.title', { ns: meta.id })}>
21
+ <SettingsForm.Section title={t('settings.title', { ns: meta.profile.key })}>
22
22
  <SettingsForm.FieldSet
23
23
  readonly={!onSettingsChange}
24
24
  schema={Settings.Settings}
@@ -9,7 +9,7 @@ import { AppSurface } from '@dxos/app-toolkit/ui';
9
9
  import { type Collection, Obj } from '@dxos/echo';
10
10
  import { Panel } from '@dxos/react-ui';
11
11
 
12
- import { PageNumber, Pager, Layout as PresenterLayout } from '#components';
12
+ import { PageNumber, Pager, PresentationShell, Layout as PresenterLayout } from '#components';
13
13
  import { PresenterContext } from '#types';
14
14
 
15
15
  import { useExitPresenter } from '../../useExitPresenter';
@@ -24,26 +24,20 @@ export const CollectionPresenterArticle = ({ role, subject: collection }: Collec
24
24
  return (
25
25
  <Panel.Root role={role} classNames='relative'>
26
26
  <Panel.Content asChild>
27
- <PresenterLayout
28
- bottomRight={<PageNumber index={slide} count={collection.objects.length} />}
29
- bottomLeft={
30
- <Pager
31
- index={slide}
32
- count={collection.objects.length}
33
- keys={running}
34
- onChange={setSlide}
35
- onExit={handleExit}
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
+ }}
36
38
  />
37
- }
38
- >
39
- <Surface.Surface
40
- type={AppSurface.Slide}
41
- data={{
42
- subject: collection.objects[slide],
43
- attendableId: Obj.getURI(collection),
44
- }}
45
- />
46
- </PresenterLayout>
39
+ </PresenterLayout>
40
+ </PresentationShell>
47
41
  </Panel.Content>
48
42
  </Panel.Root>
49
43
  );
@@ -7,7 +7,7 @@ import React, { type FC } from 'react';
7
7
  import { type Markdown } from '@dxos/plugin-markdown';
8
8
  import { Panel } from '@dxos/react-ui';
9
9
 
10
- import { RevealPlayer } from '#components';
10
+ import { PresentationShell, RevealPlayer } from '#components';
11
11
 
12
12
  import { useExitPresenter } from '../../useExitPresenter';
13
13
 
@@ -17,7 +17,9 @@ export const DocumentPresenterContainer: FC<{ document: Markdown.Document }> = (
17
17
  return (
18
18
  <Panel.Root classNames='relative'>
19
19
  <Panel.Content asChild>
20
- <RevealPlayer content={document.content.target?.content ?? ''} onExit={handleExit} />
20
+ <PresentationShell onExit={handleExit}>
21
+ <RevealPlayer content={document.content.target?.content ?? ''} />
22
+ </PresentationShell>
21
23
  </Panel.Content>
22
24
  </Panel.Root>
23
25
  );
package/src/meta.ts CHANGED
@@ -3,35 +3,7 @@
3
3
  //
4
4
 
5
5
  import { Plugin } from '@dxos/app-framework';
6
- import { DXN } from '@dxos/keys';
7
- import { trim } from '@dxos/util';
8
6
 
9
- export const meta = Plugin.makeMeta({
10
- key: DXN.make('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.
7
+ import config from '../dx.config';
19
8
 
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
- icon: 'ph--presentation--regular',
34
- iconHue: 'indigo',
35
- source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-presenter',
36
- spec: 'PLUGIN.mdl',
37
- });
9
+ export const meta = Plugin.getMetaFromConfig(config);
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { OperationHandlerSet } from '@dxos/compute';
6
+
7
+ export const PresenterOperationHandlerSet = OperationHandlerSet.lazy(() => import('./toggle-presentation'));