@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
@@ -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']['map']>;
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;
@@ -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']['outline']>;
5
+ editable?: boolean;
5
6
  };
6
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
8
+ connectedUsers: import("vue").ComputedRef<{
9
+ clientId: number;
10
+ name: string;
11
+ color: string;
12
+ avatar: string | undefined;
13
+ publicKey: any;
14
+ }[]>;
15
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
16
+ editable: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
18
  declare const _default: typeof __VLS_export;
8
19
  export default _default;
@@ -1,13 +1,16 @@
1
1
  <script setup>
2
- import { ref, computed, nextTick } from "vue";
2
+ import { ref, computed, nextTick, 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,166 +18,323 @@ const locale = computed(() => ({
15
18
  ...config.public?.abracadabra?.locale?.renderers?.outline ?? {},
16
19
  ...props.labels ?? {}
17
20
  }));
18
- const { tree } = useRendererBase(props);
19
- const editingId = ref(null);
20
- const editValues = ref({});
21
- const expandedIds = ref(/* @__PURE__ */ new Set());
22
- function childrenOf(parentId) {
23
- return tree.entries.value.filter((e) => e.parentId === (parentId === null ? props.docId : parentId)).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
24
- }
25
- function buildFlat(parentId, depth) {
21
+ const { tree, childProviderRef, states, setLocalState, connectedUsers } = useRendererBase(props);
22
+ const {
23
+ openNodeId,
24
+ openNodeLabel,
25
+ openNodeProvider,
26
+ openNode,
27
+ closePanel
28
+ } = useNodePanel(childProviderRef);
29
+ const myClientId = computed(() => props.childProvider?.awareness?.clientID ?? 0);
30
+ const flatItems = computed(() => {
26
31
  const result = [];
27
- for (const entry of childrenOf(parentId)) {
28
- const expanded = expandedIds.value.has(entry.id);
29
- const hc = tree.entries.value.some((e) => e.parentId === entry.id);
30
- result.push({
31
- id: entry.id,
32
- label: entry.label,
33
- depth,
34
- parentId: entry.parentId,
35
- order: entry.order,
36
- hasChildren: hc,
37
- isExpanded: expanded
38
- });
39
- if (expanded || !hc) {
40
- result.push(...buildFlat(entry.id, depth + 1));
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
+ });
41
+ walk(child.id, depth + 1);
41
42
  }
42
43
  }
44
+ walk(null, 0);
43
45
  return result;
44
- }
45
- const flatItems = computed(() => buildFlat(null, 0));
46
+ });
47
+ const editingId = ref(null);
48
+ const editingValue = ref("");
49
+ const inputRefs = ref({});
46
50
  function startEdit(id, label) {
51
+ if (!props.editable) return;
47
52
  editingId.value = id;
48
- editValues.value[id] = label;
53
+ editingValue.value = label;
54
+ setLocalState({ "outline:editing": id });
55
+ }
56
+ function commitEdit() {
57
+ if (!props.editable) return;
58
+ if (editingId.value && editingValue.value.trim()) {
59
+ tree.renameEntry(editingId.value, editingValue.value.trim());
60
+ } else if (editingId.value && !editingValue.value.trim()) {
61
+ tree.deleteEntry(editingId.value);
62
+ }
63
+ editingId.value = null;
64
+ setLocalState({ "outline:editing": null });
65
+ }
66
+ function createSiblingAfter(item) {
67
+ if (!props.editable) return;
68
+ const id = tree.createChild(item.parentId, "");
49
69
  nextTick(() => {
50
- const el = document.getElementById(`outline-${id}`);
51
- el?.focus();
52
- el?.select();
70
+ editingId.value = id;
71
+ editingValue.value = "";
72
+ nextTick(() => inputRefs.value[id]?.focus());
53
73
  });
54
74
  }
55
- function commitEdit(id) {
56
- const val = (editValues.value[id] ?? "").trim();
57
- if (val) {
58
- const entry = tree.treeMap.get(id);
59
- if (entry) tree.treeMap.set(id, { ...entry, label: val, updatedAt: Date.now() });
75
+ function onEnter(item, e) {
76
+ e.preventDefault();
77
+ if (!props.editable) return;
78
+ if (editingValue.value.trim()) {
79
+ tree.renameEntry(item.id, editingValue.value.trim());
60
80
  }
61
81
  editingId.value = null;
82
+ createSiblingAfter(item);
62
83
  }
63
- function onKeydown(e, item, idx) {
64
- if (e.key === "Enter") {
65
- e.preventDefault();
66
- commitEdit(item.id);
67
- const newId = tree.createChild(item.parentId === props.docId ? null : item.parentId, "");
68
- nextTick(() => startEdit(newId, ""));
69
- } else if (e.key === "Tab") {
70
- e.preventDefault();
71
- const val = (editValues.value[item.id] ?? "").trim();
72
- if (val) {
73
- const entry = tree.treeMap.get(item.id);
74
- if (entry) tree.treeMap.set(item.id, { ...entry, label: val, updatedAt: Date.now() });
75
- }
76
- if (!e.shiftKey) {
77
- const prev = flatItems.value[idx - 1];
78
- if (prev && prev.depth <= item.depth) {
79
- const entry = tree.treeMap.get(item.id);
80
- if (entry) tree.treeMap.set(item.id, { ...entry, parentId: prev.id, order: Date.now(), updatedAt: Date.now() });
81
- expandedIds.value.add(prev.id);
82
- expandedIds.value = new Set(expandedIds.value);
83
- }
84
- } else {
85
- if (item.depth > 0) {
86
- const parentEntry = tree.treeMap.get(item.parentId);
87
- const grandParentId = parentEntry?.parentId ?? props.docId;
88
- const entry = tree.treeMap.get(item.id);
89
- if (entry) tree.treeMap.set(item.id, { ...entry, parentId: grandParentId, order: Date.now(), updatedAt: Date.now() });
90
- }
91
- }
92
- } else if (e.key === "Backspace" && !(editValues.value[item.id] ?? "").length) {
93
- e.preventDefault();
94
- tree.treeMap.remove(item.id);
84
+ function onTab(item, e) {
85
+ e.preventDefault();
86
+ if (!props.editable) return;
87
+ if (e.shiftKey) {
88
+ if (!item.parentId) return;
89
+ const parent = tree.entries.value.find((en) => en.id === item.parentId);
90
+ if (!parent) return;
91
+ const grandparentId = parent.parentId;
92
+ const uncles = tree.childrenOf(grandparentId);
93
+ const parentIdx = uncles.findIndex((u) => u.id === item.parentId);
94
+ const newOrder = parentIdx >= 0 ? (uncles[parentIdx]?.order ?? Date.now()) + 0.5 : Date.now();
95
+ tree.moveEntry(item.id, grandparentId, newOrder);
96
+ } else {
97
+ const siblings = tree.childrenOf(item.parentId);
98
+ const idx = siblings.findIndex((s) => s.id === item.id);
99
+ if (idx <= 0) return;
100
+ const prevSibling = siblings[idx - 1];
101
+ tree.moveEntry(item.id, prevSibling.id, Date.now());
102
+ }
103
+ }
104
+ function onKeydown(item, e) {
105
+ if (e.key === "Enter") onEnter(item, e);
106
+ else if (e.key === "Tab") onTab(item, e);
107
+ else if (e.key === "Escape") {
95
108
  editingId.value = null;
96
- const prev = flatItems.value[idx - 1];
97
- if (prev) nextTick(() => startEdit(prev.id, prev.label));
98
- } else if (e.key === "ArrowUp") {
109
+ } else if (e.key === "Backspace" && editingValue.value === "" && props.editable) {
99
110
  e.preventDefault();
100
- commitEdit(item.id);
101
- const prev = flatItems.value[idx - 1];
102
- if (prev) nextTick(() => startEdit(prev.id, prev.label));
103
- } else if (e.key === "ArrowDown") {
104
- e.preventDefault();
105
- commitEdit(item.id);
106
- const next = flatItems.value[idx + 1];
107
- if (next) nextTick(() => startEdit(next.id, next.label));
111
+ tree.deleteEntry(item.id);
112
+ editingId.value = null;
108
113
  }
109
114
  }
115
+ function itemMenuItems(item) {
116
+ return [
117
+ [
118
+ { label: "Open as doc", icon: "i-lucide-external-link", onSelect: () => openNode(item.id, item.label) },
119
+ { label: "Rename", icon: "i-lucide-pencil", onSelect: () => startEdit(item.id, item.label) },
120
+ {
121
+ label: "Add child",
122
+ icon: "i-lucide-plus",
123
+ onSelect: () => {
124
+ const id = tree.createChild(item.id, "");
125
+ nextTick(() => {
126
+ editingId.value = id;
127
+ editingValue.value = "";
128
+ nextTick(() => inputRefs.value[id]?.focus());
129
+ });
130
+ }
131
+ },
132
+ { label: "Duplicate", icon: "i-lucide-copy", onSelect: () => tree.duplicateEntry(item.id) }
133
+ ],
134
+ [
135
+ { label: "Delete", icon: "i-lucide-trash-2", color: "error", onSelect: () => tree.deleteEntry(item.id) }
136
+ ]
137
+ ];
138
+ }
139
+ function orderBetween(list, targetIdx) {
140
+ const prev = list[targetIdx - 1];
141
+ const next = list[targetIdx];
142
+ if (!prev && !next) return Date.now();
143
+ if (!prev) return next.order - 1e3;
144
+ if (!next) return prev.order + 1e3;
145
+ return (prev.order + next.order) / 2;
146
+ }
147
+ const { dragId, dragOverId, handlePointerDown } = useTouchDrag({
148
+ onDrop: (srcId, targetId) => {
149
+ if (!props.editable) return;
150
+ const targetItem = flatItems.value.find((i) => i.id === targetId);
151
+ if (!targetItem) return;
152
+ const siblings = tree.childrenOf(targetItem.parentId);
153
+ const targetIdx = siblings.findIndex((s) => s.id === targetId);
154
+ const newOrder = orderBetween(siblings, targetIdx);
155
+ tree.moveEntry(srcId, targetItem.parentId, newOrder);
156
+ }
157
+ });
110
158
  function addRoot() {
111
- const newId = tree.createChild(null, locale.value.addItem);
112
- nextTick(() => startEdit(newId, locale.value.addItem));
159
+ if (!props.editable) return;
160
+ const id = tree.createChild(null, "");
161
+ nextTick(() => {
162
+ editingId.value = id;
163
+ editingValue.value = "";
164
+ nextTick(() => inputRefs.value[id]?.focus());
165
+ });
166
+ }
167
+ function nodeEditors(nodeId) {
168
+ return states.value.filter(
169
+ (s) => s.clientId !== myClientId.value && s["outline:editing"] === nodeId
170
+ );
171
+ }
172
+ function nodeSelectors(nodeId) {
173
+ return states.value.filter(
174
+ (s) => s.clientId !== myClientId.value && s["outline:selected"] === nodeId
175
+ );
176
+ }
177
+ function onItemPointerEnter(itemId) {
178
+ if (editingId.value !== itemId) {
179
+ setLocalState({ "outline:selected": itemId });
180
+ }
181
+ }
182
+ function onItemPointerLeave() {
183
+ setLocalState({ "outline:selected": null });
113
184
  }
185
+ onBeforeUnmount(() => {
186
+ setLocalState({ "outline:editing": null, "outline:selected": null });
187
+ });
188
+ defineExpose({ connectedUsers });
114
189
  </script>
115
190
 
116
191
  <template>
117
- <div class="p-4 max-w-2xl">
118
- <div class="space-y-0.5">
119
- <div
120
- v-for="(item, idx) in flatItems"
121
- :key="item.id"
122
- :style="{ paddingLeft: `${item.depth * 24}px` }"
123
- class="flex items-center gap-2 group py-0.5"
124
- >
125
- <!-- Expand toggle -->
126
- <UButton
127
- :icon="item.isExpanded ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
128
- variant="ghost"
129
- color="neutral"
130
- size="xs"
131
- :class="item.hasChildren ? 'opacity-100' : 'opacity-0 pointer-events-none'"
132
- @click="() => {
133
- const s = new Set(expandedIds);
134
- s.has(item.id) ? s.delete(item.id) : s.add(item.id);
135
- expandedIds = s;
136
- }"
137
- />
138
-
139
- <UIcon name="i-lucide-circle-dot" class="size-3 text-muted flex-shrink-0" />
140
-
141
- <template v-if="editingId === item.id">
142
- <input
143
- :id="`outline-${item.id}`"
144
- v-model="editValues[item.id]"
145
- class="flex-1 bg-transparent border-none outline-none text-sm"
146
- @blur="commitEdit(item.id)"
147
- @keydown="onKeydown($event, item, idx)"
148
- >
149
- </template>
150
- <span
151
- v-else
152
- class="flex-1 text-sm cursor-text"
153
- :class="!item.label ? 'text-muted italic' : ''"
154
- @click="startEdit(item.id, item.label)"
155
- >{{ item.label || locale.empty }}</span>
156
-
157
- <UButton
158
- icon="i-lucide-trash"
159
- variant="ghost"
160
- color="error"
161
- size="xs"
162
- class="opacity-0 group-hover:opacity-100"
163
- @click="tree.treeMap.remove(item.id)"
164
- />
165
- </div>
166
- </div>
167
-
168
- <!-- Add root item -->
169
- <div class="mt-2">
192
+ <div class="flex-1 min-h-0 flex flex-col relative">
193
+ <!-- Toolbar -->
194
+ <div class="flex items-center justify-between px-4 py-2 border-b border-(--ui-border) shrink-0">
195
+ <span class="text-xs text-(--ui-text-muted)">Outline</span>
170
196
  <UButton
197
+ v-if="editable"
171
198
  icon="i-lucide-plus"
199
+ size="xs"
172
200
  variant="ghost"
173
201
  color="neutral"
174
- size="sm"
175
202
  :label="locale.addItem"
176
203
  @click="addRoot"
177
204
  />
178
205
  </div>
206
+
207
+ <div class="flex-1 min-h-0 overflow-y-auto">
208
+ <div class="p-4 max-w-3xl mx-auto w-full">
209
+ <!-- Empty state -->
210
+ <div
211
+ v-if="flatItems.length === 0"
212
+ class="flex flex-col items-center justify-center py-16 gap-3 text-center"
213
+ >
214
+ <UIcon
215
+ name="i-lucide-list-tree"
216
+ class="size-10 text-(--ui-text-dimmed)"
217
+ />
218
+ <p class="text-sm text-(--ui-text-muted)">
219
+ Start your outline. Press Enter to add items.
220
+ </p>
221
+ <UButton
222
+ v-if="editable"
223
+ icon="i-lucide-plus"
224
+ :label="locale.addItem"
225
+ size="sm"
226
+ @click="addRoot"
227
+ />
228
+ </div>
229
+
230
+ <TransitionGroup
231
+ v-else
232
+ name="outline"
233
+ tag="div"
234
+ class="space-y-0.5"
235
+ >
236
+ <UContextMenu
237
+ v-for="item in flatItems"
238
+ :key="item.id"
239
+ :items="editable ? itemMenuItems(item) : []"
240
+ >
241
+ <div
242
+ class="group flex items-center gap-1.5 py-1 rounded-(--ui-radius) hover:bg-(--ui-bg-elevated) relative"
243
+ :class="{
244
+ 'border-t-2 border-(--ui-primary)': dragOverId === item.id,
245
+ 'opacity-30': dragId === item.id
246
+ }"
247
+ :style="[
248
+ { paddingLeft: `${item.depth * 20 + 4}px` },
249
+ nodeSelectors(item.id).length ? { backgroundColor: nodeSelectors(item.id)[0].user?.color + '18' } : {}
250
+ ]"
251
+ :data-drag-id="item.id"
252
+ @pointerdown="editable && handlePointerDown($event, item.id)"
253
+ @pointerenter="onItemPointerEnter(item.id)"
254
+ @pointerleave="onItemPointerLeave"
255
+ >
256
+ <!-- Remote editing indicator -->
257
+ <div
258
+ v-if="nodeEditors(item.id).length"
259
+ class="absolute left-0 top-0 bottom-0 w-0.5 rounded-full"
260
+ :style="{ background: nodeEditors(item.id)[0].user?.color }"
261
+ />
262
+
263
+ <span class="size-4 flex items-center justify-center text-(--ui-text-dimmed) shrink-0">
264
+ <UIcon
265
+ :name="item.hasChildren ? 'i-lucide-chevron-right' : 'i-lucide-dot'"
266
+ class="size-3"
267
+ />
268
+ </span>
269
+
270
+ <input
271
+ v-if="editable && editingId === item.id"
272
+ :ref="(el) => inputRefs[item.id] = el"
273
+ v-model="editingValue"
274
+ class="flex-1 bg-transparent outline-none text-sm text-(--ui-text-highlighted) py-0.5"
275
+ autofocus
276
+ @keydown="onKeydown(item, $event)"
277
+ @blur="commitEdit"
278
+ >
279
+ <span
280
+ v-else
281
+ class="flex-1 text-sm py-0.5 cursor-text"
282
+ :class="item.label && item.label !== 'Untitled' ? 'text-(--ui-text-highlighted)' : 'text-(--ui-text-dimmed) italic'"
283
+ @click="editable && startEdit(item.id, item.label)"
284
+ >
285
+ {{ item.label || locale.empty }}
286
+ </span>
287
+
288
+ <!-- Hover actions -->
289
+ <div
290
+ v-if="editable"
291
+ class="hidden group-hover:flex items-center gap-0.5 shrink-0"
292
+ >
293
+ <UButton
294
+ icon="i-lucide-external-link"
295
+ size="xs"
296
+ variant="ghost"
297
+ color="neutral"
298
+ @click.stop="openNode(item.id, item.label)"
299
+ />
300
+ <UButton
301
+ icon="i-lucide-plus"
302
+ size="xs"
303
+ variant="ghost"
304
+ color="neutral"
305
+ @click.stop="() => {
306
+ const id = tree.createChild(item.id, '');
307
+ nextTick(() => {
308
+ editingId = id;
309
+ editingValue = '';
310
+ nextTick(() => inputRefs[id]?.focus());
311
+ });
312
+ }"
313
+ />
314
+ <UButton
315
+ icon="i-lucide-trash-2"
316
+ size="xs"
317
+ variant="ghost"
318
+ color="error"
319
+ @click.stop="tree.deleteEntry(item.id)"
320
+ />
321
+ </div>
322
+ </div>
323
+ </UContextMenu>
324
+ </TransitionGroup>
325
+ </div>
326
+ </div>
327
+
328
+ <!-- Node panel -->
329
+ <ANodePanel
330
+ :node-id="openNodeId"
331
+ :node-label="openNodeLabel"
332
+ :child-provider="openNodeProvider"
333
+ @close="closePanel"
334
+ />
179
335
  </div>
180
336
  </template>
337
+
338
+ <style scoped>
339
+ .outline-move{transition:transform .25s ease}.outline-enter-active{transition:opacity .18s ease,transform .18s ease}.outline-enter-from{opacity:0;transform:translateY(-6px) scale(.97)}.outline-leave-active{transition:opacity .15s ease}.outline-leave-to{opacity:0}
340
+ </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']['outline']>;
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;
@@ -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;