@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,177 +18,466 @@ const locale = computed(() => ({
15
18
  ...config.public?.abracadabra?.locale?.renderers?.table ?? {},
16
19
  ...props.labels ?? {}
17
20
  }));
18
- const { tree } = useRendererBase(props);
19
- const rows = computed(() => tree.childrenOf(null).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));
20
- const detectedColumns = computed(() => {
21
- const keys = /* @__PURE__ */ new Set(["label"]);
22
- for (const row of rows.value) {
23
- if (row.meta) {
24
- for (const key of Object.keys(row.meta)) {
25
- if (!["_metaFields", "_metaInitialized", "coverUploadId", "coverDocId", "coverMimeType"].includes(key)) {
26
- keys.add(key);
27
- }
21
+ const { tree, childProviderRef, childDoc, 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 INTERNAL_META_KEYS = /* @__PURE__ */ new Set([
31
+ "_metaFields",
32
+ "_metaInitialized",
33
+ "coverUploadId",
34
+ "coverDocId",
35
+ "coverMimeType",
36
+ "geoType",
37
+ "geoLat",
38
+ "geoLng",
39
+ "icon",
40
+ "color",
41
+ "geoDescription"
42
+ ]);
43
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
+ function camelToTitle(s) {
45
+ return s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
46
+ }
47
+ const columns = computed(
48
+ () => tree.childrenOf(null).sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
49
+ );
50
+ const colChildren = computed(() => {
51
+ const map = /* @__PURE__ */ new Map();
52
+ for (const col of columns.value) {
53
+ map.set(col.id, tree.childrenOf(col.id).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));
54
+ }
55
+ return map;
56
+ });
57
+ const rowCount = computed(
58
+ () => Math.max(0, ...columns.value.map((c) => colChildren.value.get(c.id)?.length ?? 0))
59
+ );
60
+ function getCellEntry(colId, rowIdx) {
61
+ return colChildren.value.get(colId)?.[rowIdx];
62
+ }
63
+ function getRowRep(rowIdx) {
64
+ for (const col of columns.value) {
65
+ const entry = getCellEntry(col.id, rowIdx);
66
+ if (entry) return entry;
67
+ }
68
+ return void 0;
69
+ }
70
+ const metaColumns = computed(() => {
71
+ const colIds = new Set(columns.value.map((c) => c.id));
72
+ const keys = /* @__PURE__ */ new Set();
73
+ for (const col of columns.value) {
74
+ const children = colChildren.value.get(col.id) ?? [];
75
+ for (const cell of children) {
76
+ if (!cell.meta) continue;
77
+ for (const key of Object.keys(cell.meta)) {
78
+ if (INTERNAL_META_KEYS.has(key)) continue;
79
+ if (UUID_RE.test(key)) continue;
80
+ if (colIds.has(key)) continue;
81
+ keys.add(key);
28
82
  }
29
83
  }
30
84
  }
31
- return [...keys];
85
+ return [...keys].sort().map((key) => ({
86
+ id: `__meta_${key}`,
87
+ label: camelToTitle(key),
88
+ metaKey: key
89
+ }));
32
90
  });
91
+ function getCellValue(colId, rowIdx) {
92
+ const entry = getCellEntry(colId, rowIdx);
93
+ return entry?.label && entry.label !== "Untitled" ? entry.label : "";
94
+ }
95
+ function getMetaCellValue(rowIdx, metaKey) {
96
+ for (const col of columns.value) {
97
+ const entry = getCellEntry(col.id, rowIdx);
98
+ if (entry?.meta?.[metaKey] !== void 0) return String(entry.meta[metaKey]);
99
+ }
100
+ return "";
101
+ }
102
+ function setCellValue(colId, rowIdx, value) {
103
+ if (!props.editable) return;
104
+ const entry = getCellEntry(colId, rowIdx);
105
+ if (entry) {
106
+ tree.renameEntry(entry.id, value.trim() || "Untitled");
107
+ } else {
108
+ tree.createChild(colId, value.trim() || "Untitled");
109
+ }
110
+ }
111
+ function setMetaCellValue(rowIdx, metaKey, value) {
112
+ if (!props.editable) return;
113
+ const rep = getRowRep(rowIdx);
114
+ if (rep) tree.updateMeta(rep.id, { [metaKey]: value });
115
+ }
116
+ function addColumn() {
117
+ if (!props.editable) return;
118
+ tree.createChild(null, locale.value.addColumn === "Add column" ? "New Column" : locale.value.addColumn);
119
+ }
120
+ function deleteColumn(col) {
121
+ if (!props.editable) return;
122
+ tree.deleteEntry(col.id);
123
+ }
124
+ function deleteMetaColumn(metaKey) {
125
+ if (!props.editable) return;
126
+ for (const col of columns.value) {
127
+ const children = colChildren.value.get(col.id) ?? [];
128
+ for (const cell of children) {
129
+ if (cell.meta?.[metaKey] !== void 0) {
130
+ const newMeta = { ...cell.meta };
131
+ delete newMeta[metaKey];
132
+ tree.treeMap.set(cell.id, { ...tree.treeMap.get(cell.id), meta: newMeta });
133
+ }
134
+ }
135
+ }
136
+ }
137
+ function addRow() {
138
+ if (!props.editable) return;
139
+ if (!columns.value[0]) return;
140
+ tree.createChild(columns.value[0].id, "");
141
+ }
142
+ function deleteRow(rowIdx) {
143
+ if (!props.editable) return;
144
+ for (const col of columns.value) {
145
+ const entry = getCellEntry(col.id, rowIdx);
146
+ if (entry) tree.deleteEntry(entry.id);
147
+ }
148
+ }
33
149
  const editingCell = ref(null);
34
- const editValue = ref("");
35
- function getCellValue(row, col) {
36
- if (col === "label") return row.label;
37
- const v = row.meta?.[col];
38
- if (v === void 0 || v === null) return "";
39
- if (Array.isArray(v)) return v.join(", ");
40
- return String(v);
41
- }
42
- function startEdit(rowId, col, value) {
43
- editingCell.value = { rowId, col };
44
- editValue.value = value;
150
+ const editingValue = ref("");
151
+ function startEditDocCol(rowIdx, col) {
152
+ if (!props.editable) return;
153
+ editingCell.value = { rowIdx, colId: col.id };
154
+ editingValue.value = getCellValue(col.id, rowIdx);
155
+ setLocalState({ "table:editing": { rowIdx, fieldId: col.id } });
156
+ }
157
+ function startEditMetaCol(rowIdx, mc) {
158
+ if (!props.editable) return;
159
+ editingCell.value = { rowIdx, colId: mc.id, metaKey: mc.metaKey };
160
+ editingValue.value = getMetaCellValue(rowIdx, mc.metaKey);
161
+ setLocalState({ "table:editing": { rowIdx, fieldId: mc.id } });
45
162
  }
46
163
  function commitEdit() {
47
164
  if (!editingCell.value) return;
48
- const { rowId, col } = editingCell.value;
49
- const entry = tree.treeMap.get(rowId);
50
- if (!entry) {
51
- editingCell.value = null;
52
- return;
53
- }
54
- if (col === "label") {
55
- tree.treeMap.set(rowId, { ...entry, label: editValue.value, updatedAt: Date.now() });
165
+ if (editingCell.value.metaKey) {
166
+ setMetaCellValue(editingCell.value.rowIdx, editingCell.value.metaKey, editingValue.value);
56
167
  } else {
57
- tree.treeMap.set(rowId, {
58
- ...entry,
59
- meta: { ...entry.meta ?? {}, [col]: editValue.value },
60
- updatedAt: Date.now()
61
- });
168
+ setCellValue(editingCell.value.colId, editingCell.value.rowIdx, editingValue.value);
62
169
  }
63
170
  editingCell.value = null;
171
+ setLocalState({ "table:editing": null });
64
172
  }
65
- function addRow() {
66
- tree.createChild(null, locale.value.untitled);
173
+ const editingColId = ref(null);
174
+ const editingColValue = ref("");
175
+ function startRenameCol(col) {
176
+ if (!props.editable) return;
177
+ editingColId.value = col.id;
178
+ editingColValue.value = col.label;
179
+ }
180
+ function commitRenameCol() {
181
+ if (!props.editable) return;
182
+ if (editingColId.value && editingColValue.value.trim()) {
183
+ tree.renameEntry(editingColId.value, editingColValue.value.trim());
184
+ }
185
+ editingColId.value = null;
67
186
  }
68
- function deleteRow(id) {
69
- tree.treeMap.remove(id);
187
+ function rowIdxFromDragId(id) {
188
+ return Number.parseInt(id.replace("row-", ""), 10);
70
189
  }
71
- const newColName = ref("");
72
- const showAddCol = ref(false);
73
- function addColumn() {
74
- const name = newColName.value.trim();
75
- if (!name) return;
76
- for (const row of rows.value) {
77
- const entry = tree.treeMap.get(row.id);
78
- if (entry && !entry.meta?.[name]) {
79
- tree.treeMap.set(row.id, {
80
- ...entry,
81
- meta: { ...entry.meta ?? {}, [name]: "" },
82
- updatedAt: Date.now()
83
- });
190
+ const {
191
+ dragId: rowDragId,
192
+ dragOverId: rowDragOverId,
193
+ handlePointerDown: handleRowPointerDown
194
+ } = useTouchDrag({
195
+ idAttr: "data-drag-id",
196
+ onDrop: (srcDragId, targetDragId) => {
197
+ if (!props.editable) return;
198
+ const srcIdx = rowIdxFromDragId(srcDragId);
199
+ const targetIdx = rowIdxFromDragId(targetDragId);
200
+ if (srcIdx === targetIdx) return;
201
+ for (const col of columns.value) {
202
+ const children = colChildren.value.get(col.id) ?? [];
203
+ const srcEntry = children[srcIdx];
204
+ if (!srcEntry) continue;
205
+ const prev = children[targetIdx - (targetIdx > srcIdx ? 0 : 1)];
206
+ const next = children[targetIdx + (targetIdx > srcIdx ? 1 : 0)];
207
+ let newOrder;
208
+ if (!prev && !next) newOrder = Date.now();
209
+ else if (!prev) newOrder = next.order - 1e3;
210
+ else if (!next) newOrder = prev.order + 1e3;
211
+ else newOrder = (prev.order + next.order) / 2;
212
+ tree.moveEntry(srcEntry.id, col.id, newOrder);
84
213
  }
85
214
  }
86
- newColName.value = "";
87
- showAddCol.value = false;
215
+ });
216
+ function cellEditor(rowIdx, fieldId) {
217
+ return states.value.filter((s) => s.clientId !== myClientId.value).find(
218
+ (s) => s["table:editing"]?.rowIdx === rowIdx && s["table:editing"]?.fieldId === fieldId
219
+ );
220
+ }
221
+ function ensureTableStructure() {
222
+ if (!childDoc.value) return;
223
+ const allChildren = tree.childrenOf(null);
224
+ if (allChildren.length === 0) return;
225
+ const hasHierarchy = allChildren.some((item) => tree.childrenOf(item.id).length > 0);
226
+ if (hasHierarchy) return;
227
+ const colId = tree.createChild(null, "Name");
228
+ tree.moveEntry(colId, null, -1e3);
229
+ for (const item of allChildren) {
230
+ tree.moveEntry(item.id, colId, item.order);
231
+ }
88
232
  }
233
+ watch(childDoc, (doc) => {
234
+ if (doc) ensureTableStructure();
235
+ }, { immediate: true });
236
+ onBeforeUnmount(() => {
237
+ setLocalState({ "table:editing": null });
238
+ });
239
+ defineExpose({ connectedUsers });
89
240
  </script>
90
241
 
91
242
  <template>
92
- <div class="p-4 overflow-auto">
93
- <div class="border border-muted rounded-lg overflow-hidden">
94
- <table class="w-full text-sm border-collapse">
95
- <!-- Header -->
96
- <thead>
97
- <tr class="bg-muted/50">
98
- <th
99
- v-for="col in detectedColumns"
100
- :key="col"
101
- class="px-3 py-2 text-left font-medium text-muted capitalize border-b border-muted"
102
- >
103
- {{ col }}
104
- </th>
105
- <th class="px-2 py-2 border-b border-muted w-8">
106
- <UButton
107
- icon="i-lucide-plus"
108
- variant="ghost"
109
- color="neutral"
110
- size="xs"
111
- @click="showAddCol = !showAddCol"
112
- />
113
- </th>
114
- </tr>
115
- <tr v-if="showAddCol">
116
- <td
117
- :colspan="detectedColumns.length + 1"
118
- class="px-3 py-1 border-b border-muted"
119
- >
120
- <div class="flex gap-2">
121
- <UInput
122
- v-model="newColName"
123
- placeholder="Column name"
124
- size="xs"
125
- class="flex-1"
126
- @keydown.enter="addColumn"
127
- @keydown.escape="showAddCol = false"
128
- />
129
- <UButton
130
- size="xs"
131
- label="Add"
132
- @click="addColumn"
133
- />
134
- </div>
135
- </td>
136
- </tr>
137
- </thead>
138
- <!-- Rows -->
139
- <tbody>
140
- <tr
141
- v-for="row in rows"
142
- :key="row.id"
143
- class="group hover:bg-muted/30 border-b border-muted last:border-b-0"
243
+ <div class="flex-1 min-h-0 flex flex-col relative">
244
+ <!-- Toolbar -->
245
+ <div class="flex items-center justify-between px-4 py-2 border-b border-(--ui-border) shrink-0">
246
+ <span class="text-xs text-(--ui-text-muted)">
247
+ {{ rowCount }} row{{ rowCount !== 1 ? "s" : "" }}
248
+ </span>
249
+ <div
250
+ v-if="editable"
251
+ class="flex gap-1"
252
+ >
253
+ <UButton
254
+ icon="i-lucide-columns-3"
255
+ size="xs"
256
+ variant="ghost"
257
+ color="neutral"
258
+ :label="locale.addColumn"
259
+ @click="addColumn"
260
+ />
261
+ <UButton
262
+ icon="i-lucide-plus"
263
+ size="xs"
264
+ variant="ghost"
265
+ color="neutral"
266
+ :label="locale.addRow"
267
+ @click="addRow"
268
+ />
269
+ </div>
270
+ </div>
271
+
272
+ <!-- Content -->
273
+ <div class="flex-1 overflow-auto">
274
+ <!-- Empty state -->
275
+ <div
276
+ v-if="columns.length === 0"
277
+ class="flex flex-col items-center justify-center h-full gap-3 text-center"
278
+ >
279
+ <UIcon
280
+ name="i-lucide-table"
281
+ class="size-10 text-(--ui-text-dimmed)"
282
+ />
283
+ <p class="text-sm text-(--ui-text-muted)">
284
+ {{ locale.noRows }}
285
+ </p>
286
+ <UButton
287
+ v-if="editable"
288
+ icon="i-lucide-plus"
289
+ :label="locale.addColumn"
290
+ size="sm"
291
+ @click="addColumn"
292
+ />
293
+ </div>
294
+
295
+ <template v-else>
296
+ <table class="w-full text-sm border-collapse">
297
+ <thead class="sticky top-0 bg-(--ui-bg) z-10">
298
+ <tr class="border-b border-(--ui-border)">
299
+ <th class="w-6 px-1" />
300
+ <th class="text-left px-3 py-2 text-xs font-medium text-(--ui-text-muted) w-8">
301
+ #
302
+ </th>
303
+ <!-- Document columns -->
304
+ <th
305
+ v-for="col in columns"
306
+ :key="col.id"
307
+ class="text-left px-3 py-2 text-xs font-medium text-(--ui-text-muted) min-w-32"
308
+ >
309
+ <div class="group flex items-center gap-1">
310
+ <input
311
+ v-if="editingColId === col.id"
312
+ v-model="editingColValue"
313
+ class="bg-transparent outline-none text-xs font-medium w-full"
314
+ autofocus
315
+ @keydown.enter="commitRenameCol"
316
+ @keydown.escape="editingColId = null"
317
+ @blur="commitRenameCol"
318
+ >
319
+ <span
320
+ v-else
321
+ class="cursor-text"
322
+ @dblclick="startRenameCol(col)"
323
+ >
324
+ {{ col.label }}
325
+ </span>
326
+ <UButton
327
+ v-if="editable"
328
+ icon="i-lucide-x"
329
+ size="xs"
330
+ variant="ghost"
331
+ color="neutral"
332
+ class="opacity-0 group-hover:opacity-100"
333
+ @click="deleteColumn(col)"
334
+ />
335
+ </div>
336
+ </th>
337
+ <!-- Meta columns -->
338
+ <th
339
+ v-for="mc in metaColumns"
340
+ :key="mc.id"
341
+ class="text-left px-3 py-2 text-xs font-medium text-(--ui-text-muted) min-w-32"
342
+ >
343
+ <div class="group flex items-center gap-1">
344
+ <span class="cursor-default">{{ mc.label }}</span>
345
+ <UButton
346
+ v-if="editable"
347
+ icon="i-lucide-x"
348
+ size="xs"
349
+ variant="ghost"
350
+ color="neutral"
351
+ class="opacity-0 group-hover:opacity-100"
352
+ @click="deleteMetaColumn(mc.metaKey)"
353
+ />
354
+ </div>
355
+ </th>
356
+ </tr>
357
+ </thead>
358
+ <TransitionGroup
359
+ name="trow"
360
+ tag="tbody"
144
361
  >
145
- <td
146
- v-for="col in detectedColumns"
147
- :key="col"
148
- class="px-3 py-1.5"
149
- @dblclick="startEdit(row.id, col, getCellValue(row, col))"
362
+ <tr
363
+ v-for="idx in rowCount"
364
+ :key="getRowRep(idx - 1)?.id ?? `row-${idx}`"
365
+ :data-drag-id="`row-${idx - 1}`"
366
+ class="hover:bg-(--ui-bg-elevated) group"
367
+ :class="[
368
+ rowDragOverId === `row-${idx - 1}` ? 'border-t-2 border-(--ui-primary)' : 'border-b border-(--ui-border)',
369
+ rowDragId === `row-${idx - 1}` ? 'opacity-30' : ''
370
+ ]"
150
371
  >
151
- <template v-if="editingCell?.rowId === row.id && editingCell?.col === col">
372
+ <td
373
+ class="px-1 py-1.5 text-(--ui-text-dimmed) w-6"
374
+ :class="editable ? 'opacity-0 group-hover:opacity-60 cursor-grab touch-none' : 'opacity-0'"
375
+ v-on="editable ? { pointerdown: ($event) => handleRowPointerDown($event, `row-${idx - 1}`) } : {}"
376
+ >
377
+ <UIcon
378
+ v-if="editable"
379
+ name="i-lucide-grip-vertical"
380
+ class="size-4"
381
+ />
382
+ </td>
383
+ <td class="px-3 py-1.5 text-xs text-(--ui-text-dimmed) select-none">
384
+ <div class="flex items-center gap-0.5">
385
+ <span class="w-4 text-center">{{ idx }}</span>
386
+ <UButton
387
+ v-if="getRowRep(idx - 1)"
388
+ icon="i-lucide-external-link"
389
+ size="xs"
390
+ variant="ghost"
391
+ color="neutral"
392
+ class="opacity-0 group-hover:opacity-100"
393
+ @click="openNode(getRowRep(idx - 1).id, getRowRep(idx - 1).label)"
394
+ />
395
+ <UButton
396
+ v-if="editable"
397
+ icon="i-lucide-trash-2"
398
+ size="xs"
399
+ variant="ghost"
400
+ color="error"
401
+ class="opacity-0 group-hover:opacity-100"
402
+ @click="deleteRow(idx - 1)"
403
+ />
404
+ </div>
405
+ </td>
406
+ <!-- Document column cells -->
407
+ <td
408
+ v-for="col in columns"
409
+ :key="col.id"
410
+ class="px-3 py-1.5 cursor-text"
411
+ :style="cellEditor(idx - 1, col.id) ? { outline: `2px solid ${cellEditor(idx - 1, col.id).user?.color}` } : {}"
412
+ @click="startEditDocCol(idx - 1, col)"
413
+ >
152
414
  <input
153
- v-model="editValue"
154
- class="w-full bg-transparent border-none outline-none text-sm"
415
+ v-if="editingCell?.rowIdx === idx - 1 && editingCell?.colId === col.id"
416
+ v-model="editingValue"
417
+ class="w-full bg-transparent outline-none text-sm"
418
+ autofocus
155
419
  @blur="commitEdit"
156
420
  @keydown.enter="commitEdit"
157
421
  @keydown.escape="editingCell = null"
422
+ @click.stop
158
423
  >
159
- </template>
160
- <span
161
- v-else
162
- class="truncate block"
163
- >{{ getCellValue(row, col) }}</span>
164
- </td>
165
- <td class="px-2 py-1">
166
- <UButton
167
- icon="i-lucide-trash"
168
- variant="ghost"
169
- color="error"
170
- size="xs"
171
- class="opacity-0 group-hover:opacity-100"
172
- @click="deleteRow(row.id)"
173
- />
174
- </td>
175
- </tr>
176
- </tbody>
177
- </table>
178
- </div>
424
+ <span
425
+ v-else
426
+ class="block text-sm text-(--ui-text) min-h-5"
427
+ >
428
+ {{ getCellValue(col.id, idx - 1) }}
429
+ </span>
430
+ </td>
431
+ <!-- Meta column cells -->
432
+ <td
433
+ v-for="mc in metaColumns"
434
+ :key="mc.id"
435
+ class="px-3 py-1.5 cursor-text"
436
+ :style="cellEditor(idx - 1, mc.id) ? { outline: `2px solid ${cellEditor(idx - 1, mc.id).user?.color}` } : {}"
437
+ @click="startEditMetaCol(idx - 1, mc)"
438
+ >
439
+ <input
440
+ v-if="editingCell?.rowIdx === idx - 1 && editingCell?.colId === mc.id"
441
+ v-model="editingValue"
442
+ class="w-full bg-transparent outline-none text-sm"
443
+ autofocus
444
+ @blur="commitEdit"
445
+ @keydown.enter="commitEdit"
446
+ @keydown.escape="editingCell = null"
447
+ @click.stop
448
+ >
449
+ <span
450
+ v-else
451
+ class="block text-sm text-(--ui-text) min-h-5"
452
+ >
453
+ {{ getMetaCellValue(idx - 1, mc.metaKey) }}
454
+ </span>
455
+ </td>
456
+ </tr>
457
+ </TransitionGroup>
458
+ </table>
179
459
 
180
- <div class="mt-2">
181
- <UButton
182
- icon="i-lucide-plus"
183
- variant="ghost"
184
- color="neutral"
185
- size="sm"
186
- :label="locale.addRow"
187
- @click="addRow"
188
- />
460
+ <!-- Add row footer -->
461
+ <button
462
+ v-if="editable"
463
+ class="w-full text-left px-3 py-2 text-xs text-(--ui-text-dimmed) hover:bg-(--ui-bg-elevated) border-b border-(--ui-border) transition-colors"
464
+ @click="addRow"
465
+ >
466
+ + {{ locale.addRow }}
467
+ </button>
468
+ </template>
189
469
  </div>
470
+
471
+ <!-- Node panel -->
472
+ <ANodePanel
473
+ :node-id="openNodeId"
474
+ :node-label="openNodeLabel"
475
+ :child-provider="openNodeProvider"
476
+ @close="closePanel"
477
+ />
190
478
  </div>
191
479
  </template>
480
+
481
+ <style scoped>
482
+ .trow-move{transition:transform .25s ease}.trow-enter-active{transition:opacity .18s ease,transform .18s ease}.trow-enter-from{opacity:0;transform:translateY(-6px) scale(.97)}.trow-leave-active{transition:opacity .15s ease}.trow-leave-to{opacity:0}
483
+ </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']['table']>;
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']['timeline']>;
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;