19 | Open in IIIF Viewer
20 | A Firefox/Chrome extension to open IIIF manifest link in your favorite IIIF viewer.
21 | (This browser extension is formerly named “Open IIIF Manifest Link in Favorite Viewer.”)
22 | Install
23 | v0.1.13 (2022-07-10: For details, see the GitHub repo.)
24 |
25 | - Firefox
26 |
27 | -
28 | Install (signed by Mozilla)
29 |
30 | - If you use Firefox ESR 52, please set
webextensions.storage.sync.enabled
to true
in about:config.
31 |
32 |
33 | - Google Chrome
34 |
39 |
40 |
41 | Usage
42 | When the web page you are browsing contains a link to a IIIF manifest, by clicking on the toolbar button of this extension, you can open the link in the IIIF viewer specified on the options page.
43 | 
44 | By right-clicking on a link to the IIIF manifest, a context menu item “Open link in IIIF viewer” appears, which allows you to open the link in your preferred IIIF viewer.
45 | 
46 | Options
47 | On the options page, set the URL field depending on the particular IIIF viewer you want to use.
48 | Note that IIIF viewers hosted on HTTPS servers cannot display IIIF manifests hosted on HTTP servers.
49 | For example:
50 |
51 | - Open in IIIF Curation Viewer (default)
52 |
53 | http://codh.rois.ac.jp/software/iiif-curation-viewer/demo/?manifest=
54 |
55 |
56 | - Open in Mirador 2 (thanks to Kiyonori Nagasaki)
57 |
58 | http://candra.dhii.jp/nagasaki/mirador_if.php?manifest=
59 |
60 |
61 | - Open in Mirador 3
62 |
63 | https://projectmirador.org/embed/?manifest=
64 |
65 |
66 | - Open in Universal Viewer v2
67 |
68 | http://universalviewer.io/uv.html?manifest=
69 |
70 |
71 | - Open in Universal Viewer v3
72 |
73 | https://uv-v3.netlify.app/#?manifest=
74 |
75 |
76 | - Open in Universal Viewer v4
77 |
78 | https://uv-v4.netlify.app/#?manifest=
79 |
80 |
81 | - Open as JSON
82 |
83 | - Leave the URL setting field blank.
84 |
85 |
86 |
87 | 
88 | Also, you can set a more complex pattern containing placeholders {manifest_URI}
and {canvas_URI}
.
89 | For example:
90 |
91 | - Open in IIIF Curation Viewer
92 |
93 | http://codh.rois.ac.jp/software/iiif-curation-viewer/demo/?manifest={manifest_URI}&canvas={canvas_URI}&lang=en
94 |
95 |
96 | - Open in Mirador
97 |
98 | http://2sc1815j.net/mirador-loader/?manifest={manifest_URI}&canvas={canvas_URI}
99 |
100 |
101 | - Open in Universal Viewer
102 |
103 | http://2sc1815j.net/uv-loader/?manifest={manifest_URI}&canvas={canvas_URI}
104 |
105 |
106 |
107 | Notes
108 | On some web pages, the extension cannot recognize links to IIIF manifests or may misrecognize links to non-IIIF manifests.
109 | This uncertainty is reduced if the IIIF community specifies (or recommends) the machine-readable patterns for a link to a IIIF manifest. See a proposal.
110 | The brief list of IIIF websites on which the extension works or not is as follows.
111 |
112 |
113 |
114 | Website |
115 | Works? |
116 | Example |
117 |
118 |
119 |
120 |
121 | Biblissima |
122 | Yes |
123 |
124 | test
125 | |
126 |
127 |
128 | Bodleian Libraries |
129 | Yes14 |
130 |
131 | test
132 | |
133 |
134 |
135 | Cambridge University Library |
136 | Yes |
137 |
138 | test
139 | |
140 |
141 |
142 | Digital Vatican Library |
143 | Yes |
144 |
145 | test
146 | |
147 |
148 |
149 | e-codices |
150 | Yes |
151 |
152 | test
153 | |
154 |
155 |
156 | Europeana |
157 | Yes3 |
158 |
159 | test
160 | |
161 |
162 |
163 | Gallica |
164 | Yes3 |
165 |
166 | test
167 | |
168 |
169 |
170 | Harvard Art Museums |
171 | Yes |
172 |
173 | test
174 | |
175 |
176 |
177 | Heidelberg University Library |
178 | Yes |
179 |
180 | test
181 | |
182 |
183 |
184 | Internet Archive |
185 | Yes3 |
186 |
187 | test
188 | |
189 |
190 |
191 | J. Paul Getty Museum |
192 | Yes |
193 |
194 | test
195 | |
196 |
197 |
198 | Qatar Digital Library |
199 | Yes |
200 |
201 | test
202 | |
203 |
204 |
205 | Stanford Libraries |
206 | Yes/Yes |
207 |
208 | test/test
209 | |
210 |
211 |
212 | UCLA Library |
213 | Yes |
214 |
215 | test
216 | |
217 |
218 |
219 | University of Illinois at Urbana-Champaign Library |
220 | Yes |
221 |
222 | test
223 | |
224 |
225 |
226 | Wellcome Library |
227 | Yes4 |
228 |
229 | test
230 | |
231 |
232 |
233 | World Digital Library |
234 | Yes |
235 |
236 | test
237 | |
238 |
239 |
240 | Yale Center for British Art |
241 | Yes |
242 |
243 | test
244 | |
245 |
246 |
247 |
248 | (more...)
249 | IIIF Websites in Japan
250 |
251 |
252 |
253 | Website |
254 | Works? |
255 | Example |
256 |
257 |
258 |
259 |
260 | National Diet Library Digital Collections (NDL) |
261 | Yes |
262 |
263 | test
264 | |
265 |
266 |
267 | Database of Pre-modern Japanese Text (CODH) |
268 | Yes |
269 |
270 | test
271 | |
272 |
273 |
274 | Database of Pre-Modern Japanese Works (NIJL) |
275 | Yes |
276 |
277 | test
278 | |
279 |
280 |
281 | Collection for Study of the Japanese Language History (NINJAL) |
282 | Yes2 |
283 |
284 | test
285 | |
286 |
287 |
288 | Digital Collections of Keio University Libraries |
289 | Yes |
290 |
291 | test
292 | |
293 |
294 |
295 | Kyoto University Rare Materials Digital Archive |
296 | Yes |
297 |
298 | test
299 | |
300 |
301 |
302 | University of Tokyo Library System |
303 | Yes |
304 |
305 | test
306 | |
307 |
308 |
309 | Shimane University Library Digital Archive Collection |
310 | Yes |
311 |
312 | test
313 | |
314 |
315 |
316 | Kyushu University Collections |
317 | Yes |
318 |
319 | test
320 | |
321 |
322 |
323 |
324 | (more...)
325 | 1: Though the tooltip of the toolbar button is not updated in real-time, clicking on the button will open the correct manifest.
326 | 2: Use a context menu item “Open link in IIIF viewer” as needed.
327 | 3: Ad-hoc support.
328 | 4: It used to work, but as of July 2022, it no longer works due to changes on the site.
329 | Author
330 | @2SC1815J
331 |
332 |
333 |
--------------------------------------------------------------------------------
/docs/open_in_iiif_viewer.xpi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2SC1815J/open-in-iiif-viewer/d50b0d677f89d513f98bff5cf8b918ecfa68c3fc/docs/open_in_iiif_viewer.xpi
--------------------------------------------------------------------------------
/docs/updates.json:
--------------------------------------------------------------------------------
1 | {
2 | "addons": {
3 | "openiiifmanifestlink@example.com": {
4 | "updates": [
5 | {
6 | "version": "0.1.13",
7 | "update_link": "https://2sc1815j.net/open-in-iiif-viewer/open_in_iiif_viewer.xpi"
8 | }
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/background.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Open in IIIF Viewer
3 | * https://github.com/2sc1815j/open-in-iiif-viewer
4 | *
5 | * Copyright 2017 2SC1815J
6 | * Released under the MIT license
7 | */
8 | (function() {
9 | 'use strict';
10 |
11 | let timeoutHandle = null;
12 |
13 | // ref.
14 | // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/sendMessage
15 | // https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
16 | // Code samples added on or after August 20, 2010 are in the public domain.
17 |
18 | function sendMessageToActiveTab(message, function_) {
19 | browser.tabs.query({
20 | currentWindow: true,
21 | active: true
22 | }).then(tabs => {
23 | for (let tab of tabs) {
24 | browser.tabs.sendMessage(
25 | tab.id,
26 | message
27 | ).then(response => {
28 | function_(response.url);
29 | }).catch(onError);
30 | }
31 | }).catch(onError);
32 | }
33 |
34 | function onError() {
35 | browserActionButtonOff();
36 | }
37 |
38 | function encodeURIComponentForQuery(str) {
39 | // This function is taken from 'IIIF Curation Viewer' released under the MIT license,
40 | // Copyright 2016 Center for Open Data in the Humanities, Research Organization of Information and Systems
41 | var result = encodeURIComponent(str).replace(/%(?:3A|2F|2C)/g, function(c) {
42 | return decodeURIComponent(c);
43 | });
44 | return result;
45 | }
46 |
47 | function openInViewer(url) {
48 | if (url && url.manifest) {
49 | const openInBaseUrlWithoutPlaceholderDefault = 'http://codh.rois.ac.jp/software/iiif-curation-viewer/demo/?manifest=';
50 | browser.storage.sync.get({
51 | openInBaseUrl: openInBaseUrlWithoutPlaceholderDefault
52 | }).then(options => {
53 | var viewerUrl;
54 | const placeholderManifest = '{manifest_URI}';
55 | const placeholderCanvas = '{canvas_URI}';
56 | var manifestUrl = encodeURIComponentForQuery(url.manifest);
57 | var canvasUrl;
58 | if (url.canvas) {
59 | canvasUrl = encodeURIComponentForQuery(url.canvas);
60 | }
61 | if (options.openInBaseUrl.indexOf(placeholderManifest) !== -1) {
62 | //Placeholder format (v0.1.9-)
63 | //eg. 'http://codh.rois.ac.jp/software/iiif-curation-viewer/demo/?manifest={manifest_URI}&canvas={canvas_URI}'
64 | viewerUrl = options.openInBaseUrl.replace(placeholderManifest, manifestUrl);
65 | if (options.openInBaseUrl.indexOf(placeholderCanvas) !== -1) {
66 | viewerUrl = viewerUrl.replace(placeholderCanvas, canvasUrl ? canvasUrl : '');
67 | }
68 | } else {
69 | //Append format (-v0.1.8)
70 | viewerUrl = options.openInBaseUrl + manifestUrl;
71 | if (canvasUrl && options.openInBaseUrl === openInBaseUrlWithoutPlaceholderDefault) {
72 | viewerUrl += '&canvas=' + canvasUrl;
73 | }
74 | }
75 | browser.tabs.create({ url: viewerUrl });
76 | });
77 | }
78 | }
79 |
80 | function openNewTab() {
81 | sendMessageToActiveTab({ message: 'getManifestUrl' }, openInViewer);
82 | }
83 | browser.browserAction.onClicked.addListener(openNewTab);
84 |
85 | // icons: 'Ligature Symbols' (by Kazuyuki Motoyama) under the SIL Open Font License.
86 | // http://kudakurage.com/ligature_symbols/
87 | function browserActionButtonOff() {
88 | browser.browserAction.setTitle({ title: 'IIIF manifest not found' });
89 | browser.browserAction.setIcon({ path: 'icon_off.svg' });
90 | }
91 | function updateBrowserActionButton() {
92 | sendMessageToActiveTab({ message: 'getManifestUrl' }, url => {
93 | if (url && url.manifest) {
94 | stopTimer();
95 | browser.browserAction.setTitle({ title: url.manifest });
96 | browser.browserAction.setIcon({ path: 'icon.svg' });
97 | } else {
98 | browserActionButtonOff();
99 | }
100 | });
101 | }
102 | function stopTimer() {
103 | if (timeoutHandle !== null) {
104 | clearTimeout(timeoutHandle);
105 | timeoutHandle = null;
106 | }
107 | }
108 | browserActionButtonOff(); //initial state
109 | browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
110 | if (changeInfo && changeInfo.status === 'complete') {
111 | //check twice
112 | const delay = 1000; //ms
113 | const delay2 = 5000; //ms
114 | stopTimer();
115 | timeoutHandle = setTimeout(() => {
116 | updateBrowserActionButton();
117 | timeoutHandle = setTimeout(updateBrowserActionButton, delay2);
118 | }, delay);
119 | }
120 | });
121 | browser.tabs.onActivated.addListener(updateBrowserActionButton);
122 |
123 | browser.contextMenus.create({
124 | id: 'open-link-in-iiif-viewer',
125 | title: 'Open link in IIIF viewer',
126 | contexts: ['link'],
127 | targetUrlPatterns: ['*://*/*/manifest.json', '*://*/*/manifest', '*://*/*?manifest=*', '*://*/*&manifest=*']
128 | });
129 | browser.contextMenus.onClicked.addListener(info => {
130 | if (info.menuItemId === 'open-link-in-iiif-viewer') {
131 | if (info.linkUrl) {
132 | var manifestUrl = info.linkUrl; //linkUrl is an absolute URL
133 | var canvasUrl;
134 | if (manifestUrl.indexOf('?') !== -1) {
135 | try {
136 | var match = manifestUrl.match(/(?:&|\?)manifest=([^&]+?)(?:&|$)/);
137 | if (match) {
138 | var val = decodeURIComponent(match[1]);
139 | if (val) {
140 | manifestUrl = (new URL(val, info.linkUrl)).href;
141 | match = info.linkUrl.match(/(?:&|\?)canvas=([^&]+?)(?:&|$)/);
142 | if (match) {
143 | val = decodeURIComponent(match[1]);
144 | if (val) {
145 | canvasUrl = (new URL(val, info.linkUrl)).href;
146 | }
147 | }
148 | }
149 | }
150 | } catch(e) {
151 | //
152 | }
153 | }
154 | if (manifestUrl) {
155 | var url = {};
156 | url.manifest = manifestUrl;
157 | url.canvas = canvasUrl;
158 | openInViewer(url);
159 | }
160 | }
161 | }
162 | });
163 | })();
--------------------------------------------------------------------------------
/src/browser-polyfill.min.js:
--------------------------------------------------------------------------------
1 | // webextension-polyfill v.0.2.1 (https://github.com/mozilla/webextension-polyfill)
2 | /* This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 | (function(a,b){if("function"==typeof define&&define.amd)define("webextension-polyfill",["module"],b);else if("undefined"!=typeof exports)b(module);else{var c={exports:{}};b(c),a.browser=c.exports}})(this,function(a){"use strict";if("undefined"==typeof browser){a.exports=(()=>{const c={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},export:{minArgs:0,maxArgs:0},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},getSubTree:{minArgs:1,maxArgs:1},import:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},setIcon:{minArgs:1,maxArgs:1}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{update:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0}}},downloads:{download:{minArgs:1,maxArgs:1},cancel:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:0,maxArgs:0},setIcon:{minArgs:1,maxArgs:1},show:{minArgs:0,maxArgs:0}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getBrowserInfo:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{create:{minArgs:1,maxArgs:1},captureVisibleTab:{minArgs:0,maxArgs:2},detectLanguage:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},query:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(c).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class d extends WeakMap{constructor(o,p=void 0){super(p),this.createItem=o}get(o){return this.has(o)||this.set(o,this.createItem(o)),super.get(o)}}const e=o=>{return o&&"object"==typeof o&&"function"==typeof o.then},f=(o,p)=>{return(...q)=>{chrome.runtime.lastError?o.reject(chrome.runtime.lastError):p.singleCallbackArg||1===q.length?o.resolve(q[0]):o.resolve(q)}},g=(o,p)=>{const q=r=>1==r?"argument":"arguments";return function(s,...t){if(t.length