@abraca/nuxt 0.2.0 → 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 (152) 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/AIconPicker.vue +8 -2
  15. package/dist/runtime/components/ANodePanel.vue +100 -61
  16. package/dist/runtime/components/ANotifications.vue +35 -8
  17. package/dist/runtime/components/APermissionGuard.vue +3 -1
  18. package/dist/runtime/components/APresence.vue +14 -3
  19. package/dist/runtime/components/AProvider.vue +7 -1
  20. package/dist/runtime/components/AVoiceBar.vue +57 -15
  21. package/dist/runtime/components/AVoiceTile.vue +4 -1
  22. package/dist/runtime/components/aware/AArea.vue +1 -1
  23. package/dist/runtime/components/aware/AAvatar.vue +85 -16
  24. package/dist/runtime/components/aware/AButton.vue +5 -1
  25. package/dist/runtime/components/aware/ACursorLabel.vue +5 -1
  26. package/dist/runtime/components/aware/ADocBadge.vue +4 -1
  27. package/dist/runtime/components/aware/AFacepile.vue +13 -3
  28. package/dist/runtime/components/aware/AInput.vue +5 -1
  29. package/dist/runtime/components/aware/ATextarea.vue +5 -1
  30. package/dist/runtime/components/aware/AUserList.vue +8 -2
  31. package/dist/runtime/components/renderers/ACalendarRenderer.d.vue.ts +12 -1
  32. package/dist/runtime/components/renderers/ACalendarRenderer.vue +388 -114
  33. package/dist/runtime/components/renderers/ACalendarRenderer.vue.d.ts +12 -1
  34. package/dist/runtime/components/renderers/ACallRenderer.d.vue.ts +13 -0
  35. package/dist/runtime/components/renderers/ACallRenderer.vue +169 -0
  36. package/dist/runtime/components/renderers/ACallRenderer.vue.d.ts +13 -0
  37. package/dist/runtime/components/renderers/AChecklistRenderer.d.vue.ts +19 -0
  38. package/dist/runtime/components/renderers/AChecklistRenderer.vue +581 -0
  39. package/dist/runtime/components/renderers/AChecklistRenderer.vue.d.ts +19 -0
  40. package/dist/runtime/components/renderers/ADashboardRenderer.d.vue.ts +19 -0
  41. package/dist/runtime/components/renderers/ADashboardRenderer.vue +1372 -0
  42. package/dist/runtime/components/renderers/ADashboardRenderer.vue.d.ts +19 -0
  43. package/dist/runtime/components/renderers/AGalleryCoverImage.d.vue.ts +8 -0
  44. package/dist/runtime/components/renderers/AGalleryCoverImage.vue +60 -0
  45. package/dist/runtime/components/renderers/AGalleryCoverImage.vue.d.ts +8 -0
  46. package/dist/runtime/components/renderers/AGalleryRenderer.d.vue.ts +12 -1
  47. package/dist/runtime/components/renderers/AGalleryRenderer.vue +221 -55
  48. package/dist/runtime/components/renderers/AGalleryRenderer.vue.d.ts +12 -1
  49. package/dist/runtime/components/renderers/AGraphRenderer.d.vue.ts +19 -0
  50. package/dist/runtime/components/renderers/AGraphRenderer.vue +1027 -0
  51. package/dist/runtime/components/renderers/AGraphRenderer.vue.d.ts +19 -0
  52. package/dist/runtime/components/renderers/AKanbanRenderer.d.vue.ts +13 -1
  53. package/dist/runtime/components/renderers/AKanbanRenderer.vue +474 -140
  54. package/dist/runtime/components/renderers/AKanbanRenderer.vue.d.ts +13 -1
  55. package/dist/runtime/components/renderers/AMapRenderer.d.vue.ts +19 -0
  56. package/dist/runtime/components/renderers/AMapRenderer.vue +1622 -0
  57. package/dist/runtime/components/renderers/AMapRenderer.vue.d.ts +19 -0
  58. package/dist/runtime/components/renderers/AOutlineRenderer.d.vue.ts +12 -1
  59. package/dist/runtime/components/renderers/AOutlineRenderer.vue +294 -134
  60. package/dist/runtime/components/renderers/AOutlineRenderer.vue.d.ts +12 -1
  61. package/dist/runtime/components/renderers/ATableRenderer.d.vue.ts +12 -1
  62. package/dist/runtime/components/renderers/ATableRenderer.vue +437 -145
  63. package/dist/runtime/components/renderers/ATableRenderer.vue.d.ts +12 -1
  64. package/dist/runtime/components/renderers/ATimelineRenderer.d.vue.ts +19 -0
  65. package/dist/runtime/components/renderers/ATimelineRenderer.vue +446 -0
  66. package/dist/runtime/components/renderers/ATimelineRenderer.vue.d.ts +19 -0
  67. package/dist/runtime/composables/useAwareness.js +5 -0
  68. package/dist/runtime/composables/useBroadcastSync.d.ts +18 -0
  69. package/dist/runtime/composables/useBroadcastSync.js +26 -0
  70. package/dist/runtime/composables/useChat.js +4 -2
  71. package/dist/runtime/composables/useChatUsers.js +2 -1
  72. package/dist/runtime/composables/useCommandPalette.js +62 -3
  73. package/dist/runtime/composables/useConnectionStatus.js +7 -0
  74. package/dist/runtime/composables/useDevicePairing.d.ts +58 -0
  75. package/dist/runtime/composables/useDevicePairing.js +108 -0
  76. package/dist/runtime/composables/useDocExport.d.ts +5 -0
  77. package/dist/runtime/composables/useDocExport.js +2 -2
  78. package/dist/runtime/composables/useDocImport.js +4 -3
  79. package/dist/runtime/composables/useDocSeo.d.ts +20 -0
  80. package/dist/runtime/composables/useDocSeo.js +44 -0
  81. package/dist/runtime/composables/useDocSlugs.d.ts +7 -0
  82. package/dist/runtime/composables/useDocSlugs.js +20 -0
  83. package/dist/runtime/composables/useDocTree.d.ts +34 -0
  84. package/dist/runtime/composables/useDocTree.js +35 -0
  85. package/dist/runtime/composables/useEditorDragHandle.js +2 -1
  86. package/dist/runtime/composables/useEditorMentions.js +4 -2
  87. package/dist/runtime/composables/useEditorSuggestions.d.ts +1 -0
  88. package/dist/runtime/composables/useEditorSuggestions.js +9 -2
  89. package/dist/runtime/composables/useEditorToolbar.js +2 -1
  90. package/dist/runtime/composables/useFileIndex.js +2 -1
  91. package/dist/runtime/composables/useFileTransfer.d.ts +112 -0
  92. package/dist/runtime/composables/useFileTransfer.js +171 -0
  93. package/dist/runtime/composables/useFollowUser.js +2 -1
  94. package/dist/runtime/composables/useInvites.d.ts +56 -0
  95. package/dist/runtime/composables/useInvites.js +77 -0
  96. package/dist/runtime/composables/useNodePanel.d.ts +14 -0
  97. package/dist/runtime/composables/useNodePanel.js +52 -0
  98. package/dist/runtime/composables/useNotifications.js +4 -2
  99. package/dist/runtime/composables/usePasskeyAccounts.js +4 -2
  100. package/dist/runtime/composables/useSearchIndex.d.ts +1 -0
  101. package/dist/runtime/composables/useSearchIndex.js +13 -5
  102. package/dist/runtime/composables/useServerInfo.d.ts +31 -0
  103. package/dist/runtime/composables/useServerInfo.js +80 -0
  104. package/dist/runtime/composables/useSlugRoute.d.ts +6 -0
  105. package/dist/runtime/composables/useSlugRoute.js +19 -0
  106. package/dist/runtime/composables/useSpaces.d.ts +37 -0
  107. package/dist/runtime/composables/useSpaces.js +83 -0
  108. package/dist/runtime/composables/useTouchDrag.d.ts +34 -0
  109. package/dist/runtime/composables/useTouchDrag.js +191 -0
  110. package/dist/runtime/composables/useTrash.d.ts +1 -1
  111. package/dist/runtime/composables/useTrash.js +6 -3
  112. package/dist/runtime/composables/useWebRTC.d.ts +50 -0
  113. package/dist/runtime/composables/useWebRTC.js +177 -0
  114. package/dist/runtime/extensions/meta-field.d.ts +4 -1
  115. package/dist/runtime/extensions/steps.js +1 -1
  116. package/dist/runtime/extensions/views/AccordionItemView.vue +13 -3
  117. package/dist/runtime/extensions/views/AccordionView.vue +4 -1
  118. package/dist/runtime/extensions/views/BadgeView.vue +11 -2
  119. package/dist/runtime/extensions/views/CalloutView.vue +4 -1
  120. package/dist/runtime/extensions/views/CardGroupView.vue +4 -1
  121. package/dist/runtime/extensions/views/CardView.vue +17 -3
  122. package/dist/runtime/extensions/views/CodeGroupView.vue +4 -1
  123. package/dist/runtime/extensions/views/CollapsibleView.vue +8 -2
  124. package/dist/runtime/extensions/views/FileNodeView.vue +32 -8
  125. package/dist/runtime/extensions/views/KbdView.vue +8 -2
  126. package/dist/runtime/extensions/views/MetaFieldView.vue +208 -46
  127. package/dist/runtime/extensions/views/ProseIconView.vue +8 -2
  128. package/dist/runtime/extensions/views/TabsView.vue +17 -4
  129. package/dist/runtime/locale.d.ts +71 -0
  130. package/dist/runtime/locale.js +71 -0
  131. package/dist/runtime/plugin-abracadabra.client.js +29 -3
  132. package/dist/runtime/plugin-abracadabra.server.js +2 -0
  133. package/dist/runtime/server/api/_abracadabra/render/[docId].get.d.ts +1 -1
  134. package/dist/runtime/server/api/_abracadabra/render/[docId].get.js +29 -4
  135. package/dist/runtime/server/api/_abracadabra/resolve/[...slug].get.d.ts +2 -0
  136. package/dist/runtime/server/api/_abracadabra/resolve/[...slug].get.js +43 -0
  137. package/dist/runtime/server/api/_abracadabra/slugs.get.d.ts +2 -0
  138. package/dist/runtime/server/api/_abracadabra/slugs.get.js +7 -0
  139. package/dist/runtime/server/plugins/abracadabra-service.js +10 -5
  140. package/dist/runtime/server/runners/doc-tree-cache.js +4 -0
  141. package/dist/runtime/server/utils/slugMap.d.ts +32 -0
  142. package/dist/runtime/server/utils/slugMap.js +58 -0
  143. package/dist/runtime/types.d.ts +1 -0
  144. package/dist/runtime/utils/docTypes.d.ts +29 -1
  145. package/dist/runtime/utils/docTypes.js +129 -1
  146. package/dist/runtime/utils/markdownToYjs.js +2 -2
  147. package/dist/runtime/utils/sdkRef.d.ts +2 -0
  148. package/dist/runtime/utils/sdkRef.js +7 -0
  149. package/dist/runtime/utils/slugify.d.ts +40 -0
  150. package/dist/runtime/utils/slugify.js +36 -0
  151. package/dist/types.d.mts +6 -0
  152. package/package.json +32 -19
@@ -1,13 +1,16 @@
1
1
  <script setup>
2
- import { ref, computed } from "vue";
2
+ import { ref, computed, watch, 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,165 +18,496 @@ const locale = computed(() => ({
15
18
  ...config.public?.abracadabra?.locale?.renderers?.kanban ?? {},
16
19
  ...props.labels ?? {}
17
20
  }));
18
- const { tree, childDoc } = useRendererBase(props);
19
- const columns = computed(() => tree.childrenOf(null).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));
20
- function cardsOf(colId) {
21
- return tree.childrenOf(colId).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
22
- }
23
- const CARD_MIME = "application/x-abracadabra-card";
24
- const COL_MIME = "application/x-abracadabra-col";
25
- const draggingCardId = ref(null);
26
- const draggingColId = ref(null);
27
- const dragOverColId = ref(null);
28
- const dragOverCardId = ref(null);
29
- function onColDragStart(e, colId) {
30
- draggingColId.value = colId;
31
- e.dataTransfer?.setData(COL_MIME, colId);
32
- }
33
- function onColDragOver(e, colId) {
34
- if (!draggingColId.value) return;
35
- e.preventDefault();
36
- dragOverColId.value = colId;
37
- }
38
- function onColDrop(e, targetColId) {
39
- e.preventDefault();
40
- const srcId = e.dataTransfer?.getData(COL_MIME) || draggingColId.value;
41
- draggingColId.value = null;
42
- dragOverColId.value = null;
43
- if (!srcId || srcId === targetColId) return;
44
- const targetCol = columns.value.find((c) => c.id === targetColId);
45
- if (!targetCol) return;
46
- const srcEntry = tree.treeMap.get(srcId);
47
- if (!srcEntry) return;
48
- tree.treeMap.set(srcId, { ...srcEntry, order: (targetCol.order ?? 0) - 0.5, updatedAt: Date.now() });
49
- }
50
- function onCardDragStart(e, cardId) {
51
- draggingCardId.value = cardId;
52
- e.dataTransfer?.setData(CARD_MIME, cardId);
53
- e.stopPropagation();
54
- }
55
- function onCardDragOver(e, cardId) {
56
- if (!draggingCardId.value) return;
57
- e.preventDefault();
58
- e.stopPropagation();
59
- dragOverCardId.value = cardId;
60
- }
61
- function onCardDropOnCard(e, targetCardId) {
62
- e.preventDefault();
63
- e.stopPropagation();
64
- const srcId = e.dataTransfer?.getData(CARD_MIME) || draggingCardId.value;
65
- draggingCardId.value = null;
66
- dragOverCardId.value = null;
67
- if (!srcId || srcId === targetCardId) return;
68
- const targetEntry = tree.treeMap.get(targetCardId);
69
- const srcEntry = tree.treeMap.get(srcId);
70
- if (!targetEntry || !srcEntry) return;
71
- tree.treeMap.set(srcId, {
72
- ...srcEntry,
73
- parentId: targetEntry.parentId,
74
- order: (targetEntry.order ?? 0) - 0.5,
75
- updatedAt: Date.now()
76
- });
77
- }
78
- function onCardDropOnColumn(e, colId) {
79
- e.preventDefault();
80
- const srcId = e.dataTransfer?.getData(CARD_MIME) || draggingCardId.value;
81
- draggingCardId.value = null;
82
- dragOverCardId.value = null;
83
- dragOverColId.value = null;
84
- if (!srcId) return;
85
- const srcEntry = tree.treeMap.get(srcId);
86
- if (!srcEntry) return;
87
- const existingCards = cardsOf(colId);
88
- const maxOrder = existingCards.length ? Math.max(...existingCards.map((c) => c.order ?? 0)) : 0;
89
- tree.treeMap.set(srcId, {
90
- ...srcEntry,
91
- parentId: colId,
92
- order: maxOrder + 1,
93
- updatedAt: Date.now()
94
- });
21
+ const { tree, childProviderRef, states, setLocalState, connectedUsers } = useRendererBase(props);
22
+ const {
23
+ openNodeId,
24
+ openNodeLabel,
25
+ openNodeProvider,
26
+ isLoading: nodePanelLoading,
27
+ openNode,
28
+ closePanel
29
+ } = useNodePanel(childProviderRef);
30
+ const columns = computed(() => tree.childrenOf(null));
31
+ function orderBetween(list, targetIdx) {
32
+ const prev = list[targetIdx - 1];
33
+ const next = list[targetIdx];
34
+ if (!prev && !next) return Date.now();
35
+ if (!prev) return next.order - 1e3;
36
+ if (!next) return prev.order + 1e3;
37
+ return (prev.order + next.order) / 2;
95
38
  }
96
39
  function addColumn() {
40
+ if (!props.editable) return;
97
41
  tree.createChild(null, `${locale.value.newColumn} ${columns.value.length + 1}`);
98
42
  }
99
43
  function addCard(colId) {
44
+ if (!props.editable) return;
100
45
  tree.createChild(colId, locale.value.newCard);
101
46
  }
47
+ const {
48
+ dragId: colDragId,
49
+ dragOverId: colDragOver,
50
+ handlePointerDown: colPointerDown
51
+ } = useTouchDrag({
52
+ onDrop: (draggedId, targetId) => {
53
+ if (!props.editable) return;
54
+ const targetIdx = columns.value.findIndex((c) => c.id === targetId);
55
+ if (targetIdx < 0) return;
56
+ const newOrder = orderBetween(columns.value, targetIdx);
57
+ tree.moveEntry(draggedId, null, newOrder);
58
+ },
59
+ onDragStart: () => {
60
+ },
61
+ onDragEnd: () => {
62
+ }
63
+ });
64
+ const {
65
+ dragId: dragCardId,
66
+ dragOverId: dragOverCardId,
67
+ dragOverContainer: dragOverColumnId,
68
+ handlePointerDown: cardPointerDown
69
+ } = useTouchDrag({
70
+ onDrop: (draggedId, targetId) => {
71
+ if (!props.editable) return;
72
+ for (const col of columns.value) {
73
+ const cards = tree.childrenOf(col.id);
74
+ const hasSource = cards.some((c) => c.id === draggedId);
75
+ const targetIdx = cards.findIndex((c) => c.id === targetId);
76
+ if (hasSource && targetIdx >= 0) {
77
+ const newOrder = orderBetween(cards, targetIdx);
78
+ tree.moveEntry(draggedId, col.id, newOrder);
79
+ break;
80
+ }
81
+ }
82
+ setLocalState({ "kanban:dragging": null });
83
+ },
84
+ onMoveToContainer: (draggedId, containerId, targetId) => {
85
+ if (!props.editable) return;
86
+ const cards = tree.childrenOf(containerId);
87
+ if (targetId) {
88
+ const targetIdx = cards.findIndex((c) => c.id === targetId);
89
+ const newOrder = orderBetween(cards, targetIdx >= 0 ? targetIdx : cards.length);
90
+ tree.moveEntry(draggedId, containerId, newOrder);
91
+ } else {
92
+ const newOrder = orderBetween(cards, cards.length);
93
+ tree.moveEntry(draggedId, containerId, newOrder);
94
+ }
95
+ setLocalState({ "kanban:dragging": null });
96
+ },
97
+ onDragStart: (id) => {
98
+ for (const col of columns.value) {
99
+ if (tree.childrenOf(col.id).some((c) => c.id === id)) {
100
+ setLocalState({ "kanban:dragging": { cardId: id, toColumnId: col.id } });
101
+ break;
102
+ }
103
+ }
104
+ },
105
+ onDragEnd: () => {
106
+ setLocalState({ "kanban:dragging": null });
107
+ }
108
+ });
109
+ watch(dragOverColumnId, (colId) => {
110
+ if (dragCardId.value && colId) {
111
+ setLocalState({
112
+ "kanban:dragging": { cardId: dragCardId.value, toColumnId: colId }
113
+ });
114
+ }
115
+ });
116
+ function onCardPointerEnter(cardId) {
117
+ if (dragCardId.value) return;
118
+ setLocalState({ "kanban:hovering": cardId });
119
+ }
120
+ function onCardPointerLeave() {
121
+ if (dragCardId.value) return;
122
+ setLocalState({ "kanban:hovering": null });
123
+ }
124
+ const myClientId = computed(
125
+ () => props.childProvider?.awareness?.clientID ?? 0
126
+ );
127
+ function remoteDragColor(colId) {
128
+ for (const s of states.value) {
129
+ if (s.clientId === myClientId.value) continue;
130
+ if (s["kanban:dragging"]?.toColumnId === colId) {
131
+ return s.user?.color ?? null;
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+ function remoteHovers(cardId) {
137
+ const result = [];
138
+ for (const s of states.value) {
139
+ if (s.clientId === myClientId.value) continue;
140
+ if (s["kanban:hovering"] === cardId && s.user) {
141
+ result.push({ name: s.user.name ?? "", color: s.user.color ?? "#888" });
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ onBeforeUnmount(() => {
147
+ setLocalState({ "kanban:dragging": null, "kanban:hovering": null });
148
+ });
149
+ function duplicateCard(cardId) {
150
+ tree.duplicateEntry(cardId);
151
+ }
152
+ function duplicateColumn(colId) {
153
+ const newColId = tree.duplicateEntry(colId);
154
+ for (const card of tree.childrenOf(colId)) {
155
+ const newCardId = tree.duplicateEntry(card.id);
156
+ tree.moveEntry(newCardId, newColId, card.order);
157
+ }
158
+ }
159
+ function moveColumnLeft(colId) {
160
+ const cols = columns.value;
161
+ const idx = cols.findIndex((c) => c.id === colId);
162
+ if (idx <= 0) return;
163
+ const prev = cols[idx - 1];
164
+ tree.moveEntry(colId, null, prev.order - 1);
165
+ }
166
+ function moveColumnRight(colId) {
167
+ const cols = columns.value;
168
+ const idx = cols.findIndex((c) => c.id === colId);
169
+ if (idx < 0 || idx >= cols.length - 1) return;
170
+ const next = cols[idx + 1];
171
+ tree.moveEntry(colId, null, next.order + 1);
172
+ }
173
+ function moveCardToColumn(cardId, targetColId) {
174
+ const cards = tree.childrenOf(targetColId);
175
+ tree.moveEntry(cardId, targetColId, Date.now() + cards.length);
176
+ }
177
+ function colMenuItems(col) {
178
+ const cols = columns.value;
179
+ const idx = cols.findIndex((c) => c.id === col.id);
180
+ return [
181
+ [
182
+ {
183
+ label: "Rename",
184
+ icon: "i-lucide-pencil",
185
+ onSelect: () => startRename(col.id, col.label)
186
+ },
187
+ {
188
+ label: "Add card",
189
+ icon: "i-lucide-plus",
190
+ onSelect: () => addCard(col.id)
191
+ },
192
+ {
193
+ label: "Duplicate column",
194
+ icon: "i-lucide-copy",
195
+ onSelect: () => duplicateColumn(col.id)
196
+ }
197
+ ],
198
+ [
199
+ {
200
+ label: "Move left",
201
+ icon: "i-lucide-arrow-left",
202
+ disabled: idx <= 0,
203
+ onSelect: () => moveColumnLeft(col.id)
204
+ },
205
+ {
206
+ label: "Move right",
207
+ icon: "i-lucide-arrow-right",
208
+ disabled: idx >= cols.length - 1,
209
+ onSelect: () => moveColumnRight(col.id)
210
+ }
211
+ ],
212
+ [
213
+ {
214
+ label: "Open as doc",
215
+ icon: "i-lucide-external-link",
216
+ onSelect: () => openNode(col.id, col.label)
217
+ }
218
+ ],
219
+ [
220
+ {
221
+ label: "Delete column",
222
+ icon: "i-lucide-trash-2",
223
+ color: "error",
224
+ onSelect: () => tree.deleteEntry(col.id)
225
+ }
226
+ ]
227
+ ];
228
+ }
229
+ function cardMenuItems(card, currentColId) {
230
+ return [
231
+ [
232
+ {
233
+ label: "Open",
234
+ icon: "i-lucide-external-link",
235
+ onSelect: () => openNode(card.id, card.label)
236
+ },
237
+ {
238
+ label: "Rename",
239
+ icon: "i-lucide-pencil",
240
+ onSelect: () => startRename(card.id, card.label)
241
+ },
242
+ {
243
+ label: "Duplicate",
244
+ icon: "i-lucide-copy",
245
+ onSelect: () => duplicateCard(card.id)
246
+ }
247
+ ],
248
+ [
249
+ {
250
+ label: "Move to column",
251
+ icon: "i-lucide-arrow-right",
252
+ children: columns.value.filter((c) => c.id !== currentColId).map((c) => ({
253
+ label: c.label,
254
+ onSelect: () => moveCardToColumn(card.id, c.id)
255
+ }))
256
+ }
257
+ ],
258
+ [
259
+ {
260
+ label: "Delete",
261
+ icon: "i-lucide-trash-2",
262
+ color: "error",
263
+ onSelect: () => tree.deleteEntry(card.id)
264
+ }
265
+ ]
266
+ ];
267
+ }
268
+ const renameId = ref(null);
269
+ const renameValue = ref("");
270
+ let renameCooldown = false;
271
+ function startRename(id, label) {
272
+ renameId.value = id;
273
+ renameValue.value = label;
274
+ renameCooldown = true;
275
+ setTimeout(() => {
276
+ renameCooldown = false;
277
+ }, 200);
278
+ }
279
+ function commitRename() {
280
+ if (renameCooldown) return;
281
+ if (renameId.value && renameValue.value.trim()) {
282
+ tree.renameEntry(renameId.value, renameValue.value.trim());
283
+ }
284
+ renameId.value = null;
285
+ }
286
+ defineExpose({ connectedUsers });
102
287
  </script>
103
288
 
104
289
  <template>
105
- <div class="flex gap-4 p-4 overflow-x-auto h-full items-start">
106
- <!-- Columns -->
107
- <div
108
- v-for="col in columns"
109
- :key="col.id"
110
- class="flex-shrink-0 w-64 bg-muted/50 rounded-lg p-2 flex flex-col gap-2 min-h-[200px]"
111
- :class="dragOverColId === col.id ? 'ring-2 ring-primary' : ''"
112
- draggable="true"
113
- @dragstart="onColDragStart($event, col.id)"
114
- @dragover="onColDragOver($event, col.id)"
115
- @drop="onCardDropOnColumn($event, col.id)"
116
- @dragleave="dragOverColId = null"
117
- >
118
- <!-- Column header -->
119
- <div class="flex items-center gap-2 px-1">
120
- <span class="font-medium text-sm flex-1 truncate">{{ col.label }}</span>
121
- <UBadge
122
- variant="soft"
123
- size="xs"
124
- >{{ cardsOf(col.id).length }}</UBadge>
290
+ <div class="flex-1 min-h-0 flex flex-col relative">
291
+ <!-- Toolbar -->
292
+ <div class="flex items-center justify-between px-4 py-2 border-b border-(--ui-border) shrink-0">
293
+ <span class="text-xs text-(--ui-text-muted)">
294
+ {{ columns.length }} column{{ columns.length !== 1 ? "s" : "" }}
295
+ </span>
296
+ <UButton
297
+ v-if="editable"
298
+ icon="i-lucide-plus"
299
+ size="xs"
300
+ variant="ghost"
301
+ color="neutral"
302
+ :label="locale.addColumn"
303
+ @click="addColumn"
304
+ />
305
+ </div>
306
+
307
+ <!-- Board -->
308
+ <div class="flex-1 overflow-x-auto p-4">
309
+ <!-- Empty state -->
310
+ <div
311
+ v-if="columns.length === 0"
312
+ class="flex flex-col items-center justify-center h-full gap-3 text-center"
313
+ >
314
+ <UIcon
315
+ name="i-lucide-kanban"
316
+ class="size-10 text-(--ui-text-dimmed)"
317
+ />
318
+ <p class="text-sm text-(--ui-text-muted)">
319
+ No columns yet
320
+ </p>
321
+ <UButton
322
+ v-if="editable"
323
+ icon="i-lucide-plus"
324
+ :label="locale.addColumn"
325
+ size="sm"
326
+ @click="addColumn"
327
+ />
125
328
  </div>
126
329
 
127
- <!-- Cards -->
128
- <div class="flex flex-col gap-1.5">
330
+ <TransitionGroup
331
+ v-else
332
+ name="kcol"
333
+ tag="div"
334
+ class="flex gap-3 h-full items-start min-w-max"
335
+ >
129
336
  <div
130
- v-for="card in cardsOf(col.id)"
131
- :key="card.id"
132
- class="bg-elevated border border-muted rounded-md p-2.5 text-sm cursor-grab active:cursor-grabbing select-none"
133
- :class="dragOverCardId === card.id ? 'ring-2 ring-primary' : ''"
134
- :style="card.meta?.color ? { borderLeftColor: card.meta.color, borderLeftWidth: '3px' } : {}"
135
- draggable="true"
136
- @dragstart="onCardDragStart($event, card.id)"
137
- @dragover="onCardDragOver($event, card.id)"
138
- @drop="onCardDropOnCard($event, card.id)"
337
+ v-for="col in columns"
338
+ :key="col.id"
339
+ :data-drag-id="col.id"
340
+ class="flex-shrink-0 w-64 rounded-lg border transition-colors"
341
+ :class="[
342
+ dragOverColumnId === col.id ? 'border-(--ui-primary) bg-(--ui-primary)/5' : remoteDragColor(col.id) ? 'bg-(--ui-primary)/5' : 'border-(--ui-border) bg-(--ui-bg-elevated)',
343
+ colDragOver === col.id ? 'border-l-4 border-l-(--ui-primary)' : '',
344
+ colDragId === col.id ? 'opacity-40' : ''
345
+ ]"
346
+ :style="remoteDragColor(col.id) && dragOverColumnId !== col.id ? `border-color: ${remoteDragColor(col.id)}` : void 0"
347
+ :data-drop-container="col.id"
139
348
  >
140
- <p class="font-medium">{{ card.label }}</p>
349
+ <!-- Column header -->
350
+ <UContextMenu :items="editable ? colMenuItems(col) : []">
351
+ <div
352
+ class="flex items-center justify-between px-3 py-2 border-b border-(--ui-border)"
353
+ :class="editable ? 'cursor-grab' : ''"
354
+ :style="editable ? 'touch-action: none' : ''"
355
+ @pointerdown="editable ? colPointerDown($event, col.id) : void 0"
356
+ >
357
+ <UInput
358
+ v-if="renameId === col.id"
359
+ v-model="renameValue"
360
+ size="xs"
361
+ variant="none"
362
+ class="flex-1"
363
+ autofocus
364
+ @keydown.enter="commitRename"
365
+ @keydown.escape="renameId = null"
366
+ @blur="commitRename"
367
+ />
368
+ <span
369
+ v-else
370
+ class="text-sm font-medium truncate flex-1"
371
+ @dblclick.stop="editable ? startRename(col.id, col.label) : void 0"
372
+ >
373
+ {{ col.label }}
374
+ </span>
375
+ <div class="flex items-center gap-1 shrink-0">
376
+ <UBadge
377
+ :label="String(tree.childrenOf(col.id).length)"
378
+ color="neutral"
379
+ variant="subtle"
380
+ size="xs"
381
+ />
382
+ <UDropdownMenu
383
+ v-if="editable"
384
+ :items="colMenuItems(col)"
385
+ :content="{ align: 'start' }"
386
+ >
387
+ <UButton
388
+ icon="i-lucide-ellipsis"
389
+ size="xs"
390
+ variant="ghost"
391
+ color="neutral"
392
+ @click.stop
393
+ />
394
+ </UDropdownMenu>
395
+ </div>
396
+ </div>
397
+ </UContextMenu>
398
+
399
+ <!-- Cards -->
400
+ <TransitionGroup
401
+ name="kcard"
402
+ tag="div"
403
+ class="p-2 space-y-1.5 min-h-16"
404
+ >
405
+ <UContextMenu
406
+ v-for="card in tree.childrenOf(col.id)"
407
+ :key="card.id"
408
+ :items="editable ? cardMenuItems(card, col.id) : []"
409
+ >
410
+ <div
411
+ :data-drag-id="card.id"
412
+ class="group bg-(--ui-bg) rounded border px-3 py-2 text-sm cursor-pointer hover:border-(--ui-primary) transition-colors flex items-center justify-between gap-1 relative"
413
+ :class="{
414
+ 'opacity-30': dragCardId === card.id,
415
+ 'border-t-2 border-t-(--ui-primary)': dragOverCardId === card.id,
416
+ 'border-(--ui-border)': remoteHovers(card.id).length === 0 && dragOverCardId !== card.id
417
+ }"
418
+ :style="[
419
+ remoteHovers(card.id).length > 0 ? `border-left: 3px solid ${remoteHovers(card.id)[0].color}` : void 0,
420
+ editable ? 'touch-action: none' : ''
421
+ ]"
422
+ @pointerdown="editable ? cardPointerDown($event, card.id) : void 0"
423
+ @pointerenter="onCardPointerEnter(card.id)"
424
+ @pointerleave="onCardPointerLeave"
425
+ @click="openNode(card.id, card.label)"
426
+ >
427
+ <UInput
428
+ v-if="renameId === card.id"
429
+ v-model="renameValue"
430
+ size="xs"
431
+ variant="none"
432
+ class="flex-1"
433
+ autofocus
434
+ @keydown.enter.stop="commitRename"
435
+ @keydown.escape.stop="renameId = null"
436
+ @blur="commitRename"
437
+ @click.stop
438
+ />
439
+ <span
440
+ v-else
441
+ class="truncate"
442
+ @dblclick.stop="editable ? startRename(card.id, card.label) : void 0"
443
+ >
444
+ {{ card.label }}
445
+ </span>
446
+ <!-- Remote hover name badge -->
447
+ <span
448
+ v-if="remoteHovers(card.id).length > 0"
449
+ class="absolute -top-2.5 left-2 text-[10px] px-1 rounded text-white leading-tight"
450
+ :style="{ backgroundColor: remoteHovers(card.id)[0].color }"
451
+ >
452
+ {{ remoteHovers(card.id)[0].name }}
453
+ </span>
454
+ <UDropdownMenu
455
+ v-if="editable"
456
+ :items="cardMenuItems(card, col.id)"
457
+ :content="{ align: 'start' }"
458
+ >
459
+ <UButton
460
+ icon="i-lucide-ellipsis"
461
+ size="xs"
462
+ variant="ghost"
463
+ color="neutral"
464
+ class="md:opacity-0 md:group-hover:opacity-100"
465
+ @click.stop
466
+ />
467
+ </UDropdownMenu>
468
+ </div>
469
+ </UContextMenu>
470
+ </TransitionGroup>
471
+
472
+ <!-- Add card -->
141
473
  <div
142
- v-if="card.meta?.tags?.length"
143
- class="flex flex-wrap gap-1 mt-1"
474
+ v-if="editable"
475
+ class="p-2 border-t border-(--ui-border)"
144
476
  >
145
- <UBadge
146
- v-for="tag in card.meta.tags"
147
- :key="tag"
148
- variant="soft"
477
+ <UButton
478
+ icon="i-lucide-plus"
149
479
  size="xs"
150
- >{{ tag }}</UBadge>
480
+ variant="ghost"
481
+ color="neutral"
482
+ block
483
+ :label="locale.newCard"
484
+ @click="addCard(col.id)"
485
+ />
151
486
  </div>
152
487
  </div>
153
- </div>
154
488
 
155
- <!-- Add card button -->
156
- <UButton
157
- icon="i-lucide-plus"
158
- variant="ghost"
159
- color="neutral"
160
- size="xs"
161
- :label="locale.newCard"
162
- class="w-full justify-start mt-auto"
163
- @click="addCard(col.id)"
164
- />
489
+ <!-- Add column placeholder -->
490
+ <button
491
+ v-if="editable"
492
+ :key="'__add-col__'"
493
+ class="flex-shrink-0 w-64 h-10 rounded-lg border-2 border-dashed border-(--ui-border) hover:border-(--ui-primary) text-xs text-(--ui-text-muted) hover:text-(--ui-primary) transition-colors"
494
+ @click="addColumn"
495
+ >
496
+ + {{ locale.addColumn }}
497
+ </button>
498
+ </TransitionGroup>
165
499
  </div>
166
500
 
167
- <!-- Add column button -->
168
- <div class="flex-shrink-0">
169
- <UButton
170
- icon="i-lucide-plus"
171
- variant="soft"
172
- color="neutral"
173
- size="sm"
174
- :label="locale.addColumn"
175
- @click="addColumn"
176
- />
177
- </div>
501
+ <!-- Node panel -->
502
+ <ANodePanel
503
+ :node-id="openNodeId"
504
+ :node-label="openNodeLabel"
505
+ :child-provider="openNodeProvider"
506
+ @close="closePanel"
507
+ />
178
508
  </div>
179
509
  </template>
510
+
511
+ <style scoped>
512
+ .kcol-move{transition:transform .25s ease}.kcol-enter-active{transition:opacity .18s ease,transform .18s ease}.kcol-enter-from{opacity:0;transform:translateX(-8px) scale(.97)}.kcol-leave-active{transition:opacity .15s ease}.kcol-leave-to{opacity:0}.kcard-move{transition:transform .25s ease}.kcard-enter-active{transition:opacity .18s ease,transform .18s ease}.kcard-enter-from{opacity:0;transform:translateY(-6px) scale(.97)}.kcard-leave-active{transition:opacity .15s ease}.kcard-leave-to{opacity:0}
513
+ </style>
@@ -2,7 +2,19 @@ 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']['kanban']>;
5
+ /** Whether the board is editable. Default: true */
6
+ editable?: boolean;
5
7
  };
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>;
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
9
+ connectedUsers: import("vue").ComputedRef<{
10
+ clientId: number;
11
+ name: string;
12
+ color: string;
13
+ avatar: string | undefined;
14
+ publicKey: any;
15
+ }[]>;
16
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
17
+ editable: boolean;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
19
  declare const _default: typeof __VLS_export;
8
20
  export default _default;