@abraca/nuxt 1.8.0 → 1.9.1
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/README.md +27 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +42 -4
- package/dist/runtime/components/docs/ADocsNavigation.d.vue.ts +155 -0
- package/dist/runtime/components/docs/ADocsNavigation.vue +154 -0
- package/dist/runtime/components/docs/ADocsNavigation.vue.d.ts +155 -0
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +249 -0
- package/dist/runtime/components/docs/ADocsSearch.vue +187 -0
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +249 -0
- package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +253 -0
- package/dist/runtime/components/docs/ADocsSearchButton.vue +99 -0
- package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +253 -0
- package/dist/runtime/components/docs/ADocsSurround.d.vue.ts +56 -0
- package/dist/runtime/components/docs/ADocsSurround.vue +68 -0
- package/dist/runtime/components/docs/ADocsSurround.vue.d.ts +56 -0
- package/dist/runtime/components/docs/ADocsToc.d.vue.ts +117 -0
- package/dist/runtime/components/docs/ADocsToc.vue +194 -0
- package/dist/runtime/components/docs/ADocsToc.vue.d.ts +117 -0
- package/dist/runtime/components/editor/ADocLinkPopover.vue +1 -5
- package/dist/runtime/components/renderers/AMediaRenderer.vue +1 -1
- package/dist/runtime/components/shell/ADocPanelSettings.d.vue.ts +32 -2
- package/dist/runtime/components/shell/ADocPanelSettings.vue +289 -2
- package/dist/runtime/components/shell/ADocPanelSettings.vue.d.ts +32 -2
- package/dist/runtime/composables/useDocsSearch.d.ts +24 -0
- package/dist/runtime/composables/useDocsSearch.js +34 -0
- package/dist/runtime/extensions/doc-embed.js +2 -1
- package/dist/runtime/extensions/doc-link-drop.js +4 -4
- package/dist/runtime/extensions/doc-link.d.ts +12 -0
- package/dist/runtime/extensions/doc-link.js +60 -0
- package/dist/runtime/extensions/views/DocEmbedView.vue +18 -3
- package/dist/runtime/extensions/views/DocLinkView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/DocLinkView.vue +71 -0
- package/dist/runtime/extensions/views/DocLinkView.vue.d.ts +4 -0
- package/dist/runtime/plugin-abracadabra.client.js +18 -2
- package/dist/runtime/plugins/core.plugin.js +3 -0
- package/dist/runtime/server/plugins/abracadabra-service.js +3 -1
- package/dist/runtime/theme/content/_shared.d.ts +6 -0
- package/dist/runtime/theme/content/_shared.js +6 -0
- package/dist/runtime/theme/content/content-navigation.d.ts +120 -0
- package/dist/runtime/theme/content/content-navigation.js +155 -0
- package/dist/runtime/theme/content/content-search-button.d.ts +16 -0
- package/dist/runtime/theme/content/content-search-button.js +15 -0
- package/dist/runtime/theme/content/content-search.d.ts +24 -0
- package/dist/runtime/theme/content/content-search.js +23 -0
- package/dist/runtime/theme/content/content-surround.d.ts +22 -0
- package/dist/runtime/theme/content/content-surround.js +23 -0
- package/dist/runtime/theme/content/content-toc.d.ts +84 -0
- package/dist/runtime/theme/content/content-toc.js +94 -0
- package/dist/runtime/theme/content/index.d.ts +5 -0
- package/dist/runtime/theme/content/index.js +5 -0
- package/dist/runtime/utils/content.d.ts +19 -0
- package/dist/runtime/utils/content.js +23 -0
- package/dist/runtime/utils/docReferenceEdges.js +1 -1
- package/package.json +20 -13
|
@@ -20,9 +20,15 @@ const props = defineProps({
|
|
|
20
20
|
lastSyncedLabel: { type: [String, null], required: false, default: null },
|
|
21
21
|
syncError: { type: [String, null], required: false, default: null },
|
|
22
22
|
docTypeKey: { type: String, required: false, default: void 0 },
|
|
23
|
-
docMeta: { type: [Object, null], required: false, default: null }
|
|
23
|
+
docMeta: { type: [Object, null], required: false, default: null },
|
|
24
|
+
snapshots: { type: Array, required: false, default: () => [] },
|
|
25
|
+
loadingSnapshots: { type: Boolean, required: false, default: false },
|
|
26
|
+
snapshotsError: { type: [String, null], required: false, default: null },
|
|
27
|
+
hasMoreSnapshots: { type: Boolean, required: false, default: false },
|
|
28
|
+
pendingSnapshotVersion: { type: [Number, null], required: false, default: null },
|
|
29
|
+
creatingSnapshot: { type: Boolean, required: false, default: false }
|
|
24
30
|
});
|
|
25
|
-
const emit = defineEmits(["grant-permission", "change-role", "revoke-permission", "create-invite", "sync-doc", "open-encryption", "user-context-menu", "update-meta"]);
|
|
31
|
+
const emit = defineEmits(["grant-permission", "change-role", "revoke-permission", "create-invite", "sync-doc", "open-encryption", "user-context-menu", "update-meta", "create-snapshot", "delete-snapshot", "restore-snapshot", "fork-snapshot", "load-more-snapshots"]);
|
|
26
32
|
const grantUserId = ref("");
|
|
27
33
|
const grantRole = ref("editor");
|
|
28
34
|
const showManualKeyInput = ref(false);
|
|
@@ -82,6 +88,91 @@ function handleGrant(userId, role) {
|
|
|
82
88
|
emit("grant-permission", id, r);
|
|
83
89
|
grantUserId.value = "";
|
|
84
90
|
}
|
|
91
|
+
const createSnapOpen = ref(false);
|
|
92
|
+
const createSnapLabel = ref("");
|
|
93
|
+
const snapConfirm = ref(null);
|
|
94
|
+
function openCreateSnap() {
|
|
95
|
+
createSnapLabel.value = "";
|
|
96
|
+
createSnapOpen.value = true;
|
|
97
|
+
}
|
|
98
|
+
function submitCreateSnap() {
|
|
99
|
+
const label = createSnapLabel.value.trim();
|
|
100
|
+
emit("create-snapshot", label || void 0);
|
|
101
|
+
createSnapOpen.value = false;
|
|
102
|
+
}
|
|
103
|
+
function askSnapDelete(version) {
|
|
104
|
+
snapConfirm.value = { kind: "delete", version };
|
|
105
|
+
}
|
|
106
|
+
function askSnapRestore(version) {
|
|
107
|
+
snapConfirm.value = { kind: "restore", version };
|
|
108
|
+
}
|
|
109
|
+
function runSnapConfirm() {
|
|
110
|
+
const c = snapConfirm.value;
|
|
111
|
+
if (!c) return;
|
|
112
|
+
snapConfirm.value = null;
|
|
113
|
+
if (c.kind === "delete") emit("delete-snapshot", c.version);
|
|
114
|
+
else emit("restore-snapshot", c.version);
|
|
115
|
+
}
|
|
116
|
+
function snapshotMenu(version) {
|
|
117
|
+
const manage = [
|
|
118
|
+
{
|
|
119
|
+
label: "Restore",
|
|
120
|
+
icon: "i-lucide-history",
|
|
121
|
+
onSelect: () => askSnapRestore(version)
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
label: "Fork into new document",
|
|
125
|
+
icon: "i-lucide-git-branch",
|
|
126
|
+
onSelect: () => emit("fork-snapshot", version)
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
const destructive = [
|
|
130
|
+
{
|
|
131
|
+
label: "Delete",
|
|
132
|
+
icon: "i-lucide-trash-2",
|
|
133
|
+
color: "error",
|
|
134
|
+
onSelect: () => askSnapDelete(version)
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
return props.isOwner ? [manage, destructive] : [manage];
|
|
138
|
+
}
|
|
139
|
+
function triggerColor(trigger) {
|
|
140
|
+
switch (trigger) {
|
|
141
|
+
case "manual":
|
|
142
|
+
return "primary";
|
|
143
|
+
case "unload":
|
|
144
|
+
case "shutdown":
|
|
145
|
+
return "warning";
|
|
146
|
+
default:
|
|
147
|
+
return "neutral";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function triggerLabel(trigger) {
|
|
151
|
+
switch (trigger) {
|
|
152
|
+
case "manual":
|
|
153
|
+
return "Manual";
|
|
154
|
+
case "auto":
|
|
155
|
+
return "Auto";
|
|
156
|
+
case "unload":
|
|
157
|
+
return "Unload";
|
|
158
|
+
case "shutdown":
|
|
159
|
+
return "Shutdown";
|
|
160
|
+
default:
|
|
161
|
+
return trigger;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function formatSnapTime(unixSeconds) {
|
|
165
|
+
const delta = Date.now() / 1e3 - unixSeconds;
|
|
166
|
+
if (delta < 60) return "just now";
|
|
167
|
+
if (delta < 3600) return `${Math.floor(delta / 60)}m ago`;
|
|
168
|
+
if (delta < 86400) return `${Math.floor(delta / 3600)}h ago`;
|
|
169
|
+
return `${Math.floor(delta / 86400)}d ago`;
|
|
170
|
+
}
|
|
171
|
+
function formatSnapSize(bytes) {
|
|
172
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
173
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
174
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
175
|
+
}
|
|
85
176
|
const registry = usePluginRegistry();
|
|
86
177
|
const metaSchema = computed(() => {
|
|
87
178
|
if (!props.docTypeKey) return [];
|
|
@@ -560,6 +651,202 @@ function patchMeta(key, value) {
|
|
|
560
651
|
</div>
|
|
561
652
|
</template>
|
|
562
653
|
|
|
654
|
+
<!-- Snapshots section -->
|
|
655
|
+
<USeparator />
|
|
656
|
+
<div class="space-y-3">
|
|
657
|
+
<div class="flex items-center justify-between">
|
|
658
|
+
<p class="text-xs font-semibold uppercase tracking-wider text-(--ui-text-muted) flex items-center gap-1.5">
|
|
659
|
+
<UIcon
|
|
660
|
+
name="i-lucide-history"
|
|
661
|
+
class="size-3.5"
|
|
662
|
+
/>
|
|
663
|
+
Snapshots
|
|
664
|
+
</p>
|
|
665
|
+
<UTooltip
|
|
666
|
+
v-if="isOwner"
|
|
667
|
+
text="Create snapshot"
|
|
668
|
+
:content="{ side: 'bottom' }"
|
|
669
|
+
>
|
|
670
|
+
<UButton
|
|
671
|
+
icon="i-lucide-plus"
|
|
672
|
+
variant="ghost"
|
|
673
|
+
color="neutral"
|
|
674
|
+
size="xs"
|
|
675
|
+
:disabled="creatingSnapshot"
|
|
676
|
+
@click="openCreateSnap"
|
|
677
|
+
/>
|
|
678
|
+
</UTooltip>
|
|
679
|
+
</div>
|
|
680
|
+
|
|
681
|
+
<!-- Error -->
|
|
682
|
+
<div
|
|
683
|
+
v-if="snapshotsError && !loadingSnapshots && snapshots.length === 0"
|
|
684
|
+
class="flex items-start gap-2 px-3 py-2.5 rounded-lg bg-(--ui-bg-elevated) text-xs text-(--ui-color-error-500)"
|
|
685
|
+
>
|
|
686
|
+
<UIcon
|
|
687
|
+
name="i-lucide-alert-circle"
|
|
688
|
+
class="size-3.5 shrink-0 mt-0.5"
|
|
689
|
+
/>
|
|
690
|
+
<span>{{ snapshotsError }}</span>
|
|
691
|
+
</div>
|
|
692
|
+
|
|
693
|
+
<!-- Loading -->
|
|
694
|
+
<div
|
|
695
|
+
v-else-if="loadingSnapshots && snapshots.length === 0"
|
|
696
|
+
class="space-y-1.5"
|
|
697
|
+
>
|
|
698
|
+
<div
|
|
699
|
+
v-for="i in 3"
|
|
700
|
+
:key="i"
|
|
701
|
+
class="flex items-center gap-3 p-3 rounded-lg bg-(--ui-bg-elevated)"
|
|
702
|
+
>
|
|
703
|
+
<USkeleton class="h-4 w-10 rounded" />
|
|
704
|
+
<USkeleton class="h-3 flex-1 rounded" />
|
|
705
|
+
<USkeleton class="h-5 w-14 rounded-full shrink-0" />
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
|
|
709
|
+
<!-- Empty -->
|
|
710
|
+
<div
|
|
711
|
+
v-else-if="snapshots.length === 0"
|
|
712
|
+
class="text-sm text-(--ui-text-muted) py-4 text-center"
|
|
713
|
+
>
|
|
714
|
+
<UIcon
|
|
715
|
+
name="i-lucide-history"
|
|
716
|
+
class="size-6 mb-1 text-(--ui-text-dimmed) mx-auto block"
|
|
717
|
+
/>
|
|
718
|
+
No snapshots yet
|
|
719
|
+
</div>
|
|
720
|
+
|
|
721
|
+
<!-- List -->
|
|
722
|
+
<ul
|
|
723
|
+
v-else
|
|
724
|
+
class="space-y-1"
|
|
725
|
+
>
|
|
726
|
+
<li
|
|
727
|
+
v-for="snap in snapshots"
|
|
728
|
+
:key="snap.version"
|
|
729
|
+
class="flex items-center gap-2.5 p-3 rounded-lg bg-(--ui-bg-elevated)"
|
|
730
|
+
>
|
|
731
|
+
<div class="flex-1 min-w-0">
|
|
732
|
+
<div class="flex items-center gap-2">
|
|
733
|
+
<span class="text-sm font-medium text-(--ui-text-highlighted) tabular-nums">
|
|
734
|
+
v{{ snap.version }}
|
|
735
|
+
</span>
|
|
736
|
+
<UBadge
|
|
737
|
+
:label="triggerLabel(snap.trigger)"
|
|
738
|
+
:color="triggerColor(snap.trigger)"
|
|
739
|
+
variant="subtle"
|
|
740
|
+
size="xs"
|
|
741
|
+
/>
|
|
742
|
+
<span
|
|
743
|
+
v-if="snap.label"
|
|
744
|
+
class="text-sm text-(--ui-text) truncate"
|
|
745
|
+
>
|
|
746
|
+
{{ snap.label }}
|
|
747
|
+
</span>
|
|
748
|
+
</div>
|
|
749
|
+
<p class="text-xs text-(--ui-text-dimmed) mt-0.5">
|
|
750
|
+
{{ formatSnapTime(snap.created_at) }} · {{ formatSnapSize(snap.size_bytes) }}
|
|
751
|
+
</p>
|
|
752
|
+
</div>
|
|
753
|
+
<UDropdownMenu
|
|
754
|
+
:items="snapshotMenu(snap.version)"
|
|
755
|
+
:content="{ align: 'end' }"
|
|
756
|
+
>
|
|
757
|
+
<UButton
|
|
758
|
+
icon="i-lucide-ellipsis"
|
|
759
|
+
color="neutral"
|
|
760
|
+
variant="ghost"
|
|
761
|
+
size="xs"
|
|
762
|
+
:loading="pendingSnapshotVersion === snap.version"
|
|
763
|
+
/>
|
|
764
|
+
</UDropdownMenu>
|
|
765
|
+
</li>
|
|
766
|
+
</ul>
|
|
767
|
+
|
|
768
|
+
<!-- Load more -->
|
|
769
|
+
<UButton
|
|
770
|
+
v-if="hasMoreSnapshots"
|
|
771
|
+
label="Load older"
|
|
772
|
+
icon="i-lucide-chevron-down"
|
|
773
|
+
color="neutral"
|
|
774
|
+
variant="ghost"
|
|
775
|
+
size="xs"
|
|
776
|
+
block
|
|
777
|
+
:loading="loadingSnapshots"
|
|
778
|
+
@click="emit('load-more-snapshots')"
|
|
779
|
+
/>
|
|
780
|
+
</div>
|
|
781
|
+
|
|
782
|
+
<!-- Create snapshot modal -->
|
|
783
|
+
<UModal
|
|
784
|
+
v-model:open="createSnapOpen"
|
|
785
|
+
title="Create a snapshot"
|
|
786
|
+
description="Save a named checkpoint of the current document state. You can restore or fork from it later."
|
|
787
|
+
>
|
|
788
|
+
<template #body>
|
|
789
|
+
<UFormField
|
|
790
|
+
label="Label"
|
|
791
|
+
hint="Optional — shown in the list"
|
|
792
|
+
size="md"
|
|
793
|
+
>
|
|
794
|
+
<UInput
|
|
795
|
+
v-model="createSnapLabel"
|
|
796
|
+
placeholder="e.g. Before rewrite"
|
|
797
|
+
size="md"
|
|
798
|
+
class="w-full"
|
|
799
|
+
autofocus
|
|
800
|
+
@keyup.enter="submitCreateSnap"
|
|
801
|
+
/>
|
|
802
|
+
</UFormField>
|
|
803
|
+
</template>
|
|
804
|
+
<template #footer>
|
|
805
|
+
<div class="flex justify-end gap-2 w-full">
|
|
806
|
+
<UButton
|
|
807
|
+
label="Cancel"
|
|
808
|
+
color="neutral"
|
|
809
|
+
variant="ghost"
|
|
810
|
+
@click="createSnapOpen = false"
|
|
811
|
+
/>
|
|
812
|
+
<UButton
|
|
813
|
+
label="Create"
|
|
814
|
+
icon="i-lucide-camera"
|
|
815
|
+
color="primary"
|
|
816
|
+
:loading="creatingSnapshot"
|
|
817
|
+
@click="submitCreateSnap"
|
|
818
|
+
/>
|
|
819
|
+
</div>
|
|
820
|
+
</template>
|
|
821
|
+
</UModal>
|
|
822
|
+
|
|
823
|
+
<!-- Delete/restore confirm modal -->
|
|
824
|
+
<UModal
|
|
825
|
+
:open="!!snapConfirm"
|
|
826
|
+
:title="snapConfirm?.kind === 'delete' ? `Delete snapshot v${snapConfirm.version}?` : `Restore snapshot v${snapConfirm?.version ?? 0}?`"
|
|
827
|
+
:description="snapConfirm?.kind === 'delete' ? 'This permanently removes the saved state. It cannot be undone.' : 'Historical content is merged forward into the current document. Existing content is preserved \u2014 this is a non-destructive merge.'"
|
|
828
|
+
@update:open="(v) => {
|
|
829
|
+
if (!v) snapConfirm = null;
|
|
830
|
+
}"
|
|
831
|
+
>
|
|
832
|
+
<template #footer>
|
|
833
|
+
<div class="flex justify-end gap-2 w-full">
|
|
834
|
+
<UButton
|
|
835
|
+
label="Cancel"
|
|
836
|
+
color="neutral"
|
|
837
|
+
variant="ghost"
|
|
838
|
+
@click="snapConfirm = null"
|
|
839
|
+
/>
|
|
840
|
+
<UButton
|
|
841
|
+
:label="snapConfirm?.kind === 'delete' ? 'Delete' : 'Restore'"
|
|
842
|
+
:icon="snapConfirm?.kind === 'delete' ? 'i-lucide-trash-2' : 'i-lucide-history'"
|
|
843
|
+
:color="snapConfirm?.kind === 'delete' ? 'error' : 'primary'"
|
|
844
|
+
@click="runSnapConfirm"
|
|
845
|
+
/>
|
|
846
|
+
</div>
|
|
847
|
+
</template>
|
|
848
|
+
</UModal>
|
|
849
|
+
|
|
563
850
|
<!-- Offline sync section -->
|
|
564
851
|
<USeparator />
|
|
565
852
|
<div class="space-y-3">
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DropdownMenuItem } from '@nuxt/ui';
|
|
2
|
+
import type { SnapshotMeta } from '@abraca/dabra';
|
|
2
3
|
import type { DocPageMeta } from '../../types.js';
|
|
4
|
+
export type { SnapshotMeta } from '@abraca/dabra';
|
|
3
5
|
export interface PermissionEntry {
|
|
4
6
|
userId: string;
|
|
5
7
|
username: string;
|
|
@@ -49,12 +51,24 @@ type __VLS_Props = {
|
|
|
49
51
|
docTypeKey?: string;
|
|
50
52
|
/** Current document meta (CRDT state) */
|
|
51
53
|
docMeta?: DocPageMeta | null;
|
|
54
|
+
/** Snapshots list for the snapshot history section */
|
|
55
|
+
snapshots?: SnapshotMeta[];
|
|
56
|
+
/** Whether the snapshot list is currently loading */
|
|
57
|
+
loadingSnapshots?: boolean;
|
|
58
|
+
/** Snapshot load error (shown inline in the section) */
|
|
59
|
+
snapshotsError?: string | null;
|
|
60
|
+
/** Whether more snapshots are available beyond the current page */
|
|
61
|
+
hasMoreSnapshots?: boolean;
|
|
62
|
+
/** Version number of the row whose mutation is currently in flight */
|
|
63
|
+
pendingSnapshotVersion?: number | null;
|
|
64
|
+
/** Whether a snapshot is currently being created */
|
|
65
|
+
creatingSnapshot?: boolean;
|
|
52
66
|
};
|
|
53
|
-
declare var __VLS_223: {},
|
|
67
|
+
declare var __VLS_223: {}, __VLS_387: {};
|
|
54
68
|
type __VLS_Slots = {} & {
|
|
55
69
|
encryption?: (props: typeof __VLS_223) => any;
|
|
56
70
|
} & {
|
|
57
|
-
extra?: (props: typeof
|
|
71
|
+
extra?: (props: typeof __VLS_387) => any;
|
|
58
72
|
};
|
|
59
73
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
60
74
|
"update-meta": (patch: Partial<DocPageMeta>) => any;
|
|
@@ -69,6 +83,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
69
83
|
"sync-doc": () => any;
|
|
70
84
|
"open-encryption": () => any;
|
|
71
85
|
"user-context-menu": (perm: PermissionEntry, items: DropdownMenuItem[][]) => any;
|
|
86
|
+
"create-snapshot": (label: string | undefined) => any;
|
|
87
|
+
"delete-snapshot": (version: number) => any;
|
|
88
|
+
"restore-snapshot": (version: number) => any;
|
|
89
|
+
"fork-snapshot": (version: number) => any;
|
|
90
|
+
"load-more-snapshots": () => any;
|
|
72
91
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
73
92
|
"onUpdate-meta"?: ((patch: Partial<DocPageMeta>) => any) | undefined;
|
|
74
93
|
"onGrant-permission"?: ((userId: string, role: string) => any) | undefined;
|
|
@@ -82,6 +101,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
82
101
|
"onSync-doc"?: (() => any) | undefined;
|
|
83
102
|
"onOpen-encryption"?: (() => any) | undefined;
|
|
84
103
|
"onUser-context-menu"?: ((perm: PermissionEntry, items: DropdownMenuItem[][]) => any) | undefined;
|
|
104
|
+
"onCreate-snapshot"?: ((label: string | undefined) => any) | undefined;
|
|
105
|
+
"onDelete-snapshot"?: ((version: number) => any) | undefined;
|
|
106
|
+
"onRestore-snapshot"?: ((version: number) => any) | undefined;
|
|
107
|
+
"onFork-snapshot"?: ((version: number) => any) | undefined;
|
|
108
|
+
"onLoad-more-snapshots"?: (() => any) | undefined;
|
|
85
109
|
}>, {
|
|
86
110
|
userName: string;
|
|
87
111
|
docMeta: DocPageMeta | null;
|
|
@@ -100,6 +124,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
100
124
|
lastSyncedLabel: string | null;
|
|
101
125
|
syncError: string | null;
|
|
102
126
|
docTypeKey: string;
|
|
127
|
+
snapshots: SnapshotMeta[];
|
|
128
|
+
loadingSnapshots: boolean;
|
|
129
|
+
snapshotsError: string | null;
|
|
130
|
+
hasMoreSnapshots: boolean;
|
|
131
|
+
pendingSnapshotVersion: number | null;
|
|
132
|
+
creatingSnapshot: boolean;
|
|
103
133
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
104
134
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
105
135
|
declare const _default: typeof __VLS_export;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ContentNavigationItem } from '../utils/content.js';
|
|
2
|
+
export interface DocsSearchFile {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
titles: string[];
|
|
6
|
+
content: string;
|
|
7
|
+
level: number;
|
|
8
|
+
}
|
|
9
|
+
export interface DocsSearchItem {
|
|
10
|
+
prefix?: string;
|
|
11
|
+
label: string;
|
|
12
|
+
suffix: string;
|
|
13
|
+
to: string;
|
|
14
|
+
icon?: string;
|
|
15
|
+
level: number;
|
|
16
|
+
}
|
|
17
|
+
declare function _useDocsSearch(): {
|
|
18
|
+
open: import("vue").Ref<boolean, boolean>;
|
|
19
|
+
mapFile: (file: DocsSearchFile, link: ContentNavigationItem, parent?: ContentNavigationItem) => DocsSearchItem;
|
|
20
|
+
mapNavigationItems: (children: ContentNavigationItem[], files: DocsSearchFile[], parent?: ContentNavigationItem) => DocsSearchItem[];
|
|
21
|
+
postFilter: (query: string, items: DocsSearchItem[]) => DocsSearchItem[];
|
|
22
|
+
};
|
|
23
|
+
export declare const useDocsSearch: typeof _useDocsSearch;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { createSharedComposable } from "@vueuse/core";
|
|
3
|
+
import { useAppConfig } from "#imports";
|
|
4
|
+
function _useDocsSearch() {
|
|
5
|
+
const open = ref(false);
|
|
6
|
+
const appConfig = useAppConfig();
|
|
7
|
+
function mapFile(file, link, parent) {
|
|
8
|
+
const prefix = [...new Set([parent?.title, ...file.titles].filter(Boolean))];
|
|
9
|
+
return {
|
|
10
|
+
prefix: prefix?.length ? prefix.join(" > ") + " >" : void 0,
|
|
11
|
+
label: file.id === link.path ? link.title : file.title,
|
|
12
|
+
suffix: file.content.replaceAll("<", "<").replaceAll(">", ">"),
|
|
13
|
+
to: file.id,
|
|
14
|
+
icon: link.icon || parent?.icon || (file.level > 1 ? appConfig.ui.icons.hash : appConfig.ui.icons.file),
|
|
15
|
+
level: file.level
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function mapNavigationItems(children, files, parent) {
|
|
19
|
+
return children.flatMap((link) => {
|
|
20
|
+
if (link.children?.length) {
|
|
21
|
+
return mapNavigationItems(link.children, files, link);
|
|
22
|
+
}
|
|
23
|
+
return files?.filter((file) => file.id === link.path || file.id.startsWith(`${link.path}#`))?.map((file) => mapFile(file, link, parent)) || [];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function postFilter(query, items) {
|
|
27
|
+
if (!query) {
|
|
28
|
+
return items?.filter((item) => item.level === 1);
|
|
29
|
+
}
|
|
30
|
+
return items;
|
|
31
|
+
}
|
|
32
|
+
return { open, mapFile, mapNavigationItems, postFilter };
|
|
33
|
+
}
|
|
34
|
+
export const useDocsSearch = /* @__PURE__ */ createSharedComposable(_useDocsSearch);
|
|
@@ -39,10 +39,10 @@ export const DocLinkDrop = Extension.create({
|
|
|
39
39
|
if (dropPos == null) return true;
|
|
40
40
|
const { schema, tr } = view.state;
|
|
41
41
|
if (de.altKey) {
|
|
42
|
-
const
|
|
43
|
-
if (!
|
|
44
|
-
const
|
|
45
|
-
tr.insert(dropPos,
|
|
42
|
+
const linkType = schema.nodes.docLink;
|
|
43
|
+
if (!linkType) return true;
|
|
44
|
+
const node = linkType.create({ docId: data.id });
|
|
45
|
+
tr.insert(dropPos, node);
|
|
46
46
|
} else {
|
|
47
47
|
const nodeType = schema.nodes.docEmbed;
|
|
48
48
|
if (!nodeType) return true;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Node } from '@tiptap/core';
|
|
2
|
+
declare module '@tiptap/core' {
|
|
3
|
+
interface Commands<ReturnType> {
|
|
4
|
+
docLink: {
|
|
5
|
+
insertDocLink: (attrs: {
|
|
6
|
+
docId: string;
|
|
7
|
+
}) => ReturnType;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export declare const DocLink: Node<any, any>;
|
|
12
|
+
export default DocLink;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Node, mergeAttributes, nodePasteRule, nodeInputRule } from "@tiptap/core";
|
|
2
|
+
import { VueNodeViewRenderer } from "@tiptap/vue-3";
|
|
3
|
+
import DocLinkView from "./views/DocLinkView.vue";
|
|
4
|
+
const INLINE_REGEX = /(?<!!)\[\[([a-f0-9-]{36})(?:\|[^\]]+)?\]\]/gi;
|
|
5
|
+
export const DocLink = Node.create({
|
|
6
|
+
name: "docLink",
|
|
7
|
+
group: "inline",
|
|
8
|
+
inline: true,
|
|
9
|
+
atom: true,
|
|
10
|
+
selectable: true,
|
|
11
|
+
draggable: true,
|
|
12
|
+
addAttributes() {
|
|
13
|
+
return {
|
|
14
|
+
docId: { default: null }
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
parseHTML() {
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
tag: "span[data-doc-link]",
|
|
21
|
+
getAttrs: (el) => ({ docId: el.getAttribute("data-doc-id") })
|
|
22
|
+
}
|
|
23
|
+
];
|
|
24
|
+
},
|
|
25
|
+
renderHTML({ HTMLAttributes }) {
|
|
26
|
+
return ["span", mergeAttributes({
|
|
27
|
+
"data-doc-link": "",
|
|
28
|
+
"data-doc-id": HTMLAttributes.docId
|
|
29
|
+
})];
|
|
30
|
+
},
|
|
31
|
+
addNodeView() {
|
|
32
|
+
return VueNodeViewRenderer(DocLinkView);
|
|
33
|
+
},
|
|
34
|
+
addCommands() {
|
|
35
|
+
return {
|
|
36
|
+
insertDocLink: (attrs) => ({ commands }) => {
|
|
37
|
+
return commands.insertContent({ type: this.name, attrs });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
addInputRules() {
|
|
42
|
+
return [
|
|
43
|
+
nodeInputRule({
|
|
44
|
+
find: INLINE_REGEX,
|
|
45
|
+
type: this.type,
|
|
46
|
+
getAttributes: (match) => ({ docId: match[1] })
|
|
47
|
+
})
|
|
48
|
+
];
|
|
49
|
+
},
|
|
50
|
+
addPasteRules() {
|
|
51
|
+
return [
|
|
52
|
+
nodePasteRule({
|
|
53
|
+
find: INLINE_REGEX,
|
|
54
|
+
type: this.type,
|
|
55
|
+
getAttributes: (match) => ({ docId: match[1] })
|
|
56
|
+
})
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
export default DocLink;
|
|
@@ -57,12 +57,16 @@ function navigate() {
|
|
|
57
57
|
}
|
|
58
58
|
const collapsed = computed(() => props.node.attrs.collapsed);
|
|
59
59
|
const tall = computed(() => props.node.attrs.tall);
|
|
60
|
+
const seamless = computed(() => props.node.attrs.seamless);
|
|
60
61
|
function toggleCollapsed() {
|
|
61
62
|
props.updateAttributes({ collapsed: !collapsed.value });
|
|
62
63
|
}
|
|
63
64
|
function toggleHeight() {
|
|
64
65
|
props.updateAttributes({ tall: !tall.value });
|
|
65
66
|
}
|
|
67
|
+
function toggleSeamless() {
|
|
68
|
+
props.updateAttributes({ seamless: !seamless.value });
|
|
69
|
+
}
|
|
66
70
|
function removeEmbed() {
|
|
67
71
|
props.deleteNode();
|
|
68
72
|
}
|
|
@@ -73,6 +77,7 @@ function removeEmbed() {
|
|
|
73
77
|
<div
|
|
74
78
|
v-if="exists"
|
|
75
79
|
class="de-container"
|
|
80
|
+
:class="{ 'de-seamless': seamless }"
|
|
76
81
|
contenteditable="false"
|
|
77
82
|
>
|
|
78
83
|
<!-- Header -->
|
|
@@ -100,6 +105,7 @@ function removeEmbed() {
|
|
|
100
105
|
|
|
101
106
|
<div class="de-controls">
|
|
102
107
|
<button
|
|
108
|
+
v-if="!seamless"
|
|
103
109
|
class="de-ctrl-btn de-ctrl-toggle"
|
|
104
110
|
@click.stop="toggleCollapsed"
|
|
105
111
|
>
|
|
@@ -109,7 +115,7 @@ function removeEmbed() {
|
|
|
109
115
|
/>
|
|
110
116
|
</button>
|
|
111
117
|
<button
|
|
112
|
-
v-if="!collapsed"
|
|
118
|
+
v-if="!seamless && !collapsed"
|
|
113
119
|
class="de-ctrl-btn"
|
|
114
120
|
@click.stop="toggleHeight"
|
|
115
121
|
>
|
|
@@ -118,6 +124,15 @@ function removeEmbed() {
|
|
|
118
124
|
class="size-3"
|
|
119
125
|
/>
|
|
120
126
|
</button>
|
|
127
|
+
<button
|
|
128
|
+
class="de-ctrl-btn"
|
|
129
|
+
@click.stop="toggleSeamless"
|
|
130
|
+
>
|
|
131
|
+
<UIcon
|
|
132
|
+
:name="seamless ? 'i-lucide-frame' : 'i-lucide-square-dashed'"
|
|
133
|
+
class="size-3"
|
|
134
|
+
/>
|
|
135
|
+
</button>
|
|
121
136
|
<button
|
|
122
137
|
class="de-ctrl-btn de-ctrl-expand"
|
|
123
138
|
@click.stop="navigate"
|
|
@@ -142,7 +157,7 @@ function removeEmbed() {
|
|
|
142
157
|
<!-- Body -->
|
|
143
158
|
<div
|
|
144
159
|
class="de-body"
|
|
145
|
-
:class="{ 'de-body-tall': tall, 'de-body-collapsed': collapsed }"
|
|
160
|
+
:class="{ 'de-body-tall': tall && !seamless, 'de-body-collapsed': collapsed && !seamless, 'de-body-seamless': seamless }"
|
|
146
161
|
>
|
|
147
162
|
<div
|
|
148
163
|
v-if="isLoading"
|
|
@@ -194,5 +209,5 @@ function removeEmbed() {
|
|
|
194
209
|
</template>
|
|
195
210
|
|
|
196
211
|
<style scoped>
|
|
197
|
-
.de-container{background:var(--ui-bg);border:1px solid var(--ui-border);border-radius:10px;box-shadow:0 8px 32px rgba(0,0,0,.1),0 2px 8px rgba(0,0,0,.06);overflow:hidden;transition:box-shadow .28s ease}.de-container:has(.de-body-collapsed){box-shadow:0 2px 8px rgba(0,0,0,.06),0 1px 3px rgba(0,0,0,.04)}.de-header{align-items:center;background:var(--ui-bg-elevated);border-bottom:1px solid var(--ui-border);cursor:grab;display:flex;flex-shrink:0;gap:6px;height:34px;padding:0 4px 0 10px;transition:border-color .28s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}.de-header:active{cursor:grabbing}.de-header-collapsed{border-bottom-color:transparent}.de-body{display:flex;flex-direction:column;height:400px;overflow:hidden;transition:height .28s cubic-bezier(.4,0,.2,1),border-color .28s ease}.de-body-tall{height:700px}.de-body-collapsed{height:0}.de-title{align-items:center;display:flex;flex:1;gap:4px;min-width:0;overflow:hidden;pointer-events:none}.de-title button,.de-title-link{pointer-events:auto}.de-title-link{cursor:pointer}.de-title-link .de-title-text{color:var(--ui-primary);text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .12s}.de-title-link:hover .de-title-text{text-decoration-color:var(--ui-primary)}.de-title-text{color:var(--ui-text-muted);font-size:12px;font-weight:500;overflow:hidden;pointer-events:none;text-overflow:ellipsis;white-space:nowrap}.de-type-btn{align-items:center;border-radius:var(--ui-radius);color:var(--ui-text-muted);cursor:pointer;display:flex;flex-shrink:0;height:22px;justify-content:center;transition:background .1s,color .1s;width:22px}.de-type-btn:hover{background:var(--ui-bg-accented);color:var(--ui-text)}.de-controls{flex-shrink:0;gap:2px}.de-controls,.de-ctrl-btn{align-items:center;display:flex}.de-ctrl-btn{border-radius:50%;color:var(--ui-text-dimmed);cursor:pointer;height:22px;justify-content:center;transition:background .12s,color .12s;width:22px}.de-ctrl-btn:hover{background:var(--ui-bg-accented);color:var(--ui-text)}.de-ctrl-expand:hover{background:rgba(34,197,94,.15);color:#148f3e}.de-ctrl-close:hover{background:rgba(239,68,68,.15);color:#dc2626}
|
|
212
|
+
.de-container{background:var(--ui-bg);border:1px solid var(--ui-border);border-radius:10px;box-shadow:0 8px 32px rgba(0,0,0,.1),0 2px 8px rgba(0,0,0,.06);overflow:hidden;transition:box-shadow .28s ease}.de-container:has(.de-body-collapsed){box-shadow:0 2px 8px rgba(0,0,0,.06),0 1px 3px rgba(0,0,0,.04)}.de-header{align-items:center;background:var(--ui-bg-elevated);border-bottom:1px solid var(--ui-border);cursor:grab;display:flex;flex-shrink:0;gap:6px;height:34px;padding:0 4px 0 10px;transition:border-color .28s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}.de-header:active{cursor:grabbing}.de-header-collapsed{border-bottom-color:transparent}.de-body{display:flex;flex-direction:column;height:400px;overflow:hidden;transition:height .28s cubic-bezier(.4,0,.2,1),border-color .28s ease}.de-body-tall{height:700px}.de-body-collapsed{height:0}.de-seamless{background:transparent;border:none;box-shadow:none}.de-seamless .de-header{background:transparent;border-bottom:none;height:26px;opacity:0;pointer-events:none;transition:opacity .15s ease}.de-seamless:focus-within .de-header,.de-seamless:hover .de-header{opacity:1;pointer-events:auto}.de-body-seamless{height:auto;min-height:0}.de-title{align-items:center;display:flex;flex:1;gap:4px;min-width:0;overflow:hidden;pointer-events:none}.de-title button,.de-title-link{pointer-events:auto}.de-title-link{cursor:pointer}.de-title-link .de-title-text{color:var(--ui-primary);text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .12s}.de-title-link:hover .de-title-text{text-decoration-color:var(--ui-primary)}.de-title-text{color:var(--ui-text-muted);font-size:12px;font-weight:500;overflow:hidden;pointer-events:none;text-overflow:ellipsis;white-space:nowrap}.de-type-btn{align-items:center;border-radius:var(--ui-radius);color:var(--ui-text-muted);cursor:pointer;display:flex;flex-shrink:0;height:22px;justify-content:center;transition:background .1s,color .1s;width:22px}.de-type-btn:hover{background:var(--ui-bg-accented);color:var(--ui-text)}.de-controls{flex-shrink:0;gap:2px}.de-controls,.de-ctrl-btn{align-items:center;display:flex}.de-ctrl-btn{border-radius:50%;color:var(--ui-text-dimmed);cursor:pointer;height:22px;justify-content:center;transition:background .12s,color .12s;width:22px}.de-ctrl-btn:hover{background:var(--ui-bg-accented);color:var(--ui-text)}.de-ctrl-expand:hover{background:rgba(34,197,94,.15);color:#148f3e}.de-ctrl-close:hover{background:rgba(239,68,68,.15);color:#dc2626}
|
|
198
213
|
</style>
|
|
@@ -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;
|