Skip to content

Commit 6a2313c

Browse files
committed
Add new Svelte examples and update docs
Introduces new Svelte example components for annotation, commands, document manager, i18n, password, redaction, and view manager features. Updates existing example content for capture, export, and other plugins with improved UI and logic. Adds corresponding TypeScript entry points and code examples to website documentation. Expands Svelte plugin documentation with new .mdx files for each plugin and updates meta and getting started docs.
1 parent bcb1e43 commit 6a2313c

File tree

79 files changed

+5415
-1317
lines changed

Some content is hidden

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

79 files changed

+5415
-1317
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<script lang="ts">
2+
import { onMount, onDestroy } from 'svelte';
3+
import { Viewport } from '@embedpdf/plugin-viewport/svelte';
4+
import { Scroller, type RenderPageProps } from '@embedpdf/plugin-scroll/svelte';
5+
import { RenderLayer } from '@embedpdf/plugin-render/svelte';
6+
import { AnnotationLayer, useAnnotationCapability } from '@embedpdf/plugin-annotation/svelte';
7+
import { PagePointerProvider } from '@embedpdf/plugin-interaction-manager/svelte';
8+
import { SelectionLayer } from '@embedpdf/plugin-selection/svelte';
9+
import { Check, X, Pencil, Square, Highlighter, Trash2 } from 'lucide-svelte';
10+
11+
interface Props {
12+
documentId: string;
13+
}
14+
15+
let { documentId }: Props = $props();
16+
17+
let activeTool = $state<string | null>(null);
18+
let canDelete = $state(false);
19+
20+
const annotationCapability = useAnnotationCapability();
21+
const annotationApi = $derived(annotationCapability.provides?.forDocument(documentId));
22+
23+
const tools = [
24+
{ id: 'stampCheckmark', name: 'Checkmark', icon: Check },
25+
{ id: 'stampCross', name: 'Cross', icon: X },
26+
{ id: 'ink', name: 'Pen', icon: Pencil },
27+
{ id: 'square', name: 'Square', icon: Square },
28+
{ id: 'highlight', name: 'Highlight', icon: Highlighter },
29+
];
30+
31+
let unsubscribeToolChange: (() => void) | undefined;
32+
let unsubscribeStateChange: (() => void) | undefined;
33+
34+
onMount(() => {
35+
if (!annotationApi) return;
36+
37+
unsubscribeToolChange = annotationApi.onActiveToolChange((tool) => {
38+
activeTool = tool?.id ?? null;
39+
});
40+
41+
unsubscribeStateChange = annotationApi.onStateChange((state) => {
42+
canDelete = !!state.selectedUid;
43+
});
44+
});
45+
46+
onDestroy(() => {
47+
unsubscribeToolChange?.();
48+
unsubscribeStateChange?.();
49+
});
50+
51+
const handleToolClick = (toolId: string) => {
52+
annotationApi?.setActiveTool(activeTool === toolId ? null : toolId);
53+
};
54+
55+
const handleDelete = () => {
56+
const selection = annotationApi?.getSelectedAnnotation();
57+
if (selection) {
58+
annotationApi?.deleteAnnotation(selection.object.pageIndex, selection.object.id);
59+
}
60+
};
61+
62+
const handleDeleteFromMenu = (pageIndex: number, id: string) => {
63+
annotationApi?.deleteAnnotation(pageIndex, id);
64+
};
65+
</script>
66+
67+
<div
68+
class="overflow-hidden rounded-lg border border-gray-300 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-900"
69+
style="user-select: none"
70+
>
71+
<!-- Toolbar -->
72+
<div
73+
class="flex flex-wrap items-center gap-3 border-b border-gray-300 bg-gray-100 px-3 py-2 dark:border-gray-700 dark:bg-gray-800"
74+
>
75+
<span class="text-xs font-medium uppercase tracking-wide text-gray-600 dark:text-gray-300">
76+
Tools
77+
</span>
78+
<div class="h-4 w-px bg-gray-300 dark:bg-gray-600"></div>
79+
<div class="flex items-center gap-1.5">
80+
{#each tools as tool (tool.id)}
81+
<button
82+
type="button"
83+
onclick={() => handleToolClick(tool.id)}
84+
class={[
85+
'inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium shadow-sm transition-all',
86+
activeTool === tool.id
87+
? 'bg-blue-500 text-white ring-1 ring-blue-600'
88+
: 'bg-white text-gray-600 ring-1 ring-gray-300 hover:bg-gray-50 hover:text-gray-900 dark:bg-gray-700 dark:text-gray-300 dark:ring-gray-600 dark:hover:bg-gray-600 dark:hover:text-gray-100',
89+
].join(' ')}
90+
title={tool.name}
91+
>
92+
<tool.icon size={14} />
93+
<span class="hidden sm:inline">{tool.name}</span>
94+
</button>
95+
{/each}
96+
</div>
97+
<div class="h-4 w-px bg-gray-300 dark:bg-gray-600"></div>
98+
<button
99+
type="button"
100+
onclick={handleDelete}
101+
disabled={!canDelete}
102+
class="inline-flex items-center gap-1.5 rounded-md bg-red-500 px-2.5 py-1.5 text-xs font-medium text-white shadow-sm transition-all hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
103+
>
104+
<Trash2 size={14} />
105+
<span class="hidden sm:inline">Delete</span>
106+
</button>
107+
</div>
108+
109+
<!-- PDF Viewer Area -->
110+
<div class="relative h-[450px] sm:h-[550px]">
111+
{#snippet renderPage(page: RenderPageProps)}
112+
<PagePointerProvider {documentId} pageIndex={page.pageIndex}>
113+
<RenderLayer
114+
{documentId}
115+
pageIndex={page.pageIndex}
116+
scale={1}
117+
class="pointer-events-none"
118+
/>
119+
<SelectionLayer {documentId} pageIndex={page.pageIndex} />
120+
<AnnotationLayer {documentId} pageIndex={page.pageIndex}>
121+
{#snippet selectionMenuSnippet({ selected, context, menuWrapperProps, rect })}
122+
{#if selected}
123+
<span style={menuWrapperProps.style} use:menuWrapperProps.action>
124+
<div
125+
class="rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800"
126+
style:position="absolute"
127+
style:top="{rect.size.height + 8}px"
128+
style:pointer-events="auto"
129+
style:cursor="default"
130+
>
131+
<div class="flex items-center gap-1 px-2 py-1">
132+
<button
133+
type="button"
134+
onclick={() =>
135+
handleDeleteFromMenu(
136+
context.annotation.object.pageIndex,
137+
context.annotation.object.id,
138+
)}
139+
class="flex items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-red-600 dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-red-400"
140+
aria-label="Delete annotation"
141+
title="Delete annotation"
142+
>
143+
<Trash2 size={16} />
144+
</button>
145+
</div>
146+
</div>
147+
</span>
148+
{/if}
149+
{/snippet}
150+
</AnnotationLayer>
151+
</PagePointerProvider>
152+
{/snippet}
153+
<Viewport {documentId} class="absolute inset-0 bg-gray-200 dark:bg-gray-800">
154+
<Scroller {documentId} {renderPage} />
155+
</Viewport>
156+
</div>
157+
</div>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<script lang="ts">
2+
import { usePdfiumEngine } from '@embedpdf/engines/svelte';
3+
import { EmbedPDF } from '@embedpdf/core/svelte';
4+
import { createPluginRegistration, type PluginRegistry } from '@embedpdf/core';
5+
import {
6+
DocumentManagerPluginPackage,
7+
DocumentContent,
8+
} from '@embedpdf/plugin-document-manager/svelte';
9+
import { ViewportPluginPackage } from '@embedpdf/plugin-viewport/svelte';
10+
import { ScrollPluginPackage } from '@embedpdf/plugin-scroll/svelte';
11+
import { RenderPluginPackage } from '@embedpdf/plugin-render/svelte';
12+
import {
13+
type AnnotationPlugin,
14+
AnnotationPluginPackage,
15+
type AnnotationTool,
16+
} from '@embedpdf/plugin-annotation/svelte';
17+
import { InteractionManagerPluginPackage } from '@embedpdf/plugin-interaction-manager/svelte';
18+
import { SelectionPluginPackage } from '@embedpdf/plugin-selection/svelte';
19+
import { HistoryPluginPackage } from '@embedpdf/plugin-history/svelte';
20+
import { PdfAnnotationSubtype, type PdfStampAnnoObject } from '@embedpdf/models';
21+
import AnnotationExampleContent from './annotation-example-content.svelte';
22+
23+
const pdfEngine = usePdfiumEngine();
24+
25+
const plugins = [
26+
createPluginRegistration(DocumentManagerPluginPackage, {
27+
initialDocuments: [{ url: 'https://snippet.embedpdf.com/ebook.pdf' }],
28+
}),
29+
createPluginRegistration(ViewportPluginPackage),
30+
createPluginRegistration(ScrollPluginPackage),
31+
createPluginRegistration(RenderPluginPackage),
32+
createPluginRegistration(InteractionManagerPluginPackage),
33+
createPluginRegistration(SelectionPluginPackage),
34+
createPluginRegistration(HistoryPluginPackage),
35+
createPluginRegistration(AnnotationPluginPackage, {
36+
annotationAuthor: 'EmbedPDF User',
37+
}),
38+
];
39+
40+
const handleInitialized = async (registry: PluginRegistry) => {
41+
const annotation = registry.getPlugin<AnnotationPlugin>('annotation')?.provides();
42+
43+
annotation?.addTool<AnnotationTool<PdfStampAnnoObject>>({
44+
id: 'stampCheckmark',
45+
name: 'Checkmark',
46+
interaction: {
47+
exclusive: false,
48+
cursor: 'crosshair',
49+
},
50+
matchScore: () => 0,
51+
defaults: {
52+
type: PdfAnnotationSubtype.STAMP,
53+
imageSrc: '/circle-checkmark.png',
54+
imageSize: { width: 30, height: 30 },
55+
},
56+
});
57+
58+
annotation?.addTool<AnnotationTool<PdfStampAnnoObject>>({
59+
id: 'stampCross',
60+
name: 'Cross',
61+
interaction: {
62+
exclusive: false,
63+
cursor: 'crosshair',
64+
},
65+
matchScore: () => 0,
66+
defaults: {
67+
type: PdfAnnotationSubtype.STAMP,
68+
imageSrc: '/circle-cross.png',
69+
imageSize: { width: 30, height: 30 },
70+
},
71+
});
72+
};
73+
</script>
74+
75+
{#if pdfEngine.isLoading || !pdfEngine.engine}
76+
<div>Loading PDF Engine...</div>
77+
{:else}
78+
<EmbedPDF engine={pdfEngine.engine} {plugins} onInitialized={handleInitialized}>
79+
{#snippet children({ activeDocumentId })}
80+
{#if activeDocumentId}
81+
<DocumentContent documentId={activeDocumentId}>
82+
{#snippet children(documentContent)}
83+
{#if documentContent.isLoaded}
84+
<AnnotationExampleContent documentId={activeDocumentId} />
85+
{/if}
86+
{/snippet}
87+
</DocumentContent>
88+
{/if}
89+
{/snippet}
90+
</EmbedPDF>
91+
{/if}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './annotation-example.svelte';

0 commit comments

Comments
 (0)