@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,581 @@
1
+ <script setup>
2
+ import { ref, computed, nextTick, onBeforeUnmount } from "vue";
3
+ import { useRendererBase } from "../../composables/useRendererBase";
4
+ import { useTouchDrag } from "../../composables/useTouchDrag";
5
+ import { useNodePanel } from "../../composables/useNodePanel";
6
+ import { DEFAULT_LOCALE } from "../../locale";
7
+ const props = defineProps({
8
+ docId: { type: String, required: true },
9
+ childProvider: { type: null, required: true },
10
+ docLabel: { type: String, required: true },
11
+ pageTypes: { type: Array, required: false },
12
+ labels: { type: Object, required: false },
13
+ editable: { type: Boolean, required: false, default: true }
14
+ });
15
+ const config = useRuntimeConfig();
16
+ const locale = computed(() => ({
17
+ ...DEFAULT_LOCALE.renderers.checklist,
18
+ ...config.public?.abracadabra?.locale?.renderers?.checklist ?? {},
19
+ ...props.labels ?? {}
20
+ }));
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 flatItems = computed(() => {
31
+ const result = [];
32
+ function walk(parentId, depth) {
33
+ for (const child of tree.childrenOf(parentId)) {
34
+ result.push({
35
+ id: child.id,
36
+ label: child.label,
37
+ parentId: child.parentId,
38
+ depth,
39
+ hasChildren: tree.childrenOf(child.id).length > 0,
40
+ checked: !!child.meta?.checked,
41
+ priority: child.meta?.priority ?? 0,
42
+ dateEnd: child.meta?.dateEnd ?? null,
43
+ order: child.order
44
+ });
45
+ walk(child.id, depth + 1);
46
+ }
47
+ }
48
+ walk(null, 0);
49
+ return result;
50
+ });
51
+ const filterMode = ref("all");
52
+ const sortMode = ref("manual");
53
+ function isDescendantOf(item, ancestorId) {
54
+ let current = item;
55
+ while (current) {
56
+ if (current.parentId === ancestorId) return true;
57
+ current = flatItems.value.find((i) => i.id === current.parentId);
58
+ }
59
+ return false;
60
+ }
61
+ const visibleItems = computed(() => {
62
+ let items = flatItems.value;
63
+ if (filterMode.value === "active")
64
+ items = items.filter((i) => !i.checked);
65
+ else if (filterMode.value === "completed")
66
+ items = items.filter((i) => i.checked);
67
+ if (sortMode.value !== "manual") {
68
+ const topLevel = items.filter((i) => i.depth === 0);
69
+ if (sortMode.value === "priority")
70
+ topLevel.sort((a, b) => b.priority - a.priority);
71
+ else if (sortMode.value === "due")
72
+ topLevel.sort((a, b) => {
73
+ if (!a.dateEnd && !b.dateEnd) return 0;
74
+ if (!a.dateEnd) return 1;
75
+ if (!b.dateEnd) return -1;
76
+ return new Date(a.dateEnd).getTime() - new Date(b.dateEnd).getTime();
77
+ });
78
+ const result = [];
79
+ for (const top of topLevel) {
80
+ result.push(top);
81
+ const subtree = items.filter((i) => i.depth > 0 && isDescendantOf(i, top.id));
82
+ result.push(...subtree);
83
+ }
84
+ return result;
85
+ }
86
+ return items;
87
+ });
88
+ const totalCount = computed(() => flatItems.value.length);
89
+ const checkedCount = computed(() => flatItems.value.filter((i) => i.checked).length);
90
+ const percent = computed(
91
+ () => totalCount.value === 0 ? 0 : Math.round(checkedCount.value / totalCount.value * 100)
92
+ );
93
+ const editingId = ref(null);
94
+ const editingValue = ref("");
95
+ const inputRefs = ref({});
96
+ function startEdit(id, label) {
97
+ if (!props.editable) return;
98
+ editingId.value = id;
99
+ editingValue.value = label;
100
+ setLocalState({ "checklist:editing": id });
101
+ }
102
+ function commitEdit() {
103
+ if (!props.editable) return;
104
+ if (editingId.value && editingValue.value.trim()) {
105
+ tree.renameEntry(editingId.value, editingValue.value.trim());
106
+ } else if (editingId.value && !editingValue.value.trim()) {
107
+ tree.deleteEntry(editingId.value);
108
+ }
109
+ editingId.value = null;
110
+ setLocalState({ "checklist:editing": null });
111
+ }
112
+ function addRoot() {
113
+ if (!props.editable) return;
114
+ const id = tree.createChild(null, "");
115
+ tree.updateMeta(id, { checked: false, priority: 0 });
116
+ nextTick(() => {
117
+ editingId.value = id;
118
+ editingValue.value = "";
119
+ nextTick(() => inputRefs.value[id]?.focus());
120
+ });
121
+ }
122
+ function addChild(parentId) {
123
+ if (!props.editable) return;
124
+ const id = tree.createChild(parentId, "");
125
+ tree.updateMeta(id, { checked: false, priority: 0 });
126
+ nextTick(() => {
127
+ editingId.value = id;
128
+ editingValue.value = "";
129
+ nextTick(() => inputRefs.value[id]?.focus());
130
+ });
131
+ }
132
+ function createSiblingAfter(item) {
133
+ if (!props.editable) return;
134
+ const id = tree.createChild(item.parentId, "");
135
+ tree.updateMeta(id, { checked: false, priority: 0 });
136
+ nextTick(() => {
137
+ editingId.value = id;
138
+ editingValue.value = "";
139
+ nextTick(() => inputRefs.value[id]?.focus());
140
+ });
141
+ }
142
+ function onKeydown(item, e) {
143
+ if (e.key === "Enter") {
144
+ e.preventDefault();
145
+ if (editingValue.value.trim()) {
146
+ tree.renameEntry(item.id, editingValue.value.trim());
147
+ }
148
+ editingId.value = null;
149
+ createSiblingAfter(item);
150
+ } else if (e.key === "Tab") {
151
+ e.preventDefault();
152
+ if (e.shiftKey) {
153
+ if (!item.parentId) return;
154
+ const parent = tree.entries.value.find((e2) => e2.id === item.parentId);
155
+ if (!parent) return;
156
+ const grandparentId = parent.parentId;
157
+ const uncles = tree.childrenOf(grandparentId);
158
+ const parentIdx = uncles.findIndex((u) => u.id === item.parentId);
159
+ const newOrder = parentIdx >= 0 ? (uncles[parentIdx]?.order ?? Date.now()) + 0.5 : Date.now();
160
+ tree.moveEntry(item.id, grandparentId, newOrder);
161
+ } else {
162
+ const siblings = tree.childrenOf(item.parentId);
163
+ const idx = siblings.findIndex((s) => s.id === item.id);
164
+ if (idx <= 0) return;
165
+ const prevSibling = siblings[idx - 1];
166
+ tree.moveEntry(item.id, prevSibling.id, Date.now());
167
+ }
168
+ } else if (e.key === "Escape") {
169
+ editingId.value = null;
170
+ setLocalState({ "checklist:editing": null });
171
+ } else if (e.key === "Backspace" && editingValue.value === "") {
172
+ e.preventDefault();
173
+ tree.deleteEntry(item.id);
174
+ editingId.value = null;
175
+ }
176
+ }
177
+ function orderBetween(list, targetIdx) {
178
+ const prev = list[targetIdx - 1];
179
+ const next = list[targetIdx];
180
+ if (!prev && !next) return Date.now();
181
+ if (!prev) return next.order - 1e3;
182
+ if (!next) return prev.order + 1e3;
183
+ return (prev.order + next.order) / 2;
184
+ }
185
+ const { dragId, dragOverId, handlePointerDown } = useTouchDrag({
186
+ onDrop: (srcId, targetId) => {
187
+ if (!props.editable) return;
188
+ const targetItem = flatItems.value.find((i) => i.id === targetId);
189
+ if (!targetItem) return;
190
+ const siblings = tree.childrenOf(targetItem.parentId);
191
+ const targetIdx = siblings.findIndex((s) => s.id === targetId);
192
+ const newOrder = orderBetween(siblings, targetIdx);
193
+ tree.moveEntry(srcId, targetItem.parentId, newOrder);
194
+ },
195
+ onDragStart: (id) => {
196
+ setLocalState({ "checklist:dragging": id });
197
+ },
198
+ onDragEnd: () => {
199
+ setLocalState({ "checklist:dragging": null });
200
+ }
201
+ });
202
+ const myClientId = computed(() => props.childProvider?.awareness?.clientID ?? 0);
203
+ function nodeEditors(nodeId) {
204
+ return states.value.filter(
205
+ (s) => s.clientId !== myClientId.value && s["checklist:editing"] === nodeId
206
+ );
207
+ }
208
+ function nodeHoverers(nodeId) {
209
+ return states.value.filter(
210
+ (s) => s.clientId !== myClientId.value && s["checklist:focused"] === nodeId
211
+ );
212
+ }
213
+ function onItemPointerEnter(itemId) {
214
+ if (editingId.value !== itemId && !dragId.value) {
215
+ setLocalState({ "checklist:focused": itemId });
216
+ }
217
+ }
218
+ function onItemPointerLeave() {
219
+ if (!dragId.value) {
220
+ setLocalState({ "checklist:focused": null });
221
+ }
222
+ }
223
+ onBeforeUnmount(() => {
224
+ setLocalState({
225
+ "checklist:focused": null,
226
+ "checklist:editing": null,
227
+ "checklist:dragging": null
228
+ });
229
+ });
230
+ function isOverdue(dateStr) {
231
+ return new Date(dateStr) < new Date((/* @__PURE__ */ new Date()).toDateString());
232
+ }
233
+ function isDueSoon(dateStr) {
234
+ const due = new Date(dateStr);
235
+ const now = /* @__PURE__ */ new Date();
236
+ const twoDays = 2 * 24 * 60 * 60 * 1e3;
237
+ return due.getTime() - now.getTime() < twoDays && !isOverdue(dateStr);
238
+ }
239
+ function formatRelativeDate(dateStr) {
240
+ const date = new Date(dateStr);
241
+ const now = /* @__PURE__ */ new Date();
242
+ const diffDays = Math.round((date.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24));
243
+ if (diffDays === 0) return "Today";
244
+ if (diffDays === 1) return "Tomorrow";
245
+ if (diffDays === -1) return "Yesterday";
246
+ if (diffDays < -1) return `${Math.abs(diffDays)}d ago`;
247
+ if (diffDays <= 7) return `In ${diffDays}d`;
248
+ return date.toLocaleDateString(void 0, { month: "short", day: "numeric" });
249
+ }
250
+ const PRIORITY_ITEMS = [
251
+ { label: "None", value: 0 },
252
+ { label: "Low", value: 1 },
253
+ { label: "Medium", value: 2 },
254
+ { label: "High", value: 3 }
255
+ ];
256
+ function itemMenuItems(item) {
257
+ if (!props.editable) return [];
258
+ return [
259
+ [
260
+ {
261
+ label: item.checked ? locale.value.markIncomplete : locale.value.markComplete,
262
+ icon: item.checked ? "i-lucide-circle" : "i-lucide-check-circle",
263
+ onSelect: () => tree.updateMeta(item.id, { checked: !item.checked })
264
+ },
265
+ {
266
+ label: "Rename",
267
+ icon: "i-lucide-pencil",
268
+ onSelect: () => startEdit(item.id, item.label)
269
+ },
270
+ {
271
+ label: locale.value.addSubTask,
272
+ icon: "i-lucide-list-plus",
273
+ onSelect: () => addChild(item.id)
274
+ }
275
+ ],
276
+ [
277
+ {
278
+ label: "Priority",
279
+ icon: "i-lucide-flag",
280
+ children: PRIORITY_ITEMS.map((p) => ({
281
+ label: p.label,
282
+ icon: item.priority === p.value ? "i-lucide-check" : void 0,
283
+ onSelect: () => tree.updateMeta(item.id, { priority: p.value })
284
+ }))
285
+ },
286
+ {
287
+ label: "Open as doc",
288
+ icon: "i-lucide-external-link",
289
+ onSelect: () => openNode(item.id, item.label)
290
+ },
291
+ {
292
+ label: "Duplicate",
293
+ icon: "i-lucide-copy",
294
+ onSelect: () => tree.duplicateEntry(item.id)
295
+ }
296
+ ],
297
+ [
298
+ {
299
+ label: "Delete",
300
+ icon: "i-lucide-trash-2",
301
+ color: "error",
302
+ onSelect: () => tree.deleteEntry(item.id)
303
+ }
304
+ ]
305
+ ];
306
+ }
307
+ const sortMenuItems = computed(() => [
308
+ [
309
+ { label: locale.value.sortManual, icon: sortMode.value === "manual" ? "i-lucide-check" : void 0, onSelect: () => {
310
+ sortMode.value = "manual";
311
+ } },
312
+ { label: locale.value.sortPriority, icon: sortMode.value === "priority" ? "i-lucide-check" : void 0, onSelect: () => {
313
+ sortMode.value = "priority";
314
+ } },
315
+ { label: locale.value.sortDue, icon: sortMode.value === "due" ? "i-lucide-check" : void 0, onSelect: () => {
316
+ sortMode.value = "due";
317
+ } }
318
+ ]
319
+ ]);
320
+ defineExpose({ connectedUsers });
321
+ </script>
322
+
323
+ <template>
324
+ <div class="flex-1 min-h-0 flex flex-col relative">
325
+ <!-- Toolbar -->
326
+ <div class="flex items-center justify-between px-4 py-2 border-b border-(--ui-border) shrink-0 gap-2">
327
+ <!-- Left: progress -->
328
+ <div class="flex items-center gap-2 shrink-0">
329
+ <span class="text-xs text-(--ui-text-muted) whitespace-nowrap">
330
+ {{ checkedCount }} / {{ totalCount }} {{ locale.done }}
331
+ </span>
332
+ <div
333
+ v-if="totalCount > 0"
334
+ class="w-20 h-1.5 rounded-full bg-(--ui-bg-elevated) overflow-hidden"
335
+ >
336
+ <div
337
+ class="h-full rounded-full bg-(--ui-primary) transition-all"
338
+ :style="{ width: `${percent}%` }"
339
+ />
340
+ </div>
341
+ </div>
342
+
343
+ <!-- Right: filter + sort + add -->
344
+ <div class="flex items-center gap-1">
345
+ <div class="inline-flex rounded-(--ui-radius) border border-(--ui-border)">
346
+ <UButton
347
+ :label="locale.all"
348
+ size="xs"
349
+ :variant="filterMode === 'all' ? 'solid' : 'ghost'"
350
+ :color="filterMode === 'all' ? 'primary' : 'neutral'"
351
+ @click="filterMode = 'all'"
352
+ />
353
+ <UButton
354
+ :label="locale.active"
355
+ size="xs"
356
+ :variant="filterMode === 'active' ? 'solid' : 'ghost'"
357
+ :color="filterMode === 'active' ? 'primary' : 'neutral'"
358
+ @click="filterMode = 'active'"
359
+ />
360
+ <UButton
361
+ :label="locale.done"
362
+ size="xs"
363
+ :variant="filterMode === 'completed' ? 'solid' : 'ghost'"
364
+ :color="filterMode === 'completed' ? 'primary' : 'neutral'"
365
+ @click="filterMode = 'completed'"
366
+ />
367
+ </div>
368
+
369
+ <UDropdownMenu
370
+ v-if="editable"
371
+ :items="sortMenuItems"
372
+ >
373
+ <UButton
374
+ icon="i-lucide-arrow-up-down"
375
+ size="xs"
376
+ variant="ghost"
377
+ color="neutral"
378
+ />
379
+ </UDropdownMenu>
380
+
381
+ <UButton
382
+ v-if="editable"
383
+ icon="i-lucide-plus"
384
+ size="xs"
385
+ variant="ghost"
386
+ color="neutral"
387
+ :label="locale.addTask"
388
+ @click="addRoot"
389
+ />
390
+ </div>
391
+ </div>
392
+
393
+ <!-- Task list -->
394
+ <div class="flex-1 min-h-0 overflow-y-auto">
395
+ <div class="p-4 max-w-3xl mx-auto w-full">
396
+ <!-- Empty state -->
397
+ <div
398
+ v-if="flatItems.length === 0"
399
+ class="flex flex-col items-center justify-center h-full gap-3 text-center py-12"
400
+ >
401
+ <UIcon
402
+ name="i-lucide-check-square"
403
+ class="size-10 text-(--ui-text-dimmed)"
404
+ />
405
+ <p class="text-sm text-(--ui-text-muted)">
406
+ {{ locale.empty }}
407
+ </p>
408
+ <UButton
409
+ v-if="editable"
410
+ icon="i-lucide-plus"
411
+ :label="locale.addTask"
412
+ size="sm"
413
+ @click="addRoot"
414
+ />
415
+ </div>
416
+
417
+ <TransitionGroup
418
+ v-else
419
+ name="checklist"
420
+ tag="div"
421
+ class="space-y-0.5"
422
+ >
423
+ <UContextMenu
424
+ v-for="item in visibleItems"
425
+ :key="item.id"
426
+ :items="itemMenuItems(item)"
427
+ >
428
+ <div
429
+ class="group flex items-center gap-1.5 py-1 pr-1 rounded-(--ui-radius) hover:bg-(--ui-bg-elevated) relative"
430
+ :class="{
431
+ 'border-t-2 border-(--ui-primary)': dragOverId === item.id,
432
+ 'opacity-30': dragId === item.id
433
+ }"
434
+ :style="[
435
+ { paddingLeft: `${item.depth * 20 + 4}px` },
436
+ nodeHoverers(item.id).length ? { backgroundColor: nodeHoverers(item.id)[0].user?.color + '18' } : {}
437
+ ]"
438
+ :data-drag-id="item.id"
439
+ @pointerdown="editable && sortMode === 'manual' ? handlePointerDown($event, item.id) : void 0"
440
+ @pointerenter="onItemPointerEnter(item.id)"
441
+ @pointerleave="onItemPointerLeave"
442
+ >
443
+ <!-- Remote editing indicator (left bar) -->
444
+ <div
445
+ v-if="nodeEditors(item.id).length"
446
+ class="absolute left-0 top-0 bottom-0 w-0.5 rounded-full"
447
+ :style="{ background: nodeEditors(item.id)[0].user?.color }"
448
+ />
449
+
450
+ <!-- Remote hover name badge -->
451
+ <span
452
+ v-if="nodeHoverers(item.id).length"
453
+ class="absolute -top-2.5 left-2 text-[10px] px-1 rounded text-white leading-tight z-10"
454
+ :style="{ backgroundColor: nodeHoverers(item.id)[0].user?.color }"
455
+ >
456
+ {{ nodeHoverers(item.id)[0].user?.name }}
457
+ </span>
458
+
459
+ <!-- Hierarchy indicator -->
460
+ <span class="size-4 flex items-center justify-center text-(--ui-text-dimmed) shrink-0">
461
+ <UIcon
462
+ :name="item.hasChildren ? 'i-lucide-chevron-right' : 'i-lucide-grip-vertical'"
463
+ class="size-3"
464
+ />
465
+ </span>
466
+
467
+ <!-- Checkbox -->
468
+ <button
469
+ class="shrink-0 size-4 rounded border flex items-center justify-center transition-colors"
470
+ :class="item.checked ? 'bg-(--ui-primary) border-(--ui-primary)' : 'border-(--ui-border) hover:border-(--ui-primary)'"
471
+ :disabled="!editable"
472
+ @click.stop="editable && tree.updateMeta(item.id, { checked: !item.checked })"
473
+ >
474
+ <UIcon
475
+ v-if="item.checked"
476
+ name="i-lucide-check"
477
+ class="size-2.5 text-white"
478
+ />
479
+ </button>
480
+
481
+ <!-- Label / inline edit -->
482
+ <input
483
+ v-if="editingId === item.id"
484
+ :ref="(el) => inputRefs[item.id] = el"
485
+ v-model="editingValue"
486
+ class="flex-1 bg-transparent outline-none text-sm text-(--ui-text-highlighted) py-0.5"
487
+ autofocus
488
+ @keydown="onKeydown(item, $event)"
489
+ @blur="commitEdit"
490
+ >
491
+ <span
492
+ v-else
493
+ class="flex-1 text-sm py-0.5 cursor-text truncate"
494
+ :class="item.checked ? 'line-through text-(--ui-text-muted)' : item.label ? 'text-(--ui-text-highlighted)' : 'text-(--ui-text-dimmed) italic'"
495
+ @click="editable && startEdit(item.id, item.label)"
496
+ >
497
+ {{ item.label || "Empty" }}
498
+ </span>
499
+
500
+ <!-- Due date badge -->
501
+ <span
502
+ v-if="item.dateEnd"
503
+ class="text-[11px] shrink-0 px-1.5 py-0.5 rounded whitespace-nowrap"
504
+ :class="isOverdue(item.dateEnd) ? 'text-red-400 bg-red-400/10' : isDueSoon(item.dateEnd) ? 'text-amber-400 bg-amber-400/10' : 'text-(--ui-text-dimmed) bg-(--ui-bg-elevated)'"
505
+ >
506
+ {{ formatRelativeDate(item.dateEnd) }}
507
+ </span>
508
+
509
+ <!-- Priority flag -->
510
+ <UIcon
511
+ v-if="item.priority >= 2"
512
+ name="i-lucide-flag"
513
+ class="size-3.5 shrink-0"
514
+ :class="{
515
+ 'text-amber-400': item.priority === 2,
516
+ 'text-red-400': item.priority === 3
517
+ }"
518
+ />
519
+
520
+ <!-- Hover actions -->
521
+ <div
522
+ v-if="editable"
523
+ class="hidden group-hover:flex items-center gap-0.5 shrink-0"
524
+ >
525
+ <UTooltip
526
+ text="Open as doc"
527
+ :content="{ side: 'bottom' }"
528
+ >
529
+ <UButton
530
+ icon="i-lucide-external-link"
531
+ size="xs"
532
+ variant="ghost"
533
+ color="neutral"
534
+ @click.stop="openNode(item.id, item.label)"
535
+ />
536
+ </UTooltip>
537
+ <UTooltip
538
+ :text="locale.addSubTask"
539
+ :content="{ side: 'bottom' }"
540
+ >
541
+ <UButton
542
+ icon="i-lucide-plus"
543
+ size="xs"
544
+ variant="ghost"
545
+ color="neutral"
546
+ @click.stop="addChild(item.id)"
547
+ />
548
+ </UTooltip>
549
+ <UTooltip
550
+ text="Delete"
551
+ :content="{ side: 'bottom' }"
552
+ >
553
+ <UButton
554
+ icon="i-lucide-trash-2"
555
+ size="xs"
556
+ variant="ghost"
557
+ color="error"
558
+ @click.stop="tree.deleteEntry(item.id)"
559
+ />
560
+ </UTooltip>
561
+ </div>
562
+ </div>
563
+ </UContextMenu>
564
+ </TransitionGroup>
565
+ </div>
566
+ </div>
567
+
568
+ <!-- Node panel (side panel for child docs) -->
569
+ <ANodePanel
570
+ :node-id="openNodeId"
571
+ :node-label="openNodeLabel"
572
+ :child-provider="openNodeProvider"
573
+ :is-loading="nodePanelLoading"
574
+ @close="closePanel"
575
+ />
576
+ </div>
577
+ </template>
578
+
579
+ <style scoped>
580
+ .checklist-move{transition:transform .25s ease}.checklist-enter-active{transition:opacity .18s ease,transform .18s ease}.checklist-enter-from{opacity:0;transform:translateY(-6px) scale(.97)}.checklist-leave-active{transition:opacity .15s ease}.checklist-leave-to{opacity:0}
581
+ </style>
@@ -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']['checklist']>;
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,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;