@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.
Files changed (54) hide show
  1. package/README.md +27 -2
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +42 -4
  4. package/dist/runtime/components/docs/ADocsNavigation.d.vue.ts +155 -0
  5. package/dist/runtime/components/docs/ADocsNavigation.vue +154 -0
  6. package/dist/runtime/components/docs/ADocsNavigation.vue.d.ts +155 -0
  7. package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +249 -0
  8. package/dist/runtime/components/docs/ADocsSearch.vue +187 -0
  9. package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +249 -0
  10. package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +253 -0
  11. package/dist/runtime/components/docs/ADocsSearchButton.vue +99 -0
  12. package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +253 -0
  13. package/dist/runtime/components/docs/ADocsSurround.d.vue.ts +56 -0
  14. package/dist/runtime/components/docs/ADocsSurround.vue +68 -0
  15. package/dist/runtime/components/docs/ADocsSurround.vue.d.ts +56 -0
  16. package/dist/runtime/components/docs/ADocsToc.d.vue.ts +117 -0
  17. package/dist/runtime/components/docs/ADocsToc.vue +194 -0
  18. package/dist/runtime/components/docs/ADocsToc.vue.d.ts +117 -0
  19. package/dist/runtime/components/editor/ADocLinkPopover.vue +1 -5
  20. package/dist/runtime/components/renderers/AMediaRenderer.vue +1 -1
  21. package/dist/runtime/components/shell/ADocPanelSettings.d.vue.ts +32 -2
  22. package/dist/runtime/components/shell/ADocPanelSettings.vue +289 -2
  23. package/dist/runtime/components/shell/ADocPanelSettings.vue.d.ts +32 -2
  24. package/dist/runtime/composables/useDocsSearch.d.ts +24 -0
  25. package/dist/runtime/composables/useDocsSearch.js +34 -0
  26. package/dist/runtime/extensions/doc-embed.js +2 -1
  27. package/dist/runtime/extensions/doc-link-drop.js +4 -4
  28. package/dist/runtime/extensions/doc-link.d.ts +12 -0
  29. package/dist/runtime/extensions/doc-link.js +60 -0
  30. package/dist/runtime/extensions/views/DocEmbedView.vue +18 -3
  31. package/dist/runtime/extensions/views/DocLinkView.d.vue.ts +4 -0
  32. package/dist/runtime/extensions/views/DocLinkView.vue +71 -0
  33. package/dist/runtime/extensions/views/DocLinkView.vue.d.ts +4 -0
  34. package/dist/runtime/plugin-abracadabra.client.js +18 -2
  35. package/dist/runtime/plugins/core.plugin.js +3 -0
  36. package/dist/runtime/server/plugins/abracadabra-service.js +3 -1
  37. package/dist/runtime/theme/content/_shared.d.ts +6 -0
  38. package/dist/runtime/theme/content/_shared.js +6 -0
  39. package/dist/runtime/theme/content/content-navigation.d.ts +120 -0
  40. package/dist/runtime/theme/content/content-navigation.js +155 -0
  41. package/dist/runtime/theme/content/content-search-button.d.ts +16 -0
  42. package/dist/runtime/theme/content/content-search-button.js +15 -0
  43. package/dist/runtime/theme/content/content-search.d.ts +24 -0
  44. package/dist/runtime/theme/content/content-search.js +23 -0
  45. package/dist/runtime/theme/content/content-surround.d.ts +22 -0
  46. package/dist/runtime/theme/content/content-surround.js +23 -0
  47. package/dist/runtime/theme/content/content-toc.d.ts +84 -0
  48. package/dist/runtime/theme/content/content-toc.js +94 -0
  49. package/dist/runtime/theme/content/index.d.ts +5 -0
  50. package/dist/runtime/theme/content/index.js +5 -0
  51. package/dist/runtime/utils/content.d.ts +19 -0
  52. package/dist/runtime/utils/content.js +23 -0
  53. package/dist/runtime/utils/docReferenceEdges.js +1 -1
  54. 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: {}, __VLS_258: {};
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 __VLS_258) => any;
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("<", "&lt;").replaceAll(">", "&gt;"),
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);
@@ -12,7 +12,8 @@ export const DocEmbed = Node.create({
12
12
  return {
13
13
  docId: { default: null },
14
14
  collapsed: { default: false },
15
- tall: { default: false }
15
+ tall: { default: false },
16
+ seamless: { default: false }
16
17
  };
17
18
  },
18
19
  parseHTML() {
@@ -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 linkMark = schema.marks.link?.create({ href: `/doc/${data.id}` });
43
- if (!linkMark) return true;
44
- const textNode = schema.text(data.label, [linkMark]);
45
- tr.insert(dropPos, textNode);
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;