@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
@@ -1,5 +1,8 @@
1
1
  <script setup>
2
2
  import { shallowRef, ref, watch, watchEffect, computed, nextTick } from "vue";
3
+ import { Fragment } from "@tiptap/pm/model";
4
+ import { resolveDocType, GEO_TYPE_META_SCHEMAS } from "../utils/docTypes";
5
+ import { schemaFieldToAttrs, userFieldToAttrs } from "../extensions/meta-field";
3
6
  import { useEditor } from "../composables/useEditor";
4
7
  import { useEditorToolbar } from "../composables/useEditorToolbar";
5
8
  import { useEditorSuggestions } from "../composables/useEditorSuggestions";
@@ -13,7 +16,9 @@ const props = defineProps({
13
16
  showToolbar: { type: Boolean, required: false, default: true },
14
17
  showSuggestionMenu: { type: Boolean, required: false, default: true },
15
18
  showDragHandle: { type: Boolean, required: false, default: true },
16
- docMeta: { type: Object, required: false }
19
+ docMeta: { type: Object, required: false },
20
+ parentType: { type: String, required: false },
21
+ metaSchema: { type: Array, required: false }
17
22
  });
18
23
  const emit = defineEmits(["ready", "update", "rename", "updateMeta"]);
19
24
  const model = defineModel({ type: null });
@@ -37,7 +42,35 @@ watch(ready, (val) => {
37
42
  });
38
43
  const editorRef = ref(null);
39
44
  const { items: toolbarItems } = useEditorToolbar({ docId: props.docId });
40
- const { items: suggestionItems } = useEditorSuggestions({ docId: props.docId });
45
+ const resolvedMetaSchema = computed(() => {
46
+ if (props.metaSchema) return props.metaSchema;
47
+ const geoType = props.docMeta?.geoType;
48
+ if (geoType && geoType in GEO_TYPE_META_SCHEMAS)
49
+ return GEO_TYPE_META_SCHEMAS[geoType];
50
+ return resolveDocType(props.parentType, registry).metaSchema ?? [];
51
+ });
52
+ const { items: allSuggestionItems, propertiesOnlyItems } = useEditorSuggestions({ docId: props.docId });
53
+ const isInDocumentMeta = ref(false);
54
+ watchEffect((onCleanup) => {
55
+ const ed = editorRef.value?.editor;
56
+ if (!ed || !ready.value) return;
57
+ const check = () => {
58
+ const { $head } = ed.state.selection;
59
+ let inMeta = false;
60
+ for (let d = $head.depth; d >= 0; d--) {
61
+ if ($head.node(d).type.name === "documentMeta") {
62
+ inMeta = true;
63
+ break;
64
+ }
65
+ }
66
+ isInDocumentMeta.value = inMeta;
67
+ };
68
+ ed.on("selectionUpdate", check);
69
+ onCleanup(() => ed.off("selectionUpdate", check));
70
+ });
71
+ const suggestionItems = computed(
72
+ () => isInDocumentMeta.value ? propertiesOnlyItems.value : allSuggestionItems.value
73
+ );
41
74
  const dragHandle = useEditorDragHandle();
42
75
  watchEffect(() => {
43
76
  const isReady = ready.value;
@@ -49,6 +82,57 @@ watchEffect(() => {
49
82
  storage.pageMeta = docMeta ?? null;
50
83
  storage.updateMeta = (patch) => emit("updateMeta", patch);
51
84
  });
85
+ let metaInitDone = false;
86
+ function handleMetaUpdate(patch) {
87
+ emit("updateMeta", patch);
88
+ }
89
+ function initDocumentMeta(ed) {
90
+ if (metaInitDone) return;
91
+ const doc2 = ed.state.doc;
92
+ let metaPos = -1;
93
+ let metaNode = null;
94
+ doc2.forEach((node, offset) => {
95
+ if (node.type.name === "documentMeta") {
96
+ metaPos = offset;
97
+ metaNode = node;
98
+ }
99
+ });
100
+ if (metaPos === -1 || !metaNode) return;
101
+ if (metaNode.childCount > 0) {
102
+ metaInitDone = true;
103
+ return;
104
+ }
105
+ const docMeta = props.docMeta;
106
+ if (!docMeta) return;
107
+ const schema = resolvedMetaSchema.value;
108
+ const userFields = docMeta._metaFields ?? [];
109
+ const wasAlreadyInitialized = !!docMeta._metaInitialized;
110
+ if (wasAlreadyInitialized && !schema.length) {
111
+ metaInitDone = true;
112
+ return;
113
+ }
114
+ if (!wasAlreadyInitialized) {
115
+ handleMetaUpdate({ _metaInitialized: true });
116
+ }
117
+ if (!schema.length && !userFields.length) {
118
+ metaInitDone = true;
119
+ return;
120
+ }
121
+ const metaFieldType = ed.schema.nodes.metaField;
122
+ if (!metaFieldType) return;
123
+ const nodesToInsert = wasAlreadyInitialized ? schema.map((f) => metaFieldType.create(schemaFieldToAttrs(f))) : [
124
+ ...schema.map((f) => metaFieldType.create(schemaFieldToAttrs(f))),
125
+ ...userFields.map((f) => metaFieldType.create(userFieldToAttrs(f)))
126
+ ];
127
+ const { tr } = ed.state;
128
+ const insertPos = metaPos + 1;
129
+ tr.insert(insertPos, Fragment.fromArray(nodesToInsert));
130
+ ed.view.dispatch(tr);
131
+ if (!wasAlreadyInitialized && userFields.length) {
132
+ handleMetaUpdate({ _metaFields: void 0 });
133
+ }
134
+ metaInitDone = true;
135
+ }
52
136
  let syncingFromEditor = false;
53
137
  const _lastEmittedHeader = ref("");
54
138
  function getHeaderText(ed) {
@@ -106,8 +190,12 @@ watchEffect((onCleanup) => {
106
190
  }
107
191
  function onUpdate() {
108
192
  syncHeaderToTree(ed);
193
+ initDocumentMeta(ed);
109
194
  }
110
- nextTick(() => doInitialSync());
195
+ nextTick(() => {
196
+ doInitialSync();
197
+ initDocumentMeta(ed);
198
+ });
111
199
  ed.on("update", onUpdate);
112
200
  onCleanup(() => ed.off("update", onUpdate));
113
201
  });
@@ -141,7 +229,10 @@ function onPlusClick(e, onClick) {
141
229
  <template>
142
230
  <ClientOnly>
143
231
  <!-- Loading skeleton while extensions and child provider are loading -->
144
- <div v-if="!extensions.length" class="p-6 space-y-3">
232
+ <div
233
+ v-if="!extensions.length"
234
+ class="p-6 space-y-3"
235
+ >
145
236
  <div class="h-8 w-64 animate-pulse rounded bg-elevated" />
146
237
  <div class="h-4 w-full animate-pulse rounded bg-elevated" />
147
238
  <div class="h-4 w-3/4 animate-pulse rounded bg-elevated" />
@@ -151,6 +242,7 @@ function onPlusClick(e, onClick) {
151
242
  <UEditor
152
243
  v-else
153
244
  ref="editorRef"
245
+ v-slot="{ editor }"
154
246
  v-model="model"
155
247
  :content-type="contentType"
156
248
  :editable="editable"
@@ -158,11 +250,14 @@ function onPlusClick(e, onClick) {
158
250
  :starter-kit="{ undoRedo: false, codeBlock: false, document: false }"
159
251
  :handlers="editorHandlers"
160
252
  :placeholder="placeholder"
161
- v-slot="{ editor }"
162
253
  @update:model-value="emit('update', $event)"
163
254
  >
164
255
  <!-- Default slot: app can override entire editor content -->
165
- <slot :editor="editor" :connected-users="connectedUsers" :ready="ready">
256
+ <slot
257
+ :editor="editor"
258
+ :connected-users="connectedUsers"
259
+ :ready="ready"
260
+ >
166
261
  <!-- ── Bubble toolbar (appears when text is selected) ─────────────── -->
167
262
  <UEditorToolbar
168
263
  v-if="showToolbar"
@@ -182,8 +277,8 @@ function onPlusClick(e, onClick) {
182
277
  <!-- ── Drag handle — plus button + grip dropdown ───────────────────── -->
183
278
  <UEditorDragHandle
184
279
  v-if="showDragHandle"
185
- :editor="editor"
186
280
  v-slot="{ ui, onClick }"
281
+ :editor="editor"
187
282
  @node-change="dragHandle.onNodeChange"
188
283
  >
189
284
  <!-- Plus: insert block via slash menu -->
@@ -1,4 +1,5 @@
1
1
  import type { DocPageMeta } from '../types.js';
2
+ import type { MetaField } from '../utils/docTypes.js';
2
3
  type __VLS_Props = {
3
4
  /** Y.Doc ID to load as a child document */
4
5
  docId: string;
@@ -18,6 +19,10 @@ type __VLS_Props = {
18
19
  showDragHandle?: boolean;
19
20
  /** Current document metadata from the doc-tree — written into MetaField storage */
20
21
  docMeta?: DocPageMeta;
22
+ /** Parent document's type key — used to resolve metaSchema for auto-inserting chips */
23
+ parentType?: string;
24
+ /** Override metaSchema directly instead of resolving from parentType */
25
+ metaSchema?: MetaField[];
21
26
  };
22
27
  type __VLS_ModelProps = {
23
28
  modelValue?: any;
@@ -39,15 +44,15 @@ type __VLS_Slots = {} & {
39
44
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
40
45
  "update:modelValue": (value: any) => any;
41
46
  rename: (label: string) => any;
42
- ready: () => any;
43
- update: (content: any) => any;
44
47
  updateMeta: (patch: Partial<DocPageMeta>) => any;
48
+ update: (content: any) => any;
49
+ ready: () => any;
45
50
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
46
51
  "onUpdate:modelValue"?: ((value: any) => any) | undefined;
47
52
  onRename?: ((label: string) => any) | undefined;
48
- onReady?: (() => any) | undefined;
49
- onUpdate?: ((content: any) => any) | undefined;
50
53
  onUpdateMeta?: ((patch: Partial<DocPageMeta>) => any) | undefined;
54
+ onUpdate?: ((content: any) => any) | undefined;
55
+ onReady?: (() => any) | undefined;
51
56
  }>, {
52
57
  contentType: "json" | "html" | "markdown";
53
58
  editable: boolean;
@@ -19,7 +19,7 @@ const {
19
19
  moveWindow,
20
20
  resizeWindow
21
21
  } = useWindowManager();
22
- const win = computed(() => windows.value.get(props.windowId));
22
+ const win = computed(() => windows.get(props.windowId));
23
23
  const isAnimatingOut = ref(false);
24
24
  const animStyle = ref({});
25
25
  watch(() => win.value?.minimized, (minimized, wasMinimized) => {
@@ -207,7 +207,10 @@ function onOpen(v) {
207
207
  </script>
208
208
 
209
209
  <template>
210
- <UPopover v-model:open="open" @update:open="onOpen">
210
+ <UPopover
211
+ v-model:open="open"
212
+ @update:open="onOpen"
213
+ >
211
214
  <button
212
215
  type="button"
213
216
  class="flex items-center gap-2 h-8 px-2 rounded-md border border-default hover:bg-elevated/60 transition-colors"
@@ -241,7 +244,10 @@ function onOpen(v) {
241
244
  :title="name"
242
245
  @click="select(name)"
243
246
  >
244
- <UIcon :name="`i-lucide-${name}`" class="size-4" />
247
+ <UIcon
248
+ :name="`i-lucide-${name}`"
249
+ class="size-4"
250
+ />
245
251
  </button>
246
252
  </div>
247
253
  <button
@@ -23,17 +23,16 @@ const tree = computed(() => {
23
23
  if (!props.nodeId) return null;
24
24
  return useChildTree(rootDoc, props.nodeId);
25
25
  });
26
+ const treeMap = useSyncedMap(rootDoc, "doc-tree");
26
27
  const entry = computed(() => {
27
28
  if (!props.nodeId) return null;
28
- const treeMap = rootDoc.value?.getMap("doc-tree");
29
- return treeMap?.get(props.nodeId) ?? null;
29
+ return treeMap.data[props.nodeId] ?? null;
30
30
  });
31
31
  const meta = computed(() => entry.value?.meta ?? {});
32
32
  const currentType = computed(() => entry.value?.type ?? "doc");
33
33
  function patchMeta(patch) {
34
- if (!props.nodeId || !rootDoc.value) return;
35
- const treeMap = rootDoc.value.getMap("doc-tree");
36
- const e = treeMap.get(props.nodeId);
34
+ if (!props.nodeId) return;
35
+ const e = treeMap.data[props.nodeId];
37
36
  if (e) {
38
37
  treeMap.set(props.nodeId, {
39
38
  ...e,
@@ -43,32 +42,11 @@ function patchMeta(patch) {
43
42
  }
44
43
  }
45
44
  function patchType(type) {
46
- if (!props.nodeId || !rootDoc.value) return;
47
- const treeMap = rootDoc.value.getMap("doc-tree");
48
- const e = treeMap.get(props.nodeId);
45
+ if (!props.nodeId) return;
46
+ const e = treeMap.data[props.nodeId];
49
47
  if (e) treeMap.set(props.nodeId, { ...e, type, updatedAt: Date.now() });
50
48
  }
51
49
  const activeTab = ref("editor");
52
- const tabs = computed(() => {
53
- const base = [
54
- { key: "editor", label: locale.value.editorTab, icon: "i-lucide-file-text" },
55
- { key: "properties", label: locale.value.propertiesTab, icon: "i-lucide-sliders-horizontal" }
56
- ];
57
- const pluginSlots2 = registry.getAllNodePanelSlots().filter((slot) => {
58
- if (!props.nodeId || !tree.value) return false;
59
- const e = tree.value.entries.value.find((en) => en.id === props.nodeId);
60
- if (!e) return false;
61
- return slot.when({
62
- childId: props.nodeId,
63
- childDoc: props.childProvider?.document ?? null,
64
- parentDocId: props.nodeId,
65
- parentType: props.docType,
66
- meta: meta.value,
67
- tree: tree.value
68
- });
69
- });
70
- return [...base, ...pluginSlots2.map((s) => ({ key: s.id, label: s.label, icon: s.icon }))];
71
- });
72
50
  const pluginSlots = computed(() => {
73
51
  return registry.getAllNodePanelSlots().filter((slot) => {
74
52
  if (!props.nodeId || !tree.value) return false;
@@ -129,19 +107,19 @@ function setCustomFieldValue(field, value) {
129
107
  patchMeta({ [field.key]: value });
130
108
  }
131
109
  const statusOptions = [
132
- { label: "None", value: "" },
110
+ { label: "None", value: "none" },
133
111
  { label: "Todo", value: "todo" },
134
112
  { label: "In progress", value: "in-progress" },
135
113
  { label: "Done", value: "done" },
136
114
  { label: "Archived", value: "archived" }
137
115
  ];
138
116
  const priorityOptions = [
139
- { label: "None", value: 0 },
140
- { label: "1 \u2014 Low", value: 1 },
141
- { label: "2", value: 2 },
142
- { label: "3 \u2014 Medium", value: 3 },
143
- { label: "4", value: 4 },
144
- { label: "5 \u2014 High", value: 5 }
117
+ { label: "None", value: "none" },
118
+ { label: "1 \u2014 Low", value: "1" },
119
+ { label: "2", value: "2" },
120
+ { label: "3 \u2014 Medium", value: "3" },
121
+ { label: "4", value: "4" },
122
+ { label: "5 \u2014 High", value: "5" }
145
123
  ];
146
124
  const addFieldMenuItems = computed(
147
125
  () => META_FIELD_DEFINITIONS.map((def) => ({
@@ -157,43 +135,85 @@ const addFieldMenuItems = computed(
157
135
  v-model:open="open"
158
136
  side="right"
159
137
  :title="nodeLabel || 'Document'"
160
- :ui="{ width: 'max-w-xl' }"
138
+ :ui="{ width: 'sm:max-w-2xl' }"
161
139
  @update:open="(v) => !v && emit('close')"
162
140
  >
163
141
  <template #header>
164
- <div class="flex items-center gap-2 min-w-0">
142
+ <div class="flex items-center gap-2 min-w-0 flex-1">
165
143
  <UIcon
166
144
  :name="meta.icon ?? 'i-lucide-file-text'"
167
145
  class="size-4 shrink-0"
168
146
  :style="meta.color ? `color: ${meta.color}` : ''"
169
147
  :class="meta.color ? '' : 'text-muted'"
170
148
  />
171
- <span class="font-medium truncate">{{ nodeLabel || "Document" }}</span>
149
+ <span class="font-medium truncate flex-1">{{ nodeLabel || "Document" }}</span>
150
+ <UTooltip
151
+ text="Open as full page"
152
+ :content="{ side: 'bottom' }"
153
+ >
154
+ <UButton
155
+ icon="i-lucide-expand"
156
+ size="xs"
157
+ variant="ghost"
158
+ color="neutral"
159
+ @click="navigateTo(`/app/${nodeId}`)"
160
+ />
161
+ </UTooltip>
172
162
  </div>
173
163
  </template>
174
164
 
175
165
  <template #body>
176
- <!-- Tabs -->
177
- <UTabs
178
- v-model="activeTab"
179
- :items="tabs"
180
- variant="link"
181
- class="mb-4"
182
- />
166
+ <!-- Tab bar -->
167
+ <div class="flex items-center gap-1 border-b border-(--ui-border) mb-4 -mt-2">
168
+ <UButton
169
+ icon="i-lucide-file-text"
170
+ :label="locale.editorTab"
171
+ size="sm"
172
+ :color="activeTab === 'editor' ? 'primary' : 'neutral'"
173
+ :variant="activeTab === 'editor' ? 'soft' : 'ghost'"
174
+ @click="activeTab = 'editor'"
175
+ />
176
+ <UButton
177
+ icon="i-lucide-sliders-horizontal"
178
+ :label="locale.propertiesTab"
179
+ size="sm"
180
+ :color="activeTab === 'properties' ? 'primary' : 'neutral'"
181
+ :variant="activeTab === 'properties' ? 'soft' : 'ghost'"
182
+ @click="activeTab = 'properties'"
183
+ />
184
+ <UButton
185
+ v-for="slot in pluginSlots"
186
+ :key="slot.id"
187
+ :icon="slot.icon"
188
+ :label="slot.label"
189
+ size="sm"
190
+ :color="activeTab === slot.id ? 'primary' : 'neutral'"
191
+ :variant="activeTab === slot.id ? 'soft' : 'ghost'"
192
+ @click="activeTab = slot.id"
193
+ />
194
+ </div>
183
195
 
184
196
  <!-- Editor tab -->
185
- <div v-show="activeTab === 'editor'" class="h-[60vh]">
197
+ <div
198
+ v-show="activeTab === 'editor'"
199
+ class="min-h-[60vh]"
200
+ >
186
201
  <AEditor
187
202
  v-if="nodeId"
188
203
  :doc-id="nodeId"
189
204
  :child-provider="childProvider"
205
+ :parent-type="docType"
206
+ :doc-meta="meta"
190
207
  class="h-full overflow-auto"
208
+ @update-meta="patchMeta"
191
209
  />
192
210
  </div>
193
211
 
194
212
  <!-- Properties tab -->
195
- <div v-show="activeTab === 'properties'" class="space-y-4">
196
-
213
+ <div
214
+ v-show="activeTab === 'properties'"
215
+ class="space-y-4"
216
+ >
197
217
  <!-- Document type -->
198
218
  <div class="space-y-1">
199
219
  <label class="text-xs font-medium text-muted">{{ locale.docType }}</label>
@@ -225,23 +245,24 @@ const addFieldMenuItems = computed(
225
245
  <div class="grid grid-cols-2 gap-3">
226
246
  <div class="space-y-1">
227
247
  <label class="text-xs font-medium text-muted">{{ locale.status }}</label>
228
- <USelectMenu
229
- :model-value="meta.status ?? ''"
248
+ <USelect
249
+ :model-value="meta.status || 'none'"
230
250
  :items="statusOptions"
231
251
  value-key="value"
232
- placeholder="None"
252
+ label-key="label"
233
253
  size="sm"
234
- @update:model-value="patchMeta({ status: $event || void 0 })"
254
+ @update:model-value="patchMeta({ status: $event === 'none' ? void 0 : $event })"
235
255
  />
236
256
  </div>
237
257
  <div class="space-y-1">
238
258
  <label class="text-xs font-medium text-muted">{{ locale.priority }}</label>
239
- <USelectMenu
240
- :model-value="meta.priority ?? 0"
259
+ <USelect
260
+ :model-value="meta.priority ? String(meta.priority) : 'none'"
241
261
  :items="priorityOptions"
242
262
  value-key="value"
263
+ label-key="label"
243
264
  size="sm"
244
- @update:model-value="patchMeta({ priority: $event || void 0 })"
265
+ @update:model-value="patchMeta({ priority: $event === 'none' ? void 0 : Number($event) })"
245
266
  />
246
267
  </div>
247
268
  </div>
@@ -269,7 +290,10 @@ const addFieldMenuItems = computed(
269
290
  class="ml-1 text-xs text-muted hover:text-default"
270
291
  @click="patchMeta({ rating: void 0 })"
271
292
  >
272
- <UIcon name="i-lucide-x" class="size-3" />
293
+ <UIcon
294
+ name="i-lucide-x"
295
+ class="size-3"
296
+ />
273
297
  </button>
274
298
  </div>
275
299
  </div>
@@ -317,7 +341,10 @@ const addFieldMenuItems = computed(
317
341
  @click="removeTag(tag)"
318
342
  >
319
343
  {{ tag }}
320
- <UIcon name="i-lucide-x" class="size-3 ml-1" />
344
+ <UIcon
345
+ name="i-lucide-x"
346
+ class="size-3 ml-1"
347
+ />
321
348
  </UBadge>
322
349
  </div>
323
350
  <UInput
@@ -330,10 +357,16 @@ const addFieldMenuItems = computed(
330
357
  </div>
331
358
 
332
359
  <!-- Cover image -->
333
- <div v-if="meta.coverUploadId || meta.coverDocId" class="space-y-1">
360
+ <div
361
+ v-if="meta.coverUploadId || meta.coverDocId"
362
+ class="space-y-1"
363
+ >
334
364
  <label class="text-xs font-medium text-muted">{{ locale.cover }}</label>
335
365
  <div class="flex items-center gap-2 text-xs text-muted">
336
- <UIcon name="i-lucide-image" class="size-4 shrink-0" />
366
+ <UIcon
367
+ name="i-lucide-image"
368
+ class="size-4 shrink-0"
369
+ />
337
370
  <span class="truncate">{{ meta.coverUploadId ?? meta.coverDocId }}</span>
338
371
  <UButton
339
372
  icon="i-lucide-x"
@@ -488,7 +521,10 @@ const addFieldMenuItems = computed(
488
521
  class="cursor-pointer"
489
522
  @click="setCustomFieldValue(field, getCustomFieldValue(field).filter((x) => x !== t))"
490
523
  >
491
- {{ t }}<UIcon name="i-lucide-x" class="size-3 ml-1" />
524
+ {{ t }}<UIcon
525
+ name="i-lucide-x"
526
+ class="size-3 ml-1"
527
+ />
492
528
  </UBadge>
493
529
  </div>
494
530
  </template>
@@ -522,7 +558,10 @@ const addFieldMenuItems = computed(
522
558
  </div>
523
559
 
524
560
  <!-- Plugin slots -->
525
- <template v-for="slot in pluginSlots" :key="slot.id">
561
+ <template
562
+ v-for="slot in pluginSlots"
563
+ :key="slot.id"
564
+ >
526
565
  <div v-show="activeTab === slot.id">
527
566
  <component
528
567
  :is="slot.component"
@@ -25,12 +25,24 @@ function openAndMarkFetched() {
25
25
  :aria-label="`Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ''}`"
26
26
  @click="openAndMarkFetched"
27
27
  >
28
- <template v-if="unreadCount > 0" #trailing>
29
- <UChip :text="unreadCount > 9 ? '9+' : String(unreadCount)" color="error" size="xs" />
28
+ <template
29
+ v-if="unreadCount > 0"
30
+ #trailing
31
+ >
32
+ <UChip
33
+ :text="unreadCount > 9 ? '9+' : String(unreadCount)"
34
+ color="error"
35
+ size="xs"
36
+ />
30
37
  </template>
31
38
  </UButton>
32
39
 
33
- <USlideover v-model:open="isOpen" :title="locale.title" side="right" :ui="{ width: 'max-w-sm' }">
40
+ <USlideover
41
+ v-model:open="isOpen"
42
+ :title="locale.title"
43
+ side="right"
44
+ :ui="{ width: 'max-w-sm' }"
45
+ >
34
46
  <template #header>
35
47
  <div class="flex items-center justify-between w-full">
36
48
  <span class="font-semibold">{{ locale.title }}</span>
@@ -56,15 +68,30 @@ function openAndMarkFetched() {
56
68
  ]"
57
69
  @click="markRead(n.id)"
58
70
  >
59
- <UIcon :name="n.icon ?? 'i-lucide-bell'" class="size-4 shrink-0 mt-0.5 text-muted" />
71
+ <UIcon
72
+ :name="n.icon ?? 'i-lucide-bell'"
73
+ class="size-4 shrink-0 mt-0.5 text-muted"
74
+ />
60
75
  <div class="flex-1 min-w-0">
61
- <div class="text-sm font-medium truncate">{{ n.title }}</div>
62
- <div class="text-xs text-muted line-clamp-2">{{ n.body }}</div>
76
+ <div class="text-sm font-medium truncate">
77
+ {{ n.title }}
78
+ </div>
79
+ <div class="text-xs text-muted line-clamp-2">
80
+ {{ n.body }}
81
+ </div>
63
82
  </div>
64
- <UChip v-if="!n.read" color="primary" size="xs" class="shrink-0 mt-1" />
83
+ <UChip
84
+ v-if="!n.read"
85
+ color="primary"
86
+ size="xs"
87
+ class="shrink-0 mt-1"
88
+ />
65
89
  </div>
66
90
 
67
- <div v-if="!notifications.length" class="text-center py-12 text-muted text-sm">
91
+ <div
92
+ v-if="!notifications.length"
93
+ class="text-center py-12 text-muted text-sm"
94
+ >
68
95
  {{ locale.empty }}
69
96
  </div>
70
97
  </div>
@@ -17,6 +17,8 @@ const allowed = computed(() => meetsRole(props.requires));
17
17
  v-else
18
18
  name="fallback"
19
19
  >
20
- <p class="text-sm text-muted">{{ locale.noPermission }}</p>
20
+ <p class="text-sm text-muted">
21
+ {{ locale.noPermission }}
22
+ </p>
21
23
  </slot>
22
24
  </template>
@@ -27,10 +27,21 @@ function userAvatarStyle(user) {
27
27
  </script>
28
28
 
29
29
  <template>
30
- <slot :users="users" :overflow="overflow" :current-user="currentUser" :avatar-style="userAvatarStyle">
30
+ <slot
31
+ :users="users"
32
+ :overflow="overflow"
33
+ :current-user="currentUser"
34
+ :avatar-style="userAvatarStyle"
35
+ >
31
36
  <!-- Default: render nothing, slot is the API -->
32
- <template v-for="user in users" :key="user.clientId">
33
- <slot name="user" :user="user" />
37
+ <template
38
+ v-for="user in users"
39
+ :key="user.clientId"
40
+ >
41
+ <slot
42
+ name="user"
43
+ :user="user"
44
+ />
34
45
  </template>
35
46
  </slot>
36
47
  </template>
@@ -37,6 +37,12 @@ provide(ABRA_DOC_KEY, {
37
37
 
38
38
  <template>
39
39
  <component :is="tag">
40
- <slot :provider="childProvider" :doc="childDoc" :synced="synced" :doc-id="docId" :error="error" />
40
+ <slot
41
+ :provider="childProvider"
42
+ :doc="childDoc"
43
+ :synced="synced"
44
+ :doc-id="docId"
45
+ :error="error"
46
+ />
41
47
  </component>
42
48
  </template>