@harvard-lts/mirador-pdf-plugin 1.0.0-beta.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -7
- package/dist/es/index.js +91 -45
- package/package.json +20 -24
package/README.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# mirador-pdf-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/harvard-lts/mirador-pdf-plugin/actions/workflows/coverage-node.yml)
|
|
4
|
+
|
|
5
|
+
<a href="https://github.com/harvard-lts/mirador-pdf-plugin/actions/workflows/coverage-node.yml"><img src="https://github.com/harvard-lts/mirador-pdf-plugin/raw/badges/test-coverage/coverage.svg"></a>
|
|
6
|
+
|
|
7
|
+
A [Mirador 4](https://projectmirador.org/) plugin that adds a **Download PDF** option to the window top-bar menu. It connects to an external PDF download service and allows users to optionally specify a page number or range before downloading.
|
|
8
|
+
|
|
9
|
+
## Compatibility
|
|
10
|
+
|
|
11
|
+
This plugin is **Mirador 4-compatible** (React 18/19, MUI 7). It is **not**
|
|
12
|
+
backwards compatible with Mirador 3 — the upgrade contains breaking changes
|
|
13
|
+
(top-level `mirador` imports, function/hook components, MUI 7 + Emotion styling).
|
|
14
|
+
|
|
15
|
+
Versioning convention:
|
|
16
|
+
|
|
17
|
+
- **Mirador 4** releases are tagged `2.x`.
|
|
18
|
+
- **Mirador 3** releases are tagged `0.x` or `1.x` — pin one of these if you
|
|
19
|
+
still need Mirador 3.
|
|
4
20
|
|
|
5
21
|
## Features
|
|
6
22
|
|
|
@@ -13,7 +29,7 @@ A [Mirador 3](https://projectmirador.org/) plugin that adds a **Download PDF** o
|
|
|
13
29
|
## Requirements
|
|
14
30
|
|
|
15
31
|
- [NVM](https://github.com/nvm-sh/nvm)
|
|
16
|
-
- Mirador
|
|
32
|
+
- Mirador 4
|
|
17
33
|
|
|
18
34
|
## Setup
|
|
19
35
|
|
|
@@ -33,7 +49,7 @@ The following are some useful scripts that can be run using `npm run <script>`.
|
|
|
33
49
|
|
|
34
50
|
## Installing in Mirador
|
|
35
51
|
|
|
36
|
-
The `mirador-pdf-plugin` requires an instance of Mirador
|
|
52
|
+
The `mirador-pdf-plugin` requires an instance of Mirador 4. Visit the [Mirador wiki](https://github.com/ProjectMirador/mirador/wiki) and the [Creating a Mirador 4 Plugin](https://github.com/ProjectMirador/mirador/wiki/Creating-a-Mirador-4-Plugin) page for information about installing and building plugins.
|
|
37
53
|
|
|
38
54
|
Install the package:
|
|
39
55
|
|
|
@@ -46,7 +62,7 @@ npm i @harvard-lts/mirador-pdf-plugin
|
|
|
46
62
|
Pass a `miradorPdfPlugin` object in your Mirador config block:
|
|
47
63
|
|
|
48
64
|
```js
|
|
49
|
-
import Mirador from 'mirador
|
|
65
|
+
import Mirador from 'mirador';
|
|
50
66
|
import Plugin from '@harvard-lts/mirador-pdf-plugin';
|
|
51
67
|
|
|
52
68
|
Mirador.viewer(
|
|
@@ -57,6 +73,7 @@ Mirador.viewer(
|
|
|
57
73
|
],
|
|
58
74
|
miradorPdfPlugin: {
|
|
59
75
|
pdfAPI: 'https://your-pdf-service.example.com/pdf/download/',
|
|
76
|
+
maxPages: 500,
|
|
60
77
|
},
|
|
61
78
|
},
|
|
62
79
|
[...Plugin]
|
|
@@ -65,9 +82,10 @@ Mirador.viewer(
|
|
|
65
82
|
|
|
66
83
|
### Config options
|
|
67
84
|
|
|
68
|
-
| Option
|
|
69
|
-
|
|
|
70
|
-
| `pdfAPI`
|
|
85
|
+
| Option | Type | Description |
|
|
86
|
+
| ---------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `pdfAPI` | String | Base URL of the PDF download service (required). |
|
|
88
|
+
| `maxPages` | Number | Maximum pages allowed in a single request (default: 500). Keep this in sync with the service's `MAX_PAGES_PER_REQUEST` env var so client/server agree. |
|
|
71
89
|
|
|
72
90
|
### URL construction
|
|
73
91
|
|
package/dist/es/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import MenuItem from '@mui/material/MenuItem';
|
|
2
2
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
|
3
3
|
import ListItemText from '@mui/material/ListItemText';
|
|
4
|
+
import PDFIcon from '@mui/icons-material/PictureAsPdf';
|
|
4
5
|
import { getManifestoInstance } from 'mirador';
|
|
5
|
-
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
-
import { useState, useEffect } from 'react';
|
|
7
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
8
8
|
import Button from '@mui/material/Button';
|
|
9
9
|
import Dialog from '@mui/material/Dialog';
|
|
10
10
|
import DialogActions from '@mui/material/DialogActions';
|
|
@@ -157,10 +157,10 @@ var MiradorPdfMenuItem = function MiradorPdfMenuItem(_ref3) {
|
|
|
157
157
|
children: /*#__PURE__*/jsxs(MenuItem, {
|
|
158
158
|
onClick: handleClick,
|
|
159
159
|
children: [/*#__PURE__*/jsx(ListItemIcon, {
|
|
160
|
-
children: /*#__PURE__*/jsx(
|
|
160
|
+
children: /*#__PURE__*/jsx(PDFIcon, {})
|
|
161
161
|
}), /*#__PURE__*/jsx(ListItemText, {
|
|
162
162
|
primaryTypographyProps: {
|
|
163
|
-
variant:
|
|
163
|
+
variant: 'body1'
|
|
164
164
|
},
|
|
165
165
|
children: "Download PDF"
|
|
166
166
|
})]
|
|
@@ -180,7 +180,7 @@ var miradorPdfMenuItem = {
|
|
|
180
180
|
};
|
|
181
181
|
|
|
182
182
|
var mapStateToProps = function mapStateToProps(state, _ref) {
|
|
183
|
-
var _manifest$getSequence, _state$config;
|
|
183
|
+
var _manifest$getSequence, _state$config, _state$config2;
|
|
184
184
|
var windowId = _ref.windowId;
|
|
185
185
|
var manifest = getManifestoInstance(state, {
|
|
186
186
|
windowId: windowId
|
|
@@ -190,6 +190,7 @@ var mapStateToProps = function mapStateToProps(state, _ref) {
|
|
|
190
190
|
manifestId: (manifest || {}).id,
|
|
191
191
|
totalPages: manifest ? ((_manifest$getSequence = manifest.getSequences()[0]) === null || _manifest$getSequence === void 0 ? void 0 : _manifest$getSequence.getTotalCanvases()) || 0 : 0,
|
|
192
192
|
pdfAPI: (_state$config = state.config) === null || _state$config === void 0 || (_state$config = _state$config.miradorPdfPlugin) === null || _state$config === void 0 ? void 0 : _state$config.pdfAPI,
|
|
193
|
+
maxPages: ((_state$config2 = state.config) === null || _state$config2 === void 0 || (_state$config2 = _state$config2.miradorPdfPlugin) === null || _state$config2 === void 0 ? void 0 : _state$config2.maxPages) || 500,
|
|
193
194
|
containerId: state.config.id
|
|
194
195
|
};
|
|
195
196
|
};
|
|
@@ -209,6 +210,71 @@ var extractUrn = function extractUrn(manifestId) {
|
|
|
209
210
|
var withoutLeadingSlash = pathname.replace(/^\//, '');
|
|
210
211
|
return withoutLeadingSlash.split(':MANIFEST')[0];
|
|
211
212
|
};
|
|
213
|
+
|
|
214
|
+
// Pure validation of the pages input. Returns { valid, message } so it can drive
|
|
215
|
+
// both the inline error text and the disabled state of the Download button
|
|
216
|
+
// without setting React state during render.
|
|
217
|
+
var validatePages = function validatePages(pages, totalPages, maxPages) {
|
|
218
|
+
if (pages === '') {
|
|
219
|
+
// Empty input means "all pages" — only valid if the whole document fits.
|
|
220
|
+
if (totalPages > maxPages) {
|
|
221
|
+
return {
|
|
222
|
+
valid: false,
|
|
223
|
+
message: "This document has ".concat(totalPages, " pages, which exceeds the ").concat(maxPages, "-page limit. Please enter a range of up to ").concat(maxPages, " pages.")
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
valid: true,
|
|
228
|
+
message: ''
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
var singlePattern = /^\d+$/;
|
|
232
|
+
var rangePattern = /^(\d+)-(\d+)$/;
|
|
233
|
+
if (singlePattern.test(pages)) {
|
|
234
|
+
var num = parseInt(pages, 10);
|
|
235
|
+
if (num < 1 || num > totalPages) {
|
|
236
|
+
return {
|
|
237
|
+
valid: false,
|
|
238
|
+
message: "Page must be between 1 and ".concat(totalPages, ".")
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
valid: true,
|
|
243
|
+
message: ''
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
var rangeMatch = pages.match(rangePattern);
|
|
247
|
+
if (rangeMatch) {
|
|
248
|
+
var start = parseInt(rangeMatch[1], 10);
|
|
249
|
+
var end = parseInt(rangeMatch[2], 10);
|
|
250
|
+
if (start < 1 || end > totalPages) {
|
|
251
|
+
return {
|
|
252
|
+
valid: false,
|
|
253
|
+
message: "Page range must be between 1 and ".concat(totalPages, ".")
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (start >= end) {
|
|
257
|
+
return {
|
|
258
|
+
valid: false,
|
|
259
|
+
message: 'Start page must be less than end page.'
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (end - start + 1 > maxPages) {
|
|
263
|
+
return {
|
|
264
|
+
valid: false,
|
|
265
|
+
message: "You can request at most ".concat(maxPages, " pages at once. Please split this into smaller requests.")
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
valid: true,
|
|
270
|
+
message: ''
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
valid: false,
|
|
275
|
+
message: 'Please enter a page number or range (e.g. 5 or 5-10).'
|
|
276
|
+
};
|
|
277
|
+
};
|
|
212
278
|
var MiradorPdfDialog = function MiradorPdfDialog(_ref3) {
|
|
213
279
|
var _ref3$open = _ref3.open,
|
|
214
280
|
open = _ref3$open === void 0 ? false : _ref3$open,
|
|
@@ -216,59 +282,32 @@ var MiradorPdfDialog = function MiradorPdfDialog(_ref3) {
|
|
|
216
282
|
manifestId = _ref3.manifestId,
|
|
217
283
|
totalPages = _ref3.totalPages,
|
|
218
284
|
pdfAPI = _ref3.pdfAPI,
|
|
285
|
+
_ref3$maxPages = _ref3.maxPages,
|
|
286
|
+
maxPages = _ref3$maxPages === void 0 ? 500 : _ref3$maxPages,
|
|
219
287
|
_ref3$containerId = _ref3.containerId,
|
|
220
288
|
containerId = _ref3$containerId === void 0 ? null : _ref3$containerId;
|
|
221
289
|
var _useState = useState(''),
|
|
222
290
|
_useState2 = _slicedToArray(_useState, 2),
|
|
223
291
|
pages = _useState2[0],
|
|
224
292
|
setPages = _useState2[1];
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
error = _useState4[0],
|
|
228
|
-
setError = _useState4[1];
|
|
293
|
+
|
|
294
|
+
// Reset form state whenever the dialog is closed
|
|
229
295
|
useEffect(function () {
|
|
230
296
|
if (!open) {
|
|
231
297
|
setPages('');
|
|
232
|
-
setError('');
|
|
233
298
|
}
|
|
234
299
|
}, [open]);
|
|
235
300
|
var handleClose = function handleClose() {
|
|
236
301
|
closeDialog();
|
|
237
302
|
setPages('');
|
|
238
|
-
setError('');
|
|
239
|
-
};
|
|
240
|
-
var validate = function validate() {
|
|
241
|
-
if (pages === '') return true;
|
|
242
|
-
var singlePattern = /^\d+$/;
|
|
243
|
-
var rangePattern = /^(\d+)-(\d+)$/;
|
|
244
|
-
if (singlePattern.test(pages)) {
|
|
245
|
-
var num = parseInt(pages, 10);
|
|
246
|
-
if (num < 1 || num > totalPages) {
|
|
247
|
-
setError("Page must be between 1 and ".concat(totalPages, "."));
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
var rangeMatch = pages.match(rangePattern);
|
|
253
|
-
if (rangeMatch) {
|
|
254
|
-
var start = parseInt(rangeMatch[1], 10);
|
|
255
|
-
var end = parseInt(rangeMatch[2], 10);
|
|
256
|
-
if (start < 1 || end > totalPages) {
|
|
257
|
-
setError("Page range must be between 1 and ".concat(totalPages, "."));
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
if (start >= end) {
|
|
261
|
-
setError('Start page must be less than end page.');
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
setError('Please enter a page number or range (e.g. 5 or 5-10).');
|
|
267
|
-
return false;
|
|
268
303
|
};
|
|
304
|
+
|
|
305
|
+
// Live validity drives both the disabled Download button and the helper text.
|
|
306
|
+
var validity = useMemo(function () {
|
|
307
|
+
return validatePages(pages, totalPages, maxPages);
|
|
308
|
+
}, [pages, totalPages, maxPages]);
|
|
269
309
|
var handleDownload = function handleDownload() {
|
|
270
|
-
|
|
271
|
-
if (!validate()) return;
|
|
310
|
+
if (!validity.valid) return;
|
|
272
311
|
var urn = extractUrn(manifestId);
|
|
273
312
|
var baseUrl = "".concat(pdfAPI.replace(/\/$/, ''), "/").concat(urn);
|
|
274
313
|
var url;
|
|
@@ -295,6 +334,7 @@ var MiradorPdfDialog = function MiradorPdfDialog(_ref3) {
|
|
|
295
334
|
fullWidth: true,
|
|
296
335
|
maxWidth: "sm",
|
|
297
336
|
children: [/*#__PURE__*/jsx(DialogTitle, {
|
|
337
|
+
variant: "h2",
|
|
298
338
|
sx: {
|
|
299
339
|
paddingBottom: 0
|
|
300
340
|
},
|
|
@@ -303,7 +343,11 @@ var MiradorPdfDialog = function MiradorPdfDialog(_ref3) {
|
|
|
303
343
|
children: [/*#__PURE__*/jsx(Typography, {
|
|
304
344
|
variant: "body1",
|
|
305
345
|
gutterBottom: true,
|
|
306
|
-
children: "The document contains ".concat(totalPages, " pages and has an estimated file size of ").concat((totalPages * 0.7862).toFixed(2), " MB. All pages will be included by default. If you wish to download certain portions of it, you may provide a
|
|
346
|
+
children: "The document contains ".concat(totalPages, " pages and has an estimated file size of ").concat((totalPages * 0.7862).toFixed(2), " MB. All pages will be included by default. If you wish to download certain portions of it, you may provide a single page (e.g. 5) or a range (e.g. 5-10).")
|
|
347
|
+
}), /*#__PURE__*/jsx(Typography, {
|
|
348
|
+
variant: "body2",
|
|
349
|
+
gutterBottom: true,
|
|
350
|
+
children: "You can download up to ".concat(maxPages, " pages per request. For larger documents, please request several smaller ranges (e.g. 1-").concat(maxPages, ", then ").concat(maxPages + 1, "-").concat(maxPages * 2, ").")
|
|
307
351
|
}), /*#__PURE__*/jsx(TextField, {
|
|
308
352
|
id: "pdf-pages-input",
|
|
309
353
|
label: "Pages",
|
|
@@ -314,8 +358,8 @@ var MiradorPdfDialog = function MiradorPdfDialog(_ref3) {
|
|
|
314
358
|
onKeyDown: function onKeyDown(e) {
|
|
315
359
|
if (e.key === 'Enter') handleDownload();
|
|
316
360
|
},
|
|
317
|
-
error:
|
|
318
|
-
helperText:
|
|
361
|
+
error: !validity.valid,
|
|
362
|
+
helperText: validity.valid ? "Enter a page number or range (e.g. 5 or 5-10). Max ".concat(maxPages, " pages per request.") : validity.message,
|
|
319
363
|
fullWidth: true,
|
|
320
364
|
margin: "normal",
|
|
321
365
|
variant: "outlined"
|
|
@@ -323,11 +367,13 @@ var MiradorPdfDialog = function MiradorPdfDialog(_ref3) {
|
|
|
323
367
|
}), /*#__PURE__*/jsxs(DialogActions, {
|
|
324
368
|
children: [/*#__PURE__*/jsx(Button, {
|
|
325
369
|
onClick: handleClose,
|
|
370
|
+
color: "inherit",
|
|
326
371
|
children: "Close"
|
|
327
372
|
}), /*#__PURE__*/jsx(Button, {
|
|
328
373
|
onClick: handleDownload,
|
|
329
374
|
color: "primary",
|
|
330
375
|
variant: "contained",
|
|
376
|
+
disabled: !validity.valid,
|
|
331
377
|
children: "Download"
|
|
332
378
|
})]
|
|
333
379
|
})]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harvard-lts/mirador-pdf-plugin",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A Mirador 4 plugin that interacts with MPS PDF Download Service",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "A Mirador 4 plugin that interacts with MPS PDF Download Service ",
|
|
5
5
|
"module": "dist/es/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist"
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"serve": "vite",
|
|
16
16
|
"test": "vitest run",
|
|
17
17
|
"test:coverage": "vitest run --coverage",
|
|
18
|
-
"test:watch": "vitest"
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"test:unit": "node scripts/coverage-ci.mjs"
|
|
19
20
|
},
|
|
20
21
|
"repository": {
|
|
21
22
|
"type": "git",
|
|
@@ -26,43 +27,38 @@
|
|
|
26
27
|
"license": "Apache-2.0",
|
|
27
28
|
"homepage": "https://github.com/harvard-lts/mirador-pdf-plugin#readme",
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"peerDependencies": {
|
|
32
|
-
"mirador": "^4.0.0",
|
|
33
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
34
|
-
"react-dom": "^18.0.0 || ^19.0.0",
|
|
30
|
+
"@emotion/react": "^11.11.0",
|
|
31
|
+
"@emotion/styled": "^11.11.0",
|
|
35
32
|
"@mui/material": "^7.0.0",
|
|
36
33
|
"@mui/system": "^7.0.0",
|
|
37
|
-
"@
|
|
38
|
-
"
|
|
34
|
+
"@mui/icons-material": "^7.0.0",
|
|
35
|
+
"jquery": "^3.7.0",
|
|
36
|
+
"prop-types": "^15.8.1",
|
|
37
|
+
"react": "^19.0.0",
|
|
38
|
+
"react-dom": "^19.0.0",
|
|
39
|
+
"react-i18next": "^15.0.0",
|
|
40
|
+
"redux": "^5.0.0",
|
|
41
|
+
"redux-saga": "^1.2.3"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@babel/cli": "^7.26.4",
|
|
42
45
|
"@babel/core": "^7.26.0",
|
|
43
46
|
"@babel/preset-env": "^7.21.4",
|
|
44
47
|
"@babel/preset-react": "^7.18.6",
|
|
45
|
-
"@emotion/react": "^11.10.6",
|
|
46
|
-
"@emotion/styled": "^11.10.6",
|
|
47
|
-
"@mui/material": "^7.0.0",
|
|
48
|
-
"@mui/icons-material": "^7.0.0",
|
|
49
|
-
"@mui/system": "^7.0.0",
|
|
50
48
|
"@rollup/plugin-babel": "^6.0.3",
|
|
51
49
|
"@testing-library/jest-dom": "^6.1.5",
|
|
52
50
|
"@testing-library/react": "^16.0.0",
|
|
53
|
-
"@vitejs/plugin-react": "^5.
|
|
54
|
-
"@vitest/coverage-v8": "^1.
|
|
55
|
-
"@vitest/ui": "^1.
|
|
51
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
52
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
53
|
+
"@vitest/ui": "^4.1.11",
|
|
56
54
|
"jsdom": "^23.0.0",
|
|
57
|
-
"mirador": "^4.
|
|
55
|
+
"mirador": "^4.1.0",
|
|
58
56
|
"npm-run-all": "^4.1.5",
|
|
59
57
|
"parcel": "^2.8.3",
|
|
60
|
-
"react": "^18.3.0",
|
|
61
|
-
"react-dom": "^18.3.0",
|
|
62
58
|
"rollup": "^3.29.5",
|
|
63
59
|
"url": "^0.11.4",
|
|
64
|
-
"vite": "^
|
|
65
|
-
"vitest": "^1.
|
|
60
|
+
"vite": "^8.0.16",
|
|
61
|
+
"vitest": "^4.1.11",
|
|
66
62
|
"whatwg-fetch": "^3.6.20"
|
|
67
63
|
}
|
|
68
64
|
}
|