Skip to content

Commit fa58f69

Browse files
committed
Add Vue support and document scoping to plugins
Introduces Vue composables and hooks for attachment, bookmark, and document manager plugins. Refactors plugin capabilities to support document-scoped operations. Removes plugin-loader package and related files, migrating file-picker and document tab logic to document manager. Improves core state management and document state access for Vue. Updates interaction manager and capture plugins for better document scoping and state handling.
1 parent f2747f1 commit fa58f69

57 files changed

Lines changed: 497 additions & 1005 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/core/src/vue/composables/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './use-core-state';
2+
export * from './use-document-state';
23
export * from './use-registry';
34
export * from './use-plugin';
45
export * from './use-capability';
Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,40 @@
1-
import { ref, onMounted, onBeforeUnmount } from 'vue';
2-
import { arePropsEqual, type CoreState } from '@embedpdf/core';
1+
import { ref, watch } from 'vue';
2+
import { type CoreState } from '@embedpdf/core';
33
import { useRegistry } from './use-registry';
44

5+
/**
6+
* Hook that provides access to the current core state
7+
* and re-renders the component only when the core state changes
8+
*/
59
export function useCoreState() {
610
const { registry } = useRegistry();
7-
const core = ref<CoreState>();
11+
const coreState = ref<CoreState | null>(null);
812

9-
onMounted(() => {
10-
const store = registry.value!.getStore();
11-
core.value = store.getState().core;
12-
13-
const unsub = store.subscribe((action, newSt, oldSt) => {
14-
if (store.isCoreAction(action) && !arePropsEqual(newSt.core, oldSt.core)) {
15-
core.value = newSt.core;
13+
watch(
14+
registry,
15+
(registryValue, _, onCleanup) => {
16+
if (!registryValue) {
17+
coreState.value = null;
18+
return;
1619
}
17-
});
18-
onBeforeUnmount(unsub);
19-
});
2020

21-
return core;
21+
const store = registryValue.getStore();
22+
23+
// Get initial core state
24+
coreState.value = store.getState().core;
25+
26+
// Create a single subscription that handles all core actions
27+
const unsubscribe = store.subscribe((action, newState, oldState) => {
28+
// Only update if it's a core action and the core state changed
29+
if (store.isCoreAction(action) && newState.core !== oldState.core) {
30+
coreState.value = newState.core;
31+
}
32+
});
33+
34+
onCleanup(unsubscribe);
35+
},
36+
{ immediate: true },
37+
);
38+
39+
return coreState;
2240
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { computed, toValue, type MaybeRefOrGetter } from 'vue';
2+
import { useCoreState } from './use-core-state';
3+
4+
/**
5+
* Hook that provides reactive access to a specific document's state from the core store.
6+
*
7+
* @param documentId The ID of the document to retrieve (can be ref, computed, getter, or plain value).
8+
* @returns A computed ref containing the DocumentState object or null if not found.
9+
*/
10+
export function useDocumentState(documentId: MaybeRefOrGetter<string | null>) {
11+
const coreState = useCoreState();
12+
13+
const documentState = computed(() => {
14+
const core = coreState.value;
15+
const docId = toValue(documentId);
16+
17+
if (!core || !docId) return null;
18+
return core.documents[docId] ?? null;
19+
});
20+
21+
return documentState;
22+
}

packages/plugin-attachment/package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,19 @@
2121
"types": "./dist/react/index.d.ts",
2222
"import": "./dist/react/index.js",
2323
"require": "./dist/react/index.cjs"
24+
},
25+
"./vue": {
26+
"types": "./dist/vue/index.d.ts",
27+
"import": "./dist/vue/index.js",
28+
"require": "./dist/vue/index.cjs"
2429
}
2530
},
2631
"scripts": {
2732
"build:base": "vite build --mode base",
2833
"build:react": "vite build --mode react",
2934
"build:preact": "vite build --mode preact",
30-
"build": "pnpm run clean && concurrently -c auto -n base,react,preact \"vite build --mode base\" \"vite build --mode react\" \"vite build --mode preact\"",
35+
"build:vue": "vite build --mode vue",
36+
"build": "pnpm run clean && concurrently -c auto -n base,react,preact,vue \"vite build --mode base\" \"vite build --mode react\" \"vite build --mode preact\" \"vite build --mode vue\"",
3137
"clean": "rimraf dist",
3238
"lint": "eslint src --color",
3339
"lint:fix": "eslint src --color --fix"
@@ -45,7 +51,8 @@
4551
"@embedpdf/core": "workspace:*",
4652
"react": ">=16.8.0",
4753
"react-dom": ">=16.8.0",
48-
"preact": "^10.26.4"
54+
"preact": "^10.26.4",
55+
"vue": ">=3.2.0"
4956
},
5057
"files": [
5158
"dist",
Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { BasePlugin, createEmitter, PluginRegistry } from '@embedpdf/core';
1+
import { BasePlugin, PluginRegistry } from '@embedpdf/core';
22

3-
import { AttachmentCapability, AttachmentPluginConfig } from './types';
3+
import { AttachmentCapability, AttachmentPluginConfig, AttachmentScope } from './types';
44
import {
55
PdfAttachmentObject,
66
PdfErrorCode,
@@ -18,30 +18,64 @@ export class AttachmentPlugin extends BasePlugin<AttachmentPluginConfig, Attachm
1818

1919
async initialize(_: AttachmentPluginConfig): Promise<void> {}
2020

21+
// ─────────────────────────────────────────────────────────
22+
// Capability
23+
// ─────────────────────────────────────────────────────────
24+
2125
protected buildCapability(): AttachmentCapability {
2226
return {
23-
getAttachments: this.getAttachments.bind(this),
24-
downloadAttachment: this.downloadAttachment.bind(this),
27+
// Active document operations
28+
getAttachments: () => this.getAttachments(),
29+
downloadAttachment: (attachment) => this.downloadAttachment(attachment),
30+
31+
// Document-scoped operations
32+
forDocument: (documentId: string) => this.createAttachmentScope(documentId),
33+
};
34+
}
35+
36+
// ─────────────────────────────────────────────────────────
37+
// Document Scoping
38+
// ─────────────────────────────────────────────────────────
39+
40+
private createAttachmentScope(documentId: string): AttachmentScope {
41+
return {
42+
getAttachments: () => this.getAttachments(documentId),
43+
downloadAttachment: (attachment) => this.downloadAttachment(attachment, documentId),
2544
};
2645
}
2746

28-
private downloadAttachment(attachment: PdfAttachmentObject): Task<ArrayBuffer, PdfErrorReason> {
29-
const doc = this.coreState.core.document;
47+
// ─────────────────────────────────────────────────────────
48+
// Core Operations
49+
// ─────────────────────────────────────────────────────────
50+
51+
private downloadAttachment(
52+
attachment: PdfAttachmentObject,
53+
documentId?: string,
54+
): Task<ArrayBuffer, PdfErrorReason> {
55+
const id = documentId ?? this.getActiveDocumentId();
56+
const coreDoc = this.coreState.core.documents[id];
3057

31-
if (!doc) {
32-
return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: 'Document not found' });
58+
if (!coreDoc?.document) {
59+
return PdfTaskHelper.reject({
60+
code: PdfErrorCode.NotFound,
61+
message: `Document ${id} not found`,
62+
});
3363
}
3464

35-
return this.engine.readAttachmentContent(doc, attachment);
65+
return this.engine.readAttachmentContent(coreDoc.document, attachment);
3666
}
3767

38-
private getAttachments(): Task<PdfAttachmentObject[], PdfErrorReason> {
39-
const doc = this.coreState.core.document;
68+
private getAttachments(documentId?: string): Task<PdfAttachmentObject[], PdfErrorReason> {
69+
const id = documentId ?? this.getActiveDocumentId();
70+
const coreDoc = this.coreState.core.documents[id];
4071

41-
if (!doc) {
42-
return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: 'Document not found' });
72+
if (!coreDoc?.document) {
73+
return PdfTaskHelper.reject({
74+
code: PdfErrorCode.NotFound,
75+
message: `Document ${id} not found`,
76+
});
4377
}
4478

45-
return this.engine.getAttachments(doc);
79+
return this.engine.getAttachments(coreDoc.document);
4680
}
4781
}

packages/plugin-attachment/src/lib/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@ import { PdfAttachmentObject, PdfErrorReason, Task } from '@embedpdf/models';
33

44
export interface AttachmentPluginConfig extends BasePluginConfig {}
55

6+
// Scoped attachment capability for a specific document
7+
export interface AttachmentScope {
8+
getAttachments(): Task<PdfAttachmentObject[], PdfErrorReason>;
9+
downloadAttachment(attachment: PdfAttachmentObject): Task<ArrayBuffer, PdfErrorReason>;
10+
}
11+
612
export interface AttachmentCapability {
13+
// Active document operations
714
getAttachments: () => Task<PdfAttachmentObject[], PdfErrorReason>;
815
downloadAttachment: (attachment: PdfAttachmentObject) => Task<ArrayBuffer, PdfErrorReason>;
16+
17+
// Document-scoped operations
18+
forDocument(documentId: string): AttachmentScope;
919
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './use-attachment';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { useCapability, usePlugin } from '@embedpdf/core/vue';
2+
import { AttachmentPlugin } from '@embedpdf/plugin-attachment';
3+
4+
export const useAttachmentPlugin = () => usePlugin<AttachmentPlugin>(AttachmentPlugin.id);
5+
export const useAttachmentCapability = () => useCapability<AttachmentPlugin>(AttachmentPlugin.id);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './hooks';
2+
export * from '@embedpdf/plugin-attachment';

packages/plugin-loader/src/vue/tsconfig.vue.json renamed to packages/plugin-attachment/src/vue/tsconfig.vue.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"types": ["vue"],
66
"baseUrl": "../..",
77
"paths": {
8-
"@embedpdf/plugin-loader": ["src/lib/index.ts"]
8+
"@embedpdf/plugin-attachment": ["src/lib/index.ts"]
99
}
1010
},
1111
"include": ["./**/*.ts", "./**/*.vue"]

0 commit comments

Comments
 (0)