@rsj-de/pdfmergesortsplit 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RSJ Software GmbH, Germering, Germany
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,165 @@
1
- # Temporary Holding Version
1
+ # @rsj-de/pdfmergesortsplit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Embed the PDF editor of [pdfmerge.rsj.de](https://pdfmerge.rsj.de) in your own
4
+ product: merging, sorting, rotating, splitting and exporting PDFs inside one
5
+ iframe, with the documents never leaving the browser.
6
+
7
+ **Website:** <https://pdfmerge.rsj.de> · **Documentation:**
8
+ <https://pdfmerge.rsj.de/docs> · **Source and samples:**
9
+ <https://github.com/pdfmergesortsplit/pdfmergesortsplit> · **Licence:** MIT
10
+ ([LICENSE](LICENSE))
11
+
12
+ ## Features
13
+
14
+ - **Merge** any number of PDFs, or ZIP archives containing PDFs.
15
+ - **Sort** pages by drag & drop, including multi-selection (Ctrl/Cmd+click,
16
+ Shift+click a range); rotate, duplicate and delete one page or a whole
17
+ selection; undo/redo on every edit.
18
+ - **Split** into parts: every part has a title line, an editable name used as
19
+ the file name, and its own save and print button. Pages can be moved between
20
+ parts, also with the keyboard.
21
+ - **Export** one PDF per part, one merged PDF, or a ZIP of all parts — from your
22
+ own buttons or from the editor's.
23
+ - **Page provenance**: every page keeps the source file and page it came from;
24
+ a source can bring its own provenance, it can be written into exported PDFs,
25
+ and it is restored when such a PDF is imported again.
26
+ - **Google Drive**: open a file from Drive's "Open with" menu, browse Drive in
27
+ the editor's own dialog, and save the result back to Drive.
28
+ - **Your branding**: replace the title and icon, hide any control, inject your
29
+ own buttons into three injection points and receive their clicks as events.
30
+ - **Your product's look**: light and dark theme, a page viewer, seven languages.
31
+ - **Nothing uploaded**: PDFs are parsed, edited and rebuilt in the visitor's
32
+ browser; no document reaches a server.
33
+ - **Two levels**: a framework-agnostic class and a React component with a hook.
34
+
35
+ ## Business model
36
+
37
+ The **code** of this SDK is MIT-licensed (see [LICENSE](LICENSE)): read it, fork
38
+ it, change it, ship it.
39
+
40
+ The **editor it embeds** is a subscription product. An embedded editor asks for a
41
+ licence token (`apiKey`); without a valid one it runs in **test mode** — every
42
+ feature works, a banner marks the embedding as unlicensed. Licences are per
43
+ integration and volume, not per page.
44
+
45
+ - Pricing and the licence model: <https://pdfmerge.rsj.de/docs/licensing>
46
+ - A licence: [info@rsj.de](mailto:info@rsj.de?subject=RSJ%20PDF%20Merge%20SDK%20licence)
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ npm install @rsj-de/pdfmergesortsplit
52
+ ```
53
+
54
+ `react` is an optional peer dependency: the plain class does not import it.
55
+
56
+ ## Quick start
57
+
58
+ ```js
59
+ import { PDFMergeSortSplitClass } from '@rsj-de/pdfmergesortsplit';
60
+
61
+ const editor = new PDFMergeSortSplitClass('#editor', {
62
+ apiKey: '<licence JWT>', // test mode without one
63
+ title: 'Acme Document Suite',
64
+ actions: {
65
+ saveAll: {
66
+ savePdf: { title: 'Export PDF' },
67
+ saveZip: { title: 'Export ZIP' },
68
+ },
69
+ },
70
+ });
71
+
72
+ await editor.whenReady();
73
+ await editor.loadDocument({ name: 'contract.pdf', content: file });
74
+
75
+ const pdf = await editor.exportDocument(0, 'pdf'); // part 0 → Blob
76
+
77
+ editor.on('action', async ({ action, part }) => {
78
+ if (action === 'savePdf') download(await editor.exportDocument(part));
79
+ if (action === 'saveZip') download(await editor.exportDocument(undefined, 'zip'));
80
+ });
81
+ ```
82
+
83
+ React:
84
+
85
+ ```jsx
86
+ import { PDFMergeSortSplit, usePdfMergeSortSplit } from '@rsj-de/pdfmergesortsplit/react';
87
+
88
+ export function PdfEditor({ apiKey }) {
89
+ const { ref, exportDocument } = usePdfMergeSortSplit();
90
+
91
+ const handleAction = async ({ action, part }) => {
92
+ if (action === 'savePdf') download(await exportDocument(part, 'pdf'));
93
+ };
94
+
95
+ return (
96
+ <PDFMergeSortSplit
97
+ ref={ref}
98
+ config={{ apiKey }}
99
+ style={{ height: 640 }}
100
+ onAction={handleAction}
101
+ />
102
+ );
103
+ }
104
+ ```
105
+
106
+ More, including the configuration reference, the events and the provenance
107
+ model: <https://pdfmerge.rsj.de/docs>.
108
+
109
+ ## API
110
+
111
+ | Member | Description |
112
+ | --- | --- |
113
+ | `new PDFMergeSortSplitClass(container, config)` | Creates the editor in `container` (element or selector). |
114
+ | `whenReady()` | Resolves when the editor took the configuration. |
115
+ | `setConfig(config)` | Applies a new configuration to the running editor (no reload). |
116
+ | `loadDocument({name, content, provenance})` | Adds a PDF (File, Blob, ArrayBuffer, base64, data URL), optionally with per-page provenance. |
117
+ | `getPages()` | The pages in screen order, each with its provenance. |
118
+ | `exportDocument(reference?, mode?)` | One part (`"pdf"`, by index or name) or everything (`"zip"`) → `Blob`. |
119
+ | `on("action" \| "export" \| "license", handler)` | Injected buttons, finished exports, licence state; returns an unsubscribe function. |
120
+ | `destroy()` | Removes the editor and rejects pending requests. |
121
+
122
+ ## Configuration
123
+
124
+ - `apiKey` — the licence token; without one the editor runs in test mode.
125
+ - `embedUrl` — where the editor is served (default `https://pdfmerge.rsj.de`,
126
+ or your self-hosted deployment).
127
+ - `title`, `icon` — replace the title bar of the editor.
128
+ - `ui` — hide any combination of the title-bar, toolbar and part-header controls.
129
+ - `actions.loaders` / `actions.saveAll` / `actions.savePart` — buttons injected
130
+ into the editor; their clicks arrive as `on("action", …)` with
131
+ `{action, where, reference, part}`.
132
+ - `provenance.attach` — write the page provenance into exported PDFs.
133
+ - `googleClientId` — OAuth client ID of your own Google Cloud project for the
134
+ Drive integration (see <https://pdfmerge.rsj.de/docs/google-drive>).
135
+
136
+ A running editor takes a new configuration with `setConfig(config)`; the React
137
+ component calls it whenever the `config` prop changes, so the open document
138
+ stays. Only a different `embedUrl` needs a new iframe. The full schema is in
139
+ `types/index.d.ts`.
140
+
141
+ ## Samples
142
+
143
+ `../sdksimple` (plain JavaScript) and `../sdksample` (React) use this package
144
+ through `link:../sdk`, so build the SDK before starting a sample:
145
+
146
+ ```bash
147
+ pnpm --dir sdk install --ignore-workspace
148
+ pnpm --dir sdk run build
149
+ pnpm --dir sdksimple install --ignore-workspace && pnpm --dir sdksimple run dev
150
+ pnpm --dir sdksample install --ignore-workspace && pnpm --dir sdksample run dev
151
+ ```
152
+
153
+ ## Build and publish
154
+
155
+ ```bash
156
+ pnpm install --ignore-workspace # this package is not part of the app's workspace
157
+ pnpm run build # dist/index.js, dist/index.cjs, dist/react.*
158
+ npm publish # prepublishOnly builds again
159
+ ```
160
+
161
+ ## Licence
162
+
163
+ MIT — see [LICENSE](LICENSE). The MIT licence covers this SDK's code; operating
164
+ the embedded editor requires a subscription as described under
165
+ [Business model](#business-model).
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=`https://pdfmerge.rsj.de`,t=2e4,n=e=>typeof Blob<`u`&&e instanceof Blob||e instanceof ArrayBuffer||ArrayBuffer.isView(e),r=class{constructor(n,r={}){let i=typeof n==`string`?document.querySelector(n):n;if(!i)throw Error(`pdfmerge: container not found: ${String(n)}`);this.config=r??{},this.embedUrl=String(this.config.embedUrl||e).replace(/\/+$/,``),this.origin=new URL(this.embedUrl).origin,this.handlers=new Map,this.pending=new Map,this.requestId=0,this.readyDone=!1,this.destroyed=!1,this.iframe=document.createElement(`iframe`),this.iframe.src=`${this.embedUrl}/`,this.iframe.title=`PDF Merge, Sort & Split`,this.iframe.setAttribute(`allow`,`clipboard-read; clipboard-write; fullscreen`),this.iframe.setAttribute(`allowfullscreen`,`true`),this.iframe.style.cssText=`display:block;width:100%;height:100%;border:0`,i.appendChild(this.iframe),this.handleMessage=e=>this.receive(e),window.addEventListener(`message`,this.handleMessage);let a=this.config.readyTimeout??t;this.ready=new Promise((e,t)=>{this.resolveReady=e,this.rejectReady=t,this.readyTimer=setTimeout(()=>{t(Error(`pdfmerge: the editor did not answer within ${a} ms`))},a)}),this.ready.catch(()=>{})}whenReady(){return this.ready}setConfig(e={}){this.config=e??{},this.send({op:`config`,config:this.config})}on(e,t){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(t),()=>this.off(e,t)}off(e,t){this.handlers.get(e)?.delete(t)}async loadDocument(e,t,r){let i=e&&typeof e==`object`&&!n(e)?e:{name:e,content:t,provenance:r},a=await this.request(`addPages`,`addPagesRsp`,{name:i.name||`document.pdf`,content:i.content,provenance:i.provenance});if(a?.ok===!1)throw Error(`pdfmerge: loading failed: ${a.error}`);return a}async getPages(){return(await this.request(`pages`,`pagesRsp`))?.pages??[]}async exportDocument(e,t=`pdf`){let n=Number.isInteger(e),r=await this.request(`export`,`exportRsp`,{mode:t,part:n?e:void 0,reference:n?void 0:e});if(r?.error)throw Error(`pdfmerge: export failed: ${r.error}`);let i=new Blob([r.bytes],{type:r.mimeType||`application/pdf`});return this.emit(`export`,{reference:e,mode:t,fileName:r.fileName,pages:r.pages??[],blob:i}),i}destroy(){if(!this.destroyed){this.destroyed=!0,window.removeEventListener(`message`,this.handleMessage),clearTimeout(this.readyTimer);for(let[,e]of this.pending)clearTimeout(e.timer),e.reject(Error(`pdfmerge: the editor was destroyed`));this.pending.clear(),this.iframe.remove()}}send(e){this.iframe.contentWindow?.postMessage({partner:`pdfmerge`,...e},this.origin)}request(e,n,r={},i=t){let a=++this.requestId;return new Promise((t,o)=>{if(this.destroyed){o(Error(`pdfmerge: the editor was destroyed`));return}let s=setTimeout(()=>{this.pending.delete(a),o(Error(`pdfmerge: "${e}" timed out after ${i} ms`))},i);this.pending.set(a,{op:n,resolve:t,reject:o,timer:s}),this.send({op:e,id:a,...r})})}receive(e){if(e.origin!==this.origin||e.source!==this.iframe.contentWindow)return;let t=e.data;if(t&&typeof t==`object`)switch(t.op){case`init`:this.clearReadyTimer(),this.send({op:`initRsp`,config:this.config}),this.readyDone||(this.readyDone=!0,this.resolveReady(this));return;case`licenseRequest`:this.config.apiKey&&this.send({op:`licenseRsp`,token:this.config.apiKey});return;case`licenseStatus`:this.emit(`license`,{licensed:!!t.licensed,reason:t.reason});return;case`action`:this.emit(`action`,{action:t.action,where:t.where,reference:t.reference,part:t.part});return;case`pagesRsp`:case`exportRsp`:case`addPagesRsp`:case`addFilesRsp`:{let e=t.id===void 0?void 0:this.pending.get(t.id);e&&e.op===t.op&&(clearTimeout(e.timer),this.pending.delete(t.id),e.resolve(t));return}default:return}}emit(e,t){for(let n of this.handlers.get(e)??[])try{n(t)}catch(e){console.error(`pdfmerge: event handler failed`,e)}}clearReadyTimer(){this.readyTimer&&=(clearTimeout(this.readyTimer),null)}};exports.PDFMergeSortSplitClass=r,exports.default=r;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["DEFAULT_EMBED_URL","DEFAULT_TIMEOUT","isBinary","value","PDFMergeSortSplitClass","element","container","config","event","timeout","resolve","reject","handler","options","nameOrOptions","answer","byIndex","reference","blob","entry","message","id","timer","op","responseOp","payload","data","error"],"sources":["../src/index.js"],"sourcesContent":["/**\n * RSJ PDF Merge, Sort & Split - SDK\n *\n * The editor runs in an iframe (hosted by RSJ, or by you when self-hosting).\n * This class owns that iframe and the postMessage protocol between the two:\n *\n * app -> host {op: \"init\", version} the editor is up\n * host -> app {op: \"initRsp\", config} title, icon, hidden UI, actions, apiKey\n * app -> host {op: \"licenseRequest\"} framed and not licensed yet: asks for a licence\n * host -> app {op: \"licenseRsp\", token} the licence (config.apiKey), or no token\n * app -> host {op: \"licenseStatus\", licensed, reason} the verdict\n * host -> app {op: \"addFiles\", data} File/Blob/base64/data URL -> addFilesRsp\n * host -> app {op: \"addPages\", name, content, provenance} -> addPagesRsp\n * host -> app {op: \"pages\"} -> pagesRsp\n * host -> app {op: \"export\", mode, part|reference} -> exportRsp\n * app -> host {op: \"action\", action, where, reference, part} a button the host configured\n *\n * `action` is the key of the button in `config.actions`, `where` the injection\n * point it was configured in; an injection point holds any number of buttons.\n *\n * Every request carries an `id` that the answer echoes, so concurrent calls do\n * not get mixed up.\n */\n\nconst DEFAULT_EMBED_URL = 'https://pdfmerge.rsj.de';\nconst DEFAULT_TIMEOUT = 20000;\n\n/** Requests whose payload is the document itself, not options. */\nconst isBinary = (value) =>\n (typeof Blob !== 'undefined' && value instanceof Blob) ||\n value instanceof ArrayBuffer ||\n ArrayBuffer.isView(value);\n\nexport class PDFMergeSortSplitClass {\n /**\n * @param {string|HTMLElement} container element (or selector) that receives the iframe\n * @param {object} config `{ embedUrl, apiKey, title, icon, ui, actions, provenance, … }`\n */\n constructor(container, config = {}) {\n const element = typeof container === 'string' ? document.querySelector(container) : container;\n\n if (!element) {\n throw new Error(`pdfmerge: container not found: ${String(container)}`);\n }\n\n this.config = config ?? {};\n this.embedUrl = String(this.config.embedUrl || DEFAULT_EMBED_URL).replace(/\\/+$/, '');\n this.origin = new URL(this.embedUrl).origin;\n this.handlers = new Map();\n this.pending = new Map();\n this.requestId = 0;\n this.readyDone = false;\n this.destroyed = false;\n\n this.iframe = document.createElement('iframe');\n this.iframe.src = `${this.embedUrl}/`;\n this.iframe.title = 'PDF Merge, Sort & Split';\n this.iframe.setAttribute('allow', 'clipboard-read; clipboard-write; fullscreen');\n this.iframe.setAttribute('allowfullscreen', 'true');\n this.iframe.style.cssText = 'display:block;width:100%;height:100%;border:0';\n element.appendChild(this.iframe);\n\n this.handleMessage = (event) => this.receive(event);\n window.addEventListener('message', this.handleMessage);\n\n const timeout = this.config.readyTimeout ?? DEFAULT_TIMEOUT;\n this.ready = new Promise((resolve, reject) => {\n this.resolveReady = resolve;\n this.rejectReady = reject;\n this.readyTimer = setTimeout(() => {\n reject(new Error(`pdfmerge: the editor did not answer within ${timeout} ms`));\n }, timeout);\n });\n // Nobody has to await it; an unhandled rejection would only be noise.\n this.ready.catch(() => {});\n }\n\n /** Resolves once the editor has taken our configuration. */\n whenReady() {\n return this.ready;\n }\n\n /**\n * Replace the configuration on the running editor.\n *\n * The editor applies it immediately - title, icon, hidden controls, injected\n * buttons, provenance and the licence token - without reloading the iframe.\n * `embedUrl` is the exception: it is the iframe's `src`, so changing it needs\n * a new iframe (the React component does that for you).\n */\n setConfig(config = {}) {\n this.config = config ?? {};\n this.send({ op: 'config', config: this.config });\n }\n\n /** Subscribe to \"action\" / \"export\"; returns an unsubscribe function. */\n on(event, handler) {\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Set());\n }\n this.handlers.get(event).add(handler);\n\n return () => this.off(event, handler);\n }\n\n /** Remove a handler added with `on`. */\n off(event, handler) {\n this.handlers.get(event)?.delete(handler);\n }\n\n /**\n * Add a PDF to the editor.\n *\n * `loadDocument({name, content, provenance})` or `loadDocument(name, content)`.\n * `content` may be a File, a Blob, an ArrayBuffer, a Uint8Array, base64 or a\n * data URL; `provenance` is one entry per page (any JSON).\n */\n async loadDocument(nameOrOptions, content, provenance) {\n const options =\n nameOrOptions && typeof nameOrOptions === 'object' && !isBinary(nameOrOptions)\n ? nameOrOptions\n : { name: nameOrOptions, content, provenance };\n\n const answer = await this.request('addPages', 'addPagesRsp', {\n name: options.name || 'document.pdf',\n content: options.content,\n provenance: options.provenance,\n });\n\n if (answer?.ok === false) {\n throw new Error(`pdfmerge: loading failed: ${answer.error}`);\n }\n\n return answer;\n }\n\n /** The current pages, each with its provenance. */\n async getPages() {\n const answer = await this.request('pages', 'pagesRsp');\n return answer?.pages ?? [];\n }\n\n /**\n * Export a part as a PDF, or every part as a ZIP (`mode: \"zip\"`).\n *\n * `reference` is a part index or a part name; without it every page goes into\n * one document. Resolves with a Blob and emits \"export\".\n */\n async exportDocument(reference, mode = 'pdf') {\n const byIndex = Number.isInteger(reference);\n const answer = await this.request('export', 'exportRsp', {\n mode,\n part: byIndex ? reference : undefined,\n reference: byIndex ? undefined : reference,\n });\n\n if (answer?.error) {\n throw new Error(`pdfmerge: export failed: ${answer.error}`);\n }\n\n const blob = new Blob([answer.bytes], { type: answer.mimeType || 'application/pdf' });\n this.emit('export', {\n reference,\n mode,\n fileName: answer.fileName,\n pages: answer.pages ?? [],\n blob,\n });\n\n return blob;\n }\n\n /** Remove the iframe and stop listening. */\n destroy() {\n if (this.destroyed) {\n return;\n }\n this.destroyed = true;\n window.removeEventListener('message', this.handleMessage);\n clearTimeout(this.readyTimer);\n\n for (const [, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(new Error('pdfmerge: the editor was destroyed'));\n }\n this.pending.clear();\n\n this.iframe.remove();\n }\n\n /** postMessage into the iframe. */\n send(message) {\n this.iframe.contentWindow?.postMessage({ partner: 'pdfmerge', ...message }, this.origin);\n }\n\n /** Send a request and wait for the answer that echoes its id. */\n request(op, responseOp, payload = {}, timeout = DEFAULT_TIMEOUT) {\n const id = ++this.requestId;\n\n return new Promise((resolve, reject) => {\n if (this.destroyed) {\n reject(new Error('pdfmerge: the editor was destroyed'));\n return;\n }\n\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`pdfmerge: \"${op}\" timed out after ${timeout} ms`));\n }, timeout);\n\n this.pending.set(id, { op: responseOp, resolve, reject, timer });\n this.send({ op, id, ...payload });\n });\n }\n\n receive(event) {\n if (event.origin !== this.origin) {\n return;\n }\n if (event.source !== this.iframe.contentWindow) {\n return;\n }\n\n const data = event.data;\n if (!data || typeof data !== 'object') {\n return;\n }\n\n switch (data.op) {\n case 'init':\n this.clearReadyTimer();\n this.send({ op: 'initRsp', config: this.config });\n if (!this.readyDone) {\n this.readyDone = true;\n this.resolveReady(this);\n }\n return;\n\n case 'licenseRequest':\n // The editor is framed and tainted: answer with the licence it may use.\n // Without `apiKey` there is nothing to send and it stays in test mode.\n if (this.config.apiKey) {\n this.send({ op: 'licenseRsp', token: this.config.apiKey });\n }\n return;\n\n case 'licenseStatus':\n // The editor tells us whether the licence was accepted, so a host can\n // show the state instead of guessing.\n this.emit('license', { licensed: Boolean(data.licensed), reason: data.reason });\n return;\n\n case 'action':\n this.emit('action', {\n action: data.action,\n where: data.where,\n reference: data.reference,\n part: data.part,\n });\n return;\n\n case 'pagesRsp':\n case 'exportRsp':\n case 'addPagesRsp':\n case 'addFilesRsp': {\n const entry = data.id === undefined ? undefined : this.pending.get(data.id);\n\n if (entry && entry.op === data.op) {\n clearTimeout(entry.timer);\n this.pending.delete(data.id);\n entry.resolve(data);\n }\n return;\n }\n\n default:\n return;\n }\n }\n\n emit(event, payload) {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n handler(payload);\n } catch (error) {\n // A failing consumer handler must not break the message loop.\n console.error('pdfmerge: event handler failed', error);\n }\n }\n }\n\n clearReadyTimer() {\n if (this.readyTimer) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n}\n\nexport default PDFMergeSortSplitClass;\n"],"mappings":"A,+FAwBA,IAAMA,EAAoB,0BACpBC,EAAkB,IAGlBC,EAAY,GACf,OAAO,KAAS,KAAeC,aAAiB,MACjDA,aAAiB,aACjB,YAAY,OAAOA,CAAK,EAEbC,EAAb,KAAoC,CAKlC,YAAY,EAAW,EAAS,CAAC,EAAG,CAClC,IAAMC,EAAU,OAAOC,GAAc,SAAW,SAAS,cAAcA,CAAS,EAAIA,EAEpF,GAAI,CAACD,EACH,MAAU,MAAM,kCAAkC,OAAOC,CAAS,GAAG,EAGvE,KAAK,OAASC,GAAU,CAAC,EACzB,KAAK,SAAW,OAAO,KAAK,OAAO,UAAYP,CAAiB,CAAC,CAAC,QAAQ,OAAQ,EAAE,EACpF,KAAK,OAAS,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC,OACrC,KAAK,SAAW,IAAI,IACpB,KAAK,QAAU,IAAI,IACnB,KAAK,UAAY,EACjB,KAAK,UAAY,GACjB,KAAK,UAAY,GAEjB,KAAK,OAAS,SAAS,cAAc,QAAQ,EAC7C,KAAK,OAAO,IAAM,GAAG,KAAK,SAAS,GACnC,KAAK,OAAO,MAAQ,0BACpB,KAAK,OAAO,aAAa,QAAS,6CAA6C,EAC/E,KAAK,OAAO,aAAa,kBAAmB,MAAM,EAClD,KAAK,OAAO,MAAM,QAAU,gDAC5BK,EAAQ,YAAY,KAAK,MAAM,EAE/B,KAAK,cAAiB,GAAU,KAAK,QAAQG,CAAK,EAClD,OAAO,iBAAiB,UAAW,KAAK,aAAa,EAErD,IAAMC,EAAU,KAAK,OAAO,cAAgBR,EAC5C,KAAK,MAAQ,IAAI,SAAS,EAAS,IAAW,CAC5C,KAAK,aAAeS,EACpB,KAAK,YAAcC,EACnB,KAAK,WAAa,eAAiB,CACjCA,EAAW,MAAM,8CAA8CF,EAAQ,IAAI,CAAC,CAC9E,EAAGA,CAAO,CACZ,CAAC,EAED,KAAK,MAAM,UAAY,CAAC,CAAC,CAC3B,CAGA,WAAY,CACV,OAAO,KAAK,KACd,CAUA,UAAU,EAAS,CAAC,EAAG,CACrB,KAAK,OAASF,GAAU,CAAC,EACzB,KAAK,KAAK,CAAE,GAAI,SAAU,OAAQ,KAAK,MAAO,CAAC,CACjD,CAGA,GAAG,EAAO,EAAS,CAMjB,OALK,KAAK,SAAS,IAAIC,CAAK,GAC1B,KAAK,SAAS,IAAIA,EAAO,IAAI,GAAK,EAEpC,KAAK,SAAS,IAAIA,CAAK,CAAC,CAAC,IAAII,CAAO,MAEvB,KAAK,IAAIJ,EAAOI,CAAO,CACtC,CAGA,IAAI,EAAO,EAAS,CAClB,KAAK,SAAS,IAAIJ,CAAK,CAAC,EAAE,OAAOI,CAAO,CAC1C,CASA,MAAM,aAAa,EAAe,EAAS,EAAY,CACrD,IAAMC,EACJC,GAAiB,OAAOA,GAAkB,UAAY,CAACZ,EAASY,CAAa,EACzEA,EACA,CAAE,KAAMA,EAAe,UAAS,YAAW,EAE3CC,EAAS,MAAM,KAAK,QAAQ,WAAY,cAAe,CAC3D,KAAMF,EAAQ,MAAQ,eACtB,QAASA,EAAQ,QACjB,WAAYA,EAAQ,UACtB,CAAC,EAED,GAAIE,GAAQ,KAAO,GACjB,MAAU,MAAM,6BAA6BA,EAAO,OAAO,EAG7D,OAAOA,CACT,CAGA,MAAM,UAAW,CAEf,OAAO,MADc,KAAK,QAAQ,QAAS,UAAU,EAAA,EACtC,OAAS,CAAC,CAC3B,CAQA,MAAM,eAAe,EAAW,EAAO,MAAO,CAC5C,IAAMC,EAAU,OAAO,UAAUC,CAAS,EACpCF,EAAS,MAAM,KAAK,QAAQ,SAAU,YAAa,CACvD,OACA,KAAMC,EAAUC,EAAY,IAAA,GAC5B,UAAWD,EAAU,IAAA,GAAYC,CACnC,CAAC,EAED,GAAIF,GAAQ,MACV,MAAU,MAAM,4BAA4BA,EAAO,OAAO,EAG5D,IAAMG,EAAO,IAAI,KAAK,CAACH,EAAO,KAAK,EAAG,CAAE,KAAMA,EAAO,UAAY,iBAAkB,CAAC,EASpF,OARA,KAAK,KAAK,SAAU,CAClB,YACA,OACA,SAAUA,EAAO,SACjB,MAAOA,EAAO,OAAS,CAAC,EACxB,MACF,CAAC,EAEMG,CACT,CAGA,SAAU,CACR,GAAI,MAAK,UAGT,MAAK,UAAY,GACjB,OAAO,oBAAoB,UAAW,KAAK,aAAa,EACxD,aAAa,KAAK,UAAU,EAE5B,IAAK,GAAM,EAAGC,KAAU,KAAK,QAC3B,aAAaA,EAAM,KAAK,EACxBA,EAAM,OAAW,MAAM,oCAAoC,CAAC,EAE9D,KAAK,QAAQ,MAAM,EAEnB,KAAK,OAAO,OAAO,CAVF,CAWnB,CAGA,KAAK,EAAS,CACZ,KAAK,OAAO,eAAe,YAAY,CAAE,QAAS,WAAY,GAAGC,CAAQ,EAAG,KAAK,MAAM,CACzF,CAGA,QAAQ,EAAI,EAAY,EAAU,CAAC,EAAG,EAAUnB,EAAiB,CAC/D,IAAMoB,EAAK,EAAE,KAAK,UAElB,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,GAAI,KAAK,UAAW,CAClBV,EAAW,MAAM,oCAAoC,CAAC,EACtD,MACF,CAEA,IAAMW,EAAQ,eAAiB,CAC7B,KAAK,QAAQ,OAAOD,CAAE,EACtBV,EAAW,MAAM,cAAcY,EAAG,oBAAoBd,EAAQ,IAAI,CAAC,CACrE,EAAGA,CAAO,EAEV,KAAK,QAAQ,IAAIY,EAAI,CAAE,GAAIG,EAAY,UAAS,SAAQ,OAAM,CAAC,EAC/D,KAAK,KAAK,CAAE,KAAI,KAAI,GAAGC,CAAQ,CAAC,CAClC,CAAC,CACH,CAEA,QAAQ,EAAO,CAIb,GAHIjB,EAAM,SAAW,KAAK,QAGtBA,EAAM,SAAW,KAAK,OAAO,cAC/B,OAGF,IAAMkB,EAAOlB,EAAM,KACnB,GAAKkB,GAAQ,OAAOA,GAAS,SAI7B,OAAQA,EAAK,GAAb,CACE,IAAK,OACH,KAAK,gBAAgB,EACrB,KAAK,KAAK,CAAE,GAAI,UAAW,OAAQ,KAAK,MAAO,CAAC,EAC3C,KAAK,YACR,KAAK,UAAY,GACjB,KAAK,aAAa,IAAI,GAExB,OAEF,IAAK,iBAGC,KAAK,OAAO,QACd,KAAK,KAAK,CAAE,GAAI,aAAc,MAAO,KAAK,OAAO,MAAO,CAAC,EAE3D,OAEF,IAAK,gBAGH,KAAK,KAAK,UAAW,CAAE,SAAU,EAAQA,EAAK,SAAW,OAAQA,EAAK,MAAO,CAAC,EAC9E,OAEF,IAAK,SACH,KAAK,KAAK,SAAU,CAClB,OAAQA,EAAK,OACb,MAAOA,EAAK,MACZ,UAAWA,EAAK,UAChB,KAAMA,EAAK,IACb,CAAC,EACD,OAEF,IAAK,WACL,IAAK,YACL,IAAK,cACL,IAAK,cAAe,CAClB,IAAMP,EAAQO,EAAK,KAAO,IAAA,GAAY,IAAA,GAAY,KAAK,QAAQ,IAAIA,EAAK,EAAE,EAEtEP,GAASA,EAAM,KAAOO,EAAK,KAC7B,aAAaP,EAAM,KAAK,EACxB,KAAK,QAAQ,OAAOO,EAAK,EAAE,EAC3BP,EAAM,QAAQO,CAAI,GAEpB,MACF,CAEA,QACE,MACJ,CACF,CAEA,KAAK,EAAO,EAAS,CACnB,IAAK,IAAMd,KAAW,KAAK,SAAS,IAAIJ,CAAK,GAAK,CAAC,EACjD,GAAI,CACFI,EAAQa,CAAO,CACjB,OAASE,EAAO,CAEd,QAAQ,MAAM,iCAAkCA,CAAK,CACvD,CAEJ,CAEA,iBAAkB,CAChB,AAEE,KAAK,cADL,aAAa,KAAK,UAAU,EACV,KAEtB,CACF,E"}
package/dist/index.js ADDED
@@ -0,0 +1,150 @@
1
+ // src/index.js
2
+ var e = 2e4, t = (e) => typeof Blob < "u" && e instanceof Blob || e instanceof ArrayBuffer || ArrayBuffer.isView(e), n = class {
3
+ constructor(t, n = {}) {
4
+ let r = typeof t == "string" ? document.querySelector(t) : t;
5
+ if (!r) throw Error(`pdfmerge: container not found: ${String(t)}`);
6
+ this.config = n ?? {}, this.embedUrl = String(this.config.embedUrl || "https://pdfmerge.rsj.de").replace(/\/+$/, ""), this.origin = new URL(this.embedUrl).origin, this.handlers = /* @__PURE__ */ new Map(), this.pending = /* @__PURE__ */ new Map(), this.requestId = 0, this.readyDone = !1, this.destroyed = !1, this.iframe = document.createElement("iframe"), this.iframe.src = `${this.embedUrl}/`, this.iframe.title = "PDF Merge, Sort & Split", this.iframe.setAttribute("allow", "clipboard-read; clipboard-write; fullscreen"), this.iframe.setAttribute("allowfullscreen", "true"), this.iframe.style.cssText = "display:block;width:100%;height:100%;border:0", r.appendChild(this.iframe), this.handleMessage = (e) => this.receive(e), window.addEventListener("message", this.handleMessage);
7
+ let i = this.config.readyTimeout ?? e;
8
+ this.ready = new Promise((e, t) => {
9
+ this.resolveReady = e, this.rejectReady = t, this.readyTimer = setTimeout(() => {
10
+ t(/* @__PURE__ */ Error(`pdfmerge: the editor did not answer within ${i} ms`));
11
+ }, i);
12
+ }), this.ready.catch(() => {});
13
+ }
14
+ whenReady() {
15
+ return this.ready;
16
+ }
17
+ setConfig(e = {}) {
18
+ this.config = e ?? {}, this.send({
19
+ op: "config",
20
+ config: this.config
21
+ });
22
+ }
23
+ on(e, t) {
24
+ return this.handlers.has(e) || this.handlers.set(e, /* @__PURE__ */ new Set()), this.handlers.get(e).add(t), () => this.off(e, t);
25
+ }
26
+ off(e, t) {
27
+ this.handlers.get(e)?.delete(t);
28
+ }
29
+ async loadDocument(e, n, r) {
30
+ let i = e && typeof e == "object" && !t(e) ? e : {
31
+ name: e,
32
+ content: n,
33
+ provenance: r
34
+ }, a = await this.request("addPages", "addPagesRsp", {
35
+ name: i.name || "document.pdf",
36
+ content: i.content,
37
+ provenance: i.provenance
38
+ });
39
+ if (a?.ok === !1) throw Error(`pdfmerge: loading failed: ${a.error}`);
40
+ return a;
41
+ }
42
+ async getPages() {
43
+ return (await this.request("pages", "pagesRsp"))?.pages ?? [];
44
+ }
45
+ async exportDocument(e, t = "pdf") {
46
+ let n = Number.isInteger(e), r = await this.request("export", "exportRsp", {
47
+ mode: t,
48
+ part: n ? e : void 0,
49
+ reference: n ? void 0 : e
50
+ });
51
+ if (r?.error) throw Error(`pdfmerge: export failed: ${r.error}`);
52
+ let i = new Blob([r.bytes], { type: r.mimeType || "application/pdf" });
53
+ return this.emit("export", {
54
+ reference: e,
55
+ mode: t,
56
+ fileName: r.fileName,
57
+ pages: r.pages ?? [],
58
+ blob: i
59
+ }), i;
60
+ }
61
+ destroy() {
62
+ if (!this.destroyed) {
63
+ this.destroyed = !0, window.removeEventListener("message", this.handleMessage), clearTimeout(this.readyTimer);
64
+ for (let [, e] of this.pending) clearTimeout(e.timer), e.reject(/* @__PURE__ */ Error("pdfmerge: the editor was destroyed"));
65
+ this.pending.clear(), this.iframe.remove();
66
+ }
67
+ }
68
+ send(e) {
69
+ this.iframe.contentWindow?.postMessage({
70
+ partner: "pdfmerge",
71
+ ...e
72
+ }, this.origin);
73
+ }
74
+ request(t, n, r = {}, i = e) {
75
+ let a = ++this.requestId;
76
+ return new Promise((e, o) => {
77
+ if (this.destroyed) {
78
+ o(/* @__PURE__ */ Error("pdfmerge: the editor was destroyed"));
79
+ return;
80
+ }
81
+ let s = setTimeout(() => {
82
+ this.pending.delete(a), o(/* @__PURE__ */ Error(`pdfmerge: "${t}" timed out after ${i} ms`));
83
+ }, i);
84
+ this.pending.set(a, {
85
+ op: n,
86
+ resolve: e,
87
+ reject: o,
88
+ timer: s
89
+ }), this.send({
90
+ op: t,
91
+ id: a,
92
+ ...r
93
+ });
94
+ });
95
+ }
96
+ receive(e) {
97
+ if (e.origin !== this.origin || e.source !== this.iframe.contentWindow) return;
98
+ let t = e.data;
99
+ if (t && typeof t == "object") switch (t.op) {
100
+ case "init":
101
+ this.clearReadyTimer(), this.send({
102
+ op: "initRsp",
103
+ config: this.config
104
+ }), this.readyDone || (this.readyDone = !0, this.resolveReady(this));
105
+ return;
106
+ case "licenseRequest":
107
+ this.config.apiKey && this.send({
108
+ op: "licenseRsp",
109
+ token: this.config.apiKey
110
+ });
111
+ return;
112
+ case "licenseStatus":
113
+ this.emit("license", {
114
+ licensed: !!t.licensed,
115
+ reason: t.reason
116
+ });
117
+ return;
118
+ case "action":
119
+ this.emit("action", {
120
+ action: t.action,
121
+ where: t.where,
122
+ reference: t.reference,
123
+ part: t.part
124
+ });
125
+ return;
126
+ case "pagesRsp":
127
+ case "exportRsp":
128
+ case "addPagesRsp":
129
+ case "addFilesRsp": {
130
+ let e = t.id === void 0 ? void 0 : this.pending.get(t.id);
131
+ e && e.op === t.op && (clearTimeout(e.timer), this.pending.delete(t.id), e.resolve(t));
132
+ return;
133
+ }
134
+ default: return;
135
+ }
136
+ }
137
+ emit(e, t) {
138
+ for (let n of this.handlers.get(e) ?? []) try {
139
+ n(t);
140
+ } catch (e) {
141
+ console.error("pdfmerge: event handler failed", e);
142
+ }
143
+ }
144
+ clearReadyTimer() {
145
+ this.readyTimer &&= (clearTimeout(this.readyTimer), null);
146
+ }
147
+ };
148
+ export { n as PDFMergeSortSplitClass, n as default };
149
+
150
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["DEFAULT_TIMEOUT","isBinary","value","PDFMergeSortSplitClass","element","container","config","event","timeout","resolve","reject","handler","options","nameOrOptions","answer","byIndex","reference","blob","entry","message","id","timer","op","responseOp","payload","data","error"],"sources":["../src/index.js"],"sourcesContent":["/**\n * RSJ PDF Merge, Sort & Split - SDK\n *\n * The editor runs in an iframe (hosted by RSJ, or by you when self-hosting).\n * This class owns that iframe and the postMessage protocol between the two:\n *\n * app -> host {op: \"init\", version} the editor is up\n * host -> app {op: \"initRsp\", config} title, icon, hidden UI, actions, apiKey\n * app -> host {op: \"licenseRequest\"} framed and not licensed yet: asks for a licence\n * host -> app {op: \"licenseRsp\", token} the licence (config.apiKey), or no token\n * app -> host {op: \"licenseStatus\", licensed, reason} the verdict\n * host -> app {op: \"addFiles\", data} File/Blob/base64/data URL -> addFilesRsp\n * host -> app {op: \"addPages\", name, content, provenance} -> addPagesRsp\n * host -> app {op: \"pages\"} -> pagesRsp\n * host -> app {op: \"export\", mode, part|reference} -> exportRsp\n * app -> host {op: \"action\", action, where, reference, part} a button the host configured\n *\n * `action` is the key of the button in `config.actions`, `where` the injection\n * point it was configured in; an injection point holds any number of buttons.\n *\n * Every request carries an `id` that the answer echoes, so concurrent calls do\n * not get mixed up.\n */\n\nconst DEFAULT_EMBED_URL = 'https://pdfmerge.rsj.de';\nconst DEFAULT_TIMEOUT = 20000;\n\n/** Requests whose payload is the document itself, not options. */\nconst isBinary = (value) =>\n (typeof Blob !== 'undefined' && value instanceof Blob) ||\n value instanceof ArrayBuffer ||\n ArrayBuffer.isView(value);\n\nexport class PDFMergeSortSplitClass {\n /**\n * @param {string|HTMLElement} container element (or selector) that receives the iframe\n * @param {object} config `{ embedUrl, apiKey, title, icon, ui, actions, provenance, … }`\n */\n constructor(container, config = {}) {\n const element = typeof container === 'string' ? document.querySelector(container) : container;\n\n if (!element) {\n throw new Error(`pdfmerge: container not found: ${String(container)}`);\n }\n\n this.config = config ?? {};\n this.embedUrl = String(this.config.embedUrl || DEFAULT_EMBED_URL).replace(/\\/+$/, '');\n this.origin = new URL(this.embedUrl).origin;\n this.handlers = new Map();\n this.pending = new Map();\n this.requestId = 0;\n this.readyDone = false;\n this.destroyed = false;\n\n this.iframe = document.createElement('iframe');\n this.iframe.src = `${this.embedUrl}/`;\n this.iframe.title = 'PDF Merge, Sort & Split';\n this.iframe.setAttribute('allow', 'clipboard-read; clipboard-write; fullscreen');\n this.iframe.setAttribute('allowfullscreen', 'true');\n this.iframe.style.cssText = 'display:block;width:100%;height:100%;border:0';\n element.appendChild(this.iframe);\n\n this.handleMessage = (event) => this.receive(event);\n window.addEventListener('message', this.handleMessage);\n\n const timeout = this.config.readyTimeout ?? DEFAULT_TIMEOUT;\n this.ready = new Promise((resolve, reject) => {\n this.resolveReady = resolve;\n this.rejectReady = reject;\n this.readyTimer = setTimeout(() => {\n reject(new Error(`pdfmerge: the editor did not answer within ${timeout} ms`));\n }, timeout);\n });\n // Nobody has to await it; an unhandled rejection would only be noise.\n this.ready.catch(() => {});\n }\n\n /** Resolves once the editor has taken our configuration. */\n whenReady() {\n return this.ready;\n }\n\n /**\n * Replace the configuration on the running editor.\n *\n * The editor applies it immediately - title, icon, hidden controls, injected\n * buttons, provenance and the licence token - without reloading the iframe.\n * `embedUrl` is the exception: it is the iframe's `src`, so changing it needs\n * a new iframe (the React component does that for you).\n */\n setConfig(config = {}) {\n this.config = config ?? {};\n this.send({ op: 'config', config: this.config });\n }\n\n /** Subscribe to \"action\" / \"export\"; returns an unsubscribe function. */\n on(event, handler) {\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Set());\n }\n this.handlers.get(event).add(handler);\n\n return () => this.off(event, handler);\n }\n\n /** Remove a handler added with `on`. */\n off(event, handler) {\n this.handlers.get(event)?.delete(handler);\n }\n\n /**\n * Add a PDF to the editor.\n *\n * `loadDocument({name, content, provenance})` or `loadDocument(name, content)`.\n * `content` may be a File, a Blob, an ArrayBuffer, a Uint8Array, base64 or a\n * data URL; `provenance` is one entry per page (any JSON).\n */\n async loadDocument(nameOrOptions, content, provenance) {\n const options =\n nameOrOptions && typeof nameOrOptions === 'object' && !isBinary(nameOrOptions)\n ? nameOrOptions\n : { name: nameOrOptions, content, provenance };\n\n const answer = await this.request('addPages', 'addPagesRsp', {\n name: options.name || 'document.pdf',\n content: options.content,\n provenance: options.provenance,\n });\n\n if (answer?.ok === false) {\n throw new Error(`pdfmerge: loading failed: ${answer.error}`);\n }\n\n return answer;\n }\n\n /** The current pages, each with its provenance. */\n async getPages() {\n const answer = await this.request('pages', 'pagesRsp');\n return answer?.pages ?? [];\n }\n\n /**\n * Export a part as a PDF, or every part as a ZIP (`mode: \"zip\"`).\n *\n * `reference` is a part index or a part name; without it every page goes into\n * one document. Resolves with a Blob and emits \"export\".\n */\n async exportDocument(reference, mode = 'pdf') {\n const byIndex = Number.isInteger(reference);\n const answer = await this.request('export', 'exportRsp', {\n mode,\n part: byIndex ? reference : undefined,\n reference: byIndex ? undefined : reference,\n });\n\n if (answer?.error) {\n throw new Error(`pdfmerge: export failed: ${answer.error}`);\n }\n\n const blob = new Blob([answer.bytes], { type: answer.mimeType || 'application/pdf' });\n this.emit('export', {\n reference,\n mode,\n fileName: answer.fileName,\n pages: answer.pages ?? [],\n blob,\n });\n\n return blob;\n }\n\n /** Remove the iframe and stop listening. */\n destroy() {\n if (this.destroyed) {\n return;\n }\n this.destroyed = true;\n window.removeEventListener('message', this.handleMessage);\n clearTimeout(this.readyTimer);\n\n for (const [, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(new Error('pdfmerge: the editor was destroyed'));\n }\n this.pending.clear();\n\n this.iframe.remove();\n }\n\n /** postMessage into the iframe. */\n send(message) {\n this.iframe.contentWindow?.postMessage({ partner: 'pdfmerge', ...message }, this.origin);\n }\n\n /** Send a request and wait for the answer that echoes its id. */\n request(op, responseOp, payload = {}, timeout = DEFAULT_TIMEOUT) {\n const id = ++this.requestId;\n\n return new Promise((resolve, reject) => {\n if (this.destroyed) {\n reject(new Error('pdfmerge: the editor was destroyed'));\n return;\n }\n\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`pdfmerge: \"${op}\" timed out after ${timeout} ms`));\n }, timeout);\n\n this.pending.set(id, { op: responseOp, resolve, reject, timer });\n this.send({ op, id, ...payload });\n });\n }\n\n receive(event) {\n if (event.origin !== this.origin) {\n return;\n }\n if (event.source !== this.iframe.contentWindow) {\n return;\n }\n\n const data = event.data;\n if (!data || typeof data !== 'object') {\n return;\n }\n\n switch (data.op) {\n case 'init':\n this.clearReadyTimer();\n this.send({ op: 'initRsp', config: this.config });\n if (!this.readyDone) {\n this.readyDone = true;\n this.resolveReady(this);\n }\n return;\n\n case 'licenseRequest':\n // The editor is framed and tainted: answer with the licence it may use.\n // Without `apiKey` there is nothing to send and it stays in test mode.\n if (this.config.apiKey) {\n this.send({ op: 'licenseRsp', token: this.config.apiKey });\n }\n return;\n\n case 'licenseStatus':\n // The editor tells us whether the licence was accepted, so a host can\n // show the state instead of guessing.\n this.emit('license', { licensed: Boolean(data.licensed), reason: data.reason });\n return;\n\n case 'action':\n this.emit('action', {\n action: data.action,\n where: data.where,\n reference: data.reference,\n part: data.part,\n });\n return;\n\n case 'pagesRsp':\n case 'exportRsp':\n case 'addPagesRsp':\n case 'addFilesRsp': {\n const entry = data.id === undefined ? undefined : this.pending.get(data.id);\n\n if (entry && entry.op === data.op) {\n clearTimeout(entry.timer);\n this.pending.delete(data.id);\n entry.resolve(data);\n }\n return;\n }\n\n default:\n return;\n }\n }\n\n emit(event, payload) {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n handler(payload);\n } catch (error) {\n // A failing consumer handler must not break the message loop.\n console.error('pdfmerge: event handler failed', error);\n }\n }\n }\n\n clearReadyTimer() {\n if (this.readyTimer) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n}\n\nexport default PDFMergeSortSplitClass;\n"],"mappings":"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"}
package/dist/react.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));const c=require("./index.cjs");let l=require("react");l=s(l,1);let u=require("react/jsx-runtime");var d=()=>{let e=(0,l.useRef)(null);return{ref:e,loadDocument:(0,l.useCallback)(t=>e.current.loadDocument(t),[]),exportDocument:(0,l.useCallback)((t,n)=>e.current.exportDocument(t,n),[]),getPages:(0,l.useCallback)(()=>e.current.getPages(),[])}},f=(0,l.forwardRef)(function({config:e,style:t,className:n,onAction:r,onExport:i,onLicense:a,onReady:o},s){let d=(0,l.useRef)(null),f=(0,l.useRef)(null),p=(0,l.useRef)({onAction:r,onExport:i,onLicense:a,onReady:o});p.current={onAction:r,onExport:i,onLicense:a,onReady:o};let m=(0,l.useRef)(e);m.current=e;let h=(0,l.useRef)(null),g=e?.embedUrl;return(0,l.useEffect)(()=>{let e=m.current,t=new c.PDFMergeSortSplitClass(d.current,e);f.current=t,h.current=JSON.stringify(e??{}),s&&(s.current=t),t.whenReady().then(e=>p.current.onReady?.(e)).catch(()=>{});let n=t.on(`action`,e=>p.current.onAction?.(e)),r=t.on(`export`,e=>p.current.onExport?.(e)),i=t.on(`license`,e=>p.current.onLicense?.(e));return()=>{n(),r(),i(),t.destroy(),f.current=null,s&&(s.current=null)}},[g]),(0,l.useEffect)(()=>{let t=f.current;if(!t)return;let n=JSON.stringify(e??{});n!==h.current&&(h.current=n,t.setConfig(e))},[e]),(0,u.jsx)(`div`,{ref:d,className:n,style:{width:`100%`,height:`100%`,...t}})});exports.PDFMergeSortSplit=f,exports.default=f,exports.PDFMergeSortSplitClass=c.PDFMergeSortSplitClass,exports.usePdfMergeSortSplit=d;
2
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.cjs","names":["useRef","useCallback","forwardRef","PDFMergeSortSplitClass","__create","__defProp","__getOwnPropDesc","__getOwnPropNames","__getProtoOf","__hasOwnProp","__copyProps","from","keys","i","n","key","to","except","k","desc","__toESM","mod","isNodeMode","target","require_index","react","react_jsx_runtime","usePdfMergeSortSplit","ref","document","reference","mode","PDFMergeSortSplit","container","instance","handlers","configRef","config","applied","embedUrl","current","created","ready","offAction","payload","offExport","offLicense","json","style"],"sources":["../src/react.jsx"],"sourcesContent":["import React, { forwardRef, useCallback, useEffect, useRef } from 'react';\n\nimport { PDFMergeSortSplitClass } from './index.js';\n\n/**\n * The editor's methods, bound to the instance the component creates.\n *\n * ```jsx\n * const { ref, loadDocument, exportDocument, getPages } = usePdfMergeSortSplit();\n *\n * const handleAction = async ({ action }) => {\n * if (action === 'saveZip') download(await exportDocument(undefined, 'zip'));\n * };\n *\n * <PDFMergeSortSplit ref={ref} config={{ apiKey }} onAction={handleAction} />\n * ```\n *\n * The returned functions are stable, so callers need no `useCallback` or\n * `useMemo` wrapper; `ref` is only needed for the escape hatches\n * (`whenReady`, `on`, `destroy`) and to pass to the component.\n */\nexport const usePdfMergeSortSplit = () => {\n const ref = useRef(null);\n\n const loadDocument = useCallback(\n (document) => ref.current.loadDocument(document),\n [],\n );\n const exportDocument = useCallback(\n (reference, mode) => ref.current.exportDocument(reference, mode),\n [],\n );\n const getPages = useCallback(() => ref.current.getPages(), []);\n\n return { ref, loadDocument, exportDocument, getPages };\n};\n\n/**\n * The editor as a React component.\n *\n * The iframe is created once and lives for the lifetime of the component:\n * changing `onAction` / `onExport` / `onReady` does not reload it, and a new\n * `config` is applied to the running editor (`setConfig`) instead of rebuilding\n * it, so the open document stays. Only a different `config.embedUrl` needs a\n * new iframe, which the component creates itself.\n */\nexport const PDFMergeSortSplit = forwardRef(function PDFMergeSortSplit(\n { config, style, className, onAction, onExport, onLicense, onReady },\n ref,\n) {\n const container = useRef(null);\n const instance = useRef(null);\n const handlers = useRef({ onAction, onExport, onLicense, onReady });\n handlers.current = { onAction, onExport, onLicense, onReady };\n\n // The effect that creates the iframe needs the config of the render it runs\n // in, but must not re-run for every new config object.\n const configRef = useRef(config);\n configRef.current = config;\n\n // What the running editor already knows. `config={{ … }}` is a new object on\n // every render, so the comparison is by content, not by reference.\n const applied = useRef(null);\n\n // `embedUrl` is the iframe's src: only that needs a new iframe.\n const embedUrl = config?.embedUrl;\n\n useEffect(() => {\n const current = configRef.current;\n const created = new PDFMergeSortSplitClass(container.current, current);\n instance.current = created;\n applied.current = JSON.stringify(current ?? {});\n\n if (ref) {\n ref.current = created;\n }\n\n created\n .whenReady()\n .then((ready) => handlers.current.onReady?.(ready))\n .catch(() => {\n // `ready` rejects when the editor never answers; onReady is not called.\n });\n\n const offAction = created.on('action', (payload) =>\n handlers.current.onAction?.(payload),\n );\n const offExport = created.on('export', (payload) =>\n handlers.current.onExport?.(payload),\n );\n const offLicense = created.on('license', (payload) =>\n handlers.current.onLicense?.(payload),\n );\n\n return () => {\n offAction();\n offExport();\n offLicense();\n created.destroy();\n instance.current = null;\n\n if (ref) {\n ref.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [embedUrl]);\n\n // Everything except `embedUrl` is applied to the running editor, so a config\n // change does not reload the iframe and the open document stays.\n useEffect(() => {\n const current = instance.current;\n if (!current) {\n return;\n }\n\n const json = JSON.stringify(config ?? {});\n if (json === applied.current) {\n return;\n }\n applied.current = json;\n\n current.setConfig(config);\n }, [config]);\n\n return (\n <div\n ref={container}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n});\n\nexport { PDFMergeSortSplitClass } from './index.js';\nexport default PDFMergeSortSplit;\n"],"mappings":"A,qrBAqBA,IAAa2B,MAA6B,CACxC,IAAMC,GAAA,EAAM5B,EAAAA,OAAAA,CAAO,IAAI,EAYvB,MAAO,CAAE,MAAK,cAAA,EAVOC,EAAAA,YAAAA,CAClB,GAAa2B,EAAI,QAAQ,aAAaC,CAAQ,EAC/C,CAAC,CAQW,EAAc,gBAAA,EANL5B,EAAAA,YAAAA,EACpB,EAAW,IAAS2B,EAAI,QAAQ,eAAeE,EAAWC,CAAI,EAC/D,CAAC,CAIyB,EAAgB,UAAA,EAF3B9B,EAAAA,YAAAA,KAAkB2B,EAAI,QAAQ,SAAS,EAAG,CAAC,CAEhB,CAAS,CACvD,EAWaI,GAAA,EAAoB9B,EAAAA,WAAAA,CAAW,SAC1C,CAAE,SAAQ,QAAO,YAAW,WAAU,WAAU,YAAW,WAC3D,EACA,CACA,IAAM+B,GAAA,EAAYjC,EAAAA,OAAAA,CAAO,IAAI,EACvBkC,GAAA,EAAWlC,EAAAA,OAAAA,CAAO,IAAI,EACtBmC,GAAA,EAAWnC,EAAAA,OAAAA,CAAO,CAAE,WAAU,WAAU,YAAW,SAAQ,CAAC,EAClE,EAAS,QAAU,CAAE,WAAU,WAAU,YAAW,SAAQ,EAI5D,IAAMoC,GAAA,EAAYpC,EAAAA,OAAAA,CAAOqC,CAAM,EAC/B,EAAU,QAAUA,EAIpB,IAAMC,GAAA,EAAUtC,EAAAA,OAAAA,CAAO,IAAI,EAGrBuC,EAAWF,GAAQ,SA4DzB,OA1DA,EAAAZ,EAAA,UAAA,KAAgB,CACd,IAAMe,EAAUJ,EAAU,QACpBK,EAAU,IAAItC,EAAAA,uBAAuB8B,EAAU,QAASO,CAAO,EACrE,EAAS,QAAUC,EACnB,EAAQ,QAAU,KAAK,UAAUD,GAAW,CAAC,CAAC,EAE1CZ,IACF,EAAI,QAAUa,GAGhBA,EACG,UAAU,CAAC,CACX,KAAM,GAAUN,EAAS,QAAQ,UAAUO,CAAK,CAAC,CAAC,CAClD,UAAY,CAEb,CAAC,EAEH,IAAMC,EAAYF,EAAQ,GAAG,SAAW,GACtCN,EAAS,QAAQ,WAAWS,CAAO,CACrC,EACMC,EAAYJ,EAAQ,GAAG,SAAW,GACtCN,EAAS,QAAQ,WAAWS,CAAO,CACrC,EACME,EAAaL,EAAQ,GAAG,UAAY,GACxCN,EAAS,QAAQ,YAAYS,CAAO,CACtC,EAEA,UAAa,CACXD,EAAU,EACVE,EAAU,EACVC,EAAW,EACXL,EAAQ,QAAQ,EAChB,EAAS,QAAU,KAEfb,IACF,EAAI,QAAU,KAElB,CAEF,EAAG,CAACW,CAAQ,CAAC,GAIb,EAAAd,EAAA,UAAA,KAAgB,CACd,IAAMe,EAAUN,EAAS,QACzB,GAAI,CAACM,EACH,OAGF,IAAMO,EAAO,KAAK,UAAUV,GAAU,CAAC,CAAC,EACpCU,IAAST,EAAQ,UAGrB,EAAQ,QAAUS,EAElBP,EAAQ,UAAUH,CAAM,EAC1B,EAAG,CAACA,CAAM,CAAC,GAGT,EAAAX,EAAA,IAAA,CAAC,MAAD,CACE,IAAKO,EACM,YACX,MAAO,CAAE,MAAO,OAAQ,OAAQ,OAAQ,GAAGe,CAAM,CAClD,CAAA,CAEL,CAAC,E"}
package/dist/react.js ADDED
@@ -0,0 +1,53 @@
1
+ import { PDFMergeSortSplitClass as e } from "./index.js";
2
+ import { forwardRef as t, useCallback as n, useEffect as r, useRef as i } from "react";
3
+ import { jsx as a } from "react/jsx-runtime";
4
+ // src/react.jsx
5
+ var o = () => {
6
+ let e = i(null);
7
+ return {
8
+ ref: e,
9
+ loadDocument: n((t) => e.current.loadDocument(t), []),
10
+ exportDocument: n((t, n) => e.current.exportDocument(t, n), []),
11
+ getPages: n(() => e.current.getPages(), [])
12
+ };
13
+ }, s = t(function({ config: t, style: n, className: o, onAction: s, onExport: c, onLicense: l, onReady: u }, d) {
14
+ let f = i(null), p = i(null), m = i({
15
+ onAction: s,
16
+ onExport: c,
17
+ onLicense: l,
18
+ onReady: u
19
+ });
20
+ m.current = {
21
+ onAction: s,
22
+ onExport: c,
23
+ onLicense: l,
24
+ onReady: u
25
+ };
26
+ let h = i(t);
27
+ h.current = t;
28
+ let g = i(null), _ = t?.embedUrl;
29
+ return r(() => {
30
+ let t = h.current, n = new e(f.current, t);
31
+ p.current = n, g.current = JSON.stringify(t ?? {}), d && (d.current = n), n.whenReady().then((e) => m.current.onReady?.(e)).catch(() => {});
32
+ let r = n.on("action", (e) => m.current.onAction?.(e)), i = n.on("export", (e) => m.current.onExport?.(e)), a = n.on("license", (e) => m.current.onLicense?.(e));
33
+ return () => {
34
+ r(), i(), a(), n.destroy(), p.current = null, d && (d.current = null);
35
+ };
36
+ }, [_]), r(() => {
37
+ let e = p.current;
38
+ if (!e) return;
39
+ let n = JSON.stringify(t ?? {});
40
+ n !== g.current && (g.current = n, e.setConfig(t));
41
+ }, [t]), /* @__PURE__ */ a("div", {
42
+ ref: f,
43
+ className: o,
44
+ style: {
45
+ width: "100%",
46
+ height: "100%",
47
+ ...n
48
+ }
49
+ });
50
+ });
51
+ export { s as PDFMergeSortSplit, s as default, e as PDFMergeSortSplitClass, o as usePdfMergeSortSplit };
52
+
53
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","names":["usePdfMergeSortSplit","ref","useRef","useCallback","document","reference","mode","PDFMergeSortSplit","forwardRef","container","instance","handlers","configRef","config","applied","embedUrl","useEffect","current","created","PDFMergeSortSplitClass","ready","offAction","payload","offExport","offLicense","json","style"],"sources":["../src/react.jsx"],"sourcesContent":["import React, { forwardRef, useCallback, useEffect, useRef } from 'react';\n\nimport { PDFMergeSortSplitClass } from './index.js';\n\n/**\n * The editor's methods, bound to the instance the component creates.\n *\n * ```jsx\n * const { ref, loadDocument, exportDocument, getPages } = usePdfMergeSortSplit();\n *\n * const handleAction = async ({ action }) => {\n * if (action === 'saveZip') download(await exportDocument(undefined, 'zip'));\n * };\n *\n * <PDFMergeSortSplit ref={ref} config={{ apiKey }} onAction={handleAction} />\n * ```\n *\n * The returned functions are stable, so callers need no `useCallback` or\n * `useMemo` wrapper; `ref` is only needed for the escape hatches\n * (`whenReady`, `on`, `destroy`) and to pass to the component.\n */\nexport const usePdfMergeSortSplit = () => {\n const ref = useRef(null);\n\n const loadDocument = useCallback(\n (document) => ref.current.loadDocument(document),\n [],\n );\n const exportDocument = useCallback(\n (reference, mode) => ref.current.exportDocument(reference, mode),\n [],\n );\n const getPages = useCallback(() => ref.current.getPages(), []);\n\n return { ref, loadDocument, exportDocument, getPages };\n};\n\n/**\n * The editor as a React component.\n *\n * The iframe is created once and lives for the lifetime of the component:\n * changing `onAction` / `onExport` / `onReady` does not reload it, and a new\n * `config` is applied to the running editor (`setConfig`) instead of rebuilding\n * it, so the open document stays. Only a different `config.embedUrl` needs a\n * new iframe, which the component creates itself.\n */\nexport const PDFMergeSortSplit = forwardRef(function PDFMergeSortSplit(\n { config, style, className, onAction, onExport, onLicense, onReady },\n ref,\n) {\n const container = useRef(null);\n const instance = useRef(null);\n const handlers = useRef({ onAction, onExport, onLicense, onReady });\n handlers.current = { onAction, onExport, onLicense, onReady };\n\n // The effect that creates the iframe needs the config of the render it runs\n // in, but must not re-run for every new config object.\n const configRef = useRef(config);\n configRef.current = config;\n\n // What the running editor already knows. `config={{ … }}` is a new object on\n // every render, so the comparison is by content, not by reference.\n const applied = useRef(null);\n\n // `embedUrl` is the iframe's src: only that needs a new iframe.\n const embedUrl = config?.embedUrl;\n\n useEffect(() => {\n const current = configRef.current;\n const created = new PDFMergeSortSplitClass(container.current, current);\n instance.current = created;\n applied.current = JSON.stringify(current ?? {});\n\n if (ref) {\n ref.current = created;\n }\n\n created\n .whenReady()\n .then((ready) => handlers.current.onReady?.(ready))\n .catch(() => {\n // `ready` rejects when the editor never answers; onReady is not called.\n });\n\n const offAction = created.on('action', (payload) =>\n handlers.current.onAction?.(payload),\n );\n const offExport = created.on('export', (payload) =>\n handlers.current.onExport?.(payload),\n );\n const offLicense = created.on('license', (payload) =>\n handlers.current.onLicense?.(payload),\n );\n\n return () => {\n offAction();\n offExport();\n offLicense();\n created.destroy();\n instance.current = null;\n\n if (ref) {\n ref.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [embedUrl]);\n\n // Everything except `embedUrl` is applied to the running editor, so a config\n // change does not reload the iframe and the open document stays.\n useEffect(() => {\n const current = instance.current;\n if (!current) {\n return;\n }\n\n const json = JSON.stringify(config ?? {});\n if (json === applied.current) {\n return;\n }\n applied.current = json;\n\n current.setConfig(config);\n }, [config]);\n\n return (\n <div\n ref={container}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n});\n\nexport { PDFMergeSortSplitClass } from './index.js';\nexport default PDFMergeSortSplit;\n"],"mappings":";;;;AAqBA,IAAaA,UAA6B;CACxC,IAAMC,IAAMC,EAAO,IAAI;CAYvB,OAAO;EAAE;EAAK,cAVOC,GAClB,MAAaF,EAAI,QAAQ,aAAaG,CAAQ,GAC/C,CAAC,CAQW;EAAc,gBANLD,GACpB,GAAW,MAASF,EAAI,QAAQ,eAAeI,GAAWC,CAAI,GAC/D,CAAC,CAIyB;EAAgB,UAF3BH,QAAkBF,EAAI,QAAQ,SAAS,GAAG,CAAC,CAEhB;CAAS;AACvD,GAWaM,IAAoBC,EAAW,SAC1C,EAAE,WAAQ,UAAO,cAAW,aAAU,aAAU,cAAW,cAC3D,GACA;CACA,IAAMC,IAAYP,EAAO,IAAI,GACvBQ,IAAWR,EAAO,IAAI,GACtBS,IAAWT,EAAO;EAAE;EAAU;EAAU;EAAW;CAAQ,CAAC;CAClE,EAAS,UAAU;EAAE;EAAU;EAAU;EAAW;CAAQ;CAI5D,IAAMU,IAAYV,EAAOW,CAAM;CAC/B,EAAU,UAAUA;CAIpB,IAAMC,IAAUZ,EAAO,IAAI,GAGrBa,IAAWF,GAAQ;CA4DzB,OA1DAG,QAAgB;EACd,IAAMC,IAAUL,EAAU,SACpBM,IAAU,IAAIC,EAAuBV,EAAU,SAASQ,CAAO;EAQrE,AAPA,EAAS,UAAUC,GACnB,EAAQ,UAAU,KAAK,UAAUD,KAAW,CAAC,CAAC,GAE1ChB,MACF,EAAI,UAAUiB,IAGhBA,EACG,UAAU,CAAC,CACX,MAAM,MAAUP,EAAS,QAAQ,UAAUS,CAAK,CAAC,CAAC,CAClD,YAAY,CAEb,CAAC;EAEH,IAAMC,IAAYH,EAAQ,GAAG,WAAW,MACtCP,EAAS,QAAQ,WAAWW,CAAO,CACrC,GACMC,IAAYL,EAAQ,GAAG,WAAW,MACtCP,EAAS,QAAQ,WAAWW,CAAO,CACrC,GACME,IAAaN,EAAQ,GAAG,YAAY,MACxCP,EAAS,QAAQ,YAAYW,CAAO,CACtC;EAEA,aAAa;GAOX,AANAD,EAAU,GACVE,EAAU,GACVC,EAAW,GACXN,EAAQ,QAAQ,GAChB,EAAS,UAAU,MAEfjB,MACF,EAAI,UAAU;EAElB;CAEF,GAAG,CAACc,CAAQ,CAAC,GAIbC,QAAgB;EACd,IAAMC,IAAUP,EAAS;EACzB,IAAI,CAACO,GACH;EAGF,IAAMQ,IAAO,KAAK,UAAUZ,KAAU,CAAC,CAAC;EACxC,AAAIY,MAASX,EAAQ,YAGrB,EAAQ,UAAUW,GAElBR,EAAQ,UAAUJ,CAAM;CAC1B,GAAG,CAACA,CAAM,CAAC,GAGT,kBAAC,OAAD;EACE,KAAKJ;EACM;EACX,OAAO;GAAE,OAAO;GAAQ,QAAQ;GAAQ,GAAGiB;EAAM;CAClD,CAAA;AAEL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@rsj-de/pdfmergesortsplit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Embed the RSJ PDF Merge, Sort & Split editor in your own product: one iframe, a small SDK, documents never leave the browser.",
5
+ "keywords": [
6
+ "pdf",
7
+ "merge",
8
+ "split",
9
+ "pdf-editor",
10
+ "iframe",
11
+ "rsj"
12
+ ],
13
+ "homepage": "https://pdfmerge.rsj.de/docs/getting-started",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/pdfmergesortsplit/pdfmergesortsplit.git",
17
+ "directory": "sdk"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/pdfmergesortsplit/pdfmergesortsplit/issues"
21
+ },
22
+ "author": "RSJ Software GmbH <ruediger.jungbeck@rsj.de> (https://rsj.de)",
23
+ "type": "module",
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./types/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./types/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ },
33
+ "./react": {
34
+ "types": "./types/react.d.ts",
35
+ "import": "./dist/react.js",
36
+ "require": "./dist/react.cjs"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "types",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "sideEffects": false,
50
+ "license": "MIT",
51
+ "scripts": {
52
+ "build": "vite build",
53
+ "prepublishOnly": "pnpm run build"
54
+ },
55
+ "peerDependencies": {
56
+ "react": ">=18"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "react": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@vitejs/plugin-react": "^4.7.0",
65
+ "react": "^19.2.6",
66
+ "vite": "^8.0.13"
67
+ }
68
+ }
@@ -0,0 +1,129 @@
1
+ /** Configuration the embedding page hands to the editor. */
2
+ export interface PDFMergeConfig {
3
+ /** Where the editor is served (default `https://pdfmerge.rsj.de`). */
4
+ embedUrl?: string;
5
+ /** Licence JWT - removes the test-mode banner in the embedded editor. */
6
+ apiKey?: string;
7
+ /** Title shown in the editor's title bar. */
8
+ title?: string;
9
+ /** Icon (URL) that replaces ours in the title bar. */
10
+ icon?: string;
11
+ /**
12
+ * OAuth client ID of your own Google Cloud project for the Drive integration.
13
+ * It overrides the one compiled into the editor, so a self-hosted deployment
14
+ * needs no rebuild. Without it the Drive buttons are hidden.
15
+ */
16
+ googleClientId?: string;
17
+ /** Hide parts of the editor's UI. Everything is shown by default. */
18
+ ui?: {
19
+ logo?: boolean;
20
+ icon?: boolean;
21
+ landing?: boolean;
22
+ title?: boolean;
23
+ about?: boolean;
24
+ blog?: boolean;
25
+ language?: boolean;
26
+ extension?: boolean;
27
+ fullscreen?: boolean;
28
+ theme?: boolean;
29
+ toolbar?: {
30
+ new?: boolean;
31
+ savePdf?: boolean;
32
+ saveZip?: boolean;
33
+ saveDrive?: boolean;
34
+ print?: boolean;
35
+ undo?: boolean;
36
+ redo?: boolean;
37
+ upload?: boolean;
38
+ drive?: boolean;
39
+ addText?: boolean;
40
+ scaleSort?: boolean;
41
+ pages?: boolean;
42
+ rotate?: boolean;
43
+ previous?: boolean;
44
+ next?: boolean;
45
+ delete?: boolean;
46
+ scalePage?: boolean;
47
+ };
48
+ partHeader?: {
49
+ label?: boolean;
50
+ name?: boolean;
51
+ pages?: boolean;
52
+ save?: boolean;
53
+ drive?: boolean;
54
+ print?: boolean;
55
+ };
56
+ };
57
+ /** Buttons injected into the editor; clicks arrive as "action" events. */
58
+ actions?: {
59
+ loaders?: Record<string, PDFMergeAction>;
60
+ saveAll?: Record<string, PDFMergeAction>;
61
+ savePart?: Record<string, PDFMergeAction>;
62
+ };
63
+ /** Write the page provenance into exported PDFs (default off). */
64
+ provenance?: { attach?: boolean };
65
+ /** Timeout for the initial handshake in ms (default 20000). */
66
+ readyTimeout?: number;
67
+ }
68
+
69
+ export interface PDFMergeAction {
70
+ title?: string;
71
+ icon?: string;
72
+ }
73
+
74
+ export interface PDFMergePage {
75
+ id: number;
76
+ page: number;
77
+ rotate: number;
78
+ partName: string;
79
+ provenance: unknown;
80
+ }
81
+
82
+ export interface PDFMergeActionEvent {
83
+ /** Key of the clicked button in `config.actions` - not the injection point. */
84
+ action: string;
85
+ /** Injection point the button was configured in. */
86
+ where: 'loaders' | 'saveAll' | 'savePart';
87
+ reference?: string;
88
+ part?: number;
89
+ }
90
+
91
+ export interface PDFMergeExportEvent {
92
+ reference?: string | number;
93
+ mode: 'pdf' | 'zip';
94
+ fileName: string;
95
+ pages: Array<{ page: number; provenance: unknown }>;
96
+ blob: Blob;
97
+ }
98
+
99
+ export interface PDFMergeLicenseEvent {
100
+ licensed: boolean;
101
+ reason?: string;
102
+ }
103
+
104
+ export interface LoadDocumentOptions {
105
+ name?: string;
106
+ content: File | Blob | ArrayBuffer | Uint8Array | string;
107
+ provenance?: unknown[] | object;
108
+ }
109
+
110
+ export declare class PDFMergeSortSplitClass {
111
+ constructor(container: string | HTMLElement, config?: PDFMergeConfig);
112
+
113
+ readonly iframe: HTMLIFrameElement;
114
+
115
+ whenReady(): Promise<PDFMergeSortSplitClass>;
116
+ /** Apply a new configuration to the running editor (no reload). */
117
+ setConfig(config?: PDFMergeConfig): void;
118
+ on(event: 'action', handler: (event: PDFMergeActionEvent) => void): () => void;
119
+ on(event: 'export', handler: (event: PDFMergeExportEvent) => void): () => void;
120
+ on(event: 'license', handler: (event: PDFMergeLicenseEvent) => void): () => void;
121
+ off(event: string, handler: (event: unknown) => void): void;
122
+ loadDocument(options: LoadDocumentOptions): Promise<unknown>;
123
+ loadDocument(name: string, content: LoadDocumentOptions['content']): Promise<unknown>;
124
+ getPages(): Promise<PDFMergePage[]>;
125
+ exportDocument(reference?: string | number, mode?: 'pdf' | 'zip'): Promise<Blob>;
126
+ destroy(): void;
127
+ }
128
+
129
+ export default PDFMergeSortSplitClass;
@@ -0,0 +1,39 @@
1
+ import type { CSSProperties, RefObject } from 'react';
2
+
3
+ import type {
4
+ PDFMergeActionEvent,
5
+ PDFMergeConfig,
6
+ PDFMergeExportEvent,
7
+ PDFMergeLicenseEvent,
8
+ PDFMergePage,
9
+ PDFMergeSortSplitClass,
10
+ } from './index.js';
11
+
12
+ export interface PDFMergeSortSplitProps {
13
+ config?: PDFMergeConfig;
14
+ style?: CSSProperties;
15
+ className?: string;
16
+ onAction?: (event: PDFMergeActionEvent) => void;
17
+ onExport?: (event: PDFMergeExportEvent) => void;
18
+ onLicense?: (event: PDFMergeLicenseEvent) => void;
19
+ onReady?: (editor: PDFMergeSortSplitClass) => void;
20
+ }
21
+
22
+ export declare const PDFMergeSortSplit: React.ForwardRefExoticComponent<
23
+ PDFMergeSortSplitProps & React.RefAttributes<PDFMergeSortSplitClass | null>
24
+ >;
25
+
26
+ export declare function usePdfMergeSortSplit(): {
27
+ ref: RefObject<PDFMergeSortSplitClass | null>;
28
+ loadDocument: (
29
+ document:
30
+ | { name?: string; content: File | Blob | ArrayBuffer | Uint8Array | string; provenance?: unknown[] | object }
31
+ | string,
32
+ content?: File | Blob | ArrayBuffer | Uint8Array | string,
33
+ ) => Promise<unknown>;
34
+ exportDocument: (reference?: string | number, mode?: 'pdf' | 'zip') => Promise<Blob>;
35
+ getPages: () => Promise<PDFMergePage[]>;
36
+ };
37
+
38
+ export { PDFMergeSortSplitClass } from './index.js';
39
+ export default PDFMergeSortSplit;