@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
@@ -0,0 +1,19 @@
1
+ import { type RendererBaseProps } from '../../composables/useRendererBase.js';
2
+ import type { AbracadabraLocale } from '../../locale.js';
3
+ type __VLS_Props = RendererBaseProps & {
4
+ labels?: Partial<AbracadabraLocale['renderers']['dashboard']>;
5
+ editable?: boolean;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
8
+ connectedUsers: import("vue").ComputedRef<{
9
+ clientId: number;
10
+ name: string;
11
+ color: string;
12
+ avatar: string | undefined;
13
+ publicKey: any;
14
+ }[]>;
15
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
16
+ editable: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,8 @@
1
+ type __VLS_Props = {
2
+ uploadId: string;
3
+ docId: string;
4
+ mimeType?: string;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,60 @@
1
+ <script setup>
2
+ import { ref, watchEffect } from "vue";
3
+ import { useFileBlobStore } from "../../composables/useFileBlobStore";
4
+ const props = defineProps({
5
+ uploadId: { type: String, required: true },
6
+ docId: { type: String, required: true },
7
+ mimeType: { type: String, required: false }
8
+ });
9
+ const { getBlobUrl } = useFileBlobStore();
10
+ const blobUrl = ref("");
11
+ const loading = ref(true);
12
+ const errored = ref(false);
13
+ watchEffect(async () => {
14
+ if (!props.uploadId || !props.docId) return;
15
+ loading.value = true;
16
+ errored.value = false;
17
+ try {
18
+ const url = await getBlobUrl(props.docId, props.uploadId);
19
+ if (url) {
20
+ blobUrl.value = url;
21
+ } else {
22
+ errored.value = true;
23
+ }
24
+ } catch {
25
+ errored.value = true;
26
+ } finally {
27
+ loading.value = false;
28
+ }
29
+ });
30
+ </script>
31
+
32
+ <template>
33
+ <div class="w-full h-full flex items-center justify-center">
34
+ <div
35
+ v-if="loading"
36
+ class="size-6 text-(--ui-text-dimmed) opacity-40 animate-spin"
37
+ >
38
+ <UIcon
39
+ name="i-lucide-loader-circle"
40
+ class="size-6"
41
+ />
42
+ </div>
43
+ <template v-else-if="!errored && blobUrl">
44
+ <video
45
+ v-if="mimeType?.startsWith('video/')"
46
+ :src="blobUrl"
47
+ class="w-full h-full object-cover"
48
+ muted
49
+ playsinline
50
+ preload="metadata"
51
+ />
52
+ <img
53
+ v-else
54
+ :src="blobUrl"
55
+ class="w-full h-full object-cover"
56
+ draggable="false"
57
+ >
58
+ </template>
59
+ </div>
60
+ </template>
@@ -0,0 +1,8 @@
1
+ type __VLS_Props = {
2
+ uploadId: string;
3
+ docId: string;
4
+ mimeType?: string;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -2,7 +2,18 @@ import { type RendererBaseProps } from '../../composables/useRendererBase.js';
2
2
  import type { AbracadabraLocale } from '../../locale.js';
3
3
  type __VLS_Props = RendererBaseProps & {
4
4
  labels?: Partial<AbracadabraLocale['renderers']['gallery']>;
5
+ editable?: boolean;
5
6
  };
6
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
8
+ connectedUsers: import("vue").ComputedRef<{
9
+ clientId: number;
10
+ name: string;
11
+ color: string;
12
+ avatar: string | undefined;
13
+ publicKey: any;
14
+ }[]>;
15
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
16
+ editable: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
18
  declare const _default: typeof __VLS_export;
8
19
  export default _default;
@@ -1,13 +1,16 @@
1
1
  <script setup>
2
- import { computed } from "vue";
2
+ import { ref, computed, onBeforeUnmount } from "vue";
3
3
  import { useRendererBase } from "../../composables/useRendererBase";
4
+ import { useTouchDrag } from "../../composables/useTouchDrag";
5
+ import { useNodePanel } from "../../composables/useNodePanel";
4
6
  import { DEFAULT_LOCALE } from "../../locale";
5
7
  const props = defineProps({
6
8
  docId: { type: String, required: true },
7
9
  childProvider: { type: null, required: true },
8
10
  docLabel: { type: String, required: true },
9
11
  pageTypes: { type: Array, required: false },
10
- labels: { type: Object, required: false }
12
+ labels: { type: Object, required: false },
13
+ editable: { type: Boolean, required: false, default: true }
11
14
  });
12
15
  const config = useRuntimeConfig();
13
16
  const locale = computed(() => ({
@@ -15,74 +18,237 @@ const locale = computed(() => ({
15
18
  ...config.public?.abracadabra?.locale?.renderers?.gallery ?? {},
16
19
  ...props.labels ?? {}
17
20
  }));
18
- const { tree } = useRendererBase(props);
19
- const { client } = useAbracadabra();
20
- const items = computed(() => tree.childrenOf(null).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));
21
+ const { tree, childProviderRef, states, setLocalState, connectedUsers } = useRendererBase(props);
22
+ const {
23
+ openNodeId,
24
+ openNodeLabel,
25
+ openNodeProvider,
26
+ openNode,
27
+ closePanel
28
+ } = useNodePanel(childProviderRef);
29
+ const myClientId = computed(() => props.childProvider?.awareness?.clientID ?? 0);
30
+ const items = computed(() => tree.childrenOf(null));
31
+ const renameId = ref(null);
32
+ const renameValue = ref("");
33
+ let renameCooldown = false;
34
+ function startRename(id, label) {
35
+ if (!props.editable) return;
36
+ renameId.value = id;
37
+ renameValue.value = label;
38
+ renameCooldown = true;
39
+ setTimeout(() => {
40
+ renameCooldown = false;
41
+ }, 200);
42
+ }
43
+ function commitRename() {
44
+ if (!props.editable) return;
45
+ if (renameCooldown) return;
46
+ if (renameId.value && renameValue.value.trim()) {
47
+ tree.renameEntry(renameId.value, renameValue.value.trim());
48
+ }
49
+ renameId.value = null;
50
+ }
21
51
  function addItem() {
52
+ if (!props.editable) return;
22
53
  tree.createChild(null, locale.value.untitled);
23
54
  }
24
- async function getCoverUrl(item) {
25
- if (!item.meta?.coverUploadId || !item.meta?.coverDocId) return null;
26
- const { getBlobUrl } = useFileBlobStore();
27
- try {
28
- return await getBlobUrl(item.meta.coverDocId, item.meta.coverUploadId);
29
- } catch {
30
- return null;
31
- }
55
+ function duplicateItem(id) {
56
+ if (!props.editable) return;
57
+ tree.duplicateEntry(id);
32
58
  }
33
- const colorMap = computed(() => {
34
- const result = {};
35
- for (const item of items.value) {
36
- if (item.meta?.color) result[item.id] = item.meta.color;
37
- else result[item.id] = null;
59
+ function itemMenuItems(item) {
60
+ return [
61
+ [
62
+ { label: "Open", icon: "i-lucide-external-link", onSelect: () => openNode(item.id, item.label) },
63
+ { label: "Rename", icon: "i-lucide-pencil", onSelect: () => startRename(item.id, item.label) },
64
+ { label: "Duplicate", icon: "i-lucide-copy", onSelect: () => duplicateItem(item.id) }
65
+ ],
66
+ [
67
+ { label: "Delete", icon: "i-lucide-trash-2", color: "error", onSelect: () => tree.deleteEntry(item.id) }
68
+ ]
69
+ ];
70
+ }
71
+ function orderBetween(list, targetIdx) {
72
+ const prev = list[targetIdx - 1];
73
+ const next = list[targetIdx];
74
+ if (!prev && !next) return Date.now();
75
+ if (!prev) return next.order - 1e3;
76
+ if (!next) return prev.order + 1e3;
77
+ return (prev.order + next.order) / 2;
78
+ }
79
+ const { dragId, dragOverId, handlePointerDown } = useTouchDrag({
80
+ onDrop: (srcId, targetId) => {
81
+ if (!props.editable) return;
82
+ const targetIdx = items.value.findIndex((i) => i.id === targetId);
83
+ const newOrder = orderBetween(items.value, targetIdx);
84
+ tree.moveEntry(srcId, null, newOrder);
38
85
  }
39
- return result;
40
86
  });
87
+ function itemFocusers(itemId) {
88
+ return states.value.filter(
89
+ (s) => s.clientId !== myClientId.value && s["gallery:focused"] === itemId
90
+ );
91
+ }
92
+ function onItemPointerEnter(itemId) {
93
+ setLocalState({ "gallery:focused": itemId });
94
+ }
95
+ function onItemPointerLeave() {
96
+ setLocalState({ "gallery:focused": null });
97
+ }
98
+ onBeforeUnmount(() => setLocalState({ "gallery:focused": null }));
99
+ defineExpose({ connectedUsers });
41
100
  </script>
42
101
 
43
102
  <template>
44
- <div class="p-4">
45
- <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
103
+ <div class="flex-1 min-h-0 flex flex-col relative">
104
+ <!-- Toolbar -->
105
+ <div class="flex items-center justify-between px-4 py-2 border-b border-(--ui-border) shrink-0">
106
+ <span class="text-xs text-(--ui-text-muted)">
107
+ {{ items.length }} item{{ items.length !== 1 ? "s" : "" }}
108
+ </span>
109
+ <UButton
110
+ v-if="editable"
111
+ icon="i-lucide-plus"
112
+ size="xs"
113
+ variant="ghost"
114
+ color="neutral"
115
+ :label="locale.addItem"
116
+ @click="addItem"
117
+ />
118
+ </div>
119
+
120
+ <!-- Content -->
121
+ <div class="flex-1 overflow-y-auto p-4">
122
+ <!-- Empty state -->
46
123
  <div
47
- v-for="item in items"
48
- :key="item.id"
49
- class="group bg-elevated border border-muted rounded-lg overflow-hidden hover:shadow-md transition-shadow cursor-pointer"
124
+ v-if="items.length === 0"
125
+ class="flex flex-col items-center justify-center h-full gap-3 text-center"
50
126
  >
51
- <!-- Cover -->
52
- <div
53
- class="h-32 bg-muted flex items-center justify-center"
54
- :style="item.meta?.color ? { backgroundColor: item.meta.color } : {}"
55
- >
56
- <UIcon name="i-lucide-image" class="size-8 text-muted/50" />
57
- </div>
127
+ <UIcon
128
+ name="i-lucide-images"
129
+ class="size-10 text-(--ui-text-dimmed)"
130
+ />
131
+ <p class="text-sm text-(--ui-text-muted)">
132
+ {{ locale.noItems }}
133
+ </p>
134
+ <UButton
135
+ v-if="editable"
136
+ icon="i-lucide-plus"
137
+ :label="locale.addItem"
138
+ size="sm"
139
+ @click="addItem"
140
+ />
141
+ </div>
58
142
 
59
- <!-- Label -->
60
- <div class="p-2">
61
- <p class="text-sm font-medium truncate">{{ item.label }}</p>
143
+ <TransitionGroup
144
+ v-else
145
+ name="gallery"
146
+ tag="div"
147
+ class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3"
148
+ >
149
+ <UContextMenu
150
+ v-for="item in items"
151
+ :key="item.id"
152
+ :items="editable ? itemMenuItems(item) : []"
153
+ >
62
154
  <div
63
- v-if="item.meta?.tags?.length"
64
- class="flex flex-wrap gap-1 mt-1"
155
+ class="group relative rounded-lg border border-(--ui-border) hover:border-(--ui-primary) transition-colors overflow-hidden cursor-pointer"
156
+ :class="{
157
+ 'opacity-40 scale-95': dragId === item.id,
158
+ 'ring-2 ring-(--ui-primary)/40': dragOverId === item.id
159
+ }"
160
+ :style="itemFocusers(item.id).length ? `box-shadow: 0 0 0 2px ${itemFocusers(item.id)[0].user?.color ?? '#888'}` : void 0"
161
+ :data-drag-id="item.id"
162
+ @pointerdown="editable && handlePointerDown($event, item.id)"
163
+ @pointerenter="onItemPointerEnter(item.id)"
164
+ @pointerleave="onItemPointerLeave"
165
+ @click="openNode(item.id, item.label)"
65
166
  >
66
- <UBadge
67
- v-for="tag in item.meta.tags"
68
- :key="tag"
69
- variant="soft"
70
- size="xs"
71
- >{{ tag }}</UBadge>
72
- </div>
73
- </div>
74
- </div>
167
+ <!-- Remote user badge -->
168
+ <div
169
+ v-if="itemFocusers(item.id).length"
170
+ class="absolute top-1 right-1 z-10 flex gap-0.5"
171
+ >
172
+ <span
173
+ v-for="f in itemFocusers(item.id)"
174
+ :key="f.clientId"
175
+ class="text-[10px] leading-none px-1.5 py-0.5 rounded-full text-white truncate max-w-20"
176
+ :style="{ backgroundColor: f.user?.color ?? '#888' }"
177
+ >
178
+ {{ f.user?.name ?? "User" }}
179
+ </span>
180
+ </div>
75
181
 
76
- <!-- Add item tile -->
77
- <div
78
- class="h-[168px] border-2 border-dashed border-muted rounded-lg flex items-center justify-center cursor-pointer hover:border-primary transition-colors"
79
- @click="addItem"
80
- >
81
- <div class="text-center text-muted">
82
- <UIcon name="i-lucide-plus" class="size-6 mb-1" />
83
- <p class="text-xs">{{ locale.addItem }}</p>
84
- </div>
85
- </div>
182
+ <!-- Cover or placeholder -->
183
+ <div
184
+ class="aspect-[4/3] bg-(--ui-bg-elevated) flex items-center justify-center overflow-hidden"
185
+ :style="item.meta?.color ? { backgroundColor: item.meta.color + '22' } : {}"
186
+ >
187
+ <AGalleryCoverImage
188
+ v-if="item.meta?.coverUploadId"
189
+ :upload-id="item.meta.coverUploadId"
190
+ :doc-id="item.id"
191
+ :mime-type="item.meta.coverMimeType"
192
+ />
193
+ <UIcon
194
+ v-else
195
+ :name="item.meta?.icon || 'i-lucide-file-text'"
196
+ class="size-8 text-(--ui-text-dimmed) opacity-40"
197
+ />
198
+ </div>
199
+
200
+ <!-- Label -->
201
+ <div class="p-2 border-t border-(--ui-border) flex items-center justify-between gap-1">
202
+ <UInput
203
+ v-if="editable && renameId === item.id"
204
+ v-model="renameValue"
205
+ size="xs"
206
+ variant="none"
207
+ class="flex-1 -mx-1"
208
+ autofocus
209
+ @keydown.enter="commitRename"
210
+ @keydown.escape="renameId = null"
211
+ @blur="commitRename"
212
+ @click.stop
213
+ />
214
+ <p
215
+ v-else
216
+ class="text-xs font-medium truncate"
217
+ @dblclick.stop="editable && startRename(item.id, item.label)"
218
+ >
219
+ {{ item.label }}
220
+ </p>
221
+
222
+ <UDropdownMenu
223
+ v-if="editable"
224
+ :items="itemMenuItems(item)"
225
+ :content="{ align: 'start' }"
226
+ >
227
+ <UButton
228
+ icon="i-lucide-ellipsis"
229
+ size="xs"
230
+ variant="ghost"
231
+ color="neutral"
232
+ class="opacity-0 group-hover:opacity-100 shrink-0"
233
+ @click.stop
234
+ />
235
+ </UDropdownMenu>
236
+ </div>
237
+ </div>
238
+ </UContextMenu>
239
+ </TransitionGroup>
86
240
  </div>
241
+
242
+ <!-- Node panel -->
243
+ <ANodePanel
244
+ :node-id="openNodeId"
245
+ :node-label="openNodeLabel"
246
+ :child-provider="openNodeProvider"
247
+ @close="closePanel"
248
+ />
87
249
  </div>
88
250
  </template>
251
+
252
+ <style scoped>
253
+ .gallery-move{transition:transform .25s ease}.gallery-enter-active{transition:opacity .18s ease,transform .18s ease}.gallery-enter-from{opacity:0;transform:translateY(-6px) scale(.97)}.gallery-leave-active{transition:opacity .15s ease}.gallery-leave-to{opacity:0}
254
+ </style>
@@ -2,7 +2,18 @@ import { type RendererBaseProps } from '../../composables/useRendererBase.js';
2
2
  import type { AbracadabraLocale } from '../../locale.js';
3
3
  type __VLS_Props = RendererBaseProps & {
4
4
  labels?: Partial<AbracadabraLocale['renderers']['gallery']>;
5
+ editable?: boolean;
5
6
  };
6
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
8
+ connectedUsers: import("vue").ComputedRef<{
9
+ clientId: number;
10
+ name: string;
11
+ color: string;
12
+ avatar: string | undefined;
13
+ publicKey: any;
14
+ }[]>;
15
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
16
+ editable: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
18
  declare const _default: typeof __VLS_export;
8
19
  export default _default;
@@ -0,0 +1,19 @@
1
+ import { type RendererBaseProps } from '../../composables/useRendererBase.js';
2
+ import type { AbracadabraLocale } from '../../locale.js';
3
+ type __VLS_Props = RendererBaseProps & {
4
+ labels?: Partial<AbracadabraLocale['renderers']['graph']>;
5
+ editable?: boolean;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
8
+ connectedUsers: import("vue").ComputedRef<{
9
+ clientId: number;
10
+ name: string;
11
+ color: string;
12
+ avatar: string | undefined;
13
+ publicKey: any;
14
+ }[]>;
15
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
16
+ editable: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;