@abraca/nuxt 0.1.1 → 0.3.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 (154) hide show
  1. package/dist/module.d.mts +46 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +95 -2
  4. package/dist/runtime/assets/editor.css +1 -0
  5. package/dist/runtime/components/ACommandPalette.vue +4 -1
  6. package/dist/runtime/components/ADocRenderer.d.vue.ts +29 -0
  7. package/dist/runtime/components/ADocRenderer.vue +99 -0
  8. package/dist/runtime/components/ADocRenderer.vue.d.ts +29 -0
  9. package/dist/runtime/components/ADocTypeSelect.vue +4 -1
  10. package/dist/runtime/components/ADocumentTree.vue +78 -19
  11. package/dist/runtime/components/AEditor.d.vue.ts +9 -4
  12. package/dist/runtime/components/AEditor.vue +102 -7
  13. package/dist/runtime/components/AEditor.vue.d.ts +9 -4
  14. package/dist/runtime/components/AFloatingWindow.vue +1 -1
  15. package/dist/runtime/components/AIconPicker.vue +8 -2
  16. package/dist/runtime/components/ANodePanel.vue +100 -61
  17. package/dist/runtime/components/ANotifications.vue +35 -8
  18. package/dist/runtime/components/APermissionGuard.vue +3 -1
  19. package/dist/runtime/components/APresence.vue +14 -3
  20. package/dist/runtime/components/AProvider.vue +7 -1
  21. package/dist/runtime/components/AVoiceBar.vue +57 -15
  22. package/dist/runtime/components/AVoiceTile.vue +4 -1
  23. package/dist/runtime/components/AWindowLayer.vue +1 -1
  24. package/dist/runtime/components/aware/AArea.vue +1 -1
  25. package/dist/runtime/components/aware/AAvatar.vue +85 -16
  26. package/dist/runtime/components/aware/AButton.vue +5 -1
  27. package/dist/runtime/components/aware/ACursorLabel.vue +5 -1
  28. package/dist/runtime/components/aware/ADocBadge.vue +4 -1
  29. package/dist/runtime/components/aware/AFacepile.vue +13 -3
  30. package/dist/runtime/components/aware/AInput.vue +5 -1
  31. package/dist/runtime/components/aware/ATextarea.vue +5 -1
  32. package/dist/runtime/components/aware/AUserList.vue +8 -2
  33. package/dist/runtime/components/renderers/ACalendarRenderer.d.vue.ts +12 -1
  34. package/dist/runtime/components/renderers/ACalendarRenderer.vue +388 -114
  35. package/dist/runtime/components/renderers/ACalendarRenderer.vue.d.ts +12 -1
  36. package/dist/runtime/components/renderers/ACallRenderer.d.vue.ts +13 -0
  37. package/dist/runtime/components/renderers/ACallRenderer.vue +169 -0
  38. package/dist/runtime/components/renderers/ACallRenderer.vue.d.ts +13 -0
  39. package/dist/runtime/components/renderers/AChecklistRenderer.d.vue.ts +19 -0
  40. package/dist/runtime/components/renderers/AChecklistRenderer.vue +581 -0
  41. package/dist/runtime/components/renderers/AChecklistRenderer.vue.d.ts +19 -0
  42. package/dist/runtime/components/renderers/ADashboardRenderer.d.vue.ts +19 -0
  43. package/dist/runtime/components/renderers/ADashboardRenderer.vue +1372 -0
  44. package/dist/runtime/components/renderers/ADashboardRenderer.vue.d.ts +19 -0
  45. package/dist/runtime/components/renderers/AGalleryCoverImage.d.vue.ts +8 -0
  46. package/dist/runtime/components/renderers/AGalleryCoverImage.vue +60 -0
  47. package/dist/runtime/components/renderers/AGalleryCoverImage.vue.d.ts +8 -0
  48. package/dist/runtime/components/renderers/AGalleryRenderer.d.vue.ts +12 -1
  49. package/dist/runtime/components/renderers/AGalleryRenderer.vue +221 -55
  50. package/dist/runtime/components/renderers/AGalleryRenderer.vue.d.ts +12 -1
  51. package/dist/runtime/components/renderers/AGraphRenderer.d.vue.ts +19 -0
  52. package/dist/runtime/components/renderers/AGraphRenderer.vue +1027 -0
  53. package/dist/runtime/components/renderers/AGraphRenderer.vue.d.ts +19 -0
  54. package/dist/runtime/components/renderers/AKanbanRenderer.d.vue.ts +13 -1
  55. package/dist/runtime/components/renderers/AKanbanRenderer.vue +474 -140
  56. package/dist/runtime/components/renderers/AKanbanRenderer.vue.d.ts +13 -1
  57. package/dist/runtime/components/renderers/AMapRenderer.d.vue.ts +19 -0
  58. package/dist/runtime/components/renderers/AMapRenderer.vue +1622 -0
  59. package/dist/runtime/components/renderers/AMapRenderer.vue.d.ts +19 -0
  60. package/dist/runtime/components/renderers/AOutlineRenderer.d.vue.ts +12 -1
  61. package/dist/runtime/components/renderers/AOutlineRenderer.vue +294 -134
  62. package/dist/runtime/components/renderers/AOutlineRenderer.vue.d.ts +12 -1
  63. package/dist/runtime/components/renderers/ATableRenderer.d.vue.ts +12 -1
  64. package/dist/runtime/components/renderers/ATableRenderer.vue +437 -145
  65. package/dist/runtime/components/renderers/ATableRenderer.vue.d.ts +12 -1
  66. package/dist/runtime/components/renderers/ATimelineRenderer.d.vue.ts +19 -0
  67. package/dist/runtime/components/renderers/ATimelineRenderer.vue +446 -0
  68. package/dist/runtime/components/renderers/ATimelineRenderer.vue.d.ts +19 -0
  69. package/dist/runtime/composables/useAwareness.js +5 -0
  70. package/dist/runtime/composables/useBroadcastSync.d.ts +18 -0
  71. package/dist/runtime/composables/useBroadcastSync.js +26 -0
  72. package/dist/runtime/composables/useChat.js +4 -2
  73. package/dist/runtime/composables/useChatUsers.js +2 -1
  74. package/dist/runtime/composables/useCommandPalette.js +62 -3
  75. package/dist/runtime/composables/useConnectionStatus.js +7 -0
  76. package/dist/runtime/composables/useDevicePairing.d.ts +58 -0
  77. package/dist/runtime/composables/useDevicePairing.js +108 -0
  78. package/dist/runtime/composables/useDocExport.d.ts +5 -0
  79. package/dist/runtime/composables/useDocExport.js +2 -2
  80. package/dist/runtime/composables/useDocImport.js +4 -3
  81. package/dist/runtime/composables/useDocSeo.d.ts +20 -0
  82. package/dist/runtime/composables/useDocSeo.js +44 -0
  83. package/dist/runtime/composables/useDocSlugs.d.ts +7 -0
  84. package/dist/runtime/composables/useDocSlugs.js +20 -0
  85. package/dist/runtime/composables/useDocTree.d.ts +34 -0
  86. package/dist/runtime/composables/useDocTree.js +35 -0
  87. package/dist/runtime/composables/useEditorDragHandle.js +2 -1
  88. package/dist/runtime/composables/useEditorMentions.js +4 -2
  89. package/dist/runtime/composables/useEditorSuggestions.d.ts +1 -0
  90. package/dist/runtime/composables/useEditorSuggestions.js +9 -2
  91. package/dist/runtime/composables/useEditorToolbar.js +2 -1
  92. package/dist/runtime/composables/useFileIndex.js +2 -1
  93. package/dist/runtime/composables/useFileTransfer.d.ts +112 -0
  94. package/dist/runtime/composables/useFileTransfer.js +171 -0
  95. package/dist/runtime/composables/useFollowUser.js +2 -1
  96. package/dist/runtime/composables/useInvites.d.ts +56 -0
  97. package/dist/runtime/composables/useInvites.js +77 -0
  98. package/dist/runtime/composables/useNodePanel.d.ts +14 -0
  99. package/dist/runtime/composables/useNodePanel.js +52 -0
  100. package/dist/runtime/composables/useNotifications.js +4 -2
  101. package/dist/runtime/composables/usePasskeyAccounts.js +4 -2
  102. package/dist/runtime/composables/useSearchIndex.d.ts +1 -0
  103. package/dist/runtime/composables/useSearchIndex.js +13 -5
  104. package/dist/runtime/composables/useServerInfo.d.ts +31 -0
  105. package/dist/runtime/composables/useServerInfo.js +80 -0
  106. package/dist/runtime/composables/useSlugRoute.d.ts +6 -0
  107. package/dist/runtime/composables/useSlugRoute.js +19 -0
  108. package/dist/runtime/composables/useSpaces.d.ts +37 -0
  109. package/dist/runtime/composables/useSpaces.js +83 -0
  110. package/dist/runtime/composables/useTouchDrag.d.ts +34 -0
  111. package/dist/runtime/composables/useTouchDrag.js +191 -0
  112. package/dist/runtime/composables/useTrash.d.ts +1 -1
  113. package/dist/runtime/composables/useTrash.js +6 -3
  114. package/dist/runtime/composables/useWebRTC.d.ts +50 -0
  115. package/dist/runtime/composables/useWebRTC.js +177 -0
  116. package/dist/runtime/extensions/meta-field.d.ts +4 -1
  117. package/dist/runtime/extensions/steps.js +1 -1
  118. package/dist/runtime/extensions/views/AccordionItemView.vue +13 -3
  119. package/dist/runtime/extensions/views/AccordionView.vue +4 -1
  120. package/dist/runtime/extensions/views/BadgeView.vue +11 -2
  121. package/dist/runtime/extensions/views/CalloutView.vue +4 -1
  122. package/dist/runtime/extensions/views/CardGroupView.vue +4 -1
  123. package/dist/runtime/extensions/views/CardView.vue +17 -3
  124. package/dist/runtime/extensions/views/CodeGroupView.vue +4 -1
  125. package/dist/runtime/extensions/views/CollapsibleView.vue +8 -2
  126. package/dist/runtime/extensions/views/FileNodeView.vue +32 -8
  127. package/dist/runtime/extensions/views/KbdView.vue +8 -2
  128. package/dist/runtime/extensions/views/MetaFieldView.vue +208 -46
  129. package/dist/runtime/extensions/views/ProseIconView.vue +8 -2
  130. package/dist/runtime/extensions/views/TabsView.vue +17 -4
  131. package/dist/runtime/locale.d.ts +71 -0
  132. package/dist/runtime/locale.js +71 -0
  133. package/dist/runtime/plugin-abracadabra.client.js +29 -3
  134. package/dist/runtime/plugin-abracadabra.server.js +2 -0
  135. package/dist/runtime/server/api/_abracadabra/render/[docId].get.d.ts +1 -1
  136. package/dist/runtime/server/api/_abracadabra/render/[docId].get.js +29 -4
  137. package/dist/runtime/server/api/_abracadabra/resolve/[...slug].get.d.ts +2 -0
  138. package/dist/runtime/server/api/_abracadabra/resolve/[...slug].get.js +43 -0
  139. package/dist/runtime/server/api/_abracadabra/slugs.get.d.ts +2 -0
  140. package/dist/runtime/server/api/_abracadabra/slugs.get.js +7 -0
  141. package/dist/runtime/server/plugins/abracadabra-service.js +10 -5
  142. package/dist/runtime/server/runners/doc-tree-cache.js +4 -0
  143. package/dist/runtime/server/utils/slugMap.d.ts +32 -0
  144. package/dist/runtime/server/utils/slugMap.js +58 -0
  145. package/dist/runtime/types.d.ts +1 -0
  146. package/dist/runtime/utils/docTypes.d.ts +29 -1
  147. package/dist/runtime/utils/docTypes.js +129 -1
  148. package/dist/runtime/utils/markdownToYjs.js +2 -2
  149. package/dist/runtime/utils/sdkRef.d.ts +2 -0
  150. package/dist/runtime/utils/sdkRef.js +7 -0
  151. package/dist/runtime/utils/slugify.d.ts +40 -0
  152. package/dist/runtime/utils/slugify.js +36 -0
  153. package/dist/types.d.mts +6 -0
  154. package/package.json +32 -19
@@ -129,6 +129,77 @@ export interface AbracadabraLocale {
129
129
  untitled: string;
130
130
  noItems: string;
131
131
  };
132
+ checklist: {
133
+ addTask: string;
134
+ empty: string;
135
+ done: string;
136
+ active: string;
137
+ all: string;
138
+ sortManual: string;
139
+ sortPriority: string;
140
+ sortDue: string;
141
+ markComplete: string;
142
+ markIncomplete: string;
143
+ addSubTask: string;
144
+ };
145
+ timeline: {
146
+ addEpic: string;
147
+ addTask: string;
148
+ untitled: string;
149
+ empty: string;
150
+ week: string;
151
+ month: string;
152
+ quarter: string;
153
+ };
154
+ call: {
155
+ noStreams: string;
156
+ turnOnCamera: string;
157
+ };
158
+ graph: {
159
+ addNode: string;
160
+ empty: string;
161
+ pin: string;
162
+ unpin: string;
163
+ openAsDoc: string;
164
+ delete: string;
165
+ };
166
+ dashboard: {
167
+ addItem: string;
168
+ empty: string;
169
+ modeIcon: string;
170
+ modeWidgetSm: string;
171
+ modeWidgetLg: string;
172
+ cleanUp: string;
173
+ open: string;
174
+ rename: string;
175
+ duplicate: string;
176
+ delete: string;
177
+ confirmDelete: string;
178
+ confirmDeleteDesc: string;
179
+ cancel: string;
180
+ };
181
+ map: {
182
+ addMarker: string;
183
+ addLine: string;
184
+ addMeasure: string;
185
+ empty: string;
186
+ pan: string;
187
+ ping: string;
188
+ finish: string;
189
+ save: string;
190
+ points: string;
191
+ follow: string;
192
+ unfollow: string;
193
+ following: string;
194
+ showLabels: string;
195
+ hideLabels: string;
196
+ openInPanel: string;
197
+ hintMarker: string;
198
+ hintPing: string;
199
+ hintLine: string;
200
+ hintMeasure: string;
201
+ hintMeasureAdd: string;
202
+ };
132
203
  };
133
204
  }
134
205
  export declare const DEFAULT_LOCALE: AbracadabraLocale;
@@ -114,6 +114,77 @@ export const DEFAULT_LOCALE = {
114
114
  addItem: "Add item",
115
115
  untitled: "Untitled",
116
116
  noItems: "No items yet"
117
+ },
118
+ checklist: {
119
+ addTask: "Add task",
120
+ empty: "No tasks yet",
121
+ done: "done",
122
+ active: "Active",
123
+ all: "All",
124
+ sortManual: "Manual",
125
+ sortPriority: "Priority",
126
+ sortDue: "Due date",
127
+ markComplete: "Mark complete",
128
+ markIncomplete: "Mark incomplete",
129
+ addSubTask: "Add sub-task"
130
+ },
131
+ timeline: {
132
+ addEpic: "Add epic",
133
+ addTask: "Add task",
134
+ untitled: "Untitled",
135
+ empty: "No epics yet",
136
+ week: "Week",
137
+ month: "Month",
138
+ quarter: "Quarter"
139
+ },
140
+ call: {
141
+ noStreams: "No video streams",
142
+ turnOnCamera: "Turn on your camera to get started"
143
+ },
144
+ graph: {
145
+ addNode: "Add node",
146
+ empty: "No nodes yet",
147
+ pin: "Pin position",
148
+ unpin: "Unpin",
149
+ openAsDoc: "Open as document",
150
+ delete: "Delete"
151
+ },
152
+ dashboard: {
153
+ addItem: "Add item",
154
+ empty: "No items yet",
155
+ modeIcon: "Icon",
156
+ modeWidgetSm: "Small widget",
157
+ modeWidgetLg: "Large widget",
158
+ cleanUp: "Clean up",
159
+ open: "Open",
160
+ rename: "Rename",
161
+ duplicate: "Duplicate",
162
+ delete: "Delete",
163
+ confirmDelete: "Delete item?",
164
+ confirmDeleteDesc: "This action cannot be undone.",
165
+ cancel: "Cancel"
166
+ },
167
+ map: {
168
+ addMarker: "Place marker",
169
+ addLine: "Draw line",
170
+ addMeasure: "Measure distance",
171
+ empty: "No markers yet",
172
+ pan: "Pan",
173
+ ping: "Send ping",
174
+ finish: "Finish",
175
+ save: "Save",
176
+ points: "points",
177
+ follow: "Follow",
178
+ unfollow: "Unfollow",
179
+ following: "Following",
180
+ showLabels: "Show map labels",
181
+ hideLabels: "Hide map labels",
182
+ openInPanel: "Open in sidebar",
183
+ hintMarker: "Click to place marker",
184
+ hintPing: "Click to send ping",
185
+ hintLine: "Click to start drawing a line",
186
+ hintMeasure: "Click to start measuring",
187
+ hintMeasureAdd: "Click to add points"
117
188
  }
118
189
  }
119
190
  };
@@ -34,6 +34,19 @@ import {
34
34
  _initFileIndex,
35
35
  _destroyFileIndex
36
36
  } from "./composables/useFileIndex.js";
37
+ import {
38
+ _initBroadcastSync,
39
+ _destroyBroadcastSync,
40
+ useBroadcastSync
41
+ } from "./composables/useBroadcastSync.js";
42
+ import {
43
+ _destroyWebRTC
44
+ } from "./composables/useWebRTC.js";
45
+ import {
46
+ _initServerInfo,
47
+ _destroyServerInfo
48
+ } from "./composables/useServerInfo.js";
49
+ import { setSdkModule } from "./utils/sdkRef.js";
37
50
  const NUXT_UI_PRIMARY_COLORS = /* @__PURE__ */ new Set([
38
51
  "red",
39
52
  "orange",
@@ -365,6 +378,9 @@ export default defineNuxtPlugin({
365
378
  _destroyNotifications();
366
379
  _destroyChat();
367
380
  _destroyFileIndex();
381
+ _destroyBroadcastSync();
382
+ _destroyWebRTC();
383
+ _destroyServerInfo();
368
384
  }
369
385
  async function switchServer(url) {
370
386
  if (url === currentServerUrl.value) return;
@@ -530,7 +546,7 @@ export default defineNuxtPlugin({
530
546
  }
531
547
  try {
532
548
  const [
533
- { AbracadabraClient, AbracadabraProvider, HocuspocusProviderWebsocket, CryptoIdentityKeystore },
549
+ sdkModule,
534
550
  ed,
535
551
  { sha512 }
536
552
  ] = await Promise.all([
@@ -538,7 +554,9 @@ export default defineNuxtPlugin({
538
554
  import("@noble/ed25519"),
539
555
  import("@noble/hashes/sha512")
540
556
  ]);
541
- ed.etc.sha512Sync = (m) => sha512(m);
557
+ setSdkModule(sdkModule);
558
+ const { AbracadabraClient, AbracadabraProvider, HocuspocusProviderWebsocket, CryptoIdentityKeystore } = sdkModule;
559
+ ed.hashes.sha512 = (m) => sha512(m);
542
560
  const ks = new CryptoIdentityKeystore();
543
561
  keystore.value = ks;
544
562
  let privKey = null;
@@ -662,7 +680,7 @@ export default defineNuxtPlugin({
662
680
  }
663
681
  }
664
682
  const server = savedServers.value.find((s) => s.url === serverUrl);
665
- let docId = configEntryDocId ?? server?.hubDocId ?? spacesInfo?.hubDocId ?? server?.cachedSpaces?.[0]?.doc_id ?? spacesInfo?.spaces?.[0]?.doc_id ?? server?.entryDocId ?? info.index_doc_id ?? void 0;
683
+ const docId = configEntryDocId ?? server?.hubDocId ?? spacesInfo?.hubDocId ?? server?.cachedSpaces?.[0]?.doc_id ?? spacesInfo?.spaces?.[0]?.doc_id ?? server?.entryDocId ?? info.index_doc_id ?? void 0;
666
684
  if (!docId) {
667
685
  connectionError.value = "No entry document found. Configure entryDocId or ensure the server has spaces or an index document.";
668
686
  addLog("No entry document \u2014 cannot connect", "system");
@@ -736,6 +754,12 @@ export default defineNuxtPlugin({
736
754
  }).catch(() => {
737
755
  });
738
756
  }
757
+ if (features.broadcastSync !== false) {
758
+ const { BroadcastChannelSync } = sdkModule ?? {};
759
+ if (BroadcastChannelSync && _provider && _doc) {
760
+ _initBroadcastSync(BroadcastChannelSync, _doc, docId, _provider.awareness);
761
+ }
762
+ }
739
763
  try {
740
764
  _provider.sendStateless?.(JSON.stringify({ type: "notify:fetch", limit: 20 }));
741
765
  } catch {
@@ -818,6 +842,7 @@ export default defineNuxtPlugin({
818
842
  _initFileBlobStore(blobStore);
819
843
  } catch {
820
844
  }
845
+ _initServerInfo(_client);
821
846
  _initNotifications(publicKeyB64);
822
847
  _initChat(publicKeyB64, userName);
823
848
  if (_provider.onStateless !== void 0) {
@@ -890,6 +915,7 @@ export default defineNuxtPlugin({
890
915
  createInvite,
891
916
  listInvites,
892
917
  revokeInvite,
918
+ broadcastSyncActive: useBroadcastSync().isActive,
893
919
  init
894
920
  };
895
921
  nuxtApp.provide("abracadabra", abra);
@@ -43,6 +43,8 @@ export default defineNuxtPlugin({
43
43
  logs: ref([]),
44
44
  // Plugin registry (empty, frozen)
45
45
  registry,
46
+ // Sync state
47
+ broadcastSyncActive: ref(false),
46
48
  // Methods — all no-ops on server
47
49
  addLog: noop,
48
50
  setUserName: noop,
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Usage in a Nuxt page:
14
14
  * const { data } = await useFetch(`/api/_abracadabra/render/${docId}`)
15
- * // data.value: { html, title, type, meta, docId, source }
15
+ * // data.value: { html, title, type, meta, description, ogImage, docId, source }
16
16
  */
17
17
  declare const _default: any;
18
18
  export default _default;
@@ -1,3 +1,21 @@
1
+ function extractFirstParagraph(html) {
2
+ if (!html) return "";
3
+ const pMatch = html.match(/<p[^>]*>(.*?)<\/p>/is);
4
+ const raw = pMatch?.[1] ?? html;
5
+ const text = raw.replace(/<[^>]+>/g, "").replace(/&[a-z]+;/gi, " ").trim();
6
+ if (!text) return "";
7
+ return text.length > 160 ? text.slice(0, 157) + "..." : text;
8
+ }
9
+ function extractFirstImage(html) {
10
+ if (!html) return "";
11
+ const match = html.match(/<img[^>]+src=["']([^"']+)["']/i);
12
+ return match?.[1] ?? "";
13
+ }
14
+ let _maxAge = 60;
15
+ try {
16
+ _maxAge = useRuntimeConfig().public?.abracadabra?.server?.cacheMaxAge ?? 60;
17
+ } catch {
18
+ }
1
19
  export default defineCachedEventHandler(async (event) => {
2
20
  const config = useRuntimeConfig();
3
21
  const baseUrl = config.public?.abracadabra?.url;
@@ -15,12 +33,15 @@ export default defineCachedEventHandler(async (event) => {
15
33
  ]);
16
34
  if (cachedHtml !== null) {
17
35
  const treeEntry = cachedJson?.["doc-tree"]?.[docId] ?? {};
36
+ const html2 = cachedHtml ?? "";
18
37
  return {
19
38
  docId,
20
- html: cachedHtml ?? "",
39
+ html: html2,
21
40
  title: treeEntry.label ?? "",
22
41
  type: treeEntry.type ?? "doc",
23
42
  meta: treeEntry.meta ?? {},
43
+ description: treeEntry.meta?.note ?? treeEntry.meta?.description ?? extractFirstParagraph(html2),
44
+ ogImage: treeEntry.meta?.coverImage ?? extractFirstImage(html2),
24
45
  source: "crdt-cache"
25
46
  };
26
47
  }
@@ -37,15 +58,19 @@ export default defineCachedEventHandler(async (event) => {
37
58
  { headers }
38
59
  ).catch(() => null)
39
60
  ]);
61
+ const html = exportRes?.html ?? "";
62
+ const meta = metaRes?.meta ?? {};
40
63
  return {
41
64
  docId,
42
- html: exportRes?.html ?? "",
65
+ html,
43
66
  title: metaRes?.label ?? "",
44
67
  type: metaRes?.type ?? "doc",
45
- meta: metaRes?.meta ?? {},
68
+ meta,
69
+ description: meta?.note ?? meta?.description ?? extractFirstParagraph(html),
70
+ ogImage: meta?.coverImage ?? extractFirstImage(html),
46
71
  source: "rest"
47
72
  };
48
73
  }, {
49
- maxAge: 60,
74
+ maxAge: _maxAge,
50
75
  getKey: (event) => `abra:render:${getRouterParam(event, "docId")}`
51
76
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,43 @@
1
+ import { getDocIdFromSlug } from "../../../utils/slugMap.js";
2
+ import { getCachedHTML, getCachedJSON } from "../../../utils/docCache.js";
3
+ function extractFirstParagraph(html) {
4
+ if (!html) return "";
5
+ const pMatch = html.match(/<p[^>]*>(.*?)<\/p>/is);
6
+ const raw = pMatch?.[1] ?? html;
7
+ const text = raw.replace(/<[^>]+>/g, "").replace(/&[a-z]+;/gi, " ").trim();
8
+ if (!text) return "";
9
+ return text.length > 160 ? text.slice(0, 157) + "..." : text;
10
+ }
11
+ function extractFirstImage(html) {
12
+ if (!html) return "";
13
+ const match = html.match(/<img[^>]+src=["']([^"']+)["']/i);
14
+ return match?.[1] ?? "";
15
+ }
16
+ export default defineEventHandler(async (event) => {
17
+ const slugParam = getRouterParam(event, "slug");
18
+ if (!slugParam) {
19
+ throw createError({ statusCode: 400, message: "slug is required" });
20
+ }
21
+ const slug = slugParam;
22
+ const docId = getDocIdFromSlug(slug);
23
+ if (!docId) {
24
+ throw createError({ statusCode: 404, message: `Document not found for slug: ${slug}` });
25
+ }
26
+ const [cachedHtml, cachedJson] = await Promise.all([
27
+ getCachedHTML(docId),
28
+ getCachedJSON(docId)
29
+ ]);
30
+ const html = cachedHtml ?? "";
31
+ const treeEntry = cachedJson?.["doc-tree"]?.[docId] ?? {};
32
+ return {
33
+ docId,
34
+ slug,
35
+ html,
36
+ title: treeEntry.label ?? "",
37
+ type: treeEntry.type ?? "doc",
38
+ meta: treeEntry.meta ?? {},
39
+ description: treeEntry.meta?.note ?? treeEntry.meta?.description ?? extractFirstParagraph(html),
40
+ ogImage: treeEntry.meta?.coverImage ?? extractFirstImage(html),
41
+ source: cachedHtml !== null ? "crdt-cache" : "none"
42
+ };
43
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import { getSlugMapData } from "../../utils/slugMap.js";
2
+ export default defineCachedEventHandler(async () => {
3
+ return getSlugMapData();
4
+ }, {
5
+ maxAge: 30,
6
+ getKey: () => "abra:slugs"
7
+ });
@@ -4,6 +4,7 @@ import { useStorage } from "nitropack/runtime/storage";
4
4
  import { registerServerPlugin, bootRunners, shutdownAllRunners } from "../utils/serverRunner.js";
5
5
  import { createDocCacheAPI } from "../utils/docCache.js";
6
6
  import { docTreeCacheRunner } from "../runners/doc-tree-cache.js";
7
+ import { initSlugMap, loadPersistedSlugMap } from "../utils/slugMap.js";
7
8
  function fromBase64Url(str) {
8
9
  const b = atob(str.replace(/-/g, "+").replace(/_/g, "/").padEnd(str.length + (4 - str.length % 4) % 4, "="));
9
10
  const a = new Uint8Array(b.length);
@@ -18,6 +19,9 @@ function toBase64Url(bytes) {
18
19
  export default defineNitroPlugin(async (nitroApp) => {
19
20
  const config = useRuntimeConfig();
20
21
  const abraConfig = config.abracadabra;
22
+ const storage = useStorage();
23
+ initSlugMap(storage);
24
+ await loadPersistedSlugMap();
21
25
  const pubKeyB64 = abraConfig?.servicePublicKey ?? "";
22
26
  const privKeyB64 = abraConfig?.servicePrivateKey ?? "";
23
27
  const rootDocIdOverride = abraConfig?.serviceRootDocId ?? "";
@@ -38,15 +42,16 @@ export default defineNitroPlugin(async (nitroApp) => {
38
42
  import("@noble/hashes/sha2.js"),
39
43
  import("yjs")
40
44
  ]);
41
- ed.etc = ed.etc ?? {};
42
- ed.etc.sha512Sync = (m) => sha512(m);
45
+ if (ed.etc && !ed.etc.sha512Sync) {
46
+ ed.etc.sha512Sync = (m) => sha512(m);
47
+ }
43
48
  const privKey = fromBase64Url(privKeyB64);
44
49
  const client = new AbracadabraClient({
45
50
  url: config.public?.abracadabra?.url,
46
51
  persistAuth: false
47
52
  });
48
53
  await client.loginWithKey(pubKeyB64, async (challenge) => {
49
- const sig = await ed.sign(fromBase64Url(challenge), privKey);
54
+ const sig = await ed.signAsync(fromBase64Url(challenge), privKey);
50
55
  return toBase64Url(sig);
51
56
  });
52
57
  console.log("[abracadabra-service] Authenticated as service role");
@@ -87,13 +92,13 @@ export default defineNitroPlugin(async (nitroApp) => {
87
92
  new Promise((_, reject) => setTimeout(() => reject(new Error("sync timeout")), 3e4))
88
93
  ]);
89
94
  console.log("[abracadabra-service] Root doc synced");
90
- const storage = useStorage();
95
+ const storage2 = useStorage();
91
96
  const ctx = {
92
97
  client,
93
98
  wsp,
94
99
  rootDoc,
95
100
  rootProvider,
96
- storage,
101
+ storage: storage2,
97
102
  docCache: createDocCacheAPI(rootDoc)
98
103
  };
99
104
  const publicConfig = config.public?.abracadabra;
@@ -1,5 +1,6 @@
1
1
  import { initDocCache, observeDoc, unobserveDoc } from "../utils/docCache.js";
2
2
  import { getServerPlugins } from "../utils/serverRunner.js";
3
+ import { initSlugMap, rebuildSlugMapSync, rebuildSlugMap } from "../utils/slugMap.js";
3
4
  export const docTreeCacheRunner = {
4
5
  name: "abracadabra:doc-tree-cache",
5
6
  async start(ctx) {
@@ -9,6 +10,7 @@ export const docTreeCacheRunner = {
9
10
  const pendingDocs = /* @__PURE__ */ new Set();
10
11
  const serverExts = getServerPlugins().flatMap((p) => p.serverExtensions?.() ?? []);
11
12
  initDocCache(storage, serverExts);
13
+ initSlugMap(storage);
12
14
  async function trackDoc(docId) {
13
15
  if (loadedDocs.has(docId) || pendingDocs.has(docId)) return;
14
16
  pendingDocs.add(docId);
@@ -42,6 +44,7 @@ export const docTreeCacheRunner = {
42
44
  treeMap.forEach((_val, docId) => {
43
45
  trackDoc(docId);
44
46
  });
47
+ rebuildSlugMapSync(treeMap);
45
48
  const observer = (event) => {
46
49
  event.keysChanged.forEach((docId) => {
47
50
  if (treeMap.has(docId)) {
@@ -52,6 +55,7 @@ export const docTreeCacheRunner = {
52
55
  console.log(`[abracadabra:doc-tree-cache] Untracked: ${docId}`);
53
56
  }
54
57
  });
58
+ rebuildSlugMap(treeMap);
55
59
  };
56
60
  treeMap.observe(observer);
57
61
  return () => {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Server-side slug map singleton.
3
+ *
4
+ * Maintains bidirectional slug↔docId maps, persists them to Nitro storage,
5
+ * and loads them on startup so slug routes work even before CRDT sync.
6
+ */
7
+ import { type SlugMap } from '../../utils/slugify.js';
8
+ /**
9
+ * Initialize slug map with a Nitro storage handle.
10
+ * Call early in the service plugin lifecycle.
11
+ */
12
+ export declare function initSlugMap(storage: any): void;
13
+ /**
14
+ * Load persisted slug map from storage.
15
+ * Call on startup before CRDT connects so slug routes work immediately.
16
+ */
17
+ export declare function loadPersistedSlugMap(): Promise<void>;
18
+ /**
19
+ * Rebuild the slug map from a Y.Map tree (debounced).
20
+ * Called by the doc-tree-cache runner when the tree changes.
21
+ */
22
+ export declare function rebuildSlugMap(treeMap: any): void;
23
+ /**
24
+ * Rebuild immediately (used for initial load).
25
+ */
26
+ export declare function rebuildSlugMapSync(treeMap: any): void;
27
+ /** Resolve a slug path to a docId. Returns null if not found. */
28
+ export declare function getDocIdFromSlug(slug: string): string | null;
29
+ /** Get the slug for a docId. Returns null if not found. */
30
+ export declare function getSlugFromDocId(docId: string): string | null;
31
+ /** Get the full current slug map (for the slugs API endpoint). */
32
+ export declare function getSlugMapData(): SlugMap;
@@ -0,0 +1,58 @@
1
+ import { buildSlugMap } from "../../utils/slugify.js";
2
+ let _slugMap = { slugToId: {}, idToSlug: {} };
3
+ let _storage = null;
4
+ let _rebuildTimer = null;
5
+ const SLUG_STORAGE_KEY = "doc-cache:slug-map";
6
+ const REBUILD_DEBOUNCE_MS = 2e3;
7
+ export function initSlugMap(storage) {
8
+ _storage = storage;
9
+ }
10
+ export async function loadPersistedSlugMap() {
11
+ if (!_storage) return;
12
+ try {
13
+ const stored = await _storage.getItem(SLUG_STORAGE_KEY);
14
+ if (stored && typeof stored === "object" && stored.slugToId && stored.idToSlug) {
15
+ _slugMap = stored;
16
+ console.log(`[abracadabra:slug-map] Loaded ${Object.keys(_slugMap.slugToId).length} slugs from cache`);
17
+ }
18
+ } catch (e) {
19
+ console.warn("[abracadabra:slug-map] Failed to load persisted slug map:", e);
20
+ }
21
+ }
22
+ export function rebuildSlugMap(treeMap) {
23
+ if (_rebuildTimer) clearTimeout(_rebuildTimer);
24
+ _rebuildTimer = setTimeout(() => {
25
+ _rebuildTimer = null;
26
+ rebuildSlugMapSync(treeMap);
27
+ }, REBUILD_DEBOUNCE_MS);
28
+ }
29
+ export function rebuildSlugMapSync(treeMap) {
30
+ try {
31
+ const raw = treeMap.toJSON();
32
+ const entries = Object.entries(raw).map(([id, entry]) => ({
33
+ id,
34
+ label: entry.label ?? "",
35
+ parentId: entry.parentId ?? null,
36
+ order: entry.order ?? 0,
37
+ trashed: entry.trashed ?? false
38
+ }));
39
+ _slugMap = buildSlugMap(entries);
40
+ if (_storage) {
41
+ _storage.setItem(SLUG_STORAGE_KEY, _slugMap).catch((e) => {
42
+ console.warn("[abracadabra:slug-map] Failed to persist:", e);
43
+ });
44
+ }
45
+ console.log(`[abracadabra:slug-map] Rebuilt: ${Object.keys(_slugMap.slugToId).length} slugs`);
46
+ } catch (e) {
47
+ console.warn("[abracadabra:slug-map] Rebuild failed:", e);
48
+ }
49
+ }
50
+ export function getDocIdFromSlug(slug) {
51
+ return _slugMap.slugToId[slug] ?? null;
52
+ }
53
+ export function getSlugFromDocId(docId) {
54
+ return _slugMap.idToSlug[docId] ?? null;
55
+ }
56
+ export function getSlugMapData() {
57
+ return _slugMap;
58
+ }
@@ -390,6 +390,7 @@ export interface AbracadabraState {
390
390
  }) => Promise<any>;
391
391
  listInvites: () => Promise<any[]>;
392
392
  revokeInvite: (code: string) => Promise<void>;
393
+ broadcastSyncActive: Ref<boolean>;
393
394
  init: (serverUrl?: string) => Promise<void>;
394
395
  }
395
396
  /**
@@ -97,7 +97,30 @@ export interface TagsField {
97
97
  options?: string[];
98
98
  label?: string;
99
99
  }
100
- export type MetaField = DatetimeRangeField | DateRangeField | DatetimeField | DateField | SliderField | ColorPresetField | ColorPickerField | TextareaField | NumberField | ToggleField | SelectField | MultiSelectField | UrlField | RatingField | TagsField;
100
+ export interface TimeRangeField {
101
+ type: 'timerange';
102
+ startKey: string;
103
+ endKey: string;
104
+ label?: string;
105
+ }
106
+ export interface TimeField {
107
+ type: 'time';
108
+ key: string;
109
+ label?: string;
110
+ }
111
+ export interface LocationField {
112
+ type: 'location';
113
+ latKey: string;
114
+ lngKey: string;
115
+ label?: string;
116
+ }
117
+ export interface IconField {
118
+ type: 'icon';
119
+ key: string;
120
+ options: string[];
121
+ label?: string;
122
+ }
123
+ export type MetaField = DatetimeRangeField | DateRangeField | TimeRangeField | DatetimeField | DateField | TimeField | SliderField | ColorPresetField | ColorPickerField | LocationField | IconField | TextareaField | NumberField | ToggleField | SelectField | MultiSelectField | UrlField | RatingField | TagsField;
101
124
  export interface DocTypeDefinition {
102
125
  key: string;
103
126
  label: string;
@@ -121,6 +144,11 @@ export interface DocTypeDefinition {
121
144
  /** Metadata fields shown/edited in the document properties panel. */
122
145
  metaSchema?: MetaField[];
123
146
  }
147
+ /**
148
+ * Per-geoType meta schemas. Used by AEditor to show the right fields
149
+ * regardless of the parent doc's type (handles nested marker/line/measure).
150
+ */
151
+ export declare const GEO_TYPE_META_SCHEMAS: Record<string, MetaField[]>;
124
152
  export declare const DOC_TYPES: Record<string, DocTypeDefinition>;
125
153
  export declare const DEFAULT_DOC_TYPE: DocTypeDefinition;
126
154
  /** Resolve a type key to a DocTypeDefinition. Falls back to plugin types, then to 'doc'. */