@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.
- package/dist/module.d.mts +46 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +95 -2
- package/dist/runtime/assets/editor.css +1 -0
- package/dist/runtime/components/ACommandPalette.vue +4 -1
- package/dist/runtime/components/ADocRenderer.d.vue.ts +29 -0
- package/dist/runtime/components/ADocRenderer.vue +99 -0
- package/dist/runtime/components/ADocRenderer.vue.d.ts +29 -0
- package/dist/runtime/components/ADocTypeSelect.vue +4 -1
- package/dist/runtime/components/ADocumentTree.vue +78 -19
- package/dist/runtime/components/AEditor.d.vue.ts +9 -4
- package/dist/runtime/components/AEditor.vue +102 -7
- package/dist/runtime/components/AEditor.vue.d.ts +9 -4
- package/dist/runtime/components/AIconPicker.vue +8 -2
- package/dist/runtime/components/ANodePanel.vue +100 -61
- package/dist/runtime/components/ANotifications.vue +35 -8
- package/dist/runtime/components/APermissionGuard.vue +3 -1
- package/dist/runtime/components/APresence.vue +14 -3
- package/dist/runtime/components/AProvider.vue +7 -1
- package/dist/runtime/components/AVoiceBar.vue +57 -15
- package/dist/runtime/components/AVoiceTile.vue +4 -1
- package/dist/runtime/components/aware/AArea.vue +1 -1
- package/dist/runtime/components/aware/AAvatar.vue +85 -16
- package/dist/runtime/components/aware/AButton.vue +5 -1
- package/dist/runtime/components/aware/ACursorLabel.vue +5 -1
- package/dist/runtime/components/aware/ADocBadge.vue +4 -1
- package/dist/runtime/components/aware/AFacepile.vue +13 -3
- package/dist/runtime/components/aware/AInput.vue +5 -1
- package/dist/runtime/components/aware/ATextarea.vue +5 -1
- package/dist/runtime/components/aware/AUserList.vue +8 -2
- package/dist/runtime/components/renderers/ACalendarRenderer.d.vue.ts +12 -1
- package/dist/runtime/components/renderers/ACalendarRenderer.vue +388 -114
- package/dist/runtime/components/renderers/ACalendarRenderer.vue.d.ts +12 -1
- package/dist/runtime/components/renderers/ACallRenderer.d.vue.ts +13 -0
- package/dist/runtime/components/renderers/ACallRenderer.vue +169 -0
- package/dist/runtime/components/renderers/ACallRenderer.vue.d.ts +13 -0
- package/dist/runtime/components/renderers/AChecklistRenderer.d.vue.ts +19 -0
- package/dist/runtime/components/renderers/AChecklistRenderer.vue +581 -0
- package/dist/runtime/components/renderers/AChecklistRenderer.vue.d.ts +19 -0
- package/dist/runtime/components/renderers/ADashboardRenderer.d.vue.ts +19 -0
- package/dist/runtime/components/renderers/ADashboardRenderer.vue +1372 -0
- package/dist/runtime/components/renderers/ADashboardRenderer.vue.d.ts +19 -0
- package/dist/runtime/components/renderers/AGalleryCoverImage.d.vue.ts +8 -0
- package/dist/runtime/components/renderers/AGalleryCoverImage.vue +60 -0
- package/dist/runtime/components/renderers/AGalleryCoverImage.vue.d.ts +8 -0
- package/dist/runtime/components/renderers/AGalleryRenderer.d.vue.ts +12 -1
- package/dist/runtime/components/renderers/AGalleryRenderer.vue +221 -55
- package/dist/runtime/components/renderers/AGalleryRenderer.vue.d.ts +12 -1
- package/dist/runtime/components/renderers/AGraphRenderer.d.vue.ts +19 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue +1027 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue.d.ts +19 -0
- package/dist/runtime/components/renderers/AKanbanRenderer.d.vue.ts +13 -1
- package/dist/runtime/components/renderers/AKanbanRenderer.vue +474 -140
- package/dist/runtime/components/renderers/AKanbanRenderer.vue.d.ts +13 -1
- package/dist/runtime/components/renderers/AMapRenderer.d.vue.ts +19 -0
- package/dist/runtime/components/renderers/AMapRenderer.vue +1622 -0
- package/dist/runtime/components/renderers/AMapRenderer.vue.d.ts +19 -0
- package/dist/runtime/components/renderers/AOutlineRenderer.d.vue.ts +12 -1
- package/dist/runtime/components/renderers/AOutlineRenderer.vue +294 -134
- package/dist/runtime/components/renderers/AOutlineRenderer.vue.d.ts +12 -1
- package/dist/runtime/components/renderers/ATableRenderer.d.vue.ts +12 -1
- package/dist/runtime/components/renderers/ATableRenderer.vue +437 -145
- package/dist/runtime/components/renderers/ATableRenderer.vue.d.ts +12 -1
- package/dist/runtime/components/renderers/ATimelineRenderer.d.vue.ts +19 -0
- package/dist/runtime/components/renderers/ATimelineRenderer.vue +446 -0
- package/dist/runtime/components/renderers/ATimelineRenderer.vue.d.ts +19 -0
- package/dist/runtime/composables/useAwareness.js +5 -0
- package/dist/runtime/composables/useBroadcastSync.d.ts +18 -0
- package/dist/runtime/composables/useBroadcastSync.js +26 -0
- package/dist/runtime/composables/useChat.js +4 -2
- package/dist/runtime/composables/useChatUsers.js +2 -1
- package/dist/runtime/composables/useCommandPalette.js +62 -3
- package/dist/runtime/composables/useConnectionStatus.js +7 -0
- package/dist/runtime/composables/useDevicePairing.d.ts +58 -0
- package/dist/runtime/composables/useDevicePairing.js +108 -0
- package/dist/runtime/composables/useDocExport.d.ts +5 -0
- package/dist/runtime/composables/useDocExport.js +2 -2
- package/dist/runtime/composables/useDocImport.js +4 -3
- package/dist/runtime/composables/useDocSeo.d.ts +20 -0
- package/dist/runtime/composables/useDocSeo.js +44 -0
- package/dist/runtime/composables/useDocSlugs.d.ts +7 -0
- package/dist/runtime/composables/useDocSlugs.js +20 -0
- package/dist/runtime/composables/useDocTree.d.ts +34 -0
- package/dist/runtime/composables/useDocTree.js +35 -0
- package/dist/runtime/composables/useEditorDragHandle.js +2 -1
- package/dist/runtime/composables/useEditorMentions.js +4 -2
- package/dist/runtime/composables/useEditorSuggestions.d.ts +1 -0
- package/dist/runtime/composables/useEditorSuggestions.js +9 -2
- package/dist/runtime/composables/useEditorToolbar.js +2 -1
- package/dist/runtime/composables/useFileIndex.js +2 -1
- package/dist/runtime/composables/useFileTransfer.d.ts +112 -0
- package/dist/runtime/composables/useFileTransfer.js +171 -0
- package/dist/runtime/composables/useFollowUser.js +2 -1
- package/dist/runtime/composables/useInvites.d.ts +56 -0
- package/dist/runtime/composables/useInvites.js +77 -0
- package/dist/runtime/composables/useNodePanel.d.ts +14 -0
- package/dist/runtime/composables/useNodePanel.js +52 -0
- package/dist/runtime/composables/useNotifications.js +4 -2
- package/dist/runtime/composables/usePasskeyAccounts.js +4 -2
- package/dist/runtime/composables/useSearchIndex.d.ts +1 -0
- package/dist/runtime/composables/useSearchIndex.js +13 -5
- package/dist/runtime/composables/useServerInfo.d.ts +31 -0
- package/dist/runtime/composables/useServerInfo.js +80 -0
- package/dist/runtime/composables/useSlugRoute.d.ts +6 -0
- package/dist/runtime/composables/useSlugRoute.js +19 -0
- package/dist/runtime/composables/useSpaces.d.ts +37 -0
- package/dist/runtime/composables/useSpaces.js +83 -0
- package/dist/runtime/composables/useTouchDrag.d.ts +34 -0
- package/dist/runtime/composables/useTouchDrag.js +191 -0
- package/dist/runtime/composables/useTrash.d.ts +1 -1
- package/dist/runtime/composables/useTrash.js +6 -3
- package/dist/runtime/composables/useWebRTC.d.ts +50 -0
- package/dist/runtime/composables/useWebRTC.js +177 -0
- package/dist/runtime/extensions/meta-field.d.ts +4 -1
- package/dist/runtime/extensions/steps.js +1 -1
- package/dist/runtime/extensions/views/AccordionItemView.vue +13 -3
- package/dist/runtime/extensions/views/AccordionView.vue +4 -1
- package/dist/runtime/extensions/views/BadgeView.vue +11 -2
- package/dist/runtime/extensions/views/CalloutView.vue +4 -1
- package/dist/runtime/extensions/views/CardGroupView.vue +4 -1
- package/dist/runtime/extensions/views/CardView.vue +17 -3
- package/dist/runtime/extensions/views/CodeGroupView.vue +4 -1
- package/dist/runtime/extensions/views/CollapsibleView.vue +8 -2
- package/dist/runtime/extensions/views/FileNodeView.vue +32 -8
- package/dist/runtime/extensions/views/KbdView.vue +8 -2
- package/dist/runtime/extensions/views/MetaFieldView.vue +208 -46
- package/dist/runtime/extensions/views/ProseIconView.vue +8 -2
- package/dist/runtime/extensions/views/TabsView.vue +17 -4
- package/dist/runtime/locale.d.ts +71 -0
- package/dist/runtime/locale.js +71 -0
- package/dist/runtime/plugin-abracadabra.client.js +29 -3
- package/dist/runtime/plugin-abracadabra.server.js +2 -0
- package/dist/runtime/server/api/_abracadabra/render/[docId].get.d.ts +1 -1
- package/dist/runtime/server/api/_abracadabra/render/[docId].get.js +29 -4
- package/dist/runtime/server/api/_abracadabra/resolve/[...slug].get.d.ts +2 -0
- package/dist/runtime/server/api/_abracadabra/resolve/[...slug].get.js +43 -0
- package/dist/runtime/server/api/_abracadabra/slugs.get.d.ts +2 -0
- package/dist/runtime/server/api/_abracadabra/slugs.get.js +7 -0
- package/dist/runtime/server/plugins/abracadabra-service.js +10 -5
- package/dist/runtime/server/runners/doc-tree-cache.js +4 -0
- package/dist/runtime/server/utils/slugMap.d.ts +32 -0
- package/dist/runtime/server/utils/slugMap.js +58 -0
- package/dist/runtime/types.d.ts +1 -0
- package/dist/runtime/utils/docTypes.d.ts +29 -1
- package/dist/runtime/utils/docTypes.js +129 -1
- package/dist/runtime/utils/markdownToYjs.js +2 -2
- package/dist/runtime/utils/sdkRef.d.ts +2 -0
- package/dist/runtime/utils/sdkRef.js +7 -0
- package/dist/runtime/utils/slugify.d.ts +40 -0
- package/dist/runtime/utils/slugify.js +36 -0
- package/dist/types.d.mts +6 -0
- package/package.json +32 -19
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ref, computed } from "vue";
|
|
2
|
+
const loading = ref(false);
|
|
3
|
+
const error = ref(null);
|
|
4
|
+
export function useSpaces() {
|
|
5
|
+
const abra = useAbracadabra();
|
|
6
|
+
const spaces = computed(() => abra.currentServerSpaces.value ?? []);
|
|
7
|
+
const spacesEnabled = computed(() => abra.currentServerSpacesEnabled.value ?? false);
|
|
8
|
+
const currentSpace = computed(() => {
|
|
9
|
+
const route = useRoute();
|
|
10
|
+
const docId = route.params.docId;
|
|
11
|
+
if (!docId) return spaces.value[0] ?? null;
|
|
12
|
+
return spaces.value.find((s) => s.doc_id === docId) ?? null;
|
|
13
|
+
});
|
|
14
|
+
async function refresh() {
|
|
15
|
+
loading.value = true;
|
|
16
|
+
error.value = null;
|
|
17
|
+
try {
|
|
18
|
+
await abra.refreshSpaces();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
error.value = e.message ?? "Failed to refresh spaces";
|
|
21
|
+
} finally {
|
|
22
|
+
loading.value = false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function create(opts) {
|
|
26
|
+
error.value = null;
|
|
27
|
+
try {
|
|
28
|
+
return await abra.createSpace(opts);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
error.value = e.message ?? "Failed to create space";
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function update(id, opts) {
|
|
35
|
+
error.value = null;
|
|
36
|
+
try {
|
|
37
|
+
return await abra.updateSpace(id, opts);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
error.value = e.message ?? "Failed to update space";
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function remove(id) {
|
|
44
|
+
error.value = null;
|
|
45
|
+
try {
|
|
46
|
+
await abra.deleteSpace(id);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
error.value = e.message ?? "Failed to delete space";
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function switchTo(spaceDocId) {
|
|
53
|
+
error.value = null;
|
|
54
|
+
try {
|
|
55
|
+
await abra.switchSpace(abra.currentServerUrl.value, spaceDocId);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
error.value = e.message ?? "Failed to switch space";
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
/** All spaces on the current server. */
|
|
63
|
+
spaces,
|
|
64
|
+
/** Whether the current server supports spaces. */
|
|
65
|
+
spacesEnabled,
|
|
66
|
+
/** The currently active space. */
|
|
67
|
+
currentSpace,
|
|
68
|
+
/** Whether space operations are in progress. */
|
|
69
|
+
loading,
|
|
70
|
+
/** Last error message. */
|
|
71
|
+
error,
|
|
72
|
+
/** Refresh spaces list from server. */
|
|
73
|
+
refresh,
|
|
74
|
+
/** Create a new space. */
|
|
75
|
+
create,
|
|
76
|
+
/** Update an existing space. */
|
|
77
|
+
update,
|
|
78
|
+
/** Delete a space. */
|
|
79
|
+
remove,
|
|
80
|
+
/** Switch to a different space. */
|
|
81
|
+
switchTo
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile-compatible drag & drop using pointer events.
|
|
3
|
+
* Works on both touch and mouse devices, replacing HTML5 DragEvent handlers.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* 1. Add `data-drag-id="itemId"` to every draggable element
|
|
7
|
+
* 2. Optionally add `data-drop-container="containerId"` for cross-container moves
|
|
8
|
+
* 3. Call `handlePointerDown(e, id)` on `@pointerdown` of draggable items
|
|
9
|
+
* 4. Read `dragId` / `dragOverId` / `dragOverContainer` for styling
|
|
10
|
+
* 5. Provide `onDrop` / `onMoveToContainer` callbacks for the actual data mutations
|
|
11
|
+
*/
|
|
12
|
+
export declare function useTouchDrag(opts: {
|
|
13
|
+
/** Called when an item is dropped on another item (reorder) */
|
|
14
|
+
onDrop?: (dragId: string, targetId: string) => void;
|
|
15
|
+
/** Called when an item is dropped on a container (cross-container move) */
|
|
16
|
+
onMoveToContainer?: (dragId: string, containerId: string, targetId: string | null) => void;
|
|
17
|
+
/** Called when drag starts */
|
|
18
|
+
onDragStart?: (id: string) => void;
|
|
19
|
+
/** Called when drag ends (always, even if no drop) */
|
|
20
|
+
onDragEnd?: () => void;
|
|
21
|
+
/** Long-press delay in ms before touch drag activates (default: 200) */
|
|
22
|
+
longPressMs?: number;
|
|
23
|
+
/** Data attribute name for item IDs (default: 'data-drag-id') */
|
|
24
|
+
idAttr?: string;
|
|
25
|
+
/** Data attribute name for containers (default: 'data-drop-container') */
|
|
26
|
+
containerAttr?: string;
|
|
27
|
+
}): {
|
|
28
|
+
dragId: import("vue").Ref<string | null, string | null>;
|
|
29
|
+
dragOverId: import("vue").Ref<string | null, string | null>;
|
|
30
|
+
dragOverContainer: import("vue").Ref<string | null, string | null>;
|
|
31
|
+
isDragging: import("vue").Ref<boolean, boolean>;
|
|
32
|
+
handlePointerDown: (e: PointerEvent, id: string) => void;
|
|
33
|
+
cancel: () => void;
|
|
34
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { ref, onUnmounted } from "vue";
|
|
2
|
+
export function useTouchDrag(opts) {
|
|
3
|
+
const idAttr = opts.idAttr ?? "data-drag-id";
|
|
4
|
+
const containerAttr = opts.containerAttr ?? "data-drop-container";
|
|
5
|
+
const longPressMs = opts.longPressMs ?? 200;
|
|
6
|
+
const dragId = ref(null);
|
|
7
|
+
const dragOverId = ref(null);
|
|
8
|
+
const dragOverContainer = ref(null);
|
|
9
|
+
const isDragging = ref(false);
|
|
10
|
+
let startX = 0;
|
|
11
|
+
let startY = 0;
|
|
12
|
+
let delayTimer = null;
|
|
13
|
+
let ghostEl = null;
|
|
14
|
+
let sourceEl = null;
|
|
15
|
+
let pendingId = null;
|
|
16
|
+
let ghostOffsetX = 0;
|
|
17
|
+
let ghostOffsetY = 0;
|
|
18
|
+
let isTouch = false;
|
|
19
|
+
function createGhost(el, clientX, clientY) {
|
|
20
|
+
const rect = el.getBoundingClientRect();
|
|
21
|
+
ghostOffsetX = clientX - rect.left;
|
|
22
|
+
ghostOffsetY = clientY - rect.top;
|
|
23
|
+
const ghost = el.cloneNode(true);
|
|
24
|
+
ghost.style.cssText = `
|
|
25
|
+
position: fixed;
|
|
26
|
+
left: ${rect.left}px;
|
|
27
|
+
top: ${rect.top}px;
|
|
28
|
+
width: ${rect.width}px;
|
|
29
|
+
height: auto;
|
|
30
|
+
opacity: 0.85;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
z-index: 9999;
|
|
33
|
+
transition: none;
|
|
34
|
+
transform: scale(1.03);
|
|
35
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
|
|
36
|
+
border-radius: 8px;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
`;
|
|
39
|
+
ghost.querySelectorAll("button, input, textarea, [data-state]").forEach((c) => c.remove());
|
|
40
|
+
document.body.appendChild(ghost);
|
|
41
|
+
return ghost;
|
|
42
|
+
}
|
|
43
|
+
function findTarget(x, y) {
|
|
44
|
+
if (ghostEl) ghostEl.style.display = "none";
|
|
45
|
+
const el = document.elementFromPoint(x, y);
|
|
46
|
+
if (ghostEl) ghostEl.style.display = "";
|
|
47
|
+
if (!el) return { id: null, container: null };
|
|
48
|
+
const dragTarget = el.closest?.(`[${idAttr}]`);
|
|
49
|
+
const containerTarget = el.closest?.(`[${containerAttr}]`);
|
|
50
|
+
return {
|
|
51
|
+
id: dragTarget?.getAttribute(idAttr) ?? null,
|
|
52
|
+
container: containerTarget?.getAttribute(containerAttr) ?? null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function activateDrag(id, el, clientX, clientY) {
|
|
56
|
+
dragId.value = id;
|
|
57
|
+
isDragging.value = true;
|
|
58
|
+
ghostEl = createGhost(el, clientX, clientY);
|
|
59
|
+
el.style.opacity = "0.3";
|
|
60
|
+
sourceEl = el;
|
|
61
|
+
opts.onDragStart?.(id);
|
|
62
|
+
document.addEventListener("pointermove", onDocPointerMove, { passive: false });
|
|
63
|
+
document.addEventListener("pointerup", onDocPointerUp);
|
|
64
|
+
document.addEventListener("pointercancel", onDocPointerUp);
|
|
65
|
+
document.body.style.userSelect = "none";
|
|
66
|
+
document.body.style.webkitUserSelect = "none";
|
|
67
|
+
}
|
|
68
|
+
function handlePointerDown(e, id) {
|
|
69
|
+
if (e.button !== 0) return;
|
|
70
|
+
const target = e.target;
|
|
71
|
+
const interactiveAncestor = target.closest('input, textarea, button, a, [contenteditable], [role="button"]');
|
|
72
|
+
if (interactiveAncestor && interactiveAncestor !== e.currentTarget) return;
|
|
73
|
+
isTouch = e.pointerType === "touch";
|
|
74
|
+
pendingId = id;
|
|
75
|
+
startX = e.clientX;
|
|
76
|
+
startY = e.clientY;
|
|
77
|
+
sourceEl = e.currentTarget;
|
|
78
|
+
if (isTouch) {
|
|
79
|
+
delayTimer = setTimeout(() => {
|
|
80
|
+
if (!pendingId || !sourceEl) return;
|
|
81
|
+
activateDrag(pendingId, sourceEl, startX, startY);
|
|
82
|
+
if (navigator.vibrate) navigator.vibrate(30);
|
|
83
|
+
}, longPressMs);
|
|
84
|
+
document.addEventListener("pointermove", onEarlyMove, { passive: true });
|
|
85
|
+
document.addEventListener("pointerup", onEarlyUp);
|
|
86
|
+
} else {
|
|
87
|
+
document.addEventListener("pointermove", onMouseEarlyMove, { passive: false });
|
|
88
|
+
document.addEventListener("pointerup", onEarlyUp);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function onEarlyMove(e) {
|
|
92
|
+
if (isDragging.value) return;
|
|
93
|
+
const dx = Math.abs(e.clientX - startX);
|
|
94
|
+
const dy = Math.abs(e.clientY - startY);
|
|
95
|
+
if (dx > 8 || dy > 8) cancelPending();
|
|
96
|
+
}
|
|
97
|
+
function onMouseEarlyMove(e) {
|
|
98
|
+
if (isDragging.value) return;
|
|
99
|
+
const dx = Math.abs(e.clientX - startX);
|
|
100
|
+
const dy = Math.abs(e.clientY - startY);
|
|
101
|
+
if (dx > 4 || dy > 4) {
|
|
102
|
+
document.removeEventListener("pointermove", onMouseEarlyMove);
|
|
103
|
+
document.removeEventListener("pointerup", onEarlyUp);
|
|
104
|
+
if (pendingId && sourceEl) {
|
|
105
|
+
activateDrag(pendingId, sourceEl, e.clientX, e.clientY);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function onEarlyUp() {
|
|
110
|
+
cancelPending();
|
|
111
|
+
}
|
|
112
|
+
function cancelPending() {
|
|
113
|
+
if (delayTimer) {
|
|
114
|
+
clearTimeout(delayTimer);
|
|
115
|
+
delayTimer = null;
|
|
116
|
+
}
|
|
117
|
+
pendingId = null;
|
|
118
|
+
document.removeEventListener("pointermove", onEarlyMove);
|
|
119
|
+
document.removeEventListener("pointermove", onMouseEarlyMove);
|
|
120
|
+
document.removeEventListener("pointerup", onEarlyUp);
|
|
121
|
+
}
|
|
122
|
+
function onDocPointerMove(e) {
|
|
123
|
+
if (!isDragging.value || !ghostEl) return;
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
ghostEl.style.left = `${e.clientX - ghostOffsetX}px`;
|
|
126
|
+
ghostEl.style.top = `${e.clientY - ghostOffsetY}px`;
|
|
127
|
+
const { id, container } = findTarget(e.clientX, e.clientY);
|
|
128
|
+
dragOverId.value = id && id !== dragId.value ? id : null;
|
|
129
|
+
dragOverContainer.value = container;
|
|
130
|
+
autoScroll(e.clientY);
|
|
131
|
+
}
|
|
132
|
+
function autoScroll(clientY) {
|
|
133
|
+
const threshold = 60;
|
|
134
|
+
const speed = 12;
|
|
135
|
+
if (clientY < threshold) {
|
|
136
|
+
window.scrollBy(0, -speed);
|
|
137
|
+
} else if (clientY > window.innerHeight - threshold) {
|
|
138
|
+
window.scrollBy(0, speed);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function onDocPointerUp() {
|
|
142
|
+
cancelPending();
|
|
143
|
+
document.removeEventListener("pointermove", onDocPointerMove);
|
|
144
|
+
document.removeEventListener("pointerup", onDocPointerUp);
|
|
145
|
+
document.removeEventListener("pointercancel", onDocPointerUp);
|
|
146
|
+
if (isDragging.value && dragId.value) {
|
|
147
|
+
const dId = dragId.value;
|
|
148
|
+
const overId = dragOverId.value;
|
|
149
|
+
const overContainer = dragOverContainer.value;
|
|
150
|
+
if (overId && overContainer && opts.onMoveToContainer) {
|
|
151
|
+
opts.onMoveToContainer(dId, overContainer, overId);
|
|
152
|
+
} else if (overId && opts.onDrop) {
|
|
153
|
+
opts.onDrop(dId, overId);
|
|
154
|
+
} else if (!overId && overContainer && opts.onMoveToContainer) {
|
|
155
|
+
opts.onMoveToContainer(dId, overContainer, null);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
cleanup();
|
|
159
|
+
opts.onDragEnd?.();
|
|
160
|
+
}
|
|
161
|
+
function cleanup() {
|
|
162
|
+
if (ghostEl) {
|
|
163
|
+
ghostEl.remove();
|
|
164
|
+
ghostEl = null;
|
|
165
|
+
}
|
|
166
|
+
if (sourceEl) {
|
|
167
|
+
sourceEl.style.opacity = "";
|
|
168
|
+
sourceEl = null;
|
|
169
|
+
}
|
|
170
|
+
dragId.value = null;
|
|
171
|
+
dragOverId.value = null;
|
|
172
|
+
dragOverContainer.value = null;
|
|
173
|
+
isDragging.value = false;
|
|
174
|
+
pendingId = null;
|
|
175
|
+
if (delayTimer) {
|
|
176
|
+
clearTimeout(delayTimer);
|
|
177
|
+
delayTimer = null;
|
|
178
|
+
}
|
|
179
|
+
document.body.style.userSelect = "";
|
|
180
|
+
document.body.style.webkitUserSelect = "";
|
|
181
|
+
}
|
|
182
|
+
onUnmounted(cleanup);
|
|
183
|
+
return {
|
|
184
|
+
dragId,
|
|
185
|
+
dragOverId,
|
|
186
|
+
dragOverContainer,
|
|
187
|
+
isDragging,
|
|
188
|
+
handlePointerDown,
|
|
189
|
+
cancel: cleanup
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* const { trashedItems, moveToTrash, restoreFromTrash, emptyTrash } = useTrash()
|
|
10
10
|
*/
|
|
11
11
|
import type { ShallowRef } from 'vue';
|
|
12
|
-
import * as Y from 'yjs';
|
|
12
|
+
import type * as Y from 'yjs';
|
|
13
13
|
export interface TrashEntry {
|
|
14
14
|
label: string;
|
|
15
15
|
parentId: string | null;
|
|
@@ -5,14 +5,16 @@ function loadTrashSettings() {
|
|
|
5
5
|
try {
|
|
6
6
|
const raw = localStorage.getItem(TRASH_SETTINGS_KEY);
|
|
7
7
|
if (raw) return JSON.parse(raw);
|
|
8
|
-
} catch {
|
|
8
|
+
} catch (e) {
|
|
9
|
+
if (import.meta.dev) console.debug("[abracadabra] trash: failed to load settings from localStorage:", e);
|
|
9
10
|
}
|
|
10
11
|
return { autoPurgeDays: 30 };
|
|
11
12
|
}
|
|
12
13
|
function saveTrashSettings(settings) {
|
|
13
14
|
try {
|
|
14
15
|
localStorage.setItem(TRASH_SETTINGS_KEY, JSON.stringify(settings));
|
|
15
|
-
} catch {
|
|
16
|
+
} catch (e) {
|
|
17
|
+
if (import.meta.dev) console.debug("[abracadabra] trash: failed to save settings to localStorage:", e);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
export function useTrash(rootDoc) {
|
|
@@ -109,7 +111,8 @@ export function useTrash(rootDoc) {
|
|
|
109
111
|
for (const db of dbs) {
|
|
110
112
|
if (db.name?.includes(docId)) indexedDB.deleteDatabase(db.name);
|
|
111
113
|
}
|
|
112
|
-
} catch {
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (import.meta.dev) console.debug("[abracadabra] trash: failed to cleanup IDB for doc:", e);
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
return {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useWebRTC
|
|
3
|
+
*
|
|
4
|
+
* Wraps AbracadabraWebRTC from @abraca/dabra for P2P Y.js sync, awareness,
|
|
5
|
+
* and file transfer over WebRTC data channels.
|
|
6
|
+
*
|
|
7
|
+
* Follows the same singleton pattern as useVoice — module-level state,
|
|
8
|
+
* _wireEvents for event→ref mapping, and user-driven connect/disconnect.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const { isConnected, peers, peerCount, connect, disconnect } = useWebRTC()
|
|
12
|
+
*/
|
|
13
|
+
export declare function _initWebRTC(rtc: any): void;
|
|
14
|
+
export declare function _destroyWebRTC(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Connect to WebRTC P2P mesh.
|
|
17
|
+
* Uses the current provider from useAbracadabra() to create an AbracadabraWebRTC instance.
|
|
18
|
+
* When E2EE is enabled and the user has a claimed passkey, triggers WebAuthn prompt
|
|
19
|
+
* to derive X25519 private key.
|
|
20
|
+
*/
|
|
21
|
+
declare function connect(options?: {
|
|
22
|
+
e2ee?: boolean;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
declare function disconnect(): void;
|
|
25
|
+
declare function sendFile(peerId: string, file: File | Blob, filename: string): Promise<any>;
|
|
26
|
+
export declare function useWebRTC(): {
|
|
27
|
+
/** Current WebRTC connection status. */
|
|
28
|
+
webrtcStatus: import("vue").Ref<"idle" | "disconnected" | "connecting" | "connected", "idle" | "disconnected" | "connecting" | "connected">;
|
|
29
|
+
/** Whether the WebRTC signaling connection is active. */
|
|
30
|
+
isConnected: import("vue").Ref<boolean, boolean>;
|
|
31
|
+
/** Map of connected peers (peerId → PeerState). */
|
|
32
|
+
peers: import("vue").ShallowRef<Map<string, any>, Map<string, any>>;
|
|
33
|
+
/** Local peer ID assigned by the signaling server. */
|
|
34
|
+
localPeerId: import("vue").Ref<string | null, string | null>;
|
|
35
|
+
/** Whether E2EE is enabled for this connection. */
|
|
36
|
+
e2eeEnabled: import("vue").Ref<boolean, boolean>;
|
|
37
|
+
/** Set of peer IDs with established E2EE channels. */
|
|
38
|
+
e2eeEstablishedPeers: import("vue").ShallowRef<Set<string>, Set<string>>;
|
|
39
|
+
/** Array of connected peers (computed from peers map). */
|
|
40
|
+
peerList: import("vue").ComputedRef<any[]>;
|
|
41
|
+
/** Number of connected peers (computed). */
|
|
42
|
+
peerCount: import("vue").ComputedRef<number>;
|
|
43
|
+
/** Connect to WebRTC P2P mesh using the current provider. */
|
|
44
|
+
connect: typeof connect;
|
|
45
|
+
/** Disconnect and clean up WebRTC resources. */
|
|
46
|
+
disconnect: typeof disconnect;
|
|
47
|
+
/** Send a file to a specific peer. */
|
|
48
|
+
sendFile: typeof sendFile;
|
|
49
|
+
};
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ref, shallowRef, computed } from "vue";
|
|
2
|
+
import { getSdkModule } from "../utils/sdkRef.js";
|
|
3
|
+
import { _wireFileTransferEvents, _destroyFileTransfer } from "./useFileTransfer.js";
|
|
4
|
+
const webrtcStatus = ref("idle");
|
|
5
|
+
const isConnected = ref(false);
|
|
6
|
+
const peers = shallowRef(/* @__PURE__ */ new Map());
|
|
7
|
+
const localPeerId = ref(null);
|
|
8
|
+
const e2eeEnabled = ref(false);
|
|
9
|
+
const e2eeEstablishedPeers = shallowRef(/* @__PURE__ */ new Set());
|
|
10
|
+
let _rtc = null;
|
|
11
|
+
const _unbinds = [];
|
|
12
|
+
const peerList = computed(() => [...peers.value.values()]);
|
|
13
|
+
const peerCount = computed(() => peers.value.size);
|
|
14
|
+
function _wireEvents(rtc) {
|
|
15
|
+
const on = (event, handler) => {
|
|
16
|
+
rtc.on(event, handler);
|
|
17
|
+
_unbinds.push(() => rtc.off(event, handler));
|
|
18
|
+
};
|
|
19
|
+
on("connected", () => {
|
|
20
|
+
isConnected.value = true;
|
|
21
|
+
webrtcStatus.value = "connected";
|
|
22
|
+
localPeerId.value = rtc.localPeerId;
|
|
23
|
+
});
|
|
24
|
+
on("disconnected", () => {
|
|
25
|
+
isConnected.value = false;
|
|
26
|
+
webrtcStatus.value = "disconnected";
|
|
27
|
+
});
|
|
28
|
+
on("peerJoined", (peer) => {
|
|
29
|
+
const next = new Map(peers.value);
|
|
30
|
+
next.set(peer.peerId, peer);
|
|
31
|
+
peers.value = next;
|
|
32
|
+
});
|
|
33
|
+
on("peerLeft", ({ peerId }) => {
|
|
34
|
+
const next = new Map(peers.value);
|
|
35
|
+
next.delete(peerId);
|
|
36
|
+
peers.value = next;
|
|
37
|
+
const nextE2ee = new Set(e2eeEstablishedPeers.value);
|
|
38
|
+
nextE2ee.delete(peerId);
|
|
39
|
+
e2eeEstablishedPeers.value = nextE2ee;
|
|
40
|
+
});
|
|
41
|
+
on("peerConnectionState", ({ peerId, state }) => {
|
|
42
|
+
const existing = peers.value.get(peerId);
|
|
43
|
+
if (existing) {
|
|
44
|
+
const next = new Map(peers.value);
|
|
45
|
+
next.set(peerId, { ...existing, connectionState: state });
|
|
46
|
+
peers.value = next;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
on("e2eeEstablished", ({ peerId }) => {
|
|
50
|
+
const next = new Set(e2eeEstablishedPeers.value);
|
|
51
|
+
next.add(peerId);
|
|
52
|
+
e2eeEstablishedPeers.value = next;
|
|
53
|
+
});
|
|
54
|
+
on("e2eeFailed", ({ peerId, error }) => {
|
|
55
|
+
console.warn(`[abracadabra] E2EE failed for peer ${peerId}:`, error);
|
|
56
|
+
try {
|
|
57
|
+
const toast = useToast();
|
|
58
|
+
toast.add({
|
|
59
|
+
title: "E2EE handshake failed",
|
|
60
|
+
description: `Could not establish encrypted channel with peer`,
|
|
61
|
+
color: "warning",
|
|
62
|
+
icon: "i-lucide-shield-alert"
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function _cleanup() {
|
|
69
|
+
for (const unsub of _unbinds) unsub();
|
|
70
|
+
_unbinds.length = 0;
|
|
71
|
+
_destroyFileTransfer();
|
|
72
|
+
if (_rtc) {
|
|
73
|
+
_rtc.destroy();
|
|
74
|
+
_rtc = null;
|
|
75
|
+
}
|
|
76
|
+
webrtcStatus.value = "idle";
|
|
77
|
+
isConnected.value = false;
|
|
78
|
+
peers.value = /* @__PURE__ */ new Map();
|
|
79
|
+
localPeerId.value = null;
|
|
80
|
+
e2eeEnabled.value = false;
|
|
81
|
+
e2eeEstablishedPeers.value = /* @__PURE__ */ new Set();
|
|
82
|
+
}
|
|
83
|
+
export function _initWebRTC(rtc) {
|
|
84
|
+
_cleanup();
|
|
85
|
+
_rtc = rtc;
|
|
86
|
+
_wireEvents(rtc);
|
|
87
|
+
_wireFileTransferEvents(rtc);
|
|
88
|
+
webrtcStatus.value = "connecting";
|
|
89
|
+
}
|
|
90
|
+
export function _destroyWebRTC() {
|
|
91
|
+
_cleanup();
|
|
92
|
+
}
|
|
93
|
+
function fromBase64Url(str) {
|
|
94
|
+
const b = atob(str.replace(/-/g, "+").replace(/_/g, "/"));
|
|
95
|
+
const a = new Uint8Array(b.length);
|
|
96
|
+
for (let i = 0; i < b.length; i++) a[i] = b.charCodeAt(i);
|
|
97
|
+
return a;
|
|
98
|
+
}
|
|
99
|
+
async function connect(options) {
|
|
100
|
+
if (_rtc && isConnected.value) return;
|
|
101
|
+
const { provider, keystore, isClaimed } = useAbracadabra();
|
|
102
|
+
if (!provider.value) return;
|
|
103
|
+
const config = useRuntimeConfig();
|
|
104
|
+
const webrtcConfig = config.public.abracadabra?.webrtc ?? {};
|
|
105
|
+
const sdk = getSdkModule();
|
|
106
|
+
if (!sdk?.AbracadabraWebRTC) {
|
|
107
|
+
console.error("[abracadabra] SDK not loaded \u2014 cannot connect WebRTC");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const { AbracadabraWebRTC } = sdk;
|
|
111
|
+
let e2eeIdentity;
|
|
112
|
+
const wantE2ee = options?.e2ee ?? webrtcConfig.e2ee ?? false;
|
|
113
|
+
if (wantE2ee && isClaimed.value && keystore.value) {
|
|
114
|
+
try {
|
|
115
|
+
const pubKeyB64 = await keystore.value.getPublicKey();
|
|
116
|
+
if (pubKeyB64) {
|
|
117
|
+
const publicKey = fromBase64Url(pubKeyB64);
|
|
118
|
+
const x25519PrivateKey = await keystore.value.getX25519PrivateKey();
|
|
119
|
+
e2eeIdentity = { publicKey, x25519PrivateKey };
|
|
120
|
+
e2eeEnabled.value = true;
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.warn("[abracadabra] E2EE identity derivation failed:", e.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
let resolvedIceServers = webrtcConfig.iceServers;
|
|
127
|
+
if (!resolvedIceServers) {
|
|
128
|
+
try {
|
|
129
|
+
const { client } = useAbracadabra();
|
|
130
|
+
if (client.value?.getIceServers) {
|
|
131
|
+
resolvedIceServers = await client.value.getIceServers();
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
if (import.meta.dev) console.debug("[abracadabra] webrtc: failed to fetch ICE servers from server:", e);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const rtc = AbracadabraWebRTC.fromProvider(provider.value, {
|
|
138
|
+
iceServers: resolvedIceServers,
|
|
139
|
+
enableFileTransfer: webrtcConfig.fileTransfer ?? false,
|
|
140
|
+
e2ee: e2eeIdentity
|
|
141
|
+
});
|
|
142
|
+
_initWebRTC(rtc);
|
|
143
|
+
await rtc.connect();
|
|
144
|
+
}
|
|
145
|
+
function disconnect() {
|
|
146
|
+
_cleanup();
|
|
147
|
+
}
|
|
148
|
+
async function sendFile(peerId, file, filename) {
|
|
149
|
+
if (!_rtc) return null;
|
|
150
|
+
return _rtc.sendFile(peerId, file, filename);
|
|
151
|
+
}
|
|
152
|
+
export function useWebRTC() {
|
|
153
|
+
return {
|
|
154
|
+
/** Current WebRTC connection status. */
|
|
155
|
+
webrtcStatus,
|
|
156
|
+
/** Whether the WebRTC signaling connection is active. */
|
|
157
|
+
isConnected,
|
|
158
|
+
/** Map of connected peers (peerId → PeerState). */
|
|
159
|
+
peers,
|
|
160
|
+
/** Local peer ID assigned by the signaling server. */
|
|
161
|
+
localPeerId,
|
|
162
|
+
/** Whether E2EE is enabled for this connection. */
|
|
163
|
+
e2eeEnabled,
|
|
164
|
+
/** Set of peer IDs with established E2EE channels. */
|
|
165
|
+
e2eeEstablishedPeers,
|
|
166
|
+
/** Array of connected peers (computed from peers map). */
|
|
167
|
+
peerList,
|
|
168
|
+
/** Number of connected peers (computed). */
|
|
169
|
+
peerCount,
|
|
170
|
+
/** Connect to WebRTC P2P mesh using the current provider. */
|
|
171
|
+
connect,
|
|
172
|
+
/** Disconnect and clean up WebRTC resources. */
|
|
173
|
+
disconnect,
|
|
174
|
+
/** Send a file to a specific peer. */
|
|
175
|
+
sendFile
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -11,6 +11,9 @@ import { Node } from '@tiptap/core';
|
|
|
11
11
|
* Values live in tree meta (Y.Map). The storage.metaField reactive object is
|
|
12
12
|
* the bridge: AEditor writes to it via watchEffect, MetaFieldView reads it.
|
|
13
13
|
*/
|
|
14
|
-
export declare const MetaField: Node<any,
|
|
14
|
+
export declare const MetaField: Node<any, {
|
|
15
|
+
pageMeta: Record<string, unknown> | null;
|
|
16
|
+
updateMeta: ((patch: Record<string, unknown>) => void) | null;
|
|
17
|
+
}>;
|
|
15
18
|
export declare function schemaFieldToAttrs(field: any): Record<string, unknown>;
|
|
16
19
|
export declare function userFieldToAttrs(f: any): Record<string, unknown>;
|
|
@@ -19,14 +19,21 @@ const icon = computed(() => props.node.attrs.icon || "");
|
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<template>
|
|
22
|
-
<NodeViewWrapper
|
|
22
|
+
<NodeViewWrapper
|
|
23
|
+
as="div"
|
|
24
|
+
class="border-b border-(--ui-border) last:border-b-0"
|
|
25
|
+
>
|
|
23
26
|
<button
|
|
24
27
|
contenteditable="false"
|
|
25
28
|
class="group w-full flex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 px-4 text-left text-(--ui-text-highlighted) focus-visible:outline-(--ui-primary) focus:outline-none min-w-0"
|
|
26
29
|
:data-state="open ? 'open' : 'closed'"
|
|
27
30
|
@click="open = !open"
|
|
28
31
|
>
|
|
29
|
-
<UIcon
|
|
32
|
+
<UIcon
|
|
33
|
+
v-if="icon"
|
|
34
|
+
:name="icon"
|
|
35
|
+
class="shrink-0 size-5"
|
|
36
|
+
/>
|
|
30
37
|
<span class="text-start break-words flex-1">{{ label }}</span>
|
|
31
38
|
<UIcon
|
|
32
39
|
name="i-lucide-chevron-down"
|
|
@@ -34,7 +41,10 @@ const icon = computed(() => props.node.attrs.icon || "");
|
|
|
34
41
|
:class="{ 'rotate-180': open }"
|
|
35
42
|
/>
|
|
36
43
|
</button>
|
|
37
|
-
<div
|
|
44
|
+
<div
|
|
45
|
+
v-show="open"
|
|
46
|
+
class="text-sm pb-3.5 px-4"
|
|
47
|
+
>
|
|
38
48
|
<NodeViewContent class="text-(--ui-text-muted) [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&>*]:my-1.5" />
|
|
39
49
|
</div>
|
|
40
50
|
</NodeViewWrapper>
|
|
@@ -17,7 +17,16 @@ const label = computed(() => props.node.attrs.label || "Badge");
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<template>
|
|
20
|
-
<NodeViewWrapper
|
|
21
|
-
|
|
20
|
+
<NodeViewWrapper
|
|
21
|
+
as="span"
|
|
22
|
+
class="inline"
|
|
23
|
+
>
|
|
24
|
+
<UBadge
|
|
25
|
+
color="primary"
|
|
26
|
+
variant="subtle"
|
|
27
|
+
class="rounded-full"
|
|
28
|
+
>
|
|
29
|
+
{{ label }}
|
|
30
|
+
</UBadge>
|
|
22
31
|
</NodeViewWrapper>
|
|
23
32
|
</template>
|
|
@@ -28,7 +28,10 @@ const cfg = computed(() => CONFIG[props.node.attrs.type] ?? CONFIG["note"]);
|
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
30
|
<template>
|
|
31
|
-
<NodeViewWrapper
|
|
31
|
+
<NodeViewWrapper
|
|
32
|
+
as="div"
|
|
33
|
+
class="my-3"
|
|
34
|
+
>
|
|
32
35
|
<UAlert
|
|
33
36
|
:color="cfg.color"
|
|
34
37
|
:icon="cfg.icon"
|