├── .gitignore
├── src
├── plugins
│ ├── translations.js
│ ├── CustomAddResource.jsx
│ ├── CustomGalleryButton.jsx
│ └── shareMenuPlugin.jsx
├── components
│ ├── CustomBrand.js
│ └── CustomWorkspaceOptionsButton.js
└── index.js
├── .babelrc
├── demo
├── portal
│ ├── workspace.html
│ ├── embedded_collection.html
│ ├── embedded_edition.html
│ └── embedded_descriptor.html
├── demo.txt
└── index.html
├── README.md
├── package.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | package-lock.json
4 | *.log
5 | .cache/
6 |
--------------------------------------------------------------------------------
/src/plugins/translations.js:
--------------------------------------------------------------------------------
1 | const translations = {
2 | en: {
3 | share_download: 'Share & download',
4 | },
5 | fr: {
6 | share_download: 'Partager et télécharger',
7 | },
8 | };
9 |
10 | export default translations;
11 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "useBuiltIns": "usage",
7 | "corejs": 3
8 | }
9 | ],
10 | "@babel/preset-react"
11 | ],
12 | "plugins": ["@babel/plugin-transform-react-jsx"]
13 | }
14 |
--------------------------------------------------------------------------------
/demo/portal/workspace.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mirador Workspace Biblissima
5 |
6 |
8 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/plugins/CustomAddResource.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import WorkspaceAdd from 'mirador/dist/cjs/src/containers/WorkspaceAdd';
4 |
5 | const CustomAddResource = (props) => {
6 | const {
7 | showAddResource, classes
8 | } = props;
9 |
10 | classes.fab = !showAddResource && classes.displayNone
11 | return (
12 |
13 | );
14 | }
15 |
16 | const mapStateToProps = (state, { windowId }) => {
17 | return {
18 | showAddResource: state.config.customAddResource.showAddResource
19 | }
20 | };
21 |
22 | CustomAddResource.propTypes = {
23 | showAddResource: PropTypes.bool
24 | };
25 |
26 | CustomAddResource.defaultProps = {
27 | showAddResource: false
28 | };
29 |
30 | export default {
31 | target: 'WorkspaceAdd',
32 | mode: "wrap",
33 | component: CustomAddResource,
34 | mapStateToProps
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/CustomBrand.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Typography from '@material-ui/core/Typography';
3 | import IconButton from '@material-ui/core/IconButton';
4 | import SvgIcon from '@material-ui/core/SvgIcon';
5 |
6 | export default function ({ TargetComponent, targetProps, className }) {
7 |
8 | return
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
;
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/CustomWorkspaceOptionsButton.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 | import ImportExportIcon from '@material-ui/icons/ImportExport';
5 | import MiradorMenuButton from 'mirador/dist/cjs/src/containers/MiradorMenuButton';
6 | import WorkspaceOptionsMenu from 'mirador/dist/cjs/src/containers/WorkspaceOptionsMenu';
7 | import WorkspaceOptionsButton from 'mirador/dist/cjs/src/containers/WorkspaceOptionsButton';
8 |
9 | export default function ({ TargetComponent, targetProps, t, classes, anchorEl }) {
10 |
11 | return (
12 | <>
13 | { handleMenuClick }}
20 | >
21 |
22 |
23 |
24 |
28 | >
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # mirador3-biblissima
3 |
4 | This repository is for the [Mirador 3](https://github.com/ProjectMirador/mirador/) instance used by the [Biblissima portal](https://portail.biblissima.fr).
5 |
6 | The Biblissima instance includes several customizations to adjust initialization settings and a few plugins to extend viewer's functionality.
7 |
8 |
9 | ## Install dependencies:
10 |
11 | ```
12 | npm install
13 | ```
14 |
15 | ## Start app
16 |
17 | Open `demo/index.html` on localhost:4444 and watch the source files:
18 | ```
19 | npm start
20 | ```
21 |
22 | URL: http://localhost:4444/?iiif-content=https://gallica.bnf.fr/iiif/ark:/12148/btv1b525002505/manifest.json
23 |
24 | Existing URL parameters are:
25 |
26 | - `iiif-content`: URL of IIIF resource (Manifest or Collection) or encoded IIIF Content State
27 | - `mode`: mirador embedding mode (depending on the host page in the Biblissima portal). Possible values are:
28 | - `single`: load a single IIIF resource, without workspace
29 | - `catalog`: bypass the collection modal and populate the catalog window directly with all the child manifests
30 | - `panel`: select which side panel to open when the viewer is initialized (only `info` is supported)
31 | - `theme` = mirador selected theme
32 |
33 | ## Serve demo
34 |
35 | Serve static files in `demo/portal` on localhost:5555
36 | ```
37 | npm run serve
38 | ```
39 |
40 | Directory root: http://localhost:5555/demo/portal
41 |
--------------------------------------------------------------------------------
/demo/demo.txt:
--------------------------------------------------------------------------------
1 | catalog: [
2 | {
3 | manifestId: "https://iiif.bodleian.ox.ac.uk/iiif/manifest/a4b2100c-003f-4868-8587-bc39b685ee47.json",
4 | provider: "Bodleian Libraries"
5 | },
6 | {
7 | manifestId: "https://data.getty.edu/museum/api/iiif/1570/manifest.json",
8 | provider: "The J. Paul Getty Museum"
9 | },
10 | {
11 | manifestId: "https://gallica.bnf.fr/iiif/ark:/12148/btv1b52500984v/manifest.json",
12 | provider: "Bibliothèque nationale de France"
13 | },
14 | {
15 | manifestId: "https://gallica.bnf.fr/iiif/ark:/12148/btv1b8427228j/manifest.json",
16 | provider: "Bibliothèque nationale de France"
17 | }
18 | ],
19 | windows: [
20 | {
21 | manifestId: 'https://iiif.bodleian.ox.ac.uk/iiif/manifest/a4b2100c-003f-4868-8587-bc39b685ee47.json',
22 | canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/3021e338-c998-4696-b81c-477a0fdd6c60.json',
23 | },
24 | {
25 | manifestId: "https://data.getty.edu/museum/api/iiif/1570/manifest.json",
26 | canvasId: "https://data.getty.edu/museum/api/iiif/id:1570/canvas/main",
27 | },
28 | {
29 | manifestId: "https://gallica.bnf.fr/iiif/ark:/12148/btv1b52500984v/manifest.json",
30 | canvasId: "https://gallica.bnf.fr/iiif/ark:/12148/btv1b52500984v/canvas/f191",
31 | },
32 | {
33 | manifestId: "https://gallica.bnf.fr/iiif/ark:/12148/btv1b8427228j/manifest.json",
34 | canvasId: "https://gallica.bnf.fr/iiif/ark:/12148/btv1b8427228j/canvas/f220",
35 | }
36 | ],
--------------------------------------------------------------------------------
/src/plugins/CustomGalleryButton.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from 'react-redux';
3 | import MiradorMenuButton from 'mirador/dist/cjs/src/containers/MiradorMenuButton';
4 | import * as actions from 'mirador/dist/cjs/src/state/actions';
5 | import { getWindowViewType } from 'mirador/dist/cjs/src/state/selectors';
6 | import GalleryViewIcon from 'mirador/dist/cjs/src/components/icons/GalleryViewIcon';
7 |
8 | const mapStateToProps = (state, { windowId }) => (
9 | {
10 | windowViewType: getWindowViewType(state, { windowId }),
11 | }
12 | );
13 |
14 | const mapDispatchToProps = { setWindowViewType: actions.setWindowViewType };
15 |
16 | const _CustomGalleryButton = (props) => {
17 | const {TargetComponent, windowViewType, setWindowViewType, ...targetComponentProps} = props
18 |
19 | return (
20 | <>
21 | { setWindowViewType(props.windowId, "gallery");}}
24 | aria-label={props.t('gallery')}
25 | >
26 |
27 |
28 |
29 | >
30 | )
31 | }
32 |
33 | const CustomGalleryButton = connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(_CustomGalleryButton)
34 |
35 | export default {
36 | target: 'WindowTopMenuButton',
37 | mode: "wrap",
38 | component: CustomGalleryButton
39 | };
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mirador3-biblissima",
3 | "version": "0.1.0",
4 | "description": "",
5 | "private": true,
6 | "main": "index.html",
7 | "scripts": {
8 | "clean": "rm -rf ./dist",
9 | "server": "node_modules/.bin/http-server --cors -o /demo/portal -c-1",
10 | "serve": "npm run server -- -p 5555",
11 | "webpack": "webpack --config webpack.config.js",
12 | "build": "NODE_ENV=production webpack --mode=production",
13 | "start": "NODE_ENV=development webpack-dev-server --open"
14 | },
15 | "dependencies": {
16 | "mirador": "../mirador",
17 | "mirador-dl-plugin": "../mirador-plugins/mirador-dl-plugin",
18 | "mirador-image-tools": "../mirador-plugins/mirador-image-tools",
19 | "mirador-downloaddialog": "../mirador-plugins/mirador-downloaddialog"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.17.7",
23 | "@babel/preset-env": "^7.16.11",
24 | "@babel/preset-react": "^7.16.7",
25 | "@material-ui/core": "^4.12.3",
26 | "@material-ui/icons": "^4.9.1",
27 | "@material-ui/styles": "^4.11.4",
28 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
29 | "babel-loader": "^9.1.0",
30 | "babel-runtime": "^6.26.0",
31 | "classnames": "^2.2.6",
32 | "core-js": "^3.21.1",
33 | "css-loader": "^3.6.0",
34 | "eslint-plugin-react-hooks": "^4.6.0",
35 | "http-server": "^14.1.0",
36 | "react": "^17.0.0",
37 | "react-dom": "^17.0.0",
38 | "react-redux": "^7.2.4",
39 | "react-refresh": "^0.14.0",
40 | "style-loader": "^1.2.1",
41 | "webpack": "^5.70.0",
42 | "webpack-cli": "^5.0.0",
43 | "webpack-dev-server": "^4.7.4"
44 | },
45 | "keywords": []
46 | }
47 |
--------------------------------------------------------------------------------
/src/plugins/shareMenuPlugin.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import React from 'react';
3 | import SvgIcon from '@material-ui/core/SvgIcon';
4 | import { withStyles } from '@material-ui/core';
5 | import { withPlugins } from 'mirador/dist/es/src/extend/withPlugins';
6 | import { WindowTopBarPluginMenu } from 'mirador/dist/es/src/components/WindowTopBarPluginMenu';
7 | import { getContainerId } from 'mirador/dist/es/src/state/selectors';
8 | import translations from './translations';
9 | import PropTypes from "prop-types";
10 |
11 | const CustomIcon = (props) => (
12 |
13 |
18 |
19 | );
20 |
21 | const WindowTopBarShareMenu = (props) => (
22 | 'share_download'}
28 | //t={() => 'Share & download'}
29 | menuIcon={}
30 | />
31 | )
32 |
33 | /**
34 | *
35 | * @param theme
36 | * @returns {{ctrlBtn: {margin: (number|string)}}}
37 | */
38 | const styles = theme => ({
39 | ctrlBtnSelected: {
40 | backgroundColor: theme.palette.action.selected,
41 | },
42 | });
43 |
44 | const ImprovedWindowTopBarShareMenu = withStyles(styles)(
45 | withPlugins('WindowTopBarShareMenu')(
46 | WindowTopBarShareMenu
47 | )
48 | );
49 |
50 | WindowTopBarShareMenu.propTypes = {
51 | t: PropTypes.func.isRequired,
52 | };
53 |
54 | export default {
55 | component: ImprovedWindowTopBarShareMenu,
56 | mapStateToProps: (state) => ({
57 | containerId: getContainerId(state),
58 | }),
59 | mode: 'add',
60 | name: 'WindowTopBarShareMenu',
61 | target: 'WindowTopBarPluginArea',
62 | config: {
63 | translations,
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
4 |
5 | //module.exports = {
6 | const baseConfig = mode => ({
7 | entry: './src/index.js',
8 | module: {
9 | rules: [
10 | {
11 | test: /\.jsx?$/,
12 | exclude: /node_modules/,
13 | loader: "babel-loader"
14 | },
15 | {
16 | test: /\.css$/,
17 | use: [
18 | 'style-loader',
19 | 'css-loader',
20 | ],
21 | },
22 | ],
23 | },
24 | output: {
25 | filename: 'index.min.js',
26 | path: path.resolve(__dirname, 'dist'),
27 | publicPath: '/dist/',
28 | },
29 | plugins: [
30 | new webpack.IgnorePlugin({
31 | resourceRegExp: /@blueprintjs\/(core|icons)/, // ignore optional UI framework dependencies
32 | }),
33 | ],
34 | resolve: {
35 | alias: {
36 | '@material-ui/core': path.resolve('./', 'node_modules', '@material-ui/core'),
37 | '@material-ui/styles': path.resolve('./', 'node_modules', '@material-ui/styles'),
38 | 'react': path.resolve('./', 'node_modules', 'react'),
39 | 'react-dom': path.resolve('./', 'node_modules', 'react-dom'),
40 | 'react-redux': path.resolve('./', 'node_modules', 'react-redux'),
41 | 'mirador': path.resolve('./', 'node_modules', 'mirador'),
42 | },
43 | extensions: [".jsx", ".js"]
44 | }
45 | });
46 |
47 | module.exports = (env, options) => {
48 | const isProduction = options.mode === 'production';
49 | const config = baseConfig(options.mode);
50 |
51 | if (isProduction) {
52 | return {
53 | ...config,
54 | devtool: 'source-map',
55 | mode: 'production',
56 | plugins: [
57 | ...(config.plugins || []),
58 | new webpack.optimize.LimitChunkCountPlugin({
59 | maxChunks: 1,
60 | }),
61 | ],
62 | };
63 | }
64 |
65 | return {
66 | ...config,
67 | devServer: {
68 | static: {
69 | directory: path.join(__dirname, 'demo'),
70 | },
71 | hot: true,
72 | port: 4444,
73 | },
74 | devtool: 'eval-source-map',
75 | mode: 'development',
76 | plugins: [
77 | ...(config.plugins || []),
78 | new ReactRefreshWebpackPlugin(),
79 | ],
80 | };
81 | };
82 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mirador Viewer - Biblissima
5 |
19 |
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Mirador from 'mirador/dist/es/src/index';
2 | import miradorDownloadPlugin from 'mirador-dl-plugin/es/miradorDownloadPlugin';
3 | import miradorDownloadDialogPlugin from 'mirador-dl-plugin/es/MiradorDownloadDialog';
4 | import downloadDialogPlugin from 'mirador-downloaddialog/es';
5 | import { miradorImageToolsPlugin } from 'mirador-image-tools';
6 | import CustomGalleryButton from './plugins/CustomGalleryButton';
7 | import CustomBrand from './components/CustomBrand';
8 | import CustomAddResource from './plugins/CustomAddResource';
9 | //import CustomWorkspaceOptionsButton from './components/CustomWorkspaceOptionsButton';
10 | //--- BSB canvas-navigation (=> fr translation not taken into account)
11 | //import canvasNavigationPlugin from 'mirador-canvasnavigation/es';
12 | //--- BSB cropper (=> not working)
13 | //import imageCropperPlugin from 'mirador-imagecropper/es';
14 | //--- Custom button "Share and download" from Stanford (=> translations not supported)
15 | //import shareMenuPlugin from './plugins/shareMenuPlugin';
16 | //import miradorShareDialogPlugin from 'mirador-share-plugin/es/MiradorShareDialog.js';
17 | //import miradorSharePlugin from 'mirador-share-plugin/es/miradorSharePlugin.js';
18 |
19 | // get URL params
20 | let params = new URL(document.location).searchParams;
21 | let iiifResource = params.get('iiif-content') || params.get('manifest');
22 | let initializedManifest = params.get('manifest');
23 | let mode = params.get('context') || params.get('mode'); // possible values are: single | catalog
24 | let theme = params.get('theme');
25 | let panel = params.get('panel');
26 | let lang = params.get('language');
27 |
28 | // set enabled plugins
29 | const plugins = [
30 | // miradorDownloadDialogPlugin,
31 | // {
32 | // ...miradorDownloadPlugin,
33 | // target: 'WindowTopBarShareMenu',
34 | // },
35 | // shareMenuPlugin,
36 | // {
37 | // ...miradorSharePlugin,
38 | // target: 'WindowTopBarShareMenu',
39 | // },
40 | // miradorShareDialogPlugin,
41 | CustomGalleryButton,
42 | CustomAddResource,
43 | ...miradorImageToolsPlugin,
44 | ...downloadDialogPlugin,
45 | {
46 | mode: 'wrap',
47 | component: CustomBrand,
48 | target: 'Branding',
49 | },
50 | // {
51 | // mode: 'wrap',
52 | // component: CustomWorkspaceOptionsButton,
53 | // target: 'WorkspaceOptionsButton',
54 | // }
55 | //...canvasNavigationPlugin,
56 | //...imageCropperPlugin,
57 | ];
58 |
59 | // set default config
60 | var config = {
61 | id: 'm3',
62 | language: 'fr',
63 | selectedTheme: 'light',
64 | themes: {
65 | dark: {
66 | palette: {
67 | type: "dark",
68 | primary: {
69 | main: "#fec810", // Controls the color of the Add button and current window indicator
70 | },
71 | secondary: {
72 | main: "#fec810", // Controls the color of Selects and FormControls
73 | },
74 | section_divider: "rgba(255, 255, 255, 0.3)",
75 | shades: { // Shades that can be used to offset color areas of the Workspace / Window
76 | dark: "#000000",
77 | main: "#3C474C",
78 | light: "#5f676d"
79 | },
80 | background: {
81 | paper: "#435055",
82 | default: "#ffffff"
83 | }
84 | },
85 | typography: {
86 | fontFamily: '"Source Sans Pro", "Arial", "Helvetica", sans-serif'
87 | }
88 | },
89 | light: {
90 | palette: {
91 | type: 'light',
92 | primary: {
93 | main: '#2f4b60'
94 | },
95 | secondary: {
96 | main: '#2f4b60',
97 | },
98 | shades: {
99 | dark: '#dddddd',
100 | main: "#ffffff",
101 | light: "#f5f5f5",
102 | },
103 | background: {
104 | paper: "#ffffff",
105 | default: "#f5f5f5"
106 | }
107 | },
108 | },
109 | },
110 | theme: {
111 | palette: {
112 | highlights: {
113 | primary: '#fee387',
114 | secondary: '#00BFFF',
115 | },
116 | },
117 | },
118 | displayAllAnnotations: false,
119 | thumbnailNavigation: {
120 | defaultPosition: 'off',
121 | },
122 | window: {
123 | allowClose: true,
124 | allowMaximize: false,
125 | allowFullscreen: true,
126 | sideBarOpen: true,
127 | sideBarPanel: null,
128 | defaultView: 'gallery',
129 | panels: {
130 | info: true,
131 | attribution: true,
132 | canvas: true,
133 | annotations: true,
134 | search: true,
135 | layers: true,
136 | },
137 | views: [
138 | { key: 'single' /*behaviors: ['individuals']*/ },
139 | { key: 'book' /*behaviors: ['paged']*/ },
140 | { key: 'scroll', behaviors: ['continuous'] },
141 | { key: 'gallery' },
142 | ],
143 | // mirador-image-tools plugin
144 | imageToolsEnabled: true,
145 | imageToolsOpen: false,
146 | // canvasNavigation: {
147 | // handleCanvasLabel: (canvasLabel) => `${canvasLabel}...`,
148 | // },
149 | // plugin image-cropper not functional, so disabled
150 | // imageCropper: {
151 | // active: false,
152 | // enabled: true,
153 | // },
154 | },
155 | workspace: {
156 | showZoomControls: true,
157 | type: 'mosaic',
158 | },
159 | workspaceControlPanel: {
160 | enabled: true,
161 | },
162 | catalog: [],
163 | windows: [],
164 | requests: {
165 | postprocessors: []
166 | },
167 | customAddResource: {
168 | showAddResource: true,
169 | }
170 | //miradorDownloadPlugin: { restrictDownloadOnSizeDefinition: false },
171 | // miradorSharePlugin: {
172 | // embedOption: {
173 | // enabled: false,
174 | // },
175 | // dragAndDropInfoLink: 'https://library.stanford.edu/projects/international-image-interoperability-framework/viewers',
176 | // shareLink: {
177 | // enabled: true,
178 | // manifestIdReplacePattern: [
179 | // /(purl.*.stanford.edu.*)\/iiif\/manifest(.json)?$/,
180 | // '$1',
181 | // ],
182 | // },
183 | // },
184 | }
185 |
186 | // set theme
187 | if (theme == 'dark') {
188 | config.selectedTheme = 'dark';
189 | }
190 |
191 | // set en language
192 | if (lang == 'en') {
193 | config.language = 'en';
194 | }
195 |
196 | // initialize Mirador instance
197 | const miradorInstance = Mirador.viewer(config, plugins);
198 |
199 | //------ case 1: resource is an encoded iiif content state
200 | if (iiifResource && !iiifResource.startsWith('http') && !iiifResource.startsWith('{')) {
201 | if (!iiifResource.startsWith('http') && !iiifResource.startsWith('{')) {
202 | let json = decodeContentState(iiifResource);
203 | let contentState = JSON.parse(json);
204 | let target = contentState.target;
205 | if (Array.isArray(target)) {
206 | for (var i=0; i {
267 | if (action.type === "mirador/RECEIVE_MANIFEST") {
268 | var json = action.manifestJson;
269 | if (json['@type'] == 'sc:Collection') {
270 | if (json.members || json.manifests) {
271 | let items = json.members || json.manifests;
272 | items.forEach((member) => {
273 | let memberId = member['@id'] || member.id;
274 | let addMember = Mirador.actions.addResource(memberId);
275 | miradorInstance.store.dispatch(addMember);
276 | });
277 | // remove the initialized collection from the catalog
278 | let removeCollection = Mirador.actions.removeResource(iiifResource);
279 | miradorInstance.store.dispatch(removeCollection);
280 | }
281 | }
282 | return {
283 | ...action,
284 | // manifestJson: {},
285 | };
286 | }
287 | });
288 | }
289 |
290 | // panels display
291 | if (panel == 'info') {
292 | config.window.defaultSideBarPanel = 'info'; // display the info panel by default
293 | }
294 |
295 | // dispatch updated config
296 | miradorInstance.store.dispatch(Mirador.actions.importConfig(config));
297 | }
298 |
299 | function decodeContentState(encodedContentState) {
300 | let base64url = restorePadding(encodedContentState);
301 | let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
302 | let base64Decoded = atob(base64); // using built in function
303 | let uriDecoded = decodeURI(base64Decoded); // using built in function
304 | return uriDecoded;
305 | }
306 |
307 | function restorePadding(s) {
308 | let pad = s.length % 4;
309 | if (pad) {
310 | if (pad === 1) {
311 | throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
312 | }
313 | s += new Array(5 - pad).join('=');
314 | }
315 | return s;
316 | }
317 |
--------------------------------------------------------------------------------
/demo/portal/embedded_collection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | France, Paris, Bibliothèque nationale de France, Département des manuscrits, Grec 2179 (Biblissima) | Biblissima
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
44 |
45 |
46 |
47 |
48 |
98 |
99 |