@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.
Files changed (3) hide show
  1. package/README.md +25 -7
  2. package/dist/es/index.js +91 -45
  3. package/package.json +20 -24
package/README.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # mirador-pdf-plugin
2
2
 
3
- A [Mirador 3](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.
3
+ [![Node Unit Tests](https://github.com/harvard-lts/mirador-pdf-plugin/actions/workflows/coverage-node.yml/badge.svg)](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 3
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 3. Visit the [Mirador wiki](https://github.com/ProjectMirador/mirador/wiki) to learn how to [install an existing plugin](https://github.com/ProjectMirador/mirador/wiki/Mirador-3-plugins#installing-an-existing-plugin) and for additional information about plugins.
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/dist/es/src/index';
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 | Type | Description |
69
- | -------- | ------ | ------------------------------------------------ |
70
- | `pdfAPI` | String | Base URL of the PDF download service (required). |
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(PictureAsPdfIcon, {})
160
+ children: /*#__PURE__*/jsx(PDFIcon, {})
161
161
  }), /*#__PURE__*/jsx(ListItemText, {
162
162
  primaryTypographyProps: {
163
- variant: "body1"
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
- var _useState3 = useState(''),
226
- _useState4 = _slicedToArray(_useState3, 2),
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
- setError('');
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 comma separated list of pages and/or ranges.")
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: Boolean(error),
318
- helperText: error || "Enter a page number or range (e.g. 5 or 5-10). Max: ".concat(totalPages),
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": "1.0.0-beta.1",
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
- "prop-types": "^15.8.1"
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
- "@emotion/react": "^11.10.6",
38
- "@emotion/styled": "^11.10.6"
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.0.0",
54
- "@vitest/coverage-v8": "^1.0.0",
55
- "@vitest/ui": "^1.0.0",
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.0.0",
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": "^5.0.0",
65
- "vitest": "^1.0.0",
60
+ "vite": "^8.0.16",
61
+ "vitest": "^4.1.11",
66
62
  "whatwg-fetch": "^3.6.20"
67
63
  }
68
64
  }