@abraca/nuxt 2.0.11 → 2.4.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 (135) hide show
  1. package/dist/module.d.mts +68 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +99 -4
  4. package/dist/runtime/components/ACodeEditor.d.vue.ts +26 -0
  5. package/dist/runtime/components/ACodeEditor.vue +268 -0
  6. package/dist/runtime/components/ACodeEditor.vue.d.ts +26 -0
  7. package/dist/runtime/components/ADocumentTree.vue +52 -20
  8. package/dist/runtime/components/AEditor.d.vue.ts +20 -13
  9. package/dist/runtime/components/AEditor.vue +55 -2
  10. package/dist/runtime/components/AEditor.vue.d.ts +20 -13
  11. package/dist/runtime/components/ANodePanel.vue +64 -60
  12. package/dist/runtime/components/ANotificationBell.d.vue.ts +1 -1
  13. package/dist/runtime/components/ANotificationBell.vue.d.ts +1 -1
  14. package/dist/runtime/components/ASpaceFormModal.d.vue.ts +2 -2
  15. package/dist/runtime/components/ASpaceFormModal.vue.d.ts +2 -2
  16. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  17. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  18. package/dist/runtime/components/aware/APresenceBlobs.d.vue.ts +29 -1
  19. package/dist/runtime/components/aware/APresenceBlobs.vue +54 -8
  20. package/dist/runtime/components/aware/APresenceBlobs.vue.d.ts +29 -1
  21. package/dist/runtime/components/aware/APresenceCursors.d.vue.ts +11 -0
  22. package/dist/runtime/components/aware/APresenceCursors.vue +74 -9
  23. package/dist/runtime/components/aware/APresenceCursors.vue.d.ts +11 -0
  24. package/dist/runtime/components/aware/AToggleGroup.d.vue.ts +28 -13
  25. package/dist/runtime/components/aware/AToggleGroup.vue +56 -20
  26. package/dist/runtime/components/aware/AToggleGroup.vue.d.ts +28 -13
  27. package/dist/runtime/components/docs/ADocsNavigation.d.vue.ts +1 -1
  28. package/dist/runtime/components/docs/ADocsNavigation.vue.d.ts +1 -1
  29. package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
  30. package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
  31. package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +1 -1
  32. package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +1 -1
  33. package/dist/runtime/components/docs/ADocsToc.d.vue.ts +2 -2
  34. package/dist/runtime/components/docs/ADocsToc.vue.d.ts +2 -2
  35. package/dist/runtime/components/editor/AEditorRedoButton.d.vue.ts +1 -1
  36. package/dist/runtime/components/editor/AEditorRedoButton.vue.d.ts +1 -1
  37. package/dist/runtime/components/editor/AEditorUndoButton.d.vue.ts +1 -1
  38. package/dist/runtime/components/editor/AEditorUndoButton.vue.d.ts +1 -1
  39. package/dist/runtime/components/editor/AFileGlbViewer.vue +27 -10
  40. package/dist/runtime/components/editor/ANodeInlineLabel.d.vue.ts +1 -1
  41. package/dist/runtime/components/editor/ANodeInlineLabel.vue.d.ts +1 -1
  42. package/dist/runtime/components/registry/APluginBrowser.d.vue.ts +23 -0
  43. package/dist/runtime/components/registry/APluginBrowser.vue +155 -0
  44. package/dist/runtime/components/registry/APluginBrowser.vue.d.ts +23 -0
  45. package/dist/runtime/components/registry/APluginCapabilityDialog.d.vue.ts +17 -0
  46. package/dist/runtime/components/registry/APluginCapabilityDialog.vue +159 -0
  47. package/dist/runtime/components/registry/APluginCapabilityDialog.vue.d.ts +17 -0
  48. package/dist/runtime/components/registry/APluginCard.d.vue.ts +20 -0
  49. package/dist/runtime/components/registry/APluginCard.vue +91 -0
  50. package/dist/runtime/components/registry/APluginCard.vue.d.ts +20 -0
  51. package/dist/runtime/components/registry/APluginDetail.d.vue.ts +18 -0
  52. package/dist/runtime/components/registry/APluginDetail.vue +252 -0
  53. package/dist/runtime/components/registry/APluginDetail.vue.d.ts +18 -0
  54. package/dist/runtime/components/renderers/ACodeRenderer.d.vue.ts +15 -0
  55. package/dist/runtime/components/renderers/ACodeRenderer.vue +68 -0
  56. package/dist/runtime/components/renderers/ACodeRenderer.vue.d.ts +15 -0
  57. package/dist/runtime/components/renderers/AGraphRenderer.vue +416 -120
  58. package/dist/runtime/components/renderers/AProseRenderer.d.vue.ts +2 -2
  59. package/dist/runtime/components/renderers/AProseRenderer.vue.d.ts +2 -2
  60. package/dist/runtime/components/renderers/sheets/ASheetsToolbar.d.vue.ts +4 -4
  61. package/dist/runtime/components/renderers/sheets/ASheetsToolbar.vue.d.ts +4 -4
  62. package/dist/runtime/components/shell/ABreadcrumbForDoc.d.vue.ts +11 -0
  63. package/dist/runtime/components/shell/ABreadcrumbForDoc.vue +16 -0
  64. package/dist/runtime/components/shell/ABreadcrumbForDoc.vue.d.ts +11 -0
  65. package/dist/runtime/components/shell/ASettingsSection.d.vue.ts +35 -0
  66. package/dist/runtime/components/shell/ASettingsSection.vue +26 -0
  67. package/dist/runtime/components/shell/ASettingsSection.vue.d.ts +35 -0
  68. package/dist/runtime/components/shell/ASidebar.d.vue.ts +1 -1
  69. package/dist/runtime/components/shell/ASidebar.vue.d.ts +1 -1
  70. package/dist/runtime/components/shell/AUserMenu.d.vue.ts +3 -0
  71. package/dist/runtime/components/shell/AUserMenu.vue +4 -0
  72. package/dist/runtime/components/shell/AUserMenu.vue.d.ts +3 -0
  73. package/dist/runtime/composables/useAbracadabraSchema.d.ts +83 -0
  74. package/dist/runtime/composables/useAbracadabraSchema.js +52 -0
  75. package/dist/runtime/composables/useAggregatedPresence.d.ts +1 -6
  76. package/dist/runtime/composables/useCalendarView.d.ts +1 -1
  77. package/dist/runtime/composables/useChat.js +1 -0
  78. package/dist/runtime/composables/useDocBreadcrumb.d.ts +21 -0
  79. package/dist/runtime/composables/useDocBreadcrumb.js +33 -0
  80. package/dist/runtime/composables/useDocEntryTyped.d.ts +60 -0
  81. package/dist/runtime/composables/useDocEntryTyped.js +70 -0
  82. package/dist/runtime/composables/useEditorDragHandle.js +18 -0
  83. package/dist/runtime/composables/useEditorSuggestions.js +2 -1
  84. package/dist/runtime/composables/useInstalledPlugins.d.ts +3 -21
  85. package/dist/runtime/composables/useInstalledPlugins.js +2 -12
  86. package/dist/runtime/composables/useMetaMenuItems.d.ts +21 -0
  87. package/dist/runtime/composables/useMetaMenuItems.js +115 -0
  88. package/dist/runtime/composables/useMetaValidator.d.ts +27 -0
  89. package/dist/runtime/composables/useMetaValidator.js +10 -0
  90. package/dist/runtime/composables/usePluginCatalog.d.ts +161 -0
  91. package/dist/runtime/composables/usePluginCatalog.js +234 -0
  92. package/dist/runtime/composables/useQuery.d.ts +79 -0
  93. package/dist/runtime/composables/useQuery.js +97 -0
  94. package/dist/runtime/composables/useSpaces.js +4 -5
  95. package/dist/runtime/composables/useTableView.d.ts +3 -3
  96. package/dist/runtime/composables/useTypedDoc.d.ts +97 -0
  97. package/dist/runtime/composables/useTypedDoc.js +114 -0
  98. package/dist/runtime/composables/useWebRTC.js +44 -5
  99. package/dist/runtime/extensions/document-meta.js +5 -0
  100. package/dist/runtime/extensions/timeline.d.ts +11 -0
  101. package/dist/runtime/extensions/timeline.js +52 -0
  102. package/dist/runtime/extensions/views/DocumentMetaView.d.vue.ts +4 -0
  103. package/dist/runtime/extensions/views/DocumentMetaView.vue +63 -0
  104. package/dist/runtime/extensions/views/DocumentMetaView.vue.d.ts +4 -0
  105. package/dist/runtime/extensions/views/TimelineItemView.d.vue.ts +4 -0
  106. package/dist/runtime/extensions/views/TimelineItemView.vue +131 -0
  107. package/dist/runtime/extensions/views/TimelineItemView.vue.d.ts +4 -0
  108. package/dist/runtime/extensions/views/TimelineView.d.vue.ts +9 -0
  109. package/dist/runtime/extensions/views/TimelineView.vue +29 -0
  110. package/dist/runtime/extensions/views/TimelineView.vue.d.ts +9 -0
  111. package/dist/runtime/locale.d.ts +2 -0
  112. package/dist/runtime/locale.js +2 -0
  113. package/dist/runtime/plugin-abracadabra.client.js +107 -6
  114. package/dist/runtime/plugin-registry.d.ts +11 -30
  115. package/dist/runtime/plugin-registry.js +2 -82
  116. package/dist/runtime/plugins/core.plugin.js +10 -4
  117. package/dist/runtime/server/api/_abracadabra/spaces.get.d.ts +1 -1
  118. package/dist/runtime/server/plugins/abracadabra-service.js +28 -0
  119. package/dist/runtime/server/utils/docCache.js +24 -3
  120. package/dist/runtime/server/utils/schemaServerSupport.d.ts +52 -0
  121. package/dist/runtime/server/utils/schemaServerSupport.js +51 -0
  122. package/dist/runtime/types.d.ts +63 -46
  123. package/dist/runtime/utils/docTypes.d.ts +15 -0
  124. package/dist/runtime/utils/docTypes.js +20 -0
  125. package/dist/runtime/utils/loadCodeMirror.d.ts +32 -0
  126. package/dist/runtime/utils/loadCodeMirror.js +65 -0
  127. package/dist/runtime/utils/loadThree.d.ts +18 -0
  128. package/dist/runtime/utils/loadThree.js +46 -0
  129. package/dist/runtime/utils/markdownToYjs.d.ts +1 -23
  130. package/dist/runtime/utils/markdownToYjs.js +5 -440
  131. package/dist/runtime/utils/schemaSupport.d.ts +60 -0
  132. package/dist/runtime/utils/schemaSupport.js +40 -0
  133. package/dist/runtime/utils/yjsConvert.d.ts +1 -14
  134. package/dist/runtime/utils/yjsConvert.js +5 -331
  135. package/package.json +86 -21
@@ -130,6 +130,10 @@ function toNuxtUIPrimary(name) {
130
130
  function toNuxtUINeutral(name) {
131
131
  return NUXT_UI_NEUTRAL_COLORS.has(name) ? name : CUSTOM_TO_NUXT_UI_NEUTRAL[name] ?? "zinc";
132
132
  }
133
+ function hostHasCustomPalette(name) {
134
+ if (typeof document === "undefined") return false;
135
+ return getComputedStyle(document.documentElement).getPropertyValue(`--color-${name}-500`).trim() !== "";
136
+ }
133
137
  const STORAGE_KEY_EXTERNAL_PLUGINS = "abracadabra_external_plugins";
134
138
  const STORAGE_KEY_DISABLED_BUILTINS = "abracadabra_disabled_builtins";
135
139
  const CLAIMED_FLAG_KEY = "abracadabra_was_claimed";
@@ -238,10 +242,29 @@ export default defineNuxtPlugin({
238
242
  localStorage.setItem(STORAGE_KEY_EXTERNAL_PLUGINS, JSON.stringify(updated));
239
243
  }
240
244
  }
245
+ const { _collectSchemaRegistries } = await import("./composables/useAbracadabraSchema.js");
246
+ const { schemas: schemaRegistries, plugins: inlinePlugins } = await _collectSchemaRegistries(nuxtApp);
247
+ for (const plugin of inlinePlugins) {
248
+ registry.register(plugin);
249
+ }
241
250
  registry.freeze();
242
251
  if (debug) {
243
252
  console.log("[abracadabra] plugins:", registry.getPlugins().map((p) => p.name));
244
253
  }
254
+ const schemaCfg = abraConfig.schema ?? {};
255
+ if (debug && schemaRegistries.length > 0) {
256
+ console.log(
257
+ "[abracadabra] schema registries attached:",
258
+ schemaRegistries.map((r) => Array.from(r.types.keys())),
259
+ "validate=",
260
+ schemaCfg.validate,
261
+ "migrateOnRead=",
262
+ schemaCfg.migrateOnRead
263
+ );
264
+ }
265
+ if (schemaCfg.bundleDir && debug) {
266
+ console.log("[abracadabra] server bundle_dir advertised:", schemaCfg.bundleDir);
267
+ }
245
268
  const doc = shallowRef(new Y.Doc());
246
269
  const provider = shallowRef(null);
247
270
  const client = shallowRef(null);
@@ -300,6 +323,7 @@ export default defineNuxtPlugin({
300
323
  let _serversLoaded = false;
301
324
  let _initPromise = null;
302
325
  let _wsp = null;
326
+ let _tokenManager = null;
303
327
  let authFailureCount = 0;
304
328
  const AUTH_FAILURE_LIMIT = 3;
305
329
  let lastClientIds = /* @__PURE__ */ new Set();
@@ -421,14 +445,18 @@ export default defineNuxtPlugin({
421
445
  userColorName.value = colorName;
422
446
  localStorage.setItem("abracadabra_usercolor", colorName);
423
447
  const appConfig = useAppConfig();
424
- if (appConfig.ui?.colors) appConfig.ui.colors.primary = toNuxtUIPrimary(colorName);
448
+ if (appConfig.ui?.colors) {
449
+ appConfig.ui.colors.primary = hostHasCustomPalette(colorName) ? colorName : toNuxtUIPrimary(colorName);
450
+ }
425
451
  publishAwarenessUser();
426
452
  }
427
453
  function setNeutralColor(colorName) {
428
454
  userNeutralColorName.value = colorName;
429
455
  localStorage.setItem("abracadabra_neutralcolor", colorName);
430
456
  const appConfig = useAppConfig();
431
- if (appConfig.ui?.colors) appConfig.ui.colors.neutral = toNuxtUINeutral(colorName);
457
+ if (appConfig.ui?.colors) {
458
+ appConfig.ui.colors.neutral = hostHasCustomPalette(colorName) ? colorName : toNuxtUINeutral(colorName);
459
+ }
432
460
  }
433
461
  function setUserStatusIcon(icon) {
434
462
  const normalized = icon.replace(/^i-lucide-/, "");
@@ -474,6 +502,13 @@ export default defineNuxtPlugin({
474
502
  }
475
503
  provider.value = null;
476
504
  }
505
+ if (_tokenManager) {
506
+ try {
507
+ _tokenManager.dispose();
508
+ } catch {
509
+ }
510
+ _tokenManager = null;
511
+ }
477
512
  client.value = null;
478
513
  keystore.value = null;
479
514
  isReady.value = false;
@@ -537,6 +572,7 @@ export default defineNuxtPlugin({
537
572
  addLog("No active session \u2014 registering passkey from scratch", "auth");
538
573
  await client.value.registerWithKey({
539
574
  publicKey: newPubKey,
575
+ x25519Key: x25519PublicKey,
540
576
  deviceName,
541
577
  displayName: userName.value,
542
578
  inviteCode: pendingInviteCode.value ?? void 0
@@ -623,6 +659,15 @@ export default defineNuxtPlugin({
623
659
  }
624
660
  async function logout() {
625
661
  addLog("Clearing identity...", "auth");
662
+ const sessionId = _tokenManager?.currentSession?.sessionId;
663
+ if (client.value && sessionId) {
664
+ try {
665
+ await client.value.revokeDeviceSession(sessionId);
666
+ } catch (e) {
667
+ if (import.meta.dev) console.warn("[abracadabra] revoke device session failed", e);
668
+ }
669
+ }
670
+ _tokenManager?.clearSession();
626
671
  if (client.value) client.value.logout();
627
672
  if (keystore.value) await keystore.value.clear();
628
673
  localStorage.removeItem("abracadabra_privkey");
@@ -783,7 +828,7 @@ export default defineNuxtPlugin({
783
828
  import("@noble/hashes/sha2.js")
784
829
  ]);
785
830
  setSdkModule(sdkModule);
786
- const { AbracadabraClient, AbracadabraProvider, AbracadabraWS, CryptoIdentityKeystore } = sdkModule;
831
+ const { AbracadabraClient, AbracadabraProvider, AbracadabraWS, CryptoIdentityKeystore, TokenManager, LocalStorageDeviceSessionStorage } = sdkModule;
787
832
  const ed = edModule.default ?? edModule;
788
833
  if (!ed.hashes) {
789
834
  throw new Error("@noble/ed25519 v3: `hashes` export missing from imported namespace \u2014 check Vite optimizeDeps");
@@ -827,24 +872,38 @@ export default defineNuxtPlugin({
827
872
  const storedColor = localStorage.getItem("abracadabra_usercolor");
828
873
  const storedNeutral = localStorage.getItem("abracadabra_neutralcolor");
829
874
  const derived = deriveColorFromPubKey(pubKey);
875
+ const appConfig = useAppConfig();
876
+ const fromAppConfigPrimary = appConfig.ui?.colors?.primary;
877
+ const fromAppConfigNeutral = appConfig.ui?.colors?.neutral;
878
+ let primaryFromHost = false;
879
+ let neutralFromHost = false;
830
880
  if (storedColor && UI_COLORS.includes(storedColor)) {
831
881
  const h = COLOR_HUES[storedColor] ?? 0;
832
882
  userColor.value = `hsl(${h}, 70%, 75%)`;
833
883
  userColorName.value = storedColor;
884
+ primaryFromHost = true;
885
+ } else if (fromAppConfigPrimary && UI_COLORS.includes(fromAppConfigPrimary)) {
886
+ const h = COLOR_HUES[fromAppConfigPrimary] ?? 0;
887
+ userColor.value = `hsl(${h}, 70%, 75%)`;
888
+ userColorName.value = fromAppConfigPrimary;
889
+ primaryFromHost = true;
834
890
  } else {
835
891
  userColor.value = derived.hsl;
836
892
  userColorName.value = derived.name;
837
893
  }
838
894
  if (storedNeutral && UI_NEUTRALS.includes(storedNeutral)) {
839
895
  userNeutralColorName.value = storedNeutral;
896
+ neutralFromHost = true;
897
+ } else if (fromAppConfigNeutral && UI_NEUTRALS.includes(fromAppConfigNeutral)) {
898
+ userNeutralColorName.value = fromAppConfigNeutral;
899
+ neutralFromHost = true;
840
900
  } else {
841
901
  userNeutralColorName.value = derived.neutralName;
842
902
  }
843
903
  try {
844
- const appConfig = useAppConfig();
845
904
  if (appConfig.ui?.colors) {
846
- appConfig.ui.colors.primary = toNuxtUIPrimary(userColorName.value);
847
- appConfig.ui.colors.neutral = toNuxtUINeutral(userNeutralColorName.value);
905
+ appConfig.ui.colors.primary = primaryFromHost || hostHasCustomPalette(userColorName.value) ? userColorName.value : toNuxtUIPrimary(userColorName.value);
906
+ appConfig.ui.colors.neutral = neutralFromHost || hostHasCustomPalette(userNeutralColorName.value) ? userNeutralColorName.value : toNuxtUINeutral(userNeutralColorName.value);
848
907
  }
849
908
  } catch {
850
909
  }
@@ -860,6 +919,24 @@ export default defineNuxtPlugin({
860
919
  const _client = new AbracadabraClient({ url: serverUrl, persistAuth, storageKey: authStorageKey });
861
920
  client.value = _client;
862
921
  addLog(`Server: ${serverUrl}`, "connection");
922
+ const tm = new TokenManager({
923
+ client: _client,
924
+ storage: new LocalStorageDeviceSessionStorage(`${authStorageKey}:device-session`)
925
+ });
926
+ _tokenManager = tm;
927
+ tm.on("refresh", () => addLog("JWT auto-refreshed via device session", "auth"));
928
+ tm.on("session-expired", () => {
929
+ addLog("Device session rejected \u2014 please re-authenticate", "auth");
930
+ if (identityState.value === "claimed") identityState.value = "needsReauth";
931
+ });
932
+ if (tm.hasSession && !_client.isTokenValid()) {
933
+ try {
934
+ await tm.bootstrap();
935
+ addLog("Refreshed JWT via stored device session", "auth");
936
+ } catch (e) {
937
+ addLog(`Device session refresh failed: ${e instanceof Error ? e.message : String(e)}`, "auth");
938
+ }
939
+ }
863
940
  let useExistingToken = _client.isTokenValid();
864
941
  if (useExistingToken) {
865
942
  try {
@@ -876,6 +953,7 @@ export default defineNuxtPlugin({
876
953
  }
877
954
  ;
878
955
  _client.token = null;
956
+ tm.clearSession();
879
957
  useExistingToken = false;
880
958
  }
881
959
  }
@@ -913,6 +991,23 @@ export default defineNuxtPlugin({
913
991
  }
914
992
  _client.updateMe({ displayName: userName.value }).catch(() => {
915
993
  });
994
+ if (_client.isTokenValid() && !tm.hasSession) {
995
+ try {
996
+ const sess = await _client.requestDeviceSession({
997
+ publicKey: pubKey,
998
+ deviceName: "Abracadabra Web (" + (navigator.platform || "Browser") + ")"
999
+ });
1000
+ await tm.attachSession({
1001
+ sessionId: sess.sessionId,
1002
+ sessionToken: sess.sessionToken,
1003
+ expiresAt: sess.expiresAt
1004
+ });
1005
+ addLog("Device session registered \u2014 JWT auto-refresh enabled", "auth");
1006
+ } catch (e) {
1007
+ const msg = e instanceof Error ? e.message : String(e);
1008
+ addLog(`Device session unavailable: ${msg.slice(0, 80)}`, "auth");
1009
+ }
1010
+ }
916
1011
  const [info, spaceDocs] = await Promise.all([
917
1012
  fetchServerInfo(serverUrl),
918
1013
  _client.listSpaces().catch((e) => {
@@ -967,6 +1062,12 @@ export default defineNuxtPlugin({
967
1062
  document: _doc,
968
1063
  websocketProvider: wsp,
969
1064
  client: _client,
1065
+ // Route every WS auth handshake (initial + reconnects) through
1066
+ // the TokenManager so an expired JWT is silently refreshed via
1067
+ // the device session before being sent. Without this, a tab
1068
+ // left open past the 4 h JWT TTL ends up reconnecting forever
1069
+ // with a dead token.
1070
+ token: () => tm.getValidToken(),
970
1071
  onStatus({ status: s }) {
971
1072
  if (s === status.value) return;
972
1073
  const oldStatus = status.value;
@@ -1,34 +1,15 @@
1
- import type { Extension } from '@tiptap/core';
2
- import type { AbracadabraPlugin, AbracadabraPageType, AbracadabraToolbarItem, AbracadabraSuggestionItem, AbracadabraDragHandleItem, AbracadabraMentionProvider, AbracadabraAwarenessContribution, AbracadabraCommandItem, AbracadabraNodePanelSlot, AbracadabraSettingsPanel, AbracadabraKeyboardShortcut, EditorPluginCtx, DragHandlePluginCtx, CommandPaletteCtx } from './types.js';
3
1
  /**
4
- * PluginRegistrymanages the lifecycle of AbracadabraPlugin instances.
2
+ * The module's plugin registry a thin wrapper around `@abraca/plugin`'s
3
+ * generic `PluginRegistry`, typed with `AbracadabraPlugin` so consumers get
4
+ * fully-typed aggregator returns (toolbar item type, page type, command
5
+ * palette item type, etc.) without casts.
5
6
  *
6
- * Instantiated once per Nuxt app inside the main client plugin.
7
- * After freezePluginRegistry() is called, no new plugins can be registered.
8
- * All aggregator methods are safe to call after freezing.
7
+ * The class extension is intentionally empty all behaviour lives in the
8
+ * shared base. We re-export the class under the historical name so existing
9
+ * imports (`import { PluginRegistry } from './plugin-registry'`) keep
10
+ * resolving.
9
11
  */
10
- export declare class PluginRegistry {
11
- private _plugins;
12
- private _frozen;
13
- register(plugin: AbracadabraPlugin): void;
14
- /** Lock the registry. Called after all plugins (built-in + external) are loaded. */
15
- freeze(): void;
16
- isFrozen(): boolean;
17
- getPlugins(): readonly AbracadabraPlugin[];
18
- getAllExtensions(): Extension[];
19
- /** Waits until all plugins with async extension bundles have finished loading. */
20
- waitForExtensions(): Promise<void>;
21
- getServerExtensions(): Extension[];
22
- getAllPageTypes(): Record<string, AbracadabraPageType>;
23
- getAllCustomHandlers(): Record<string, (...args: any[]) => any>;
24
- getAllToolbarItems(ctx: EditorPluginCtx): AbracadabraToolbarItem[][];
25
- getAllBubbleMenuItems(ctx: EditorPluginCtx): AbracadabraToolbarItem[][];
26
- getAllSuggestionItems(ctx: EditorPluginCtx): AbracadabraSuggestionItem[][];
27
- getAllDragHandleItems(ctx: DragHandlePluginCtx): AbracadabraDragHandleItem[][];
28
- getAllMentionProviders(): AbracadabraMentionProvider[];
29
- getAllAwarenessContributions(): AbracadabraAwarenessContribution[];
30
- getAllCommandPaletteItems(ctx: CommandPaletteCtx): Promise<AbracadabraCommandItem[]>;
31
- getAllNodePanelSlots(): AbracadabraNodePanelSlot[];
32
- getSettingsPanels(): AbracadabraSettingsPanel[];
33
- getAllKeyboardShortcuts(): AbracadabraKeyboardShortcut[];
12
+ import { PluginRegistry as BasePluginRegistry } from '@abraca/plugin';
13
+ import type { AbracadabraPlugin } from './types.js';
14
+ export declare class PluginRegistry extends BasePluginRegistry<AbracadabraPlugin> {
34
15
  }
@@ -1,83 +1,3 @@
1
- export class PluginRegistry {
2
- _plugins = [];
3
- _frozen = false;
4
- register(plugin) {
5
- if (this._frozen) {
6
- console.warn(`[abracadabra] Plugin registry frozen \u2014 cannot register "${plugin.name}"`);
7
- return;
8
- }
9
- if (this._plugins.some((p) => p.name === plugin.name)) {
10
- console.warn(`[abracadabra] Plugin "${plugin.name}" already registered`);
11
- return;
12
- }
13
- this._plugins.push(plugin);
14
- }
15
- /** Lock the registry. Called after all plugins (built-in + external) are loaded. */
16
- freeze() {
17
- this._frozen = true;
18
- }
19
- isFrozen() {
20
- return this._frozen;
21
- }
22
- getPlugins() {
23
- return this._plugins;
24
- }
25
- // ── Aggregated getters ───────────────────────────────────────────────────────
26
- getAllExtensions() {
27
- return this._plugins.flatMap((p) => p.extensions?.() ?? []);
28
- }
29
- /** Waits until all plugins with async extension bundles have finished loading. */
30
- async waitForExtensions() {
31
- await Promise.all(this._plugins.map((p) => p.extensionsReady ?? Promise.resolve()));
32
- }
33
- getServerExtensions() {
34
- return this._plugins.flatMap((p) => p.serverExtensions?.() ?? []);
35
- }
36
- getAllPageTypes() {
37
- const merged = {};
38
- for (const p of this._plugins) {
39
- if (p.pageTypes) Object.assign(merged, p.pageTypes);
40
- }
41
- return merged;
42
- }
43
- getAllCustomHandlers() {
44
- const merged = {};
45
- for (const p of this._plugins) {
46
- if (p.customHandlers) Object.assign(merged, p.customHandlers());
47
- }
48
- return merged;
49
- }
50
- getAllToolbarItems(ctx) {
51
- return this._plugins.flatMap((p) => p.toolbarItems?.(ctx) ?? []);
52
- }
53
- getAllBubbleMenuItems(ctx) {
54
- return this._plugins.flatMap((p) => p.bubbleMenuItems?.(ctx) ?? []);
55
- }
56
- getAllSuggestionItems(ctx) {
57
- return this._plugins.flatMap((p) => p.suggestionItems?.(ctx) ?? []);
58
- }
59
- getAllDragHandleItems(ctx) {
60
- return this._plugins.flatMap((p) => p.dragHandleItems?.(ctx) ?? []);
61
- }
62
- getAllMentionProviders() {
63
- return this._plugins.flatMap((p) => p.mentionProviders ?? []);
64
- }
65
- getAllAwarenessContributions() {
66
- return this._plugins.flatMap((p) => p.awarenessContributions ?? []);
67
- }
68
- async getAllCommandPaletteItems(ctx) {
69
- const results = await Promise.all(
70
- this._plugins.filter((p) => p.commandPaletteItems).map((p) => Promise.resolve(p.commandPaletteItems(ctx)))
71
- );
72
- return results.flat().filter((item) => !item.when || item.when(ctx));
73
- }
74
- getAllNodePanelSlots() {
75
- return this._plugins.flatMap((p) => p.nodePanelSlots ?? []);
76
- }
77
- getSettingsPanels() {
78
- return this._plugins.flatMap((p) => p.settingsPanel ? [p.settingsPanel] : []);
79
- }
80
- getAllKeyboardShortcuts() {
81
- return this._plugins.flatMap((p) => p.keyboardShortcuts ?? []);
82
- }
1
+ import { PluginRegistry as BasePluginRegistry } from "@abraca/plugin";
2
+ export class PluginRegistry extends BasePluginRegistry {
83
3
  }
@@ -16,7 +16,9 @@ const OPTIONAL_BUILTIN_EXTENSIONS = /* @__PURE__ */ new Set([
16
16
  "mathBlock",
17
17
  "mathInline",
18
18
  "diff",
19
- "svgEmbed"
19
+ "svgEmbed",
20
+ "timeline",
21
+ "timelineItem"
20
22
  ]);
21
23
  async function loadClientExtensions() {
22
24
  const [
@@ -73,7 +75,8 @@ async function loadClientExtensions() {
73
75
  { ColorSwatch },
74
76
  { MathBlock, MathInline },
75
77
  { Diff },
76
- { SvgEmbed }
78
+ { SvgEmbed },
79
+ { Timeline, TimelineItem }
77
80
  ] = await Promise.all([
78
81
  import("@tiptap/extension-task-list"),
79
82
  import("@tiptap/extension-task-item"),
@@ -128,7 +131,8 @@ async function loadClientExtensions() {
128
131
  import("../extensions/color-swatch.js"),
129
132
  import("../extensions/math.js"),
130
133
  import("../extensions/diff.js"),
131
- import("../extensions/svg-embed.js")
134
+ import("../extensions/svg-embed.js"),
135
+ import("../extensions/timeline.js")
132
136
  ]);
133
137
  const lowlight = createLowlight(common);
134
138
  const extensions = [
@@ -202,7 +206,9 @@ async function loadClientExtensions() {
202
206
  MathBlock,
203
207
  MathInline,
204
208
  Diff,
205
- SvgEmbed
209
+ SvgEmbed,
210
+ Timeline,
211
+ TimelineItem
206
212
  ];
207
213
  try {
208
214
  const emojiPkg = "@tiptap/extension-emoji";
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<Omit<import("h3").EventHandlerRequest, "body">, Promise<never[] | unknown[]>>;
1
+ declare const _default: import("h3").EventHandler<Omit<import("h3").EventHandlerRequest, "body">, Promise<unknown[] | never[]>>;
2
2
  export default _default;
@@ -4,6 +4,7 @@ import { useStorage } from "nitropack/runtime/storage";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { dirname, join } from "node:path";
6
6
  import { registerServerPlugin, bootRunners, shutdownAllRunners } from "../utils/serverRunner.js";
7
+ import { buildServerSchemaValidator } from "../utils/schemaServerSupport.js";
7
8
  import { createDocCacheAPI } from "../utils/docCache.js";
8
9
  import { docTreeCacheRunner } from "../runners/doc-tree-cache.js";
9
10
  import { initSlugMap, loadPersistedSlugMap } from "../utils/slugMap.js";
@@ -212,7 +213,34 @@ export default defineNitroPlugin(async (nitroApp) => {
212
213
  };
213
214
  registerServerPlugin(coreServerPlugin);
214
215
  }
216
+ const attachedSchemas = [];
217
+ const schemaCfg = publicConfig?.schema ?? {};
218
+ if (schemaCfg.bundleDir) {
219
+ console.log(
220
+ `[abracadabra-service] schema bundle_dir advertised: ${schemaCfg.bundleDir} (server-side bundle_dir = same path; informational only \u2014 server is authoritative)`
221
+ );
222
+ }
223
+ Object.assign(ctx, {
224
+ attachSchema: (registry) => {
225
+ if (!registry || typeof registry !== "object") return;
226
+ if (typeof registry.validateMeta !== "function") {
227
+ console.warn("[abracadabra-service] attachSchema received a value missing validateMeta() \u2014 skipping");
228
+ return;
229
+ }
230
+ attachedSchemas.push(registry);
231
+ }
232
+ });
215
233
  await nitroApp.hooks.callHook("abracadabra:before-runners", ctx);
234
+ delete ctx.attachSchema;
235
+ if (attachedSchemas.length > 0) {
236
+ const validator = buildServerSchemaValidator(attachedSchemas);
237
+ if (validator) ctx.validateMeta = validator;
238
+ const advertisedTypes = /* @__PURE__ */ new Set();
239
+ for (const r of attachedSchemas) for (const t of r.types.keys()) advertisedTypes.add(t);
240
+ console.log(
241
+ `[abracadabra-service] ${attachedSchemas.length} schema registr${attachedSchemas.length === 1 ? "y" : "ies"} attached covering types: [${[...advertisedTypes].sort().join(", ")}]`
242
+ );
243
+ }
216
244
  await bootRunners(ctx);
217
245
  console.log("[abracadabra-service] All runners started");
218
246
  nitroApp.hooks.hook("close", async () => {
@@ -7,15 +7,36 @@ export function initDocCache(storage, extraExtensions = []) {
7
7
  _storage = storage;
8
8
  _extensions = extraExtensions;
9
9
  }
10
+ let _serverDoc = null;
11
+ async function getServerDoc() {
12
+ if (_serverDoc) return _serverDoc;
13
+ const happyDom = await import("happy-dom");
14
+ const Window = happyDom.Window ?? happyDom.default?.Window;
15
+ if (!Window) {
16
+ throw new Error("happy-dom did not export Window \u2014 install happy-dom or use the legacy DOM-global path.");
17
+ }
18
+ _serverDoc = new Window().document;
19
+ return _serverDoc;
20
+ }
10
21
  async function renderDocToHTML(ydoc) {
11
22
  try {
12
- const [{ generateHTML }, { yDocToProsemirrorJSON }, { default: StarterKit }] = await Promise.all([
23
+ const [{ getSchema }, { yDocToProsemirrorJSON }, { default: StarterKit }, { Node, DOMSerializer }] = await Promise.all([
13
24
  import("@tiptap/core"),
14
25
  import("@tiptap/y-tiptap"),
15
- import("@tiptap/starter-kit")
26
+ import("@tiptap/starter-kit"),
27
+ import("prosemirror-model")
16
28
  ]);
29
+ const schema = getSchema([StarterKit, ..._extensions]);
17
30
  const json = yDocToProsemirrorJSON(ydoc, "default");
18
- return generateHTML(json, [StarterKit, ..._extensions]);
31
+ const node = Node.fromJSON(schema, json);
32
+ const serverDoc = await getServerDoc();
33
+ const container = serverDoc.createElement("div");
34
+ DOMSerializer.fromSchema(schema).serializeFragment(
35
+ node.content,
36
+ { document: serverDoc },
37
+ container
38
+ );
39
+ return container.innerHTML;
19
40
  } catch (e) {
20
41
  console.warn("[abracadabra:doc-cache] HTML render failed:", e);
21
42
  return null;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Server-side mirror of `runtime/utils/schemaSupport.ts`.
3
+ *
4
+ * Reuses the same dynamic-import pattern so a Nitro service plugin can
5
+ * advertise + delegate to `@abraca/schema` registries without forcing
6
+ * the package onto consumers that never opt in (Rule 2). Importing
7
+ * from `runtime/utils/schemaSupport.ts` directly would pull in browser-
8
+ * leaning client-side state (`shallowRef`, the Vue cache); this file
9
+ * is a thin Nitro-only re-implementation with no Vue dependency.
10
+ */
11
+ /** Structural shape of a `@abraca/schema` SchemaRegistry. */
12
+ export interface SchemaRegistryLike<TMap = Record<string, unknown>> {
13
+ readonly types: ReadonlyMap<string, unknown>;
14
+ get: (name: string) => unknown;
15
+ validateMeta: (name: string, value: unknown) => ValidationResultLike;
16
+ validateChildType: (parent: string | null, child: string) => ValidationResultLike;
17
+ readonly __metaMap?: TMap;
18
+ }
19
+ export type ValidationResultLike = {
20
+ ok: true;
21
+ value: unknown;
22
+ } | {
23
+ ok: false;
24
+ errors: ReadonlyArray<{
25
+ path: ReadonlyArray<PropertyKey>;
26
+ message: string;
27
+ code?: string;
28
+ }>;
29
+ };
30
+ /** Subset of the `@abraca/schema` public surface the server needs at runtime. */
31
+ export interface ServerSchemaModule {
32
+ defineSchema: (spec: {
33
+ types: ReadonlyArray<unknown>;
34
+ }) => SchemaRegistryLike;
35
+ defineDocType: (spec: unknown) => unknown;
36
+ runMigrations: (registry: SchemaRegistryLike, typeName: string, meta: unknown) => unknown;
37
+ }
38
+ /**
39
+ * Resolve `@abraca/schema` if it is installed alongside the Nuxt app.
40
+ *
41
+ * Returns `null` (and warns once) when the package is missing or fails
42
+ * to import. Caches the result; subsequent calls reuse it.
43
+ */
44
+ export declare function tryLoadServerSchemaModule(): Promise<ServerSchemaModule | null>;
45
+ /**
46
+ * Build a merged "first-registry-wins" validator from a list of attached
47
+ * registries. Returns null when no registries provide a matching type
48
+ * (Rule 4: pass-through for unknown doc-types).
49
+ */
50
+ export declare function buildServerSchemaValidator(registries: ReadonlyArray<SchemaRegistryLike>): ((typeName: string, meta: unknown) => ValidationResultLike) | null;
51
+ /** Test-only — reset the cached resolution. */
52
+ export declare function __resetServerSchemaSupportCacheForTests(): void;
@@ -0,0 +1,51 @@
1
+ let cached;
2
+ let loading = null;
3
+ let warned = false;
4
+ export async function tryLoadServerSchemaModule() {
5
+ if (cached !== void 0) return cached;
6
+ if (loading) return loading;
7
+ loading = (async () => {
8
+ try {
9
+ const mod = await import("@abraca/schema");
10
+ const candidate = {
11
+ defineSchema: mod.defineSchema,
12
+ defineDocType: mod.defineDocType,
13
+ runMigrations: mod.runMigrations
14
+ };
15
+ if (typeof candidate.defineSchema !== "function" || typeof candidate.defineDocType !== "function" || typeof candidate.runMigrations !== "function") {
16
+ throw new TypeError("unexpected @abraca/schema shape \u2014 missing one of defineSchema / defineDocType / runMigrations");
17
+ }
18
+ cached = candidate;
19
+ return cached;
20
+ } catch (err) {
21
+ if (!warned) {
22
+ warned = true;
23
+ const reason = err instanceof Error ? err.message : String(err);
24
+ console.warn(
25
+ `[abracadabra-service] schema integration requested but @abraca/schema could not be loaded \u2014 install it as a peer dependency to enable server-side typed surfaces (${reason})`
26
+ );
27
+ }
28
+ cached = null;
29
+ return null;
30
+ } finally {
31
+ loading = null;
32
+ }
33
+ })();
34
+ return loading;
35
+ }
36
+ export function buildServerSchemaValidator(registries) {
37
+ if (registries.length === 0) return null;
38
+ return (typeName, meta) => {
39
+ for (const r of registries) {
40
+ if (r.types.has(typeName)) {
41
+ return r.validateMeta(typeName, meta);
42
+ }
43
+ }
44
+ return { ok: true, value: meta };
45
+ };
46
+ }
47
+ export function __resetServerSchemaSupportCacheForTests() {
48
+ cached = void 0;
49
+ loading = null;
50
+ warned = false;
51
+ }