@abraca/nuxt 2.0.11 → 2.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 +68 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +99 -4
- package/dist/runtime/components/ACodeEditor.d.vue.ts +26 -0
- package/dist/runtime/components/ACodeEditor.vue +268 -0
- package/dist/runtime/components/ACodeEditor.vue.d.ts +26 -0
- package/dist/runtime/components/ADocumentTree.vue +52 -20
- package/dist/runtime/components/AEditor.d.vue.ts +20 -13
- package/dist/runtime/components/AEditor.vue +55 -2
- package/dist/runtime/components/AEditor.vue.d.ts +20 -13
- package/dist/runtime/components/ANodePanel.vue +64 -60
- package/dist/runtime/components/ANotificationBell.d.vue.ts +1 -1
- package/dist/runtime/components/ANotificationBell.vue.d.ts +1 -1
- package/dist/runtime/components/ASpaceFormModal.d.vue.ts +2 -2
- package/dist/runtime/components/ASpaceFormModal.vue.d.ts +2 -2
- package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
- package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
- package/dist/runtime/components/aware/APresenceBlobs.d.vue.ts +29 -1
- package/dist/runtime/components/aware/APresenceBlobs.vue +54 -8
- package/dist/runtime/components/aware/APresenceBlobs.vue.d.ts +29 -1
- package/dist/runtime/components/aware/APresenceCursors.d.vue.ts +11 -0
- package/dist/runtime/components/aware/APresenceCursors.vue +74 -9
- package/dist/runtime/components/aware/APresenceCursors.vue.d.ts +11 -0
- package/dist/runtime/components/aware/AToggleGroup.d.vue.ts +28 -13
- package/dist/runtime/components/aware/AToggleGroup.vue +56 -20
- package/dist/runtime/components/aware/AToggleGroup.vue.d.ts +28 -13
- package/dist/runtime/components/docs/ADocsNavigation.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsNavigation.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsToc.d.vue.ts +2 -2
- package/dist/runtime/components/docs/ADocsToc.vue.d.ts +2 -2
- package/dist/runtime/components/editor/AEditorRedoButton.d.vue.ts +1 -1
- package/dist/runtime/components/editor/AEditorRedoButton.vue.d.ts +1 -1
- package/dist/runtime/components/editor/AEditorUndoButton.d.vue.ts +1 -1
- package/dist/runtime/components/editor/AEditorUndoButton.vue.d.ts +1 -1
- package/dist/runtime/components/editor/ANodeInlineLabel.d.vue.ts +1 -1
- package/dist/runtime/components/editor/ANodeInlineLabel.vue.d.ts +1 -1
- package/dist/runtime/components/registry/APluginBrowser.d.vue.ts +23 -0
- package/dist/runtime/components/registry/APluginBrowser.vue +155 -0
- package/dist/runtime/components/registry/APluginBrowser.vue.d.ts +23 -0
- package/dist/runtime/components/registry/APluginCapabilityDialog.d.vue.ts +17 -0
- package/dist/runtime/components/registry/APluginCapabilityDialog.vue +159 -0
- package/dist/runtime/components/registry/APluginCapabilityDialog.vue.d.ts +17 -0
- package/dist/runtime/components/registry/APluginCard.d.vue.ts +20 -0
- package/dist/runtime/components/registry/APluginCard.vue +91 -0
- package/dist/runtime/components/registry/APluginCard.vue.d.ts +20 -0
- package/dist/runtime/components/registry/APluginDetail.d.vue.ts +18 -0
- package/dist/runtime/components/registry/APluginDetail.vue +252 -0
- package/dist/runtime/components/registry/APluginDetail.vue.d.ts +18 -0
- package/dist/runtime/components/renderers/ACodeRenderer.d.vue.ts +15 -0
- package/dist/runtime/components/renderers/ACodeRenderer.vue +68 -0
- package/dist/runtime/components/renderers/ACodeRenderer.vue.d.ts +15 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue +416 -120
- package/dist/runtime/components/renderers/AProseRenderer.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/AProseRenderer.vue.d.ts +2 -2
- package/dist/runtime/components/shell/ABreadcrumbForDoc.d.vue.ts +11 -0
- package/dist/runtime/components/shell/ABreadcrumbForDoc.vue +16 -0
- package/dist/runtime/components/shell/ABreadcrumbForDoc.vue.d.ts +11 -0
- package/dist/runtime/components/shell/ASettingsSection.d.vue.ts +35 -0
- package/dist/runtime/components/shell/ASettingsSection.vue +26 -0
- package/dist/runtime/components/shell/ASettingsSection.vue.d.ts +35 -0
- package/dist/runtime/components/shell/ASidebar.d.vue.ts +1 -1
- package/dist/runtime/components/shell/ASidebar.vue.d.ts +1 -1
- package/dist/runtime/components/shell/AUserMenu.d.vue.ts +5 -2
- package/dist/runtime/components/shell/AUserMenu.vue +4 -0
- package/dist/runtime/components/shell/AUserMenu.vue.d.ts +5 -2
- package/dist/runtime/composables/useAbracadabraSchema.d.ts +83 -0
- package/dist/runtime/composables/useAbracadabraSchema.js +52 -0
- package/dist/runtime/composables/useAggregatedPresence.d.ts +1 -6
- package/dist/runtime/composables/useCalendarView.d.ts +1 -1
- package/dist/runtime/composables/useChat.js +1 -0
- package/dist/runtime/composables/useDocBreadcrumb.d.ts +21 -0
- package/dist/runtime/composables/useDocBreadcrumb.js +33 -0
- package/dist/runtime/composables/useDocEntryTyped.d.ts +60 -0
- package/dist/runtime/composables/useDocEntryTyped.js +70 -0
- package/dist/runtime/composables/useEditorDragHandle.js +18 -0
- package/dist/runtime/composables/useEditorSuggestions.js +2 -1
- package/dist/runtime/composables/useInstalledPlugins.d.ts +3 -21
- package/dist/runtime/composables/useInstalledPlugins.js +2 -12
- package/dist/runtime/composables/useMetaMenuItems.d.ts +21 -0
- package/dist/runtime/composables/useMetaMenuItems.js +115 -0
- package/dist/runtime/composables/useMetaValidator.d.ts +27 -0
- package/dist/runtime/composables/useMetaValidator.js +10 -0
- package/dist/runtime/composables/usePluginCatalog.d.ts +161 -0
- package/dist/runtime/composables/usePluginCatalog.js +234 -0
- package/dist/runtime/composables/useQuery.d.ts +79 -0
- package/dist/runtime/composables/useQuery.js +97 -0
- package/dist/runtime/composables/useSpaces.js +4 -5
- package/dist/runtime/composables/useTableView.d.ts +3 -3
- package/dist/runtime/composables/useTypedDoc.d.ts +97 -0
- package/dist/runtime/composables/useTypedDoc.js +114 -0
- package/dist/runtime/composables/useWebRTC.js +44 -5
- package/dist/runtime/extensions/document-meta.js +5 -0
- package/dist/runtime/extensions/timeline.d.ts +11 -0
- package/dist/runtime/extensions/timeline.js +52 -0
- package/dist/runtime/extensions/views/DocumentMetaView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/DocumentMetaView.vue +63 -0
- package/dist/runtime/extensions/views/DocumentMetaView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/TimelineItemView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/TimelineItemView.vue +131 -0
- package/dist/runtime/extensions/views/TimelineItemView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/TimelineView.d.vue.ts +9 -0
- package/dist/runtime/extensions/views/TimelineView.vue +29 -0
- package/dist/runtime/extensions/views/TimelineView.vue.d.ts +9 -0
- package/dist/runtime/locale.d.ts +2 -0
- package/dist/runtime/locale.js +2 -0
- package/dist/runtime/plugin-abracadabra.client.js +107 -6
- package/dist/runtime/plugin-registry.d.ts +11 -30
- package/dist/runtime/plugin-registry.js +2 -82
- package/dist/runtime/plugins/core.plugin.js +10 -4
- package/dist/runtime/server/api/_abracadabra/spaces.get.d.ts +1 -1
- package/dist/runtime/server/plugins/abracadabra-service.js +28 -0
- package/dist/runtime/server/utils/docCache.js +24 -3
- package/dist/runtime/server/utils/schemaServerSupport.d.ts +52 -0
- package/dist/runtime/server/utils/schemaServerSupport.js +51 -0
- package/dist/runtime/types.d.ts +63 -46
- package/dist/runtime/utils/docTypes.d.ts +15 -0
- package/dist/runtime/utils/docTypes.js +20 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +32 -0
- package/dist/runtime/utils/loadCodeMirror.js +65 -0
- package/dist/runtime/utils/markdownToYjs.d.ts +1 -23
- package/dist/runtime/utils/markdownToYjs.js +5 -440
- package/dist/runtime/utils/schemaSupport.d.ts +60 -0
- package/dist/runtime/utils/schemaSupport.js +40 -0
- package/dist/runtime/utils/yjsConvert.d.ts +1 -14
- package/dist/runtime/utils/yjsConvert.js +5 -331
- package/package.json +84 -23
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useQuery — query + live-subscribe to documents in the doc-tree.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Imperative (`subscribe: false`, default)** — thin Vue-reactive
|
|
7
|
+
* shell around `client.queryDocs`. Call `refresh()` to re-fetch.
|
|
8
|
+
* Use for "give me the matching docs right now" reads.
|
|
9
|
+
*
|
|
10
|
+
* 2. **Reactive (`subscribe: true`)** — opens a `provider.subscribeQuery`
|
|
11
|
+
* under the hood. The first frame fills `entries`; subsequent
|
|
12
|
+
* `delta` frames merge in via add/remove. Resubscribes when the
|
|
13
|
+
* options ref changes; cancels on unmount.
|
|
14
|
+
*
|
|
15
|
+
* Reactive mode requires an active provider — `entries` stays empty
|
|
16
|
+
* until the provider connects and the initial snapshot arrives.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
*
|
|
20
|
+
* // Imperative
|
|
21
|
+
* const { entries, loading, refresh } = useQuery({ type: 'kanban' })
|
|
22
|
+
* await refresh()
|
|
23
|
+
*
|
|
24
|
+
* // Reactive (mount → snapshot → live deltas → unmount cancels)
|
|
25
|
+
* const { entries } = useQuery(
|
|
26
|
+
* computed(() => ({ type: 'kanban', where: { priority: { gte: 3 } } })),
|
|
27
|
+
* { immediate: true, subscribe: true },
|
|
28
|
+
* )
|
|
29
|
+
*/
|
|
30
|
+
import { type Ref } from 'vue';
|
|
31
|
+
import type { DocumentMeta } from '@abraca/dabra';
|
|
32
|
+
export interface QueryOptions {
|
|
33
|
+
/** Match `documents.kind` exactly. */
|
|
34
|
+
type?: string;
|
|
35
|
+
/** Narrow to children of this doc. */
|
|
36
|
+
parentId?: string;
|
|
37
|
+
/** Case-insensitive substring match on `documents.label`. */
|
|
38
|
+
labelContains?: string;
|
|
39
|
+
/** Maximum number of results (default 50, hard cap 500 server-side). */
|
|
40
|
+
limit?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Predicate AST. Shape mirrors `@abraca/schema`'s `WhereClause`:
|
|
43
|
+
* `{ priority: { gte: 3 }, tags: { contains: "urgent" } }`
|
|
44
|
+
* Empty / missing falls through to the v1 filter set.
|
|
45
|
+
*/
|
|
46
|
+
where?: unknown;
|
|
47
|
+
}
|
|
48
|
+
export interface UseQueryReturn {
|
|
49
|
+
/** Latest result set. Empty until `refresh()` runs at least once. */
|
|
50
|
+
readonly entries: Ref<readonly DocumentMeta[]>;
|
|
51
|
+
/** True while a fetch is in flight. */
|
|
52
|
+
readonly loading: Ref<boolean>;
|
|
53
|
+
/** Last error message, or null. Cleared on the next refresh. */
|
|
54
|
+
readonly error: Ref<string | null>;
|
|
55
|
+
/** Trigger a fresh fetch with the current options. */
|
|
56
|
+
refresh: () => Promise<DocumentMeta[]>;
|
|
57
|
+
}
|
|
58
|
+
export interface UseQueryHookOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Run `refresh()` immediately when the composable mounts, and whenever
|
|
61
|
+
* the options ref changes. Default false — the caller invokes `refresh`
|
|
62
|
+
* explicitly. Note that when the options arg is a plain object (not a
|
|
63
|
+
* ref / computed), the watcher fires once at mount and then never
|
|
64
|
+
* again — change-tracking requires a reactive source.
|
|
65
|
+
*/
|
|
66
|
+
immediate?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Open a live `query:v1:` subscription on the provider's WebSocket
|
|
69
|
+
* instead of polling REST. The first `ack` frame fills `entries`;
|
|
70
|
+
* subsequent `delta` frames merge in. Cancels on unmount, and on
|
|
71
|
+
* options change (a fresh sub is opened with the new predicate).
|
|
72
|
+
*
|
|
73
|
+
* Implies `immediate: true` — subscriptions are useless without an
|
|
74
|
+
* initial snapshot. Falls back to imperative mode when no provider
|
|
75
|
+
* is bound or the WS is closed.
|
|
76
|
+
*/
|
|
77
|
+
subscribe?: boolean;
|
|
78
|
+
}
|
|
79
|
+
export declare function useQuery(options: QueryOptions | Ref<QueryOptions>, hookOpts?: UseQueryHookOptions): UseQueryReturn;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { onScopeDispose, ref, shallowRef, watch } from "vue";
|
|
2
|
+
import { useAbracadabra } from "#imports";
|
|
3
|
+
function asOptions(input) {
|
|
4
|
+
if (input.value !== void 0 && typeof input.effect !== "undefined") {
|
|
5
|
+
return input;
|
|
6
|
+
}
|
|
7
|
+
if (input.value !== void 0) {
|
|
8
|
+
return input;
|
|
9
|
+
}
|
|
10
|
+
return ref(input);
|
|
11
|
+
}
|
|
12
|
+
export function useQuery(options, hookOpts = {}) {
|
|
13
|
+
const opts = asOptions(options);
|
|
14
|
+
const entries = shallowRef([]);
|
|
15
|
+
const loading = ref(false);
|
|
16
|
+
const error = ref(null);
|
|
17
|
+
async function refresh() {
|
|
18
|
+
const abra = useAbracadabra();
|
|
19
|
+
const client = abra.client?.value;
|
|
20
|
+
if (!client) {
|
|
21
|
+
entries.value = [];
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
loading.value = true;
|
|
25
|
+
error.value = null;
|
|
26
|
+
try {
|
|
27
|
+
const result = await client.queryDocs(opts.value);
|
|
28
|
+
entries.value = Object.freeze(result.slice());
|
|
29
|
+
return result;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
error.value = err instanceof Error ? err.message : String(err);
|
|
32
|
+
entries.value = [];
|
|
33
|
+
return [];
|
|
34
|
+
} finally {
|
|
35
|
+
loading.value = false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let activeHandle = null;
|
|
39
|
+
function closeSubscription() {
|
|
40
|
+
if (activeHandle) {
|
|
41
|
+
activeHandle.cancel();
|
|
42
|
+
activeHandle = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function specFromOpts(o) {
|
|
46
|
+
return {
|
|
47
|
+
type: o.type,
|
|
48
|
+
parentId: o.parentId,
|
|
49
|
+
labelContains: o.labelContains,
|
|
50
|
+
limit: o.limit,
|
|
51
|
+
where: o.where
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function openSubscription() {
|
|
55
|
+
closeSubscription();
|
|
56
|
+
const abra = useAbracadabra();
|
|
57
|
+
const provider = abra.provider?.value;
|
|
58
|
+
if (!provider?.subscribeQuery) {
|
|
59
|
+
await refresh();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
loading.value = true;
|
|
63
|
+
error.value = null;
|
|
64
|
+
activeHandle = provider.subscribeQuery(specFromOpts(opts.value), {
|
|
65
|
+
onSnapshot: (rows) => {
|
|
66
|
+
entries.value = Object.freeze(rows.slice());
|
|
67
|
+
loading.value = false;
|
|
68
|
+
},
|
|
69
|
+
onDelta: ({ added, removed }) => {
|
|
70
|
+
const removedSet = new Set(removed);
|
|
71
|
+
const kept = entries.value.filter(
|
|
72
|
+
(e) => !removedSet.has(e.id)
|
|
73
|
+
);
|
|
74
|
+
const next = [...added, ...kept];
|
|
75
|
+
entries.value = Object.freeze(next);
|
|
76
|
+
},
|
|
77
|
+
onError: (err) => {
|
|
78
|
+
error.value = err.message;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (hookOpts.subscribe) {
|
|
83
|
+
watch(
|
|
84
|
+
opts,
|
|
85
|
+
() => {
|
|
86
|
+
void openSubscription();
|
|
87
|
+
},
|
|
88
|
+
{ immediate: true, deep: true }
|
|
89
|
+
);
|
|
90
|
+
onScopeDispose(() => closeSubscription());
|
|
91
|
+
} else if (hookOpts.immediate) {
|
|
92
|
+
watch(opts, () => {
|
|
93
|
+
void refresh();
|
|
94
|
+
}, { immediate: true, deep: true });
|
|
95
|
+
}
|
|
96
|
+
return { entries, loading, error, refresh };
|
|
97
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ref, computed } from "vue";
|
|
2
|
-
import { useRoute } from "#imports";
|
|
3
2
|
import { useAbracadabra } from "./useAbracadabra.js";
|
|
4
3
|
const loading = ref(false);
|
|
5
4
|
const error = ref(null);
|
|
@@ -7,10 +6,10 @@ export function useSpaces() {
|
|
|
7
6
|
const abra = useAbracadabra();
|
|
8
7
|
const spaces = computed(() => abra.currentServerSpaces.value ?? []);
|
|
9
8
|
const currentSpace = computed(() => {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
13
|
-
return spaces.value.find((s) => s.
|
|
9
|
+
const entry = abra.savedServers?.value?.find?.((s) => s.url === abra.currentServerUrl?.value);
|
|
10
|
+
const entryDocId = entry?.entryDocId;
|
|
11
|
+
if (!entryDocId) return spaces.value[0] ?? null;
|
|
12
|
+
return spaces.value.find((s) => s.doc_id === entryDocId) ?? spaces.value[0] ?? null;
|
|
14
13
|
});
|
|
15
14
|
async function refresh() {
|
|
16
15
|
loading.value = true;
|
|
@@ -5,9 +5,9 @@ export declare function useTableView(tree: {
|
|
|
5
5
|
treeMap: any;
|
|
6
6
|
}, tableDocId: string): {
|
|
7
7
|
tableMeta: import("vue").ComputedRef<DocPageMeta>;
|
|
8
|
-
explicitMode: import("vue").ComputedRef<"
|
|
9
|
-
detectedMode: import("vue").ComputedRef<"
|
|
10
|
-
effectiveMode: import("vue").ComputedRef<"
|
|
8
|
+
explicitMode: import("vue").ComputedRef<"hierarchy" | "flat" | undefined>;
|
|
9
|
+
detectedMode: import("vue").ComputedRef<"hierarchy" | "flat">;
|
|
10
|
+
effectiveMode: import("vue").ComputedRef<"hierarchy" | "flat">;
|
|
11
11
|
setMode: (mode: "hierarchy" | "flat" | undefined) => void;
|
|
12
12
|
columnDefs: import("vue").ComputedRef<TableColumnDef[]>;
|
|
13
13
|
addColumn: (def: TableColumnDef) => void;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useTypedDoc — typed reads + validated writes against the Abracadabra
|
|
3
|
+
* doc-tree, parameterised by a `@abraca/schema` registry.
|
|
4
|
+
*
|
|
5
|
+
* Day 2 of the Phase 3 schema rollout.
|
|
6
|
+
*
|
|
7
|
+
* The Nuxt module reads / writes the doc-tree through `useSyncedMap` on
|
|
8
|
+
* the root Y.Doc — it does NOT use `DocumentManager.docs(...)` from
|
|
9
|
+
* `@abraca/dabra`, because the module instantiates `AbracadabraProvider`
|
|
10
|
+
* directly (no DM wrapper). This composable is the Nuxt-side analogue:
|
|
11
|
+
* a typed projection over `useDocTree`'s reactive entries, with optional
|
|
12
|
+
* pre-write validation against the supplied registry.
|
|
13
|
+
*
|
|
14
|
+
* // app code
|
|
15
|
+
* import { kanbanSchema } from '@abraca/schema'
|
|
16
|
+
* const { get, all, update } = useTypedDoc(kanbanSchema)
|
|
17
|
+
* const board = computed(() => get('kanban', boardId.value))
|
|
18
|
+
* // board.value?.meta.kanbanColumnWidth is typed
|
|
19
|
+
* update('kanban', boardId.value, { kanbanColumnWidth: 'wide' })
|
|
20
|
+
*
|
|
21
|
+
* Behaviour:
|
|
22
|
+
* - `get(type, id)` returns null when the entry is missing OR its
|
|
23
|
+
* stored `type` doesn't match (cross-schema reads return null —
|
|
24
|
+
* matches the server-side smoke contract).
|
|
25
|
+
* - `update(type, id, patch)` validates the *merged* meta before
|
|
26
|
+
* writing. Throws `MetaValidationError`-equivalent on mismatch.
|
|
27
|
+
* - `set(type, id, meta)` validates the replacement meta.
|
|
28
|
+
* - `clear(type, id, keys)` is a thin wrapper over `updateMeta` —
|
|
29
|
+
* no validation (clearing keys can never fail validation).
|
|
30
|
+
* - Reads do NOT auto-call `runMigrations`. Pass `migrate: true` in
|
|
31
|
+
* the per-call options or use `useDocEntryTyped` (Day 3).
|
|
32
|
+
*
|
|
33
|
+
* Type inference: when a typed `SchemaRegistry<TMap>` is passed, every
|
|
34
|
+
* read/write narrows `meta` to `TMap[N]`. Untyped registries fall back
|
|
35
|
+
* to `Record<string, unknown>`.
|
|
36
|
+
*/
|
|
37
|
+
import { type ComputedRef } from 'vue';
|
|
38
|
+
import type { SchemaRegistryLike } from '../utils/schemaSupport.js';
|
|
39
|
+
/** Extract the doc-type names from a typed registry. */
|
|
40
|
+
type SchemaDocTypeName<S> = S extends {
|
|
41
|
+
__metaMap?: infer M;
|
|
42
|
+
} ? keyof M & string : string;
|
|
43
|
+
/** Extract the meta type for a given doc-type name from a registry. */
|
|
44
|
+
type SchemaMetaOf<S, N extends string> = S extends {
|
|
45
|
+
__metaMap?: infer M;
|
|
46
|
+
} ? N extends keyof M ? M[N] : Record<string, unknown> : Record<string, unknown>;
|
|
47
|
+
export interface TypedTreeEntry<M> {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly type: string;
|
|
50
|
+
readonly label: string;
|
|
51
|
+
readonly parentId: string | null;
|
|
52
|
+
readonly order: number;
|
|
53
|
+
readonly meta: M | undefined;
|
|
54
|
+
readonly createdAt: number | undefined;
|
|
55
|
+
readonly updatedAt: number | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Thrown by typed write methods when validation fails.
|
|
59
|
+
*
|
|
60
|
+
* Mirrors `MetaValidationError` from `@abraca/dabra` — kept module-local
|
|
61
|
+
* because the Nuxt write path doesn't go through `MetaManager`.
|
|
62
|
+
* Consumers can `import { MetaValidationError } from '@abraca/nuxt'` to
|
|
63
|
+
* type-narrow without dragging in `@abraca/dabra`.
|
|
64
|
+
*/
|
|
65
|
+
export declare class MetaValidationError extends Error {
|
|
66
|
+
readonly docId: string;
|
|
67
|
+
readonly docType: string;
|
|
68
|
+
readonly errors: ReadonlyArray<{
|
|
69
|
+
path: ReadonlyArray<PropertyKey>;
|
|
70
|
+
message: string;
|
|
71
|
+
code?: string;
|
|
72
|
+
}>;
|
|
73
|
+
constructor(docId: string, docType: string, errors: ReadonlyArray<{
|
|
74
|
+
path: ReadonlyArray<PropertyKey>;
|
|
75
|
+
message: string;
|
|
76
|
+
code?: string;
|
|
77
|
+
}>);
|
|
78
|
+
}
|
|
79
|
+
export declare class TypedDocTypeMismatchError extends Error {
|
|
80
|
+
readonly docId: string;
|
|
81
|
+
readonly expectedType: string;
|
|
82
|
+
readonly actualType: string | undefined;
|
|
83
|
+
constructor(docId: string, expectedType: string, actualType: string | undefined);
|
|
84
|
+
}
|
|
85
|
+
export declare function useTypedDoc<S extends SchemaRegistryLike>(schema: S): {
|
|
86
|
+
/** Synchronous typed read. Returns null on missing entry or type mismatch. */
|
|
87
|
+
get: <N extends SchemaDocTypeName<S>>(type: N, id: string) => TypedTreeEntry<SchemaMetaOf<S, N>> | null;
|
|
88
|
+
/** Reactive list of every entry whose stored `type` matches `type`. */
|
|
89
|
+
all: <N extends SchemaDocTypeName<S>>(type: N) => ComputedRef<TypedTreeEntry<SchemaMetaOf<S, N>>[]>;
|
|
90
|
+
/** Validated meta merge. Throws on mismatch / validation failure. */
|
|
91
|
+
update: <N extends SchemaDocTypeName<S>>(type: N, id: string, patch: Partial<SchemaMetaOf<S, N>>) => void;
|
|
92
|
+
/** Validated meta replacement. Throws on mismatch / validation failure. */
|
|
93
|
+
set: <N extends SchemaDocTypeName<S>>(type: N, id: string, meta: SchemaMetaOf<S, N>) => void;
|
|
94
|
+
/** Delete specific keys. No validation (clearing can't fail validation). */
|
|
95
|
+
clear: <N extends SchemaDocTypeName<S>>(type: N, id: string, keys: ReadonlyArray<keyof SchemaMetaOf<S, N> & string>) => void;
|
|
96
|
+
};
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { useAbracadabra } from "./useAbracadabra.js";
|
|
3
|
+
import { useDocTree } from "./useDocTree.js";
|
|
4
|
+
export class MetaValidationError extends Error {
|
|
5
|
+
docId;
|
|
6
|
+
docType;
|
|
7
|
+
errors;
|
|
8
|
+
constructor(docId, docType, errors) {
|
|
9
|
+
super(
|
|
10
|
+
`meta validation failed for doc ${docId} (type "${docType}"): ` + errors.map((e) => `${e.path.join(".") || "<root>"}: ${e.message}`).join("; ")
|
|
11
|
+
);
|
|
12
|
+
this.name = "MetaValidationError";
|
|
13
|
+
this.docId = docId;
|
|
14
|
+
this.docType = docType;
|
|
15
|
+
this.errors = errors;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class TypedDocTypeMismatchError extends Error {
|
|
19
|
+
docId;
|
|
20
|
+
expectedType;
|
|
21
|
+
actualType;
|
|
22
|
+
constructor(docId, expectedType, actualType) {
|
|
23
|
+
super(
|
|
24
|
+
`typed write on document ${docId} expected type "${expectedType}" but stored type is ` + (actualType === void 0 ? "(none)" : `"${actualType}"`)
|
|
25
|
+
);
|
|
26
|
+
this.name = "TypedDocTypeMismatchError";
|
|
27
|
+
this.docId = docId;
|
|
28
|
+
this.expectedType = expectedType;
|
|
29
|
+
this.actualType = actualType;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function projectEntry(entry, expectedType) {
|
|
33
|
+
if (!entry) return null;
|
|
34
|
+
if (entry.type !== expectedType) return null;
|
|
35
|
+
return {
|
|
36
|
+
id: entry.id,
|
|
37
|
+
type: expectedType,
|
|
38
|
+
label: entry.label,
|
|
39
|
+
parentId: entry.parentId,
|
|
40
|
+
order: entry.order,
|
|
41
|
+
meta: entry.meta,
|
|
42
|
+
createdAt: entry.createdAt,
|
|
43
|
+
updatedAt: entry.updatedAt
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function useTypedDoc(schema) {
|
|
47
|
+
const abra = useAbracadabra();
|
|
48
|
+
const tree = useDocTree();
|
|
49
|
+
function readEntry(id) {
|
|
50
|
+
return tree.getEntry(id);
|
|
51
|
+
}
|
|
52
|
+
function ensureType(id, expectedType) {
|
|
53
|
+
const entry = readEntry(id);
|
|
54
|
+
if (!entry) {
|
|
55
|
+
throw new TypedDocTypeMismatchError(id, expectedType, void 0);
|
|
56
|
+
}
|
|
57
|
+
if (entry.type !== expectedType) {
|
|
58
|
+
throw new TypedDocTypeMismatchError(id, expectedType, entry.type);
|
|
59
|
+
}
|
|
60
|
+
return entry;
|
|
61
|
+
}
|
|
62
|
+
function validateOrThrow(id, type, meta) {
|
|
63
|
+
const result = schema.validateMeta(type, meta);
|
|
64
|
+
if (result.ok) return result.value ?? meta;
|
|
65
|
+
throw new MetaValidationError(id, type, result.errors);
|
|
66
|
+
}
|
|
67
|
+
function writeMeta(id, nextMeta) {
|
|
68
|
+
const doc = abra.doc.value;
|
|
69
|
+
if (!doc) return;
|
|
70
|
+
const treeMap = doc.getMap("doc-tree");
|
|
71
|
+
const entry = treeMap.get(id);
|
|
72
|
+
if (!entry) return;
|
|
73
|
+
treeMap.set(id, {
|
|
74
|
+
...entry,
|
|
75
|
+
meta: nextMeta,
|
|
76
|
+
updatedAt: Date.now()
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
get(type, id) {
|
|
81
|
+
return projectEntry(readEntry(id), type);
|
|
82
|
+
},
|
|
83
|
+
all(type) {
|
|
84
|
+
return computed(
|
|
85
|
+
() => tree.entries.value.filter((e) => e.type === type && !e.trashed).map((e) => projectEntry(e, type)).filter((e) => e !== null)
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
update(type, id, patch) {
|
|
89
|
+
const entry = ensureType(id, type);
|
|
90
|
+
const merged = {
|
|
91
|
+
...entry.meta ?? {},
|
|
92
|
+
...patch
|
|
93
|
+
};
|
|
94
|
+
validateOrThrow(id, type, merged);
|
|
95
|
+
writeMeta(id, merged);
|
|
96
|
+
},
|
|
97
|
+
set(type, id, meta) {
|
|
98
|
+
ensureType(id, type);
|
|
99
|
+
validateOrThrow(id, type, meta);
|
|
100
|
+
writeMeta(id, meta);
|
|
101
|
+
},
|
|
102
|
+
clear(type, id, keys) {
|
|
103
|
+
const entry = ensureType(id, type);
|
|
104
|
+
if (!entry.meta) return;
|
|
105
|
+
const src = entry.meta;
|
|
106
|
+
const dropped = new Set(keys.map((k) => k));
|
|
107
|
+
const next = {};
|
|
108
|
+
for (const [k, v] of Object.entries(src)) {
|
|
109
|
+
if (!dropped.has(k)) next[k] = v;
|
|
110
|
+
}
|
|
111
|
+
writeMeta(id, next);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -18,18 +18,41 @@ function _wireEvents(rtc) {
|
|
|
18
18
|
rtc.on(event, handler);
|
|
19
19
|
_unbinds.push(() => rtc.off(event, handler));
|
|
20
20
|
};
|
|
21
|
+
function normalizePeer(p) {
|
|
22
|
+
const peerId = p?.peerId ?? p?.peer_id;
|
|
23
|
+
return {
|
|
24
|
+
...p,
|
|
25
|
+
peerId,
|
|
26
|
+
peer_id: peerId,
|
|
27
|
+
displayName: p?.displayName ?? p?.name,
|
|
28
|
+
color: p?.color
|
|
29
|
+
};
|
|
30
|
+
}
|
|
21
31
|
on("connected", () => {
|
|
22
32
|
isConnected.value = true;
|
|
23
33
|
webrtcStatus.value = "connected";
|
|
24
|
-
|
|
34
|
+
const newLocalId = rtc.localPeerId;
|
|
35
|
+
localPeerId.value = newLocalId;
|
|
36
|
+
const next = /* @__PURE__ */ new Map();
|
|
37
|
+
const sdkPeers = rtc.peers instanceof Map ? rtc.peers : null;
|
|
38
|
+
if (sdkPeers) {
|
|
39
|
+
for (const [pid, p] of sdkPeers.entries()) {
|
|
40
|
+
if (!pid || pid === newLocalId) continue;
|
|
41
|
+
next.set(pid, normalizePeer(p));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
peers.value = next;
|
|
25
45
|
});
|
|
26
46
|
on("disconnected", () => {
|
|
27
47
|
isConnected.value = false;
|
|
28
48
|
webrtcStatus.value = "disconnected";
|
|
29
49
|
});
|
|
30
50
|
on("peerJoined", (peer) => {
|
|
51
|
+
const norm = normalizePeer(peer);
|
|
52
|
+
if (!norm.peerId) return;
|
|
53
|
+
if (norm.peerId === localPeerId.value) return;
|
|
31
54
|
const next = new Map(peers.value);
|
|
32
|
-
next.set(
|
|
55
|
+
next.set(norm.peerId, norm);
|
|
33
56
|
peers.value = next;
|
|
34
57
|
});
|
|
35
58
|
on("peerLeft", ({ peerId }) => {
|
|
@@ -40,6 +63,13 @@ function _wireEvents(rtc) {
|
|
|
40
63
|
nextE2ee.delete(peerId);
|
|
41
64
|
e2eeEstablishedPeers.value = nextE2ee;
|
|
42
65
|
});
|
|
66
|
+
on("peerProfile", ({ peerId, name, color }) => {
|
|
67
|
+
const existing = peers.value.get(peerId);
|
|
68
|
+
if (!existing) return;
|
|
69
|
+
const next = new Map(peers.value);
|
|
70
|
+
next.set(peerId, { ...existing, displayName: name, name, color });
|
|
71
|
+
peers.value = next;
|
|
72
|
+
});
|
|
43
73
|
on("peerConnectionState", ({ peerId, state }) => {
|
|
44
74
|
const existing = peers.value.get(peerId);
|
|
45
75
|
if (existing) {
|
|
@@ -100,7 +130,7 @@ function fromBase64Url(str) {
|
|
|
100
130
|
}
|
|
101
131
|
async function connect(options) {
|
|
102
132
|
if (_rtc && isConnected.value) return;
|
|
103
|
-
const { provider, keystore, isClaimed } = useAbracadabra();
|
|
133
|
+
const { provider, keystore, isClaimed, userName, userColor } = useAbracadabra();
|
|
104
134
|
if (!provider.value) return;
|
|
105
135
|
const config = useRuntimeConfig();
|
|
106
136
|
const webrtcConfig = config.public.abracadabra?.webrtc ?? {};
|
|
@@ -138,8 +168,17 @@ async function connect(options) {
|
|
|
138
168
|
}
|
|
139
169
|
const rtc = AbracadabraWebRTC.fromProvider(provider.value, {
|
|
140
170
|
iceServers: resolvedIceServers,
|
|
141
|
-
|
|
142
|
-
|
|
171
|
+
// Default file-transfer ON. The SDK's default (false) means
|
|
172
|
+
// `useFileTransfer().sendFile()` queues bytes onto a data channel that
|
|
173
|
+
// never gets created → silent 0 % stuck transfer. Apps that explicitly
|
|
174
|
+
// want to disable can pass `webrtc.fileTransfer: false` in nuxt.config.
|
|
175
|
+
enableFileTransfer: webrtcConfig.fileTransfer ?? true,
|
|
176
|
+
e2ee: e2eeIdentity,
|
|
177
|
+
// Pass identity through so peers can label each other in the mesh.
|
|
178
|
+
// Without these the SDK's `peerProfile` event never fires and the UI
|
|
179
|
+
// shows opaque peer IDs ("peer-jl0z7m" etc.) instead of usernames.
|
|
180
|
+
displayName: userName.value || void 0,
|
|
181
|
+
color: userColor.value || void 0
|
|
143
182
|
});
|
|
144
183
|
_initWebRTC(rtc);
|
|
145
184
|
await rtc.connect();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Node } from "@tiptap/core";
|
|
2
2
|
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
3
|
+
import { VueNodeViewRenderer } from "@tiptap/vue-3";
|
|
4
|
+
import DocumentMetaView from "./views/DocumentMetaView.vue";
|
|
3
5
|
export const DocumentMeta = Node.create({
|
|
4
6
|
name: "documentMeta",
|
|
5
7
|
content: "inline*",
|
|
@@ -10,6 +12,9 @@ export const DocumentMeta = Node.create({
|
|
|
10
12
|
renderHTML() {
|
|
11
13
|
return ["div", { "data-type": "document-meta" }, 0];
|
|
12
14
|
},
|
|
15
|
+
addNodeView() {
|
|
16
|
+
return VueNodeViewRenderer(DocumentMetaView);
|
|
17
|
+
},
|
|
13
18
|
addKeyboardShortcuts() {
|
|
14
19
|
return {
|
|
15
20
|
Backspace: ({ editor }) => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline + TimelineItem TipTap nodes.
|
|
3
|
+
*
|
|
4
|
+
* Vertical event timeline with per-item icon, label, and freeform date.
|
|
5
|
+
* Optional builtin — disabled via `abracadabra.disabledBuiltins: ['timeline']`.
|
|
6
|
+
*
|
|
7
|
+
* Ported 1:1 from cou-sh/app/extensions/timeline.ts.
|
|
8
|
+
*/
|
|
9
|
+
import { Node } from '@tiptap/core';
|
|
10
|
+
export declare const TimelineItem: Node<any, any>;
|
|
11
|
+
export declare const Timeline: Node<any, any>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { VueNodeViewRenderer } from "@tiptap/vue-3";
|
|
3
|
+
import TimelineView from "./views/TimelineView.vue";
|
|
4
|
+
import TimelineItemView from "./views/TimelineItemView.vue";
|
|
5
|
+
export const TimelineItem = Node.create({
|
|
6
|
+
name: "timelineItem",
|
|
7
|
+
content: "block+",
|
|
8
|
+
addAttributes() {
|
|
9
|
+
return {
|
|
10
|
+
date: { default: "" },
|
|
11
|
+
label: { default: "Event" },
|
|
12
|
+
icon: { default: "" }
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
parseHTML() {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
tag: "div[data-timeline-item]",
|
|
19
|
+
getAttrs: (el) => ({
|
|
20
|
+
date: el.getAttribute("data-date") || "",
|
|
21
|
+
label: el.getAttribute("data-label") || "Event",
|
|
22
|
+
icon: el.getAttribute("data-icon") || ""
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
},
|
|
27
|
+
renderHTML({ HTMLAttributes }) {
|
|
28
|
+
return ["div", mergeAttributes({
|
|
29
|
+
"data-timeline-item": "",
|
|
30
|
+
"data-date": HTMLAttributes.date,
|
|
31
|
+
"data-label": HTMLAttributes.label,
|
|
32
|
+
"data-icon": HTMLAttributes.icon
|
|
33
|
+
}), 0];
|
|
34
|
+
},
|
|
35
|
+
addNodeView() {
|
|
36
|
+
return VueNodeViewRenderer(TimelineItemView);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
export const Timeline = Node.create({
|
|
40
|
+
name: "timeline",
|
|
41
|
+
group: "block",
|
|
42
|
+
content: "timelineItem+",
|
|
43
|
+
parseHTML() {
|
|
44
|
+
return [{ tag: "div[data-timeline]" }];
|
|
45
|
+
},
|
|
46
|
+
renderHTML() {
|
|
47
|
+
return ["div", { "data-timeline": "" }, 0];
|
|
48
|
+
},
|
|
49
|
+
addNodeView() {
|
|
50
|
+
return VueNodeViewRenderer(TimelineView);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NodeViewProps } from '@tiptap/vue-3';
|
|
2
|
+
declare const __VLS_export: import("vue").DefineComponent<NodeViewProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
3
|
+
declare const _default: typeof __VLS_export;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { NodeViewWrapper, NodeViewContent } from "@tiptap/vue-3";
|
|
4
|
+
import { buildMetaMenuItems } from "../../composables/useMetaMenuItems";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
decorations: { type: Array, required: true },
|
|
7
|
+
selected: { type: Boolean, required: true },
|
|
8
|
+
updateAttributes: { type: Function, required: true },
|
|
9
|
+
deleteNode: { type: Function, required: true },
|
|
10
|
+
node: { type: null, required: true },
|
|
11
|
+
view: { type: null, required: true },
|
|
12
|
+
getPos: { type: null, required: true },
|
|
13
|
+
innerDecorations: { type: null, required: true },
|
|
14
|
+
editor: { type: Object, required: true },
|
|
15
|
+
extension: { type: Object, required: true },
|
|
16
|
+
HTMLAttributes: { type: Object, required: true }
|
|
17
|
+
});
|
|
18
|
+
const items = computed(() => {
|
|
19
|
+
void props.node.content.size;
|
|
20
|
+
return buildMetaMenuItems(props.editor);
|
|
21
|
+
});
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<NodeViewWrapper
|
|
26
|
+
as="div"
|
|
27
|
+
data-type="document-meta"
|
|
28
|
+
class="group/meta"
|
|
29
|
+
:class="{ 'meta-empty': node.content.size === 0 && !props.editor.isEditable }"
|
|
30
|
+
draggable="false"
|
|
31
|
+
@dragstart.prevent.stop
|
|
32
|
+
>
|
|
33
|
+
<NodeViewContent
|
|
34
|
+
as="span"
|
|
35
|
+
style="display: contents"
|
|
36
|
+
/>
|
|
37
|
+
<span
|
|
38
|
+
v-show="props.editor.isEditable"
|
|
39
|
+
contenteditable="false"
|
|
40
|
+
class="inline-flex items-center"
|
|
41
|
+
>
|
|
42
|
+
<UDropdownMenu
|
|
43
|
+
:items="items"
|
|
44
|
+
:content="{ side: 'bottom', align: 'start' }"
|
|
45
|
+
:ui="{ content: 'w-48' }"
|
|
46
|
+
>
|
|
47
|
+
<UButton
|
|
48
|
+
icon="i-lucide-plus"
|
|
49
|
+
size="2xs"
|
|
50
|
+
color="neutral"
|
|
51
|
+
variant="ghost"
|
|
52
|
+
class="opacity-0 group-hover/meta:opacity-40 hover:!opacity-100 transition-opacity"
|
|
53
|
+
/>
|
|
54
|
+
</UDropdownMenu>
|
|
55
|
+
</span>
|
|
56
|
+
<span
|
|
57
|
+
v-if="node.content.size === 0 && props.editor.isEditable"
|
|
58
|
+
class="text-sm text-(--ui-text-muted) pointer-events-none opacity-60"
|
|
59
|
+
>
|
|
60
|
+
Type '/' to add a property…
|
|
61
|
+
</span>
|
|
62
|
+
</NodeViewWrapper>
|
|
63
|
+
</template>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NodeViewProps } from '@tiptap/vue-3';
|
|
2
|
+
declare const __VLS_export: import("vue").DefineComponent<NodeViewProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
3
|
+
declare const _default: typeof __VLS_export;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NodeViewProps } from '@tiptap/vue-3';
|
|
2
|
+
declare const __VLS_export: import("vue").DefineComponent<NodeViewProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
3
|
+
declare const _default: typeof __VLS_export;
|
|
4
|
+
export default _default;
|