@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
@@ -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,140 +18,411 @@ const locale = computed(() => ({
15
18
  ...config.public?.abracadabra?.locale?.renderers?.calendar ?? {},
16
19
  ...props.labels ?? {}
17
20
  }));
18
- const { tree } = useRendererBase(props);
19
- const today = /* @__PURE__ */ new Date();
20
- const viewYear = ref(today.getFullYear());
21
- const viewMonth = ref(today.getMonth());
22
- const monthName = computed(
23
- () => new Date(viewYear.value, viewMonth.value, 1).toLocaleString("default", { month: "long", year: "numeric" })
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 currentDate = ref(/* @__PURE__ */ new Date());
31
+ const year = computed(() => currentDate.value.getFullYear());
32
+ const month = computed(() => currentDate.value.getMonth());
33
+ const monthLabel = computed(
34
+ () => currentDate.value.toLocaleDateString("en-US", { month: "long", year: "numeric" })
24
35
  );
36
+ const WEEK_DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
25
37
  function prevMonth() {
26
- if (viewMonth.value === 0) {
27
- viewMonth.value = 11;
28
- viewYear.value--;
29
- } else viewMonth.value--;
38
+ currentDate.value = new Date(year.value, month.value - 1, 1);
30
39
  }
31
40
  function nextMonth() {
32
- if (viewMonth.value === 11) {
33
- viewMonth.value = 0;
34
- viewYear.value++;
35
- } else viewMonth.value++;
41
+ currentDate.value = new Date(year.value, month.value + 1, 1);
36
42
  }
37
- const events = computed(() => tree.childrenOf(null));
38
- function eventsOnDay(year, month, day) {
39
- const dayStr = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
40
- return events.value.filter((e) => {
41
- const start = e.meta?.datetimeStart ?? e.meta?.dateStart;
42
- if (!start) return false;
43
- return start.startsWith(dayStr);
44
- });
43
+ function goToday() {
44
+ currentDate.value = /* @__PURE__ */ new Date();
45
45
  }
46
- const calendarDays = computed(
47
- () => {
48
- const firstDay = new Date(viewYear.value, viewMonth.value, 1).getDay();
49
- const daysInMonth = new Date(viewYear.value, viewMonth.value + 1, 0).getDate();
50
- const days = [];
51
- for (let i = 0; i < firstDay; i++) days.push({ day: null });
52
- for (let d = 1; d <= daysInMonth; d++) days.push({ day: d });
53
- return days;
46
+ const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
47
+ const weeks = computed(() => {
48
+ const y = year.value;
49
+ const m = month.value;
50
+ const firstDay = new Date(y, m, 1).getDay();
51
+ const daysInMonth = new Date(y, m + 1, 0).getDate();
52
+ const raw = [];
53
+ for (let i = 0; i < firstDay; i++) raw.push({ date: null, dateStr: null, isToday: false });
54
+ for (let d = 1; d <= daysInMonth; d++) {
55
+ const date = new Date(y, m, d);
56
+ const dateStr = date.toISOString().slice(0, 10);
57
+ raw.push({ date, dateStr, isToday: dateStr === todayStr });
58
+ }
59
+ while (raw.length % 7 !== 0) raw.push({ date: null, dateStr: null, isToday: false });
60
+ const entries = tree.childrenOf(null);
61
+ const result = [];
62
+ for (let wi = 0; wi < raw.length; wi += 7) {
63
+ const weekRaw = raw.slice(wi, wi + 7);
64
+ const weekDates = weekRaw.map((c) => c.dateStr);
65
+ const seenIds = /* @__PURE__ */ new Set();
66
+ const multiDay = [];
67
+ const singleDay = [];
68
+ for (const entry of entries) {
69
+ const start = entry.meta?.datetimeStart?.slice(0, 10);
70
+ if (!start) continue;
71
+ const end = entry.meta?.datetimeEnd?.slice(0, 10) ?? start;
72
+ const isMulti = start !== end;
73
+ let startCol = -1;
74
+ let endCol = -1;
75
+ for (let col = 0; col < 7; col++) {
76
+ const d = weekDates[col];
77
+ if (!d) continue;
78
+ if (d >= start && d <= end) {
79
+ if (startCol === -1) startCol = col;
80
+ endCol = col;
81
+ }
82
+ }
83
+ if (startCol === -1 || seenIds.has(entry.id)) continue;
84
+ seenIds.add(entry.id);
85
+ const info = {
86
+ id: entry.id,
87
+ label: entry.label,
88
+ color: entry.meta?.color ?? "#6366f1"
89
+ };
90
+ if (isMulti) {
91
+ multiDay.push({ ...info, startCol, endCol, globalStart: start });
92
+ } else {
93
+ singleDay.push({ ...info, col: startCol });
94
+ }
95
+ }
96
+ multiDay.sort(
97
+ (a, b) => a.startCol - b.startCol || b.endCol - b.startCol - (a.endCol - a.startCol) || a.globalStart.localeCompare(b.globalStart)
98
+ );
99
+ const lanes = [];
100
+ const assigned = [];
101
+ for (const evt of multiDay) {
102
+ let lane = lanes.findIndex((lastEnd) => lastEnd < evt.startCol);
103
+ if (lane === -1) {
104
+ lane = lanes.length;
105
+ lanes.push(-1);
106
+ }
107
+ lanes[lane] = evt.endCol;
108
+ assigned.push({ ...evt, lane });
109
+ }
110
+ const laneCount = lanes.length;
111
+ const cells = weekRaw.map((r, col) => {
112
+ const laneSlots = [];
113
+ for (let l = 0; l < laneCount; l++) {
114
+ const span = assigned.find((s) => s.lane === l && col >= s.startCol && col <= s.endCol);
115
+ if (span) {
116
+ laneSlots.push({
117
+ type: "span",
118
+ id: span.id,
119
+ label: span.label,
120
+ color: span.color,
121
+ isStart: col === span.startCol,
122
+ isEnd: col === span.endCol
123
+ });
124
+ } else {
125
+ laneSlots.push({ type: "empty" });
126
+ }
127
+ }
128
+ const events = singleDay.filter((e) => e.col === col).map((e) => ({ id: e.id, label: e.label, color: e.color }));
129
+ return { ...r, laneSlots, events };
130
+ });
131
+ result.push({ cells, laneCount });
54
132
  }
133
+ return result;
134
+ });
135
+ const unscheduledEvents = computed(
136
+ () => tree.childrenOf(null).filter((e) => !e.meta?.datetimeStart)
55
137
  );
56
- function isToday(day) {
57
- return day === today.getDate() && viewMonth.value === today.getMonth() && viewYear.value === today.getFullYear();
58
- }
59
- function addEvent(day) {
60
- const dateStr = `${viewYear.value}-${String(viewMonth.value + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}T09:00`;
138
+ function createEvent(date) {
139
+ if (!props.editable) return;
140
+ const d = date ?? /* @__PURE__ */ new Date();
141
+ const dateStr = d.toISOString().slice(0, 10);
61
142
  const id = tree.createChild(null, locale.value.newEvent);
62
- const entry = tree.treeMap.get(id);
63
- if (entry) {
64
- tree.treeMap.set(id, {
65
- ...entry,
66
- meta: { datetimeStart: dateStr, datetimeEnd: dateStr },
67
- updatedAt: Date.now()
143
+ tree.updateMeta(id, {
144
+ datetimeStart: `${dateStr}T00:00:00.000Z`,
145
+ datetimeEnd: `${dateStr}T01:00:00.000Z`,
146
+ allDay: true,
147
+ color: "#6366f1"
148
+ });
149
+ openNode(id, locale.value.newEvent);
150
+ }
151
+ const { dragId, dragOverContainer, handlePointerDown } = useTouchDrag({
152
+ onMoveToContainer: (eventId, dateStr) => {
153
+ if (!props.editable) return;
154
+ const entry = tree.entries.value.find((e) => e.id === eventId);
155
+ let newEnd = `${dateStr}T01:00:00.000Z`;
156
+ if (entry?.meta?.datetimeStart && entry?.meta?.datetimeEnd) {
157
+ const duration = new Date(entry.meta.datetimeEnd).getTime() - new Date(entry.meta.datetimeStart).getTime();
158
+ newEnd = new Date((/* @__PURE__ */ new Date(`${dateStr}T00:00:00.000Z`)).getTime() + duration).toISOString();
159
+ }
160
+ tree.updateMeta(eventId, {
161
+ datetimeStart: `${dateStr}T00:00:00.000Z`,
162
+ datetimeEnd: newEnd,
163
+ allDay: entry?.meta?.allDay ?? true,
164
+ color: entry?.meta?.color ?? "#6366f1"
68
165
  });
69
- }
166
+ },
167
+ containerAttr: "data-drop-container"
168
+ });
169
+ function focusEvent(eventId) {
170
+ setLocalState({ "calendar:focused": eventId });
171
+ }
172
+ function unfocusEvent() {
173
+ setLocalState({ "calendar:focused": null });
70
174
  }
71
- const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
175
+ watch(currentDate, () => {
176
+ setLocalState({ "calendar:viewing": monthLabel.value });
177
+ }, { immediate: true });
178
+ function eventFocusers(eventId) {
179
+ return states.value.filter(
180
+ (s) => s.clientId !== myClientId.value && s["calendar:focused"] === eventId
181
+ );
182
+ }
183
+ const remoteViewers = computed(
184
+ () => states.value.filter((s) => s.clientId !== myClientId.value && s["calendar:viewing"]).map((s) => ({
185
+ clientId: s.clientId,
186
+ name: s.user?.name ?? "",
187
+ color: s.user?.color ?? "#888",
188
+ viewing: s["calendar:viewing"]
189
+ }))
190
+ );
191
+ watch(childDoc, (doc) => {
192
+ if (!doc) return;
193
+ const legacyMap = doc.getMap("event-dates");
194
+ if (legacyMap.size === 0) return;
195
+ legacyMap.forEach((val, id) => {
196
+ if (!tree.treeMap.get(id)?.meta?.datetimeStart) {
197
+ tree.updateMeta(id, {
198
+ datetimeStart: val.start,
199
+ datetimeEnd: val.end,
200
+ allDay: val.allDay,
201
+ color: val.color
202
+ });
203
+ }
204
+ });
205
+ doc.transact(() => {
206
+ for (const key of [...legacyMap.keys()]) legacyMap.delete(key);
207
+ });
208
+ }, { immediate: true });
209
+ defineShortcuts({
210
+ arrowleft: prevMonth,
211
+ arrowright: nextMonth,
212
+ t: goToday
213
+ });
214
+ onBeforeUnmount(() => {
215
+ setLocalState({ "calendar:focused": null, "calendar:viewing": null });
216
+ });
217
+ defineExpose({ connectedUsers });
72
218
  </script>
73
219
 
74
220
  <template>
75
- <div class="p-4 max-w-3xl">
76
- <!-- Navigation -->
77
- <div class="flex items-center gap-4 mb-4">
78
- <UButton
79
- icon="i-lucide-chevron-left"
80
- variant="ghost"
81
- color="neutral"
82
- @click="prevMonth"
83
- />
84
- <h2 class="text-lg font-semibold flex-1 text-center">{{ monthName }}</h2>
85
- <UButton
86
- icon="i-lucide-chevron-right"
87
- variant="ghost"
88
- color="neutral"
89
- @click="nextMonth"
90
- />
91
- <UButton
92
- icon="i-lucide-today"
93
- variant="soft"
94
- color="neutral"
95
- size="sm"
96
- :label="locale.today"
97
- @click="() => {
98
- viewYear = today.getFullYear();
99
- viewMonth = today.getMonth();
100
- }"
101
- />
102
- </div>
221
+ <div class="flex-1 min-h-0 flex flex-col relative">
222
+ <!-- Header -->
223
+ <div class="flex items-center justify-between px-4 py-2 border-b border-(--ui-border) shrink-0">
224
+ <div class="flex items-center gap-1">
225
+ <UTooltip
226
+ text="Previous month"
227
+ :kbds="['\u2190']"
228
+ :content="{ side: 'bottom' }"
229
+ >
230
+ <UButton
231
+ icon="i-lucide-chevron-left"
232
+ size="xs"
233
+ variant="ghost"
234
+ color="neutral"
235
+ @click="prevMonth"
236
+ />
237
+ </UTooltip>
238
+ <button
239
+ class="text-sm font-medium w-44 text-center hover:text-(--ui-primary) transition-colors"
240
+ @click="goToday"
241
+ >
242
+ {{ monthLabel }}
243
+ </button>
244
+ <UTooltip
245
+ text="Next month"
246
+ :kbds="['\u2192']"
247
+ :content="{ side: 'bottom' }"
248
+ >
249
+ <UButton
250
+ icon="i-lucide-chevron-right"
251
+ size="xs"
252
+ variant="ghost"
253
+ color="neutral"
254
+ @click="nextMonth"
255
+ />
256
+ </UTooltip>
257
+ </div>
103
258
 
104
- <!-- Day headers -->
105
- <div class="grid grid-cols-7 gap-px mb-1">
106
- <div
107
- v-for="name in dayNames"
108
- :key="name"
109
- class="text-center text-xs font-medium text-muted py-1"
110
- >
111
- {{ name }}
259
+ <!-- Remote viewers + new event -->
260
+ <div class="flex items-center gap-1.5">
261
+ <span
262
+ v-for="rv in remoteViewers"
263
+ :key="rv.clientId"
264
+ class="text-[10px] px-1.5 py-0.5 rounded-full border"
265
+ :style="{ borderColor: rv.color, color: rv.color }"
266
+ :title="`${rv.name} is viewing ${rv.viewing}`"
267
+ >
268
+ {{ rv.name?.split(" ")[0] || "?" }} &middot; {{ rv.viewing }}
269
+ </span>
270
+ <UButton
271
+ v-if="editable"
272
+ icon="i-lucide-plus"
273
+ size="xs"
274
+ variant="ghost"
275
+ color="neutral"
276
+ :label="locale.newEvent"
277
+ @click="createEvent()"
278
+ />
112
279
  </div>
113
280
  </div>
114
281
 
115
- <!-- Calendar grid -->
116
- <div class="grid grid-cols-7 gap-px bg-muted rounded-lg overflow-hidden">
117
- <div
118
- v-for="(cell, idx) in calendarDays"
119
- :key="idx"
120
- class="bg-elevated min-h-[80px] p-1 text-sm"
121
- :class="cell.day && isToday(cell.day) ? 'bg-primary/5' : ''"
122
- >
123
- <template v-if="cell.day !== null">
282
+ <!-- Calendar body -->
283
+ <div class="flex-1 min-h-0 flex flex-col p-4">
284
+ <!-- Day headers -->
285
+ <div class="grid grid-cols-7 shrink-0">
286
+ <div
287
+ v-for="day in WEEK_DAYS"
288
+ :key="day"
289
+ class="text-center text-xs font-medium text-(--ui-text-muted) py-1"
290
+ >
291
+ {{ day }}
292
+ </div>
293
+ </div>
294
+
295
+ <!-- Week rows -->
296
+ <div class="flex-1 min-h-0 flex flex-col">
297
+ <div
298
+ v-for="(week, wi) in weeks"
299
+ :key="wi"
300
+ class="flex-1 min-h-0 grid grid-cols-7"
301
+ >
124
302
  <div
125
- class="flex items-center justify-between"
303
+ v-for="(cell, ci) in week.cells"
304
+ :key="ci"
305
+ class="min-h-0 overflow-hidden bg-(--ui-bg-elevated) border-b border-r border-(--ui-border)/30 p-1 transition-colors"
306
+ :class="{
307
+ 'invisible pointer-events-none': !cell.date,
308
+ 'ring-2 ring-(--ui-primary)/40 bg-(--ui-primary)/5': cell.dateStr && dragOverContainer === cell.dateStr,
309
+ 'border-l': ci === 0,
310
+ 'border-t': wi === 0,
311
+ 'rounded-tl-md': wi === 0 && ci === 0,
312
+ 'rounded-tr-md': wi === 0 && ci === 6,
313
+ 'rounded-bl-md': wi === weeks.length - 1 && ci === 0,
314
+ 'rounded-br-md': wi === weeks.length - 1 && ci === 6
315
+ }"
316
+ :data-drop-container="cell.dateStr ?? void 0"
126
317
  >
127
- <span
128
- class="text-xs font-medium"
129
- :class="isToday(cell.day) ? 'text-primary font-bold' : 'text-muted'"
130
- >{{ cell.day }}</span>
131
- <UButton
132
- icon="i-lucide-plus"
133
- variant="ghost"
134
- color="neutral"
135
- size="xs"
136
- class="opacity-0 hover:opacity-100 h-4 w-4"
137
- @click="addEvent(cell.day)"
138
- />
139
- </div>
140
- <div class="space-y-0.5 mt-0.5">
141
- <div
142
- v-for="ev in eventsOnDay(viewYear, viewMonth, cell.day)"
143
- :key="ev.id"
144
- class="text-xs px-1 py-0.5 rounded truncate"
145
- :style="ev.meta?.color ? { backgroundColor: ev.meta.color + '33', color: ev.meta.color } : { backgroundColor: 'var(--color-primary-100)', color: 'var(--color-primary-700)' }"
146
- >
147
- {{ ev.label }}
148
- </div>
318
+ <template v-if="cell.date">
319
+ <!-- Date number -->
320
+ <button
321
+ class="text-xs font-medium w-6 h-6 rounded-full flex items-center justify-center mb-0.5 transition-colors"
322
+ :class="cell.isToday ? 'bg-(--ui-primary) text-white' : editable ? 'text-(--ui-text) hover:bg-(--ui-bg)' : 'text-(--ui-text) cursor-default'"
323
+ @click="editable ? createEvent(cell.date) : void 0"
324
+ >
325
+ {{ cell.date.getDate() }}
326
+ </button>
327
+
328
+ <!-- Multi-day span bar slots -->
329
+ <div
330
+ v-for="(slot, li) in cell.laneSlots"
331
+ :key="'lane-' + li"
332
+ class="h-[22px] flex items-center -mx-1"
333
+ >
334
+ <button
335
+ v-if="slot.type === 'span'"
336
+ class="w-full h-[18px] text-[10px] text-white truncate flex items-center leading-none cursor-pointer hover:brightness-110 transition-[filter]"
337
+ :class="[
338
+ slot.isStart ? 'rounded-l-sm ml-1 pl-1.5' : 'pl-0.5',
339
+ slot.isEnd ? 'rounded-r-sm mr-1' : '',
340
+ { 'opacity-40': dragId === slot.id }
341
+ ]"
342
+ :style="{
343
+ background: slot.color,
344
+ ...eventFocusers(slot.id).length > 0 ? { outline: `2px solid ${eventFocusers(slot.id)[0].user?.color ?? '#888'}`, outlineOffset: '1px' } : {},
345
+ ...editable ? { touchAction: 'none' } : {}
346
+ }"
347
+ :data-drag-id="editable ? slot.id : void 0"
348
+ @pointerdown="editable ? handlePointerDown($event, slot.id) : void 0"
349
+ @pointerenter="focusEvent(slot.id)"
350
+ @pointerleave="unfocusEvent"
351
+ @click.stop="openNode(slot.id, slot.label)"
352
+ >
353
+ <span
354
+ v-if="slot.isStart"
355
+ class="truncate"
356
+ >{{ slot.label }}</span>
357
+ </button>
358
+ </div>
359
+
360
+ <!-- Single-day events -->
361
+ <div
362
+ class="space-y-0.5"
363
+ :class="{ 'mt-0.5': cell.laneSlots.length > 0 }"
364
+ >
365
+ <button
366
+ v-for="evt in cell.events.slice(0, 3)"
367
+ :key="evt.id"
368
+ class="w-full text-left text-[10px] px-1.5 py-0.5 rounded truncate text-white leading-tight"
369
+ :class="{ 'opacity-40': dragId === evt.id }"
370
+ :style="{
371
+ background: evt.color,
372
+ ...eventFocusers(evt.id).length > 0 ? { outline: `2px solid ${eventFocusers(evt.id)[0].user?.color ?? '#888'}`, outlineOffset: '1px' } : {},
373
+ ...editable ? { touchAction: 'none' } : {}
374
+ }"
375
+ :data-drag-id="editable ? evt.id : void 0"
376
+ @pointerdown="editable ? handlePointerDown($event, evt.id) : void 0"
377
+ @pointerenter="focusEvent(evt.id)"
378
+ @pointerleave="unfocusEvent"
379
+ @click.stop="openNode(evt.id, evt.label)"
380
+ >
381
+ {{ evt.label }}
382
+ </button>
383
+ <span
384
+ v-if="cell.events.length > 3"
385
+ class="block text-[10px] text-(--ui-text-dimmed) px-1"
386
+ >
387
+ +{{ cell.events.length - 3 }} more
388
+ </span>
389
+ </div>
390
+ </template>
149
391
  </div>
150
- </template>
392
+ </div>
393
+ </div>
394
+
395
+ <!-- Unscheduled events -->
396
+ <div
397
+ v-if="unscheduledEvents.length > 0"
398
+ class="mt-4 shrink-0"
399
+ >
400
+ <p class="text-xs font-medium text-(--ui-text-muted) uppercase tracking-wide mb-2">
401
+ Unscheduled
402
+ </p>
403
+ <div class="flex flex-wrap gap-1.5">
404
+ <button
405
+ v-for="evt in unscheduledEvents"
406
+ :key="evt.id"
407
+ class="text-xs px-2 py-1 rounded border border-(--ui-border) hover:border-(--ui-primary) transition-colors"
408
+ :class="{ 'opacity-40': dragId === evt.id }"
409
+ :style="editable ? { touchAction: 'none' } : {}"
410
+ :data-drag-id="editable ? evt.id : void 0"
411
+ @pointerdown="editable ? handlePointerDown($event, evt.id) : void 0"
412
+ @click="openNode(evt.id, evt.label)"
413
+ >
414
+ {{ evt.label }}
415
+ </button>
416
+ </div>
151
417
  </div>
152
418
  </div>
419
+
420
+ <!-- Node panel -->
421
+ <ANodePanel
422
+ :node-id="openNodeId"
423
+ :node-label="openNodeLabel"
424
+ :child-provider="openNodeProvider"
425
+ @close="closePanel"
426
+ />
153
427
  </div>
154
428
  </template>
@@ -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']['calendar']>;
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,13 @@
1
+ import type { AbracadabraLocale } from '../../locale.js';
2
+ type __VLS_Props = {
3
+ docId: string;
4
+ childProvider?: any;
5
+ docLabel: string;
6
+ labels?: Partial<AbracadabraLocale['renderers']['call']>;
7
+ editable?: boolean;
8
+ };
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
10
+ editable: boolean;
11
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;