@projektemacher/storymapjs 0.10.6 → 0.10.8
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/CHANGELOG +358 -16
- package/README.md +43 -9
- package/dist/assets/demo-DBpeF31K.js +1 -0
- package/dist/assets/harness-Cr0rYpvt.js +1 -0
- package/dist/assets/harness-multi-CYHgI0a9.js +1 -0
- package/dist/assets/harness-support-Do7sIp2j.js +1 -0
- package/dist/assets/index-wHPqGrBS.js +1 -0
- package/dist/assets/main-DXalctuR.js +17 -0
- package/dist/context.json +12 -9
- package/dist/css/fonts/files/amatic-sc-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-hebrew-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-hebrew-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-100-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-100-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-200-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-600-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-800-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-100-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-100-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-200-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-600-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-800-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-100-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-100-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-200-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-600-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-800-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/dancing-script-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-hebrew-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-hebrew-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/raleway-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/raleway-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-greek-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/unica-one-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-greek-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/font.abril-droidsans.css +3 -0
- package/dist/css/fonts/font.amatic-andika.css +57 -0
- package/dist/css/fonts/font.bitter-raleway.css +111 -0
- package/dist/css/fonts/font.cairo.css +312 -0
- package/dist/css/fonts/font.clicker-garamond.css +93 -0
- package/dist/css/fonts/font.dancing-ledger.css +12 -0
- package/dist/css/fonts/font.default.css +183 -0
- package/dist/css/fonts/font.fjalla-average.css +3 -0
- package/dist/css/fonts/font.georgia-helvetica.css +3 -0
- package/dist/css/fonts/font.knightlab.css +642 -0
- package/dist/css/fonts/font.lustria-lato.css +3 -0
- package/dist/css/fonts/font.medula-lato.css +3 -0
- package/dist/css/fonts/font.oldstandard.css +101 -21
- package/dist/css/fonts/font.opensans-gentiumbook.css +183 -0
- package/dist/css/fonts/font.playfair-faunaone.css +39 -0
- package/dist/css/fonts/font.playfair.css +147 -0
- package/dist/css/fonts/font.pt.css +183 -0
- package/dist/css/fonts/font.roboto-megrim.css +3 -0
- package/dist/css/fonts/font.rufina-sintony.css +3 -0
- package/dist/css/fonts/font.ubuntu.css +291 -0
- package/dist/css/fonts/font.unicaone-vollkorn.css +84 -0
- package/dist/css/storymap.css +1 -1
- package/dist/demo.html +2 -2
- package/dist/docs/api/.nojekyll +1 -0
- package/dist/docs/api/assets/custom.css +47 -0
- package/dist/docs/api/assets/hierarchy.js +1 -0
- package/dist/docs/api/assets/highlight.css +57 -0
- package/dist/docs/api/assets/icons.js +18 -0
- package/dist/docs/api/assets/icons.svg +1 -0
- package/dist/docs/api/assets/main.js +60 -0
- package/dist/docs/api/assets/navigation.js +1 -0
- package/dist/docs/api/assets/search.js +1 -0
- package/dist/docs/api/assets/style.css +1648 -0
- package/dist/docs/api/classes/StoryMap.html +1604 -0
- package/dist/docs/api/functions/MediaType.html +51 -0
- package/dist/docs/api/functions/isPresentation3Collection.html +47 -0
- package/dist/docs/api/functions/isPresentation3Manifest.html +53 -0
- package/dist/docs/api/functions/loadCSS.html +63 -0
- package/dist/docs/api/functions/manifestToStorymapData.html +49 -0
- package/dist/docs/api/functions/setLanguage.html +54 -0
- package/dist/docs/api/functions/storymapToManifest.html +57 -0
- package/dist/docs/api/functions/validateStorymap.html +53 -0
- package/dist/docs/api/functions/validateStorymapAndReport.html +54 -0
- package/dist/docs/api/hierarchy.html +28 -0
- package/dist/docs/api/index.html +197 -0
- package/dist/docs/api/interfaces/AnimationHandle.html +60 -0
- package/dist/docs/api/interfaces/ImagereadyPayload.html +71 -0
- package/dist/docs/api/interfaces/LanguageEntry.html +102 -0
- package/dist/docs/api/interfaces/LatLngLiteral.html +73 -0
- package/dist/docs/api/interfaces/LoadOptions.html +59 -0
- package/dist/docs/api/interfaces/OlLayer.html +1400 -0
- package/dist/docs/api/interfaces/OlMap.html +1656 -0
- package/dist/docs/api/interfaces/OlProjection.html +332 -0
- package/dist/docs/api/interfaces/OlSource.html +784 -0
- package/dist/docs/api/interfaces/OlTileLayer.html +1497 -0
- package/dist/docs/api/interfaces/OlVectorLayer.html +1559 -0
- package/dist/docs/api/interfaces/OlView.html +1958 -0
- package/dist/docs/api/interfaces/StorymapData.html +71 -0
- package/dist/docs/api/interfaces/StorymapDataWrapper.html +60 -0
- package/dist/docs/api/interfaces/StorymapDocument.html +68 -0
- package/dist/docs/api/interfaces/StorymapError.html +63 -0
- package/dist/docs/api/interfaces/StorymapGeoreference.html +93 -0
- package/dist/docs/api/interfaces/StorymapGeoreferenceBody.html +74 -0
- package/dist/docs/api/interfaces/StorymapGroundControlPoint.html +69 -0
- package/dist/docs/api/interfaces/StorymapLanguageMap.html +57 -0
- package/dist/docs/api/interfaces/StorymapManifest.html +118 -0
- package/dist/docs/api/interfaces/StorymapManifestAnnotation.html +105 -0
- package/dist/docs/api/interfaces/StorymapManifestBackground.html +81 -0
- package/dist/docs/api/interfaces/StorymapManifestCanvas.html +140 -0
- package/dist/docs/api/interfaces/StorymapManifestContentResource.html +97 -0
- package/dist/docs/api/interfaces/StorymapManifestFeatureCollection.html +82 -0
- package/dist/docs/api/interfaces/StorymapManifestGeoreferencing.html +87 -0
- package/dist/docs/api/interfaces/StorymapManifestImageService.html +69 -0
- package/dist/docs/api/interfaces/StorymapManifestRange.html +75 -0
- package/dist/docs/api/interfaces/StorymapManifestService.html +77 -0
- package/dist/docs/api/interfaces/StorymapManifestSpecificResource.html +69 -0
- package/dist/docs/api/interfaces/StorymapManifestStatement.html +63 -0
- package/dist/docs/api/interfaces/StorymapMapOptions.html +92 -0
- package/dist/docs/api/interfaces/StorymapOptions.html +532 -0
- package/dist/docs/api/interfaces/StorymapOverlayLayer.html +126 -0
- package/dist/docs/api/interfaces/StorymapSlide.html +132 -0
- package/dist/docs/api/interfaces/StorymapSlideBackground.html +72 -0
- package/dist/docs/api/interfaces/StorymapSlideLocation.html +117 -0
- package/dist/docs/api/interfaces/StorymapSlideMarker.html +104 -0
- package/dist/docs/api/interfaces/StorymapSlideMedia.html +110 -0
- package/dist/docs/api/interfaces/StorymapSlideNarration.html +65 -0
- package/dist/docs/api/interfaces/StorymapSlideText.html +72 -0
- package/dist/docs/api/interfaces/StorymapTilejson.html +105 -0
- package/dist/docs/api/media/es.json +31 -0
- package/dist/docs/api/media/migration-from-knightlab.md +359 -0
- package/dist/docs/api/modules.html +50 -0
- package/dist/docs/api/types/ContentState.html +70 -0
- package/dist/docs/api/types/ImagereadyKind.html +34 -0
- package/dist/docs/api/types/MediaTypeMatch.html +78 -0
- package/dist/docs/api/types/TileSourceFactory.html +53 -0
- package/dist/docs/authoring.html +293 -0
- package/dist/docs/iiif.html +617 -62
- package/dist/docs/migration.html +314 -12
- package/dist/docs/readme.html +40 -9
- package/dist/embed/index.html +130 -5
- package/dist/examples/issue-242-nonlatin.json +55 -0
- package/dist/examples/issue-305-start-at-slide-data.json +96 -0
- package/dist/examples/issue-358-iframe.json +57 -0
- package/dist/examples/issue-iiif-geo.json +32 -0
- package/dist/examples/issue-image-alt.json +23 -0
- package/dist/examples/issue-image-region.json +42 -0
- package/dist/examples/issue-image-srcset.json +24 -0
- package/dist/examples/map-area-overlays.json +68 -0
- package/dist/examples/no-map.json +44 -0
- package/dist/examples-iiif/annotated-image.json +188 -0
- package/dist/examples-iiif/bob-boilen.json +11 -5
- package/dist/examples-iiif/courbet.json +9 -6
- package/dist/examples-iiif/empty.json +8 -4
- package/dist/examples-iiif/flickr-drewvigal.json +248 -24
- package/dist/examples-iiif/flickr-zach.wise.json +8 -4
- package/dist/examples-iiif/georeferenced-layer-unsupported.json +153 -0
- package/dist/examples-iiif/georeferenced-layer.json +318 -0
- package/dist/examples-iiif/iiif-wellcome.json +9 -6
- package/dist/examples-iiif/instagram-chenpamela.json +381 -36
- package/dist/examples-iiif/instagram_brentfoster.json +429 -40
- package/dist/examples-iiif/instagram_couch.json +261 -26
- package/dist/examples-iiif/instagram_joegermuska.json +441 -41
- package/dist/examples-iiif/instagram_matteich.json +261 -26
- package/dist/examples-iiif/instagram_mirandamulligan.json +261 -26
- package/dist/examples-iiif/instagram_raymondmccrea.json +261 -26
- package/dist/examples-iiif/instagram_test.json +261 -26
- package/dist/examples-iiif/issue-134-no-lines.json +9 -5
- package/dist/examples-iiif/issue-177-color-vars.json +9 -5
- package/dist/examples-iiif/issue-211-rtl.json +9 -5
- package/dist/examples-iiif/issue-242-nonlatin.json +239 -0
- package/dist/examples-iiif/issue-272-facebook.json +9 -5
- package/dist/examples-iiif/issue-305-start-at-slide-data.json +305 -0
- package/dist/examples-iiif/issue-305-start-at-slide.json +9 -5
- package/dist/examples-iiif/issue-341-distance.json +9 -5
- package/dist/examples-iiif/issue-358-iframe.json +190 -0
- package/dist/examples-iiif/issue-360-juxtapose.json +22 -7
- package/dist/examples-iiif/issue-368-null-fields.json +9 -5
- package/dist/examples-iiif/issue-375-code-lists.json +9 -5
- package/dist/examples-iiif/issue-381-dateline.json +9 -5
- package/dist/examples-iiif/issue-405-custom-icon.json +203 -0
- package/dist/examples-iiif/issue-425-many-slides.json +9 -5
- package/dist/examples-iiif/issue-428-background.json +20 -8
- package/dist/examples-iiif/issue-435-googlemaps.json +9 -5
- package/dist/examples-iiif/issue-437-documentcloud.json +22 -7
- package/dist/examples-iiif/issue-451-webp.json +9 -5
- package/dist/examples-iiif/issue-455-audio.json +9 -5
- package/dist/examples-iiif/issue-480-shorts.json +9 -5
- package/dist/examples-iiif/issue-506-marker-sync.json +9 -5
- package/dist/examples-iiif/issue-iiif-geo.json +200 -0
- package/dist/examples-iiif/issue-image-alt.json +90 -0
- package/dist/examples-iiif/issue-image-region.json +203 -0
- package/dist/examples-iiif/issue-image-srcset.json +87 -0
- package/dist/examples-iiif/jansteen.json +9 -6
- package/dist/examples-iiif/katrina.json +197 -41
- package/dist/examples-iiif/map-area-overlays.json +289 -0
- package/dist/examples-iiif/marktwain.json +255 -58
- package/dist/examples-iiif/no-map.json +148 -0
- package/dist/examples-iiif/population.json +440 -76
- package/dist/examples-iiif/president.json +456 -83
- package/dist/examples-iiif/scroll-hint.json +190 -0
- package/dist/examples-iiif/seurat.json +113 -22
- package/dist/examples-iiif/zoomify-clamp.json +153 -0
- package/dist/harness-multi.html +17 -0
- package/dist/harness.html +3 -2
- package/dist/index.html +33 -10
- package/dist/js/storymap.d.ts +1558 -114
- package/dist/js/storymap.js +10113 -6490
- package/dist/js/storymap.js.map +1 -1
- package/dist/navplace-properties.json +40 -0
- package/docs/migration-from-knightlab.md +359 -0
- package/docs/storymap-as-iiif-manifest.md +934 -0
- package/package.json +16 -10
- package/schema/storymap.schema.json +431 -78
- package/src/animation/easings.ts +25 -0
- package/src/animation/tween.ts +186 -0
- package/src/core/Browser.ts +20 -0
- package/src/core/Load.ts +232 -0
- package/src/core/Util.ts +416 -0
- package/src/core/mixins.ts +188 -0
- package/src/core/viewers.ts +94 -0
- package/src/dom/Dom.ts +31 -0
- package/src/dom/DomEvent.ts +80 -0
- package/src/globals.d.ts +24 -0
- package/src/language/Language.ts +281 -0
- package/src/language/locale/.expected-gaps.json +86 -0
- package/src/language/locale/be.json +31 -0
- package/src/language/locale/bg.json +31 -0
- package/src/language/locale/cs.json +31 -0
- package/src/language/locale/de.json +31 -0
- package/src/language/locale/el.json +31 -0
- package/src/language/locale/en.json +32 -0
- package/src/language/locale/es.json +31 -0
- package/src/language/locale/et.json +22 -0
- package/src/language/locale/fr.json +31 -0
- package/src/language/locale/he.json +31 -0
- package/src/language/locale/hu.json +31 -0
- package/src/language/locale/is.json +22 -0
- package/src/language/locale/it.json +31 -0
- package/src/language/locale/jp.json +31 -0
- package/src/language/locale/ko.json +31 -0
- package/src/language/locale/nl.json +31 -0
- package/src/language/locale/nn.json +22 -0
- package/src/language/locale/no.json +31 -0
- package/src/language/locale/pl.json +31 -0
- package/src/language/locale/pt.json +31 -0
- package/src/language/locale/ru.json +31 -0
- package/src/language/locale/sk.json +31 -0
- package/src/language/locale/sr.json +21 -0
- package/src/language/locale/sv.json +31 -0
- package/src/language/locale/tr.json +31 -0
- package/src/language/locale/uk.json +31 -0
- package/src/language/locale/ur.json +22 -0
- package/src/language/locale/zh-cn.json +31 -0
- package/src/language/locale/zh-tw.json +31 -0
- package/src/main.ts +143 -0
- package/src/map/Map.ts +794 -0
- package/src/map/MapMarker.ts +172 -0
- package/src/map/georeference.ts +325 -0
- package/src/map/openlayers/Map.OpenLayers.ts +2492 -0
- package/src/map/openlayers/MapMarker.OpenLayers.ts +400 -0
- package/src/map/openlayers/zoomifyTiles.ts +25 -0
- package/src/map/types.ts +17 -0
- package/src/media/EmbedUtil.ts +452 -0
- package/src/media/Media.ts +597 -0
- package/src/media/MediaType.ts +163 -0
- package/src/media/embedId.ts +104 -0
- package/src/media/types/Audio.ts +14 -0
- package/src/media/types/Blockquote.ts +39 -0
- package/src/media/types/DailyMotion.ts +53 -0
- package/src/media/types/DocumentCloud.ts +12 -0
- package/src/media/types/Facebook.ts +40 -0
- package/src/media/types/Flickr.ts +115 -0
- package/src/media/types/GoogleDoc.ts +58 -0
- package/src/media/types/HtmlMedia.ts +167 -0
- package/src/media/types/IFrame.ts +50 -0
- package/src/media/types/Image.ts +115 -0
- package/src/media/types/Juxtapose.ts +41 -0
- package/src/media/types/SoundCloud.ts +99 -0
- package/src/media/types/Text.ts +178 -0
- package/src/media/types/Twitter.ts +109 -0
- package/src/media/types/Video.ts +14 -0
- package/src/media/types/Vimeo.ts +65 -0
- package/src/media/types/Website.ts +46 -0
- package/src/media/types/Wikipedia.ts +134 -0
- package/src/media/types/YouTube.ts +224 -0
- package/src/scss/Typography.scss +235 -0
- package/src/scss/VCO.StoryMap.Dark.scss +150 -0
- package/src/scss/VCO.StoryMap.scss +195 -0
- package/src/scss/Variables.Dark.scss +57 -0
- package/src/scss/Variables.scss +48 -0
- package/src/scss/core/Mixins.scss +54 -0
- package/src/scss/core/Reset.scss +206 -0
- package/src/scss/fonts/_font.base.scss +113 -0
- package/src/scss/fonts/font.abril-droidsans.scss +75 -0
- package/src/scss/fonts/font.amatic-andika.scss +76 -0
- package/src/scss/fonts/font.bitter-raleway.scss +92 -0
- package/src/scss/fonts/font.cairo.scss +79 -0
- package/src/scss/fonts/font.clicker-garamond.scss +97 -0
- package/src/scss/fonts/font.dancing-ledger.scss +75 -0
- package/src/scss/fonts/font.default.scss +9 -0
- package/src/scss/fonts/font.fjalla-average.scss +80 -0
- package/src/scss/fonts/font.georgia-helvetica.scss +58 -0
- package/src/scss/fonts/font.knightlab.scss +111 -0
- package/src/scss/fonts/font.lustria-lato.scss +80 -0
- package/src/scss/fonts/font.medula-lato.scss +73 -0
- package/src/scss/fonts/font.oldstandard.scss +94 -0
- package/src/scss/fonts/font.opensans-gentiumbook.scss +85 -0
- package/src/scss/fonts/font.playfair-faunaone.scss +88 -0
- package/src/scss/fonts/font.playfair.scss +89 -0
- package/src/scss/fonts/font.pt.scss +96 -0
- package/src/scss/fonts/font.roboto-megrim.scss +73 -0
- package/src/scss/fonts/font.rufina-sintony.scss +73 -0
- package/src/scss/fonts/font.ubuntu.scss +82 -0
- package/src/scss/fonts/font.unicaone-vollkorn.scss +90 -0
- package/src/scss/icons/Icons.scss +239 -0
- package/src/scss/map/VCO.ImageMarker.scss +23 -0
- package/src/scss/map/VCO.Map.scss +21 -0
- package/src/scss/map/VCO.MapMarker.scss +242 -0
- package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +126 -0
- package/src/scss/media/VCO.Media.scss +219 -0
- package/src/scss/media/types/VCO.Media.Blockquote.scss +76 -0
- package/src/scss/media/types/VCO.Media.IFrame.scss +11 -0
- package/src/scss/media/types/VCO.Media.Image.scss +13 -0
- package/src/scss/media/types/VCO.Media.Text.scss +98 -0
- package/src/scss/media/types/VCO.Media.Twitter.scss +87 -0
- package/src/scss/media/types/VCO.Media.Wikipedia.scss +65 -0
- package/src/scss/site/site.scss +748 -0
- package/src/scss/slider/VCO.Slide.scss +457 -0
- package/src/scss/slider/VCO.SlideNav.scss +332 -0
- package/src/scss/slider/VCO.StorySlider.scss +66 -0
- package/src/scss/snapmap/VCO.SnapMap.scss +103 -0
- package/src/scss/ui/VCO.Consent.scss +121 -0
- package/src/scss/ui/VCO.MenuBar.Button.scss +107 -0
- package/src/scss/ui/VCO.MenuBar.scss +20 -0
- package/src/scss/ui/VCO.Message.scss +101 -0
- package/src/site/site.ts +88 -0
- package/src/slider/Slide.ts +554 -0
- package/src/slider/SlideNav.ts +246 -0
- package/src/slider/StorySlider.ts +833 -0
- package/src/storymap/Consent.ts +471 -0
- package/src/storymap/StoryMap.ts +2118 -0
- package/src/storymap/content-state.ts +185 -0
- package/src/storymap/iiif.ts +1844 -0
- package/src/storymap/to-iiif.ts +921 -0
- package/src/storymap/validate.ts +257 -0
- package/src/types.ts +494 -0
- package/src/ui/MenuBar.ts +376 -0
- package/src/ui/Message.ts +127 -0
- package/src/ui/Swipable.ts +455 -0
- package/dist/assets/demo-CVQ7kFek.js +0 -1
- package/dist/assets/harness-C-VZV-ou.js +0 -1
- package/dist/assets/index-DDsPBPZA.js +0 -1
- package/dist/assets/main-DwbXrrYn.js +0 -17
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
// Conversion of legacy StoryMapJS documents into IIIF Presentation API 3.0
|
|
2
|
+
// manifests, following the mapping proposed in
|
|
3
|
+
// docs/storymap-as-iiif-manifest.md.
|
|
4
|
+
//
|
|
5
|
+
// This is the writer half of the format. `manifestToStorymapData()` in
|
|
6
|
+
// ./iiif.ts is the reader half, and the two are the only places that know the
|
|
7
|
+
// mapping — scripts/convert-to-iiif.mjs used to hold the writer and now only
|
|
8
|
+
// does the file I/O around this function.
|
|
9
|
+
//
|
|
10
|
+
// Pure by design: no `node:fs`, no DOM, no fetch, no module state. A caller
|
|
11
|
+
// reads a document however it likes, calls storymapToManifest() and serialises
|
|
12
|
+
// the result, which is what the CLI does for public/examples-iiif/.
|
|
13
|
+
import type {
|
|
14
|
+
StorymapData,
|
|
15
|
+
StorymapGeoreference,
|
|
16
|
+
StorymapGeoreferenceBody,
|
|
17
|
+
StorymapOverlayLayer,
|
|
18
|
+
StorymapSlide,
|
|
19
|
+
StorymapSlideLocation,
|
|
20
|
+
} from "../types";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The document {@link storymapToManifest} reads: the `{ "storymap": {...} }`
|
|
24
|
+
* wrapper a `storymap.json` file is, with every member optional.
|
|
25
|
+
*
|
|
26
|
+
* The root is `Partial<StorymapData>` — every legacy key is optional and the
|
|
27
|
+
* files in the wild carry more than the schema documents — or a plain record,
|
|
28
|
+
* which is what a document read straight out of `JSON.parse` is. The wrapper
|
|
29
|
+
* itself is open, because the legacy files also carry a root-level `font_css`
|
|
30
|
+
* and the map configuration service prefers it over the key inside `storymap`.
|
|
31
|
+
*/
|
|
32
|
+
export interface StorymapDocument {
|
|
33
|
+
storymap?: Partial<StorymapData> | Record<string, unknown>;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A IIIF Presentation 3 language map; only the neutral `none` tag is written. */
|
|
38
|
+
export interface StorymapLanguageMap {
|
|
39
|
+
none: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A `{label, value}` pair — the P3 shape for a caption, credit or rights line. */
|
|
43
|
+
export interface StorymapManifestStatement {
|
|
44
|
+
label: StorymapLanguageMap;
|
|
45
|
+
value: StorymapLanguageMap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A IIIF Image API service reference. */
|
|
49
|
+
export interface StorymapManifestImageService {
|
|
50
|
+
id: string;
|
|
51
|
+
type: string;
|
|
52
|
+
profile: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A content resource: a painting body, a thumbnail or a background.
|
|
57
|
+
*
|
|
58
|
+
* One permissive interface rather than a discriminated union, because the
|
|
59
|
+
* emitter only writes these: the shapes it produces are the classified media
|
|
60
|
+
* body, a IIIF `Image`, a `TextualBody` and a `Color`, and they are
|
|
61
|
+
* distinguished by `type` for the reader, not for us.
|
|
62
|
+
*/
|
|
63
|
+
export interface StorymapManifestContentResource {
|
|
64
|
+
id?: string;
|
|
65
|
+
type: string;
|
|
66
|
+
format?: string;
|
|
67
|
+
width?: number;
|
|
68
|
+
height?: number;
|
|
69
|
+
value?: string;
|
|
70
|
+
service?: StorymapManifestImageService[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A navPlace FeatureCollection: a point for a location, a Polygon for a bbox. */
|
|
74
|
+
export interface StorymapManifestFeatureCollection {
|
|
75
|
+
id: string;
|
|
76
|
+
type: "FeatureCollection";
|
|
77
|
+
features: {
|
|
78
|
+
id: string;
|
|
79
|
+
type: "Feature";
|
|
80
|
+
geometry: {
|
|
81
|
+
type: string;
|
|
82
|
+
coordinates: number[] | number[][] | number[][][];
|
|
83
|
+
};
|
|
84
|
+
/** Marker presentation, described by navplace-properties.json (§3.3). */
|
|
85
|
+
properties: Record<string, unknown>;
|
|
86
|
+
}[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A painting annotation: the slide's own media, painted onto its canvas. */
|
|
90
|
+
export interface StorymapManifestAnnotation {
|
|
91
|
+
id: string;
|
|
92
|
+
type: "Annotation";
|
|
93
|
+
motivation: string;
|
|
94
|
+
/** The media caption, as the annotation's own `label` (§2.7). */
|
|
95
|
+
label?: StorymapLanguageMap;
|
|
96
|
+
/** The media credit (§2.7). P3 allows a single object, not an array. */
|
|
97
|
+
requiredStatement?: StorymapManifestStatement;
|
|
98
|
+
/** The media alt text (§2.7). */
|
|
99
|
+
accessibilitySummary?: StorymapLanguageMap;
|
|
100
|
+
body: StorymapManifestContentResource;
|
|
101
|
+
target: string | StorymapManifestSpecificResource;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A painting annotation whose target is a region of a larger image (§2.8). */
|
|
105
|
+
export interface StorymapManifestSpecificResource {
|
|
106
|
+
type: "SpecificResource";
|
|
107
|
+
source: string;
|
|
108
|
+
selector: { type: "ImageApiSelector"; value: string };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A Georeference Extension annotation for a placed raster (§2.10). */
|
|
112
|
+
export interface StorymapManifestGeoreferencing {
|
|
113
|
+
id: string;
|
|
114
|
+
type: "Annotation";
|
|
115
|
+
motivation: "georeferencing";
|
|
116
|
+
target: {
|
|
117
|
+
id: string;
|
|
118
|
+
type: "Image";
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
service: StorymapManifestImageService[];
|
|
122
|
+
};
|
|
123
|
+
requiredStatement?: StorymapManifestStatement;
|
|
124
|
+
body: StorymapGeoreferenceBody;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A canvas `background`: a painting annotation of the slide's backdrop (§2.6). */
|
|
128
|
+
export interface StorymapManifestBackground {
|
|
129
|
+
id: string;
|
|
130
|
+
type: "Annotation";
|
|
131
|
+
motivation: "painting";
|
|
132
|
+
body: StorymapManifestContentResource | StorymapManifestContentResource[];
|
|
133
|
+
target: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** One P3 `Range` over the canvases of a `slide.group` (§3.5). */
|
|
137
|
+
export interface StorymapManifestRange {
|
|
138
|
+
id: string;
|
|
139
|
+
type: "Range";
|
|
140
|
+
label: StorymapLanguageMap;
|
|
141
|
+
items: string[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface StorymapManifestCanvas {
|
|
145
|
+
id: string;
|
|
146
|
+
type: "Canvas";
|
|
147
|
+
width: number;
|
|
148
|
+
height: number;
|
|
149
|
+
/** The slide's date, which P3 constrains to a plain string (§2.5). */
|
|
150
|
+
navDate?: string;
|
|
151
|
+
background?: StorymapManifestBackground;
|
|
152
|
+
thumbnail?: StorymapManifestContentResource[];
|
|
153
|
+
items: {
|
|
154
|
+
id: string;
|
|
155
|
+
type: "AnnotationPage";
|
|
156
|
+
items: (StorymapManifestAnnotation | StorymapManifestGeoreferencing)[];
|
|
157
|
+
}[];
|
|
158
|
+
label?: StorymapLanguageMap;
|
|
159
|
+
summary?: StorymapLanguageMap;
|
|
160
|
+
/** An overview slide, which the map's `overview` type carries (§2.4). */
|
|
161
|
+
"storymap:type"?: string;
|
|
162
|
+
"storymap:mediaSrcset"?: unknown;
|
|
163
|
+
"storymap:mediaSizes"?: unknown;
|
|
164
|
+
navPlace?: StorymapManifestFeatureCollection;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The map configuration service entry, keyed by the `storymap:` terms.
|
|
169
|
+
*
|
|
170
|
+
* Open by necessity: the official validator's Manifest schema is
|
|
171
|
+
* `additionalProperties: false`, which is exactly why the map configuration
|
|
172
|
+
* travels on a service of its own rather than on the Manifest (§3.1).
|
|
173
|
+
*/
|
|
174
|
+
export interface StorymapManifestService {
|
|
175
|
+
id: string;
|
|
176
|
+
type: "Service";
|
|
177
|
+
profile: string;
|
|
178
|
+
[term: string]: unknown;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface StorymapManifest {
|
|
182
|
+
"@context": string[];
|
|
183
|
+
id: string;
|
|
184
|
+
type: "Manifest";
|
|
185
|
+
label: StorymapLanguageMap;
|
|
186
|
+
behavior: string[];
|
|
187
|
+
metadata: { label: StorymapLanguageMap; value: StorymapLanguageMap }[];
|
|
188
|
+
items: StorymapManifestCanvas[];
|
|
189
|
+
structures?: StorymapManifestRange[];
|
|
190
|
+
navPlace?: StorymapManifestFeatureCollection;
|
|
191
|
+
service?: StorymapManifestService[];
|
|
192
|
+
requiredStatement?: StorymapManifestStatement;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// The original zoomify tile paths of the legacy fixtures are dead, so image-map
|
|
196
|
+
// storymaps are rewritten to the IIIF reference image used by the repo's IIIF
|
|
197
|
+
// fixtures. The original zoomify definition is preserved as a storymap: term.
|
|
198
|
+
const LAOCOON_ID =
|
|
199
|
+
"https://iiif.io/api/image/3.0/example/reference/28473c77da3deebe4375c3a50572d9d3-laocoon";
|
|
200
|
+
const LAOCOON_INFO = `${LAOCOON_ID}/info.json`;
|
|
201
|
+
const LAOCOON_IMAGE = `${LAOCOON_ID}/full/max/0/default.jpg`;
|
|
202
|
+
const LAOCOON_WIDTH = 2315;
|
|
203
|
+
const LAOCOON_HEIGHT = 3000;
|
|
204
|
+
|
|
205
|
+
const STORYMAP_CONTEXT = "https://cmahnke.github.io/StoryMapJS/context.json";
|
|
206
|
+
// The navPlace extension's linked data context **must be included before** the
|
|
207
|
+
// Presentation 3 context (navplace §3.1), and every fixture here carries a
|
|
208
|
+
// navPlace, so the order is not negotiable. The two StoryMapJS contexts follow:
|
|
209
|
+
// the properties one describes the terms inside the navPlace Feature bag, which
|
|
210
|
+
// §3.2 allows only via "a registered IIIF API extension or a local linked data
|
|
211
|
+
// context" — there is no extension for marker presentation, so it is local.
|
|
212
|
+
// Frozen, and copied per manifest below: this array used to be handed out by
|
|
213
|
+
// reference, so a host mutating one manifest's @context corrupted every later
|
|
214
|
+
// call in the process.
|
|
215
|
+
const CONTEXTS: readonly string[] = Object.freeze([
|
|
216
|
+
"http://iiif.io/api/extension/navplace/context.json",
|
|
217
|
+
"http://iiif.io/api/presentation/3/context.json",
|
|
218
|
+
`${STORYMAP_CONTEXT.replace(/context\.json$/, "")}navplace-properties.json`,
|
|
219
|
+
STORYMAP_CONTEXT,
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
const IMAGE_FORMATS: Record<string, string> = {
|
|
223
|
+
jpg: "image/jpeg",
|
|
224
|
+
jpeg: "image/jpeg",
|
|
225
|
+
png: "image/png",
|
|
226
|
+
gif: "image/gif",
|
|
227
|
+
webp: "image/webp",
|
|
228
|
+
bmp: "image/bmp",
|
|
229
|
+
avif: "image/avif",
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const VIDEO_HOSTS = ["youtube.com", "youtu.be", "vimeo.com", "dailymotion.com", "vine.co"];
|
|
233
|
+
const SOUND_HOSTS = ["soundcloud.com"];
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Keys copied out of navPlace Feature properties, in the order they are
|
|
237
|
+
* written. This list mirrors the reader's (`src/storymap/iiif.ts`): `popup`
|
|
238
|
+
* and `audioBadge` are marker presentation with no IIIF vocabulary of their
|
|
239
|
+
* own, and omitting them here silently drops them on the round trip.
|
|
240
|
+
*/
|
|
241
|
+
const LOCATION_PROPERTIES = [
|
|
242
|
+
"name",
|
|
243
|
+
"zoom",
|
|
244
|
+
"line",
|
|
245
|
+
"icon",
|
|
246
|
+
"iconSize",
|
|
247
|
+
"image",
|
|
248
|
+
"use_custom_marker",
|
|
249
|
+
"popup",
|
|
250
|
+
"audioBadge",
|
|
251
|
+
] as const;
|
|
252
|
+
|
|
253
|
+
function isHttpUrl(value: unknown): value is string {
|
|
254
|
+
return typeof value === "string" && /^https?:\/\//i.test(value);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function classifyMediaUrl(url: string): { type: string; format?: string } {
|
|
258
|
+
const ext = url.split(/[?#]/)[0].split(".").pop()?.toLowerCase();
|
|
259
|
+
// Own-property check: a bare lookup hits Object.prototype, so a URL
|
|
260
|
+
// ending in `.constructor` (or `.__proto__`) classifies as an Image.
|
|
261
|
+
if (ext && Object.hasOwn(IMAGE_FORMATS, ext)) {
|
|
262
|
+
return { type: "Image", format: IMAGE_FORMATS[ext] };
|
|
263
|
+
}
|
|
264
|
+
let host: string;
|
|
265
|
+
try {
|
|
266
|
+
host = new URL(url).hostname.replace(/^www\./, "");
|
|
267
|
+
} catch {
|
|
268
|
+
return { type: "Text", format: "text/html" };
|
|
269
|
+
}
|
|
270
|
+
if (VIDEO_HOSTS.some((h) => host === h || host.endsWith(`.${h}`))) {
|
|
271
|
+
return { type: "Video" };
|
|
272
|
+
}
|
|
273
|
+
if (SOUND_HOSTS.some((h) => host === h || host.endsWith(`.${h}`))) {
|
|
274
|
+
return { type: "Sound" };
|
|
275
|
+
}
|
|
276
|
+
// Page-embedded media (tweets, Wikipedia, photo pages, ...) - the viewer
|
|
277
|
+
// decides how to render these; the manifest carries a typed reference.
|
|
278
|
+
return { type: "Text", format: "text/html" };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function languageMap(value: string): StorymapLanguageMap {
|
|
282
|
+
return { none: [value] };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* A value worth writing: neither absent nor the empty string. A type guard, so
|
|
287
|
+
* a declared `string | null` member narrows to the string it is.
|
|
288
|
+
*/
|
|
289
|
+
function present<T>(value: T | null | undefined): value is T {
|
|
290
|
+
return value !== undefined && value !== null && value !== "";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** A plain object, or null for anything else (arrays included). */
|
|
294
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
295
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
return value as Record<string, unknown>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** An array of `T`, or an empty one. */
|
|
302
|
+
function asArray<T>(value: unknown): T[] {
|
|
303
|
+
return Array.isArray(value) ? (value as T[]) : [];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** A `[west, south, east, north]` box of four finite numbers. */
|
|
307
|
+
function isLonLatBox(value: unknown): value is [number, number, number, number] {
|
|
308
|
+
return (
|
|
309
|
+
Array.isArray(value) &&
|
|
310
|
+
value.length === 4 &&
|
|
311
|
+
value.every((n) => typeof n === "number" && Number.isFinite(n))
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Convert a legacy storymap document to a IIIF Presentation 3.0 manifest.
|
|
317
|
+
*
|
|
318
|
+
* The counterpart of `manifestToStorymapData()`: what that reads, this writes.
|
|
319
|
+
* Nothing but the input is read, so the same document always produces the same
|
|
320
|
+
* manifest — which is what makes regenerating `public/examples-iiif/` a no-op.
|
|
321
|
+
*
|
|
322
|
+
* @param name - The manifest's label, and the stem of its canonical id.
|
|
323
|
+
* @param legacy - The `storymap.json` document (`{ "storymap": {...} }`).
|
|
324
|
+
* @returns The manifest, ready to serialise.
|
|
325
|
+
*/
|
|
326
|
+
export function storymapToManifest(name: string, legacy: StorymapDocument): StorymapManifest {
|
|
327
|
+
// The legacy root is open, so the declaration says "a record" where a real
|
|
328
|
+
// document is a `Partial<StorymapData>`; the two are the same thing at
|
|
329
|
+
// runtime and every field below is read the way the reader reads one.
|
|
330
|
+
const storymap = (legacy.storymap ?? {}) as Partial<StorymapData>;
|
|
331
|
+
// A hand-built document can carry anything in this array. A non-object
|
|
332
|
+
// has no text, no media and no location, so it is skipped rather than
|
|
333
|
+
// painted as an empty canvas — or crashing on `slide.text`.
|
|
334
|
+
const slides = asArray<StorymapSlide>(storymap.slides).filter(
|
|
335
|
+
(slide): slide is StorymapSlide => asRecord(slide) !== null,
|
|
336
|
+
);
|
|
337
|
+
const isZoomify = storymap.map_type === "zoomify";
|
|
338
|
+
const isImageMap = isZoomify || storymap.map_type === "iiif";
|
|
339
|
+
const manifestId = `https://example.org/storymap/${name}`;
|
|
340
|
+
// The root-level legacy fields are untyped JSON — `StorymapData` has an open
|
|
341
|
+
// index signature — so the attribution is carried verbatim rather than
|
|
342
|
+
// narrowed, exactly as the writer in scripts/convert-to-iiif.mjs did.
|
|
343
|
+
const iiif = asRecord(storymap.iiif);
|
|
344
|
+
const zoomify = asRecord(storymap.zoomify);
|
|
345
|
+
const attribution = (iiif?.attribution || zoomify?.attribution || "") as string;
|
|
346
|
+
|
|
347
|
+
const canvases = slides.map((slide, i) =>
|
|
348
|
+
buildCanvas(manifestId, i, slide, isImageMap, i === 0 ? asArray(storymap.overlays) : []),
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const manifest: StorymapManifest = {
|
|
352
|
+
"@context": [...CONTEXTS],
|
|
353
|
+
id: manifestId,
|
|
354
|
+
type: "Manifest",
|
|
355
|
+
label: languageMap(name),
|
|
356
|
+
behavior: ["paged"],
|
|
357
|
+
// P3's `provider` is whoever *published* the content, and the
|
|
358
|
+
// converter does not know that — it only knows what produced the
|
|
359
|
+
// manifest, which is what `metadata` is for. Claiming to be the
|
|
360
|
+
// provider would put "Provider: StoryMapJS" on the credit line of every
|
|
361
|
+
// converted storymap (§3.2).
|
|
362
|
+
metadata: [
|
|
363
|
+
{
|
|
364
|
+
label: languageMap("Generated by"),
|
|
365
|
+
value: languageMap("StoryMapJS"),
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
items: canvases,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// `structures` carries the groups (§3.5). A storymap states a slide's
|
|
372
|
+
// group as a plain string; P3 states it as a Range over the canvases it
|
|
373
|
+
// contains, and the reader turns that back into `slide.group`.
|
|
374
|
+
const structures = buildStructures(manifestId, slides, canvases);
|
|
375
|
+
if (structures) manifest.structures = structures;
|
|
376
|
+
|
|
377
|
+
// A map bbox has no extension term: the interoperable spelling is a
|
|
378
|
+
// navPlace Polygon, which the reader turns back into map_bbox
|
|
379
|
+
// (readNavPlaceBbox). Without this the extent was silently lost on the
|
|
380
|
+
// round trip.
|
|
381
|
+
const bbox = storymap.map_bbox;
|
|
382
|
+
if (isLonLatBox(bbox)) {
|
|
383
|
+
const [west, south, east, north] = bbox;
|
|
384
|
+
manifest.navPlace = {
|
|
385
|
+
id: `${manifestId}/navplace`,
|
|
386
|
+
type: "FeatureCollection",
|
|
387
|
+
features: [
|
|
388
|
+
{
|
|
389
|
+
id: `${manifestId}/navplace/feature/1`,
|
|
390
|
+
type: "Feature",
|
|
391
|
+
geometry: {
|
|
392
|
+
type: "Polygon",
|
|
393
|
+
coordinates: [
|
|
394
|
+
[
|
|
395
|
+
[west, south],
|
|
396
|
+
[east, south],
|
|
397
|
+
[east, north],
|
|
398
|
+
[west, north],
|
|
399
|
+
[west, south],
|
|
400
|
+
],
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
properties: {},
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const config = buildMapConfig(storymap, legacy, isZoomify);
|
|
410
|
+
if (Object.keys(config).length > 0) {
|
|
411
|
+
// The Manifest object is closed for extension terms in the official
|
|
412
|
+
// validator's JSON Schema (see docs "Validator interop"), so the
|
|
413
|
+
// storymap map configuration travels on a dedicated service entry.
|
|
414
|
+
manifest.service = [
|
|
415
|
+
{
|
|
416
|
+
id: `${manifestId}/map-config`,
|
|
417
|
+
type: "Service",
|
|
418
|
+
profile: `${STORYMAP_CONTEXT}/mapconfig`,
|
|
419
|
+
...config,
|
|
420
|
+
},
|
|
421
|
+
];
|
|
422
|
+
}
|
|
423
|
+
if (present(attribution)) {
|
|
424
|
+
manifest.requiredStatement = {
|
|
425
|
+
label: languageMap("Attribution"),
|
|
426
|
+
value: languageMap(attribution),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return manifest;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function buildMapConfig(
|
|
433
|
+
storymap: Partial<StorymapData>,
|
|
434
|
+
legacy: StorymapDocument,
|
|
435
|
+
isZoomify: boolean,
|
|
436
|
+
): Record<string, unknown> {
|
|
437
|
+
const config: Record<string, unknown> = {};
|
|
438
|
+
let mapType = storymap.map_type;
|
|
439
|
+
if (isZoomify) {
|
|
440
|
+
// zoomify support was replaced by the IIIF Image API; the legacy
|
|
441
|
+
// pyramid definition is not carried (nothing reads it)
|
|
442
|
+
mapType = "iiif";
|
|
443
|
+
config["storymap:mapAsImage"] = true;
|
|
444
|
+
}
|
|
445
|
+
if (present(mapType)) {
|
|
446
|
+
// A keyword basemap and a tile URL template are different kinds of
|
|
447
|
+
// thing, and `mapType` used to be both. A keyword the viewer knows how
|
|
448
|
+
// to configure is a `storymap:basemap`; anything with a {z} in it is
|
|
449
|
+
// TileJSON's `tiles`, which is where a tile service belongs (§2.9).
|
|
450
|
+
// The check coerces but the write must too: a non-string map_type
|
|
451
|
+
// would otherwise land verbatim in a slot the reader types as string.
|
|
452
|
+
const mapTypeString = String(mapType);
|
|
453
|
+
if (mapTypeString.includes("{z}")) {
|
|
454
|
+
config.tilejson = { tiles: mapTypeString };
|
|
455
|
+
} else {
|
|
456
|
+
config["storymap:basemap"] = mapTypeString;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (storymap.map_as_image !== undefined) {
|
|
460
|
+
config["storymap:mapAsImage"] = storymap.map_as_image;
|
|
461
|
+
}
|
|
462
|
+
if (present(storymap.map_access_token)) {
|
|
463
|
+
config["storymap:mapAccessToken"] = storymap.map_access_token;
|
|
464
|
+
}
|
|
465
|
+
if (present(storymap.map_background_color)) {
|
|
466
|
+
config["storymap:mapBackgroundColor"] = storymap.map_background_color;
|
|
467
|
+
}
|
|
468
|
+
if (storymap.map_center_offset !== undefined) {
|
|
469
|
+
config["storymap:mapCenterOffset"] = storymap.map_center_offset;
|
|
470
|
+
}
|
|
471
|
+
const fontCss: unknown = legacy.font_css || storymap.font_css;
|
|
472
|
+
if (present(fontCss)) {
|
|
473
|
+
config["storymap:fontCss"] = fontCss;
|
|
474
|
+
}
|
|
475
|
+
if (storymap.call_to_action !== undefined) {
|
|
476
|
+
config["storymap:callToAction"] = storymap.call_to_action;
|
|
477
|
+
}
|
|
478
|
+
if (present(storymap.call_to_action_text)) {
|
|
479
|
+
config["storymap:callToActionText"] = storymap.call_to_action_text;
|
|
480
|
+
}
|
|
481
|
+
if (storymap.start_at_slide !== undefined) {
|
|
482
|
+
config["storymap:startAtSlide"] = storymap.start_at_slide;
|
|
483
|
+
}
|
|
484
|
+
if (present(storymap.language)) {
|
|
485
|
+
config["storymap:language"] = storymap.language;
|
|
486
|
+
}
|
|
487
|
+
if (storymap.calculate_zoom !== undefined) {
|
|
488
|
+
config["storymap:calculateZoom"] = storymap.calculate_zoom;
|
|
489
|
+
}
|
|
490
|
+
if (storymap.line_follows_path !== undefined) {
|
|
491
|
+
config["storymap:lineFollowsPath"] = storymap.line_follows_path;
|
|
492
|
+
}
|
|
493
|
+
if (storymap.show_lines !== undefined) {
|
|
494
|
+
config["storymap:showLines"] = storymap.show_lines;
|
|
495
|
+
}
|
|
496
|
+
if (storymap.show_history_line !== undefined) {
|
|
497
|
+
config["storymap:showHistoryLine"] = storymap.show_history_line;
|
|
498
|
+
}
|
|
499
|
+
if (present(storymap.line_color)) {
|
|
500
|
+
config["storymap:lineColor"] = storymap.line_color;
|
|
501
|
+
}
|
|
502
|
+
if (present(storymap.line_color_inactive)) {
|
|
503
|
+
config["storymap:lineColorInactive"] = storymap.line_color_inactive;
|
|
504
|
+
}
|
|
505
|
+
if (storymap.line_weight !== undefined) {
|
|
506
|
+
config["storymap:lineWeight"] = storymap.line_weight;
|
|
507
|
+
}
|
|
508
|
+
if (storymap.line_opacity !== undefined) {
|
|
509
|
+
config["storymap:lineOpacity"] = storymap.line_opacity;
|
|
510
|
+
}
|
|
511
|
+
if (present(storymap.line_dash)) {
|
|
512
|
+
config["storymap:lineDash"] = storymap.line_dash;
|
|
513
|
+
}
|
|
514
|
+
if (present(storymap.line_join)) {
|
|
515
|
+
config["storymap:lineJoin"] = storymap.line_join;
|
|
516
|
+
}
|
|
517
|
+
if (storymap.use_custom_markers !== undefined) {
|
|
518
|
+
config["storymap:useCustomMarkers"] = storymap.use_custom_markers;
|
|
519
|
+
}
|
|
520
|
+
if (storymap.map_area !== undefined) {
|
|
521
|
+
config["storymap:mapArea"] = storymap.map_area;
|
|
522
|
+
}
|
|
523
|
+
if (isLonLatBox(storymap.overview_extent)) {
|
|
524
|
+
config["storymap:overviewExtent"] = storymap.overview_extent;
|
|
525
|
+
}
|
|
526
|
+
if (storymap.keyboard !== undefined) {
|
|
527
|
+
config["storymap:keyboard"] = storymap.keyboard;
|
|
528
|
+
}
|
|
529
|
+
const overlays = asArray<StorymapOverlayLayer>(storymap.overlays);
|
|
530
|
+
if (overlays.length > 0) {
|
|
531
|
+
// entries without a map_type are dropped on the way back in; an
|
|
532
|
+
// entry carrying both map_type and georeference keeps only the
|
|
533
|
+
// former here, because the georeference is also emitted as an
|
|
534
|
+
// annotation — and the reader would otherwise return two overlays
|
|
535
|
+
// for one source layer.
|
|
536
|
+
config["storymap:overlays"] = overlays
|
|
537
|
+
.filter((entry) => entry && present(entry.map_type))
|
|
538
|
+
.map((entry) => {
|
|
539
|
+
if (!entry.georeference) {
|
|
540
|
+
return entry;
|
|
541
|
+
}
|
|
542
|
+
const { georeference: _dropped, ...rest } = entry;
|
|
543
|
+
return rest;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return config;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function buildCanvas(
|
|
550
|
+
manifestId: string,
|
|
551
|
+
index: number,
|
|
552
|
+
slide: StorymapSlide,
|
|
553
|
+
isImageMap: boolean,
|
|
554
|
+
georeferencedLayers: StorymapOverlayLayer[],
|
|
555
|
+
): StorymapManifestCanvas {
|
|
556
|
+
const canvasId = `${manifestId}/canvas/${index + 1}`;
|
|
557
|
+
const headline = slide.text?.headline || "";
|
|
558
|
+
const bodyText = slide.text?.text || "";
|
|
559
|
+
const width = isImageMap ? LAOCOON_WIDTH : 1080;
|
|
560
|
+
const height = isImageMap ? LAOCOON_HEIGHT : 1080;
|
|
561
|
+
|
|
562
|
+
const canvas: StorymapManifestCanvas = {
|
|
563
|
+
id: canvasId,
|
|
564
|
+
type: "Canvas",
|
|
565
|
+
height,
|
|
566
|
+
width,
|
|
567
|
+
// P3 spells a slide's date `navDate`, and the official validator
|
|
568
|
+
// constrains it to a plain string — a language map is rejected, and it
|
|
569
|
+
// applies no format constraint at all, so the storymap value is
|
|
570
|
+
// carried verbatim ("Aug 23" and "1790-2010" both pass) (§2.5)
|
|
571
|
+
...(typeof slide.date === "string" && slide.date !== "" ? { navDate: slide.date } : {}),
|
|
572
|
+
// P3 paints a canvas background with an Annotation referenced from the
|
|
573
|
+
// canvas `background` property (§2.6)
|
|
574
|
+
...buildBackground(canvasId, slide.background),
|
|
575
|
+
// media.thumb is P3's `thumbnail`, a list of content resources (§3.1)
|
|
576
|
+
...buildThumbnail(slide),
|
|
577
|
+
items: [
|
|
578
|
+
{
|
|
579
|
+
id: `${canvasId}/annotationpage/1`,
|
|
580
|
+
type: "AnnotationPage",
|
|
581
|
+
items: [
|
|
582
|
+
{
|
|
583
|
+
id: `${canvasId}/annotation/1`,
|
|
584
|
+
type: "Annotation",
|
|
585
|
+
motivation: "painting",
|
|
586
|
+
// P3 defines label / requiredStatement /
|
|
587
|
+
// accessibilitySummary on the Annotation, which is
|
|
588
|
+
// where a caption, a credit and alt text belong for
|
|
589
|
+
// the resource being painted (§2.7)
|
|
590
|
+
...buildAnnotationPresentation(slide),
|
|
591
|
+
body: buildBody(slide, isImageMap),
|
|
592
|
+
target: buildRegionTarget(canvasId, slide.location?.region) ?? canvasId,
|
|
593
|
+
},
|
|
594
|
+
// Georeference Extension annotations for the placed rasters
|
|
595
|
+
// (§2.10). The layers are map-wide but an annotation is
|
|
596
|
+
// canvas-scoped, so they are written on the first canvas and
|
|
597
|
+
// the reader collects them from every canvas.
|
|
598
|
+
...buildGeoreferencing(canvasId, georeferencedLayers),
|
|
599
|
+
],
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
};
|
|
603
|
+
if (present(headline)) {
|
|
604
|
+
canvas.label = languageMap(headline);
|
|
605
|
+
}
|
|
606
|
+
if (present(bodyText)) {
|
|
607
|
+
canvas.summary = languageMap(bodyText);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const storymapTerms = buildCanvasTerms(slide);
|
|
611
|
+
Object.assign(canvas, storymapTerms);
|
|
612
|
+
|
|
613
|
+
const navPlace = buildNavPlace(canvasId, slide);
|
|
614
|
+
if (navPlace) {
|
|
615
|
+
canvas.navPlace = navPlace;
|
|
616
|
+
}
|
|
617
|
+
return canvas;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* A map's georeferenced rasters as Georeference Extension annotations
|
|
622
|
+
* (§2.10).
|
|
623
|
+
*
|
|
624
|
+
* Per the extension, a layer that is not part of the canvas it ships in — which
|
|
625
|
+
* is every placed raster — carries the image it places in `target` as an
|
|
626
|
+
* embedded resource, and the ground control points in `body` as a
|
|
627
|
+
* FeatureCollection whose features carry `resourceCoords`.
|
|
628
|
+
*
|
|
629
|
+
* The `opacity`, `visible`, `className` and `blendMode` the term could carry
|
|
630
|
+
* have no IIIF vocabulary for a georeferencing annotation, so they are not
|
|
631
|
+
* written; the attribution is, as a `requiredStatement`, which is where P3 puts
|
|
632
|
+
* it.
|
|
633
|
+
*/
|
|
634
|
+
function hasGeoreference(
|
|
635
|
+
entry: StorymapOverlayLayer,
|
|
636
|
+
): entry is StorymapOverlayLayer & { georeference: StorymapGeoreference } {
|
|
637
|
+
// Truthiness alone lets a string width or an Infinity height reach the
|
|
638
|
+
// annotation target, which declares them as numbers — on the one branch
|
|
639
|
+
// the validator whitelists out. Mirror the reader's shape check.
|
|
640
|
+
if (!entry || !entry.georeference) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
const { url, width, height } = entry.georeference as {
|
|
644
|
+
url?: unknown;
|
|
645
|
+
width?: unknown;
|
|
646
|
+
height?: unknown;
|
|
647
|
+
};
|
|
648
|
+
return (
|
|
649
|
+
typeof url === "string" &&
|
|
650
|
+
url !== "" &&
|
|
651
|
+
typeof width === "number" &&
|
|
652
|
+
typeof height === "number" &&
|
|
653
|
+
Number.isFinite(width) &&
|
|
654
|
+
Number.isFinite(height) &&
|
|
655
|
+
width > 0 &&
|
|
656
|
+
height > 0
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function buildGeoreferencing(
|
|
661
|
+
canvasId: string,
|
|
662
|
+
overlays: StorymapOverlayLayer[],
|
|
663
|
+
): StorymapManifestGeoreferencing[] {
|
|
664
|
+
const layers = overlays.filter(hasGeoreference);
|
|
665
|
+
return layers.map((entry) => {
|
|
666
|
+
const georeference = entry.georeference;
|
|
667
|
+
return {
|
|
668
|
+
// The index comes from `indexOf` rather than the map's own counter
|
|
669
|
+
// because that is what the script emitted: two entries that are the
|
|
670
|
+
// *same object* share an id. `JSON.parse` never produces that, so
|
|
671
|
+
// the two agree on every real document — the quirk is preserved
|
|
672
|
+
// rather than silently changed by the move into the library.
|
|
673
|
+
id: `${canvasId}/georeferencing/${layers.indexOf(entry) + 1}`,
|
|
674
|
+
type: "Annotation",
|
|
675
|
+
motivation: "georeferencing",
|
|
676
|
+
target: {
|
|
677
|
+
id: georeference.url,
|
|
678
|
+
type: "Image",
|
|
679
|
+
width: georeference.width,
|
|
680
|
+
height: georeference.height,
|
|
681
|
+
service: [{ id: georeference.url, type: "ImageService3", profile: "level2" }],
|
|
682
|
+
},
|
|
683
|
+
...(present(entry.attribution)
|
|
684
|
+
? {
|
|
685
|
+
requiredStatement: {
|
|
686
|
+
label: languageMap("Attribution"),
|
|
687
|
+
value: languageMap(entry.attribution),
|
|
688
|
+
},
|
|
689
|
+
}
|
|
690
|
+
: {}),
|
|
691
|
+
body: georeference.body,
|
|
692
|
+
};
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* A storymap's `slide.group` values as P3 `structures`: one Range per group,
|
|
698
|
+
* over the canvases it contains (§3.5).
|
|
699
|
+
*
|
|
700
|
+
* Groups come out in first-appearance order, and the slides keep their order
|
|
701
|
+
* within a group — which is what makes a grouped storymap's slide order the
|
|
702
|
+
* concatenation of its groups, rather than the original interleaving. No group
|
|
703
|
+
* anywhere means no `structures` at all.
|
|
704
|
+
*/
|
|
705
|
+
function buildStructures(
|
|
706
|
+
manifestId: string,
|
|
707
|
+
slides: StorymapSlide[],
|
|
708
|
+
canvases: StorymapManifestCanvas[],
|
|
709
|
+
): StorymapManifestRange[] | null {
|
|
710
|
+
const groups: string[] = [];
|
|
711
|
+
const byGroup = new Map<string, string[]>();
|
|
712
|
+
slides.forEach((slide, i) => {
|
|
713
|
+
const group = slide.group;
|
|
714
|
+
if (!present(group)) return;
|
|
715
|
+
if (!byGroup.has(group)) {
|
|
716
|
+
byGroup.set(group, []);
|
|
717
|
+
groups.push(group);
|
|
718
|
+
}
|
|
719
|
+
byGroup.get(group)?.push(canvases[i].id);
|
|
720
|
+
});
|
|
721
|
+
if (groups.length === 0) return null;
|
|
722
|
+
return groups.map((group, i) => ({
|
|
723
|
+
id: `${manifestId}/range/${i + 1}`,
|
|
724
|
+
type: "Range",
|
|
725
|
+
label: languageMap(group),
|
|
726
|
+
items: byGroup.get(group) ?? [],
|
|
727
|
+
}));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* A slide's `media.thumb` as P3's canvas `thumbnail`, which is a list of
|
|
732
|
+
* content resources (§3.1).
|
|
733
|
+
*/
|
|
734
|
+
function buildThumbnail(slide: StorymapSlide): Partial<StorymapManifestCanvas> {
|
|
735
|
+
const url = slide.media?.thumb;
|
|
736
|
+
if (!present(url) || typeof url !== "string") return {};
|
|
737
|
+
const { type, format } = classifyMediaUrl(url);
|
|
738
|
+
return { thumbnail: [{ id: url, type, ...(format ? { format } : {}) }] };
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* A slide background as P3's canvas `background`: a painting Annotation whose
|
|
743
|
+
* body is the image and/or the colour to paint behind the slide (§2.6). A
|
|
744
|
+
* bare-string background is a colour, as it has always been in storymap-JSON.
|
|
745
|
+
*
|
|
746
|
+
* `background.opacity` is deliberately not carried across. IIIF has no
|
|
747
|
+
* vocabulary for it — Presentation 3 has a `Color` body but nothing to fade a
|
|
748
|
+
* background with, and 4.0's `backgroundColor` is a plain hex value — and the
|
|
749
|
+
* viewer never rendered it, so a term for it would be dead vocabulary.
|
|
750
|
+
*/
|
|
751
|
+
function buildBackground(
|
|
752
|
+
canvasId: string,
|
|
753
|
+
background: StorymapSlide["background"],
|
|
754
|
+
): Partial<StorymapManifestCanvas> {
|
|
755
|
+
if (!background) return {};
|
|
756
|
+
const object = typeof background === "object" ? background : {};
|
|
757
|
+
const url = typeof background === "string" ? null : object.url;
|
|
758
|
+
const color = typeof background === "string" ? background : object.color;
|
|
759
|
+
const bodies: StorymapManifestContentResource[] = [];
|
|
760
|
+
if (present(url)) {
|
|
761
|
+
if (typeof url === "string") {
|
|
762
|
+
const { type, format } = classifyMediaUrl(url);
|
|
763
|
+
bodies.push({ id: url, type, ...(format ? { format } : {}) });
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (present(color) && typeof color === "string") {
|
|
767
|
+
bodies.push({ type: "Color", value: color });
|
|
768
|
+
}
|
|
769
|
+
if (bodies.length === 0) return {};
|
|
770
|
+
return {
|
|
771
|
+
background: {
|
|
772
|
+
id: `${canvasId}/background`,
|
|
773
|
+
type: "Annotation",
|
|
774
|
+
motivation: "painting",
|
|
775
|
+
body: bodies.length === 1 ? (bodies[0] as StorymapManifestContentResource) : bodies,
|
|
776
|
+
target: canvasId,
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* A slide's media caption, credit and alt text as the painting annotation's
|
|
783
|
+
* own P3 properties, replacing the `storymap:mediaCaption` /
|
|
784
|
+
* `mediaCredit` / `mediaAlt` canvas terms (§2.7).
|
|
785
|
+
*
|
|
786
|
+
* `requiredStatement` is a single `{label, value}` object in P3 — the official
|
|
787
|
+
* validator rejects the array form — so the credit becomes a labelled
|
|
788
|
+
* statement, and §2.1's reader turns it back into "Credit: …".
|
|
789
|
+
*/
|
|
790
|
+
function buildAnnotationPresentation(slide: StorymapSlide): Partial<StorymapManifestAnnotation> {
|
|
791
|
+
const out: Partial<StorymapManifestAnnotation> = {};
|
|
792
|
+
if (present(slide.media?.caption) && typeof slide.media?.caption === "string") {
|
|
793
|
+
out.label = languageMap(slide.media.caption);
|
|
794
|
+
}
|
|
795
|
+
if (present(slide.media?.credit) && typeof slide.media?.credit === "string") {
|
|
796
|
+
out.requiredStatement = {
|
|
797
|
+
label: languageMap("Credit"),
|
|
798
|
+
value: languageMap(slide.media.credit),
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
if (present(slide.media?.alt) && typeof slide.media?.alt === "string") {
|
|
802
|
+
out.accessibilitySummary = languageMap(slide.media.alt);
|
|
803
|
+
}
|
|
804
|
+
return out;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function buildBody(slide: StorymapSlide, isImageMap: boolean): StorymapManifestContentResource {
|
|
808
|
+
if (isImageMap) {
|
|
809
|
+
// Image-map canvas: paint the IIIF Image API resource. The service
|
|
810
|
+
// reference lets IIIF-aware viewers request arbitrary resolutions.
|
|
811
|
+
return {
|
|
812
|
+
id: LAOCOON_IMAGE,
|
|
813
|
+
type: "Image",
|
|
814
|
+
format: "image/jpeg",
|
|
815
|
+
width: LAOCOON_WIDTH,
|
|
816
|
+
height: LAOCOON_HEIGHT,
|
|
817
|
+
service: [{ id: LAOCOON_INFO, type: "ImageService3", profile: "level2" }],
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
const url = slide.media?.url;
|
|
821
|
+
if (isHttpUrl(url)) {
|
|
822
|
+
const { type, format } = classifyMediaUrl(url);
|
|
823
|
+
const body: StorymapManifestContentResource = { id: url, type };
|
|
824
|
+
if (format) {
|
|
825
|
+
body.format = format;
|
|
826
|
+
}
|
|
827
|
+
return body;
|
|
828
|
+
}
|
|
829
|
+
// A media block with a wrong-typed url is not a text-only slide: painting
|
|
830
|
+
// the caption here would claim it IS the content (and the summary already
|
|
831
|
+
// carries it, so the reader would read it back as media.url). Emit an
|
|
832
|
+
// empty body instead of a plausible lie. An empty string is not
|
|
833
|
+
// wrong-typed — it is the idiomatic "no media" marker — so it keeps the
|
|
834
|
+
// old path, as does a missing url.
|
|
835
|
+
if (url !== undefined && url !== null && url !== "" && typeof url !== "string") {
|
|
836
|
+
return { type: "TextualBody", format: "text/html", value: "" };
|
|
837
|
+
}
|
|
838
|
+
// Text-only slide (or a media value that is not a URL): paint the HTML.
|
|
839
|
+
const html = typeof url === "string" && url !== "" ? url : slide.text?.text || "";
|
|
840
|
+
return {
|
|
841
|
+
type: "TextualBody",
|
|
842
|
+
format: "text/html",
|
|
843
|
+
value: html,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function buildCanvasTerms(slide: StorymapSlide): Partial<StorymapManifestCanvas> {
|
|
848
|
+
const terms: Partial<StorymapManifestCanvas> = {};
|
|
849
|
+
if (slide.type === "overview") {
|
|
850
|
+
terms["storymap:type"] = "overview";
|
|
851
|
+
}
|
|
852
|
+
if (present(slide.media?.srcset)) {
|
|
853
|
+
terms["storymap:mediaSrcset"] = slide.media?.srcset;
|
|
854
|
+
}
|
|
855
|
+
if (present(slide.media?.sizes)) {
|
|
856
|
+
terms["storymap:mediaSizes"] = slide.media?.sizes;
|
|
857
|
+
}
|
|
858
|
+
return terms;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* A slide's `location.region` as the painting annotation's target, so the
|
|
863
|
+
* region is an Image API selector rather than a term (§2.8).
|
|
864
|
+
*
|
|
865
|
+
* `xywh=pixel:` rather than a bare `xywh=` because the field is documented in
|
|
866
|
+
* image pixels, and the prefix then says so itself instead of implying the
|
|
867
|
+
* canvas size — which matters for a non-image map, where the canvas is a
|
|
868
|
+
* nominal 1080×1080 and the region's image is something else entirely. It is
|
|
869
|
+
* the prefix Media Fragments 1.0 defines for source-image pixels; `image:` is
|
|
870
|
+
* not one of the three it defines.
|
|
871
|
+
*/
|
|
872
|
+
function buildRegionTarget(
|
|
873
|
+
canvasId: string,
|
|
874
|
+
region: StorymapSlideLocation["region"],
|
|
875
|
+
): StorymapManifestSpecificResource | null {
|
|
876
|
+
if (!Array.isArray(region) || region.length !== 4) return null;
|
|
877
|
+
if (!region.every((n) => typeof n === "number" && Number.isFinite(n))) return null;
|
|
878
|
+
return {
|
|
879
|
+
type: "SpecificResource",
|
|
880
|
+
source: canvasId,
|
|
881
|
+
selector: { type: "ImageApiSelector", value: `xywh=pixel:${region.join(",")}` },
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function buildNavPlace(
|
|
886
|
+
canvasId: string,
|
|
887
|
+
slide: StorymapSlide,
|
|
888
|
+
): StorymapManifestFeatureCollection | null {
|
|
889
|
+
const location = slide.location || {};
|
|
890
|
+
// typeof narrows for the compiler, isFinite for the runtime: a typeof
|
|
891
|
+
// check alone lets Infinity through, which serialises as null — invalid
|
|
892
|
+
// GeoJSON the reader then drops silently. Same guard as the bbox and
|
|
893
|
+
// region paths.
|
|
894
|
+
const { lat, lon } = location;
|
|
895
|
+
if (
|
|
896
|
+
typeof lat !== "number" ||
|
|
897
|
+
typeof lon !== "number" ||
|
|
898
|
+
!Number.isFinite(lat) ||
|
|
899
|
+
!Number.isFinite(lon)
|
|
900
|
+
) {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
const properties: Record<string, unknown> = {};
|
|
904
|
+
for (const key of LOCATION_PROPERTIES) {
|
|
905
|
+
if (present(location[key])) {
|
|
906
|
+
properties[key] = location[key];
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return {
|
|
910
|
+
id: `${canvasId}/navplace`,
|
|
911
|
+
type: "FeatureCollection",
|
|
912
|
+
features: [
|
|
913
|
+
{
|
|
914
|
+
id: `${canvasId}/navplace/feature/1`,
|
|
915
|
+
type: "Feature",
|
|
916
|
+
geometry: { type: "Point", coordinates: [lon, lat] },
|
|
917
|
+
properties,
|
|
918
|
+
},
|
|
919
|
+
],
|
|
920
|
+
};
|
|
921
|
+
}
|