@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,471 @@
|
|
|
1
|
+
import { Language } from "../language/Language";
|
|
2
|
+
import Dom from "../dom/Dom";
|
|
3
|
+
|
|
4
|
+
/** localStorage key holding the per-service consent state (JSON). */
|
|
5
|
+
const CONSENT_STORAGE_KEY = "storymapjs-consent";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Read the shared consent record, remapping keys written by an earlier
|
|
9
|
+
* version onto their namespaced form.
|
|
10
|
+
*
|
|
11
|
+
* @returns The state, and whether any key was remapped (so a caller can
|
|
12
|
+
* write the modern form back once and retire the legacy keys).
|
|
13
|
+
*/
|
|
14
|
+
function readStoredState(): { state: Record<string, boolean>; migrated: boolean } {
|
|
15
|
+
const state: Record<string, boolean> = {};
|
|
16
|
+
let migrated = false;
|
|
17
|
+
try {
|
|
18
|
+
const raw = window.localStorage.getItem(CONSENT_STORAGE_KEY);
|
|
19
|
+
if (!raw) {
|
|
20
|
+
return { state, migrated };
|
|
21
|
+
}
|
|
22
|
+
const parsed = JSON.parse(raw) as Record<string, boolean>;
|
|
23
|
+
for (const stored in parsed) {
|
|
24
|
+
if (!Object.hasOwn(parsed, stored)) continue;
|
|
25
|
+
const key = migrateLegacyKey(stored);
|
|
26
|
+
if (key !== stored) {
|
|
27
|
+
migrated = true;
|
|
28
|
+
}
|
|
29
|
+
state[key] = parsed[stored];
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// ignore malformed or unavailable storage
|
|
33
|
+
}
|
|
34
|
+
return { state, migrated };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Write the shared consent record. */
|
|
38
|
+
function writeStoredState(state: Record<string, boolean>): void {
|
|
39
|
+
try {
|
|
40
|
+
window.localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(state));
|
|
41
|
+
} catch {
|
|
42
|
+
// storage unavailable (e.g. sandboxed contexts)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* An external service the viewer asks permission to load.
|
|
48
|
+
*
|
|
49
|
+
* `key` is the *only* thing ever persisted, and it is stable, namespaced and
|
|
50
|
+
* never localized. `label` is display-only.
|
|
51
|
+
*
|
|
52
|
+
* These are two separate fields on purpose. The code used to pass a single
|
|
53
|
+
* string that served as both, which had two consequences:
|
|
54
|
+
*
|
|
55
|
+
* - the tile and font services used a *localized* string as the localStorage
|
|
56
|
+
* key, so translating `consent_service_tiles` would have silently
|
|
57
|
+
* invalidated every stored decision the moment the page language changed;
|
|
58
|
+
* - the per-slide panel used the media type *slug* as its display label, so
|
|
59
|
+
* it asked "Load content from youtube?" where the start-of-story dialog,
|
|
60
|
+
* 20 lines away, correctly said "YouTube".
|
|
61
|
+
*
|
|
62
|
+
* With the pair split, passing a label where a key is expected is no longer
|
|
63
|
+
* expressible.
|
|
64
|
+
*/
|
|
65
|
+
export interface ConsentService {
|
|
66
|
+
/** Stable, namespaced, non-localized. The only value that is stored. */
|
|
67
|
+
key: string;
|
|
68
|
+
/** Display name. Localized for tiles/fonts; a brand name for media. */
|
|
69
|
+
label: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Namespaced storage keys. Prefixed so a future service can never collide
|
|
73
|
+
* with a bare media type slug. */
|
|
74
|
+
const TILE_KEY = "map:tiles";
|
|
75
|
+
const FONT_KEY = "fonts:web";
|
|
76
|
+
const MEDIA_KEY_PREFIX = "media:";
|
|
77
|
+
|
|
78
|
+
/** The map tile service: the basemap, overlays, minimap and overview. */
|
|
79
|
+
export function tileService(): ConsentService {
|
|
80
|
+
return {
|
|
81
|
+
key: TILE_KEY,
|
|
82
|
+
label: consentMessage("consent_service_tiles", "map tiles"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** An externally hosted web-font theme. */
|
|
87
|
+
export function fontService(): ConsentService {
|
|
88
|
+
return {
|
|
89
|
+
key: FONT_KEY,
|
|
90
|
+
label: consentMessage("consent_service_fonts", "web fonts"),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Slide narration: the recording that plays at a tour stop. It is not a media
|
|
96
|
+
* embed, but it is the same kind of third-party fetch, so it is gated the same
|
|
97
|
+
* way — through the `Media` class it bypasses, narration would otherwise be a
|
|
98
|
+
* hole in the GDPR mode.
|
|
99
|
+
*/
|
|
100
|
+
export function narrationService(): ConsentService {
|
|
101
|
+
return {
|
|
102
|
+
key: MEDIA_KEY_PREFIX + "narration",
|
|
103
|
+
label: consentMessage("consent_service_narration", "narration"),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A media embed from an external service.
|
|
109
|
+
*
|
|
110
|
+
* `type` is the MediaType slug (the stable identity); `name` is its display
|
|
111
|
+
* name (a brand name, e.g. "YouTube"). Both are needed: the slug is what gets
|
|
112
|
+
* stored and what the error-icon class is derived from, the name is what the
|
|
113
|
+
* visitor reads.
|
|
114
|
+
*/
|
|
115
|
+
export function mediaService(type: string, name: string): ConsentService {
|
|
116
|
+
return { key: MEDIA_KEY_PREFIX + type, label: name || type };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Map a key written by an earlier version onto the current namespaced form.
|
|
121
|
+
*
|
|
122
|
+
* The pre-registry code stored bare slugs for media (`"youtube"`) and the
|
|
123
|
+
* *localized* strings for tiles and fonts (`"map tiles"`, `"web fonts"` — which
|
|
124
|
+
* is why every locale happened to fall back to the English spelling, and why
|
|
125
|
+
* translating them would have orphaned everyone's decisions).
|
|
126
|
+
*
|
|
127
|
+
* Every current key contains a `:` and no legacy key does, so that is the
|
|
128
|
+
* discriminator. Enumerating the current keys instead looked more explicit
|
|
129
|
+
* but silently re-prefixed `map:tiles` into `media:map:tiles` on every read,
|
|
130
|
+
* so a stored grant was never found again.
|
|
131
|
+
*/
|
|
132
|
+
function migrateLegacyKey(key: string): string {
|
|
133
|
+
if (key.includes(":")) {
|
|
134
|
+
return key;
|
|
135
|
+
}
|
|
136
|
+
if (key === "map tiles") return TILE_KEY;
|
|
137
|
+
if (key === "web fonts") return FONT_KEY;
|
|
138
|
+
return MEDIA_KEY_PREFIX + key;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A button in a consent dialog. */
|
|
142
|
+
function consentButton(kind: "allow" | "deny", text: string): HTMLButtonElement {
|
|
143
|
+
const button = Dom.create("button", `vco-consent-${kind}`) as HTMLButtonElement;
|
|
144
|
+
button.setAttribute("type", "button");
|
|
145
|
+
button.textContent = text;
|
|
146
|
+
return button;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The title/message pair shared by both dialog shapes. */
|
|
150
|
+
function consentHeader(
|
|
151
|
+
titleText: string,
|
|
152
|
+
messageText: string,
|
|
153
|
+
): { title: HTMLElement; message: HTMLElement } {
|
|
154
|
+
const title = Dom.create("p", "vco-consent-title");
|
|
155
|
+
title.textContent = titleText;
|
|
156
|
+
const message = Dom.create("p", "vco-consent-message");
|
|
157
|
+
message.textContent = messageText;
|
|
158
|
+
return { title: title, message: message };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Per-StoryMap GDPR consent manager. When the `consent_required` option is
|
|
163
|
+
* set, every external service (media embeds, map tiles, external font CSS)
|
|
164
|
+
* asks for permission before anything is loaded.
|
|
165
|
+
*
|
|
166
|
+
* Decisions are remembered per service in localStorage with no expiry:
|
|
167
|
+
* clearing site data is what makes the viewer ask again.
|
|
168
|
+
*/
|
|
169
|
+
export class ConsentManager {
|
|
170
|
+
private granted = new Set<string>();
|
|
171
|
+
private denied = new Set<string>();
|
|
172
|
+
/** unanswered asks per service (preloaded slides stack several) */
|
|
173
|
+
private pending = new Map<string, Array<{ el: HTMLElement; resolve: (v: boolean) => void }>>();
|
|
174
|
+
/** the start-of-story dialog's row per service, so a decision made
|
|
175
|
+
* anywhere (including a standalone per-service ask) clears it */
|
|
176
|
+
private startRows = new Map<string, HTMLElement>();
|
|
177
|
+
/** every start-of-story dialog root we put on screen, so dispose() can
|
|
178
|
+
* take the whole thing down rather than just its rows */
|
|
179
|
+
private startDialogs = new Set<HTMLElement>();
|
|
180
|
+
/** set by dispose(); late answers are ignored rather than persisted */
|
|
181
|
+
private disposed = false;
|
|
182
|
+
|
|
183
|
+
constructor() {
|
|
184
|
+
this.restore();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Seed the per-service state from localStorage, if present.
|
|
189
|
+
*
|
|
190
|
+
* Keys written by an earlier version are remapped onto the namespaced
|
|
191
|
+
* form, so an existing visitor is not asked everything again after the
|
|
192
|
+
* upgrade. The remap is written back once, which also clears the legacy
|
|
193
|
+
* keys so they cannot drift out of sync later.
|
|
194
|
+
*/
|
|
195
|
+
private restore(): void {
|
|
196
|
+
const { state, migrated } = readStoredState();
|
|
197
|
+
for (const [key, allowed] of Object.entries(state)) {
|
|
198
|
+
if (allowed) {
|
|
199
|
+
this.granted.add(key);
|
|
200
|
+
} else {
|
|
201
|
+
this.denied.add(key);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (migrated) {
|
|
205
|
+
this.persist();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Persist the per-service state to localStorage.
|
|
211
|
+
*
|
|
212
|
+
* A page can hold more than one viewer, and they all share this one
|
|
213
|
+
* record — a visitor should not answer the same question twice for the
|
|
214
|
+
* same service. So this *merges* into whatever is already stored instead
|
|
215
|
+
* of replacing it: writing this viewer's full set back would silently
|
|
216
|
+
* erase a decision a sibling viewer made, while that sibling still
|
|
217
|
+
* believed otherwise. The merged result is then adopted, which is what
|
|
218
|
+
* makes every viewer converge on the same decisions.
|
|
219
|
+
*/
|
|
220
|
+
private persist(): void {
|
|
221
|
+
const merged = readStoredState().state;
|
|
222
|
+
for (const service of this.granted) merged[service] = true;
|
|
223
|
+
for (const service of this.denied) merged[service] = false;
|
|
224
|
+
writeStoredState(merged);
|
|
225
|
+
this.adopt(merged);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Replace the in-memory decision set with `state`, in place. */
|
|
229
|
+
private adopt(state: Record<string, boolean>): void {
|
|
230
|
+
this.granted.clear();
|
|
231
|
+
this.denied.clear();
|
|
232
|
+
for (const [key, allowed] of Object.entries(state)) {
|
|
233
|
+
if (allowed) {
|
|
234
|
+
this.granted.add(key);
|
|
235
|
+
} else {
|
|
236
|
+
this.denied.add(key);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Re-read the shared record, so a decision the visitor made in a sibling
|
|
243
|
+
* viewer is honoured here without having to reconstruct this one.
|
|
244
|
+
*/
|
|
245
|
+
private sync(): void {
|
|
246
|
+
this.adopt(readStoredState().state);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Record a decision for one service and resolve every ask waiting on it.
|
|
251
|
+
*
|
|
252
|
+
* Both dialog shapes funnel through here, which is what keeps a service
|
|
253
|
+
* from ending up in the granted *and* the denied set: the opposite set is
|
|
254
|
+
* always cleared.
|
|
255
|
+
*/
|
|
256
|
+
private decide(service: string, allowed: boolean): void {
|
|
257
|
+
if (this.disposed) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (allowed) {
|
|
261
|
+
this.granted.add(service);
|
|
262
|
+
this.denied.delete(service);
|
|
263
|
+
} else {
|
|
264
|
+
this.denied.add(service);
|
|
265
|
+
this.granted.delete(service);
|
|
266
|
+
}
|
|
267
|
+
// Store before resolving. A resolved promise starts loading the media,
|
|
268
|
+
// which asks again for the next service, and that ask re-reads the
|
|
269
|
+
// shared record — so the decision has to be durable first.
|
|
270
|
+
this.persist();
|
|
271
|
+
|
|
272
|
+
// answering resolves every pending ask of that service (preloaded
|
|
273
|
+
// slides ask in parallel)
|
|
274
|
+
for (const pending of this.pending.get(service) ?? []) {
|
|
275
|
+
pending.el.remove();
|
|
276
|
+
pending.resolve(allowed);
|
|
277
|
+
}
|
|
278
|
+
this.pending.delete(service);
|
|
279
|
+
// and the start-of-story dialog's row for it. The visitor can answer
|
|
280
|
+
// the same service from the standalone ask over the map, so the row
|
|
281
|
+
// cannot be cleaned up by its own click handler alone.
|
|
282
|
+
this.startRows.get(service)?.remove();
|
|
283
|
+
this.startRows.delete(service);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
isGranted(service: string): boolean {
|
|
287
|
+
return this.granted.has(service);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
isDenied(service: string): boolean {
|
|
291
|
+
return this.denied.has(service);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** True when at least one of the services has no stored decision yet. */
|
|
295
|
+
hasUnanswered(services: string[]): boolean {
|
|
296
|
+
this.sync();
|
|
297
|
+
return services.some((service) => !this.granted.has(service) && !this.denied.has(service));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Start-of-story consent dialog: lists every known external service
|
|
302
|
+
* with per-service Allow/Deny, plus global "Allow all" / "Decline
|
|
303
|
+
* all" buttons. Individual decisions reuse the per-service state
|
|
304
|
+
* (pending slide asks resolve immediately); the dialog closes once
|
|
305
|
+
* every service has a decision.
|
|
306
|
+
*/
|
|
307
|
+
requestAll(services: ConsentService[], container: HTMLElement): void {
|
|
308
|
+
if (!this.hasUnanswered(services.map((s) => s.key))) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Only list the services still waiting on a decision. Rendering an
|
|
312
|
+
// already-answered row invited the visitor to re-answer a question
|
|
313
|
+
// they had settled on a previous visit — and, because the row shows
|
|
314
|
+
// "map tiles", it made the tile consent look unanswered even when
|
|
315
|
+
// localStorage said otherwise.
|
|
316
|
+
const unanswered = services.filter(
|
|
317
|
+
(s) => !this.granted.has(s.key) && !this.denied.has(s.key),
|
|
318
|
+
);
|
|
319
|
+
const messages = Language.messages;
|
|
320
|
+
const el = Dom.create("div", "vco-consent vco-consent-start");
|
|
321
|
+
const { title, message } = consentHeader(
|
|
322
|
+
messages.consent_start_title ?? "External content",
|
|
323
|
+
messages.consent_start_message ?? "",
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const list = Dom.create("div", "vco-consent-services");
|
|
327
|
+
const toggles: Array<{ key: string; allow: HTMLButtonElement; deny: HTMLButtonElement }> =
|
|
328
|
+
[];
|
|
329
|
+
|
|
330
|
+
const finish = () => {
|
|
331
|
+
if (this.hasUnanswered(services.map((s) => s.key))) return;
|
|
332
|
+
el.remove();
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
for (const service of unanswered) {
|
|
336
|
+
const row = Dom.create("div", "vco-consent-service");
|
|
337
|
+
const label = Dom.create("span", "vco-consent-service-label");
|
|
338
|
+
label.textContent = service.label;
|
|
339
|
+
const buttons = Dom.create("div", "vco-consent-buttons");
|
|
340
|
+
const allow = consentButton("allow", messages.consent_allow ?? "Allow");
|
|
341
|
+
const deny = consentButton("deny", messages.consent_deny ?? "Deny");
|
|
342
|
+
|
|
343
|
+
const answer = (allowed: boolean) => {
|
|
344
|
+
// decide() removes the row: a settled service must not keep
|
|
345
|
+
// showing in the dialog
|
|
346
|
+
this.decide(service.key, allowed);
|
|
347
|
+
allow.disabled = true;
|
|
348
|
+
deny.disabled = true;
|
|
349
|
+
finish();
|
|
350
|
+
};
|
|
351
|
+
allow.addEventListener("click", () => answer(true));
|
|
352
|
+
deny.addEventListener("click", () => answer(false));
|
|
353
|
+
|
|
354
|
+
buttons.append(allow, deny);
|
|
355
|
+
row.append(label, buttons);
|
|
356
|
+
list.append(row);
|
|
357
|
+
toggles.push({ key: service.key, allow, deny });
|
|
358
|
+
this.startRows.set(service.key, row);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const actions = Dom.create("div", "vco-consent-buttons vco-consent-start-actions");
|
|
362
|
+
const allowAll = consentButton("allow", messages.consent_allow_all ?? "Allow all");
|
|
363
|
+
const declineAll = consentButton("deny", messages.consent_decline_all ?? "Decline all");
|
|
364
|
+
|
|
365
|
+
const answerAll = (allowed: boolean) => {
|
|
366
|
+
for (const toggle of toggles) {
|
|
367
|
+
this.decide(toggle.key, allowed);
|
|
368
|
+
toggle.allow.disabled = true;
|
|
369
|
+
toggle.deny.disabled = true;
|
|
370
|
+
}
|
|
371
|
+
el.remove();
|
|
372
|
+
};
|
|
373
|
+
allowAll.addEventListener("click", () => answerAll(true));
|
|
374
|
+
declineAll.addEventListener("click", () => answerAll(false));
|
|
375
|
+
|
|
376
|
+
actions.append(allowAll, declineAll);
|
|
377
|
+
el.append(title, message, list, actions);
|
|
378
|
+
container.append(el);
|
|
379
|
+
this.startDialogs.add(el);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Ask the visitor for permission to load `service`, optionally naming the
|
|
384
|
+
* `host` (shown in parentheses so they can see which domain is about to be
|
|
385
|
+
* contacted). The ask is rendered into `container`; resolves
|
|
386
|
+
* `true`/`false` on the visitor's decision, or immediately when the
|
|
387
|
+
* service was already granted or denied.
|
|
388
|
+
*
|
|
389
|
+
* The service is stored and looked up by `service.key`; `service.label`
|
|
390
|
+
* is only ever rendered.
|
|
391
|
+
*/
|
|
392
|
+
request(service: ConsentService, host: string, container: HTMLElement): Promise<boolean> {
|
|
393
|
+
const key = service.key;
|
|
394
|
+
// the record is shared with any sibling viewer on the page, so re-read
|
|
395
|
+
// it: a decision the visitor just made over there answers this ask
|
|
396
|
+
// without showing a second dialog for the same service
|
|
397
|
+
this.sync();
|
|
398
|
+
if (this.granted.has(key)) return Promise.resolve(true);
|
|
399
|
+
if (this.denied.has(key)) return Promise.resolve(false);
|
|
400
|
+
return new Promise((resolve) => {
|
|
401
|
+
const messages = Language.messages;
|
|
402
|
+
const el = Dom.create("div", "vco-consent");
|
|
403
|
+
|
|
404
|
+
const template = messages.consent_message ?? "Load content from {service}?";
|
|
405
|
+
const { title, message } = consentHeader(
|
|
406
|
+
// consent_title was byte-identical to consent_start_title and
|
|
407
|
+
// present in no other locale, so the per-service panel reuses
|
|
408
|
+
// the start-of-story heading
|
|
409
|
+
messages.consent_start_title ?? "External content",
|
|
410
|
+
template.replace("{service}", service.label) + (host ? ` (${host})` : ""),
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
const buttons = Dom.create("div", "vco-consent-buttons");
|
|
414
|
+
const allow = consentButton("allow", messages.consent_allow ?? "Allow");
|
|
415
|
+
const deny = consentButton("deny", messages.consent_deny ?? "Deny");
|
|
416
|
+
|
|
417
|
+
const list = this.pending.get(key) ?? [];
|
|
418
|
+
list.push({ el, resolve });
|
|
419
|
+
this.pending.set(key, list);
|
|
420
|
+
|
|
421
|
+
const answer = (allowed: boolean) => this.decide(key, allowed);
|
|
422
|
+
allow.addEventListener("click", () => answer(true));
|
|
423
|
+
deny.addEventListener("click", () => answer(false));
|
|
424
|
+
|
|
425
|
+
buttons.append(allow, deny);
|
|
426
|
+
el.append(title, message, buttons);
|
|
427
|
+
container.append(el);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Release everything: drop the dialogs and settle every unanswered ask.
|
|
433
|
+
*
|
|
434
|
+
* `Media.loadMedia()` awaits `request()`. Without this, tearing the viewer
|
|
435
|
+
* down while a panel was on screen left that `await` pending forever, and
|
|
436
|
+
* the suspended frame kept the whole media subtree — DOM included — alive.
|
|
437
|
+
* Unanswered asks resolve `false`, which is the same outcome as declining.
|
|
438
|
+
*/
|
|
439
|
+
dispose(): void {
|
|
440
|
+
this.disposed = true;
|
|
441
|
+
// the whole dialog, not just the rows: removing the rows leaves the
|
|
442
|
+
// heading and the allow-all/deny-all buttons behind
|
|
443
|
+
for (const el of this.startDialogs) {
|
|
444
|
+
el.remove();
|
|
445
|
+
}
|
|
446
|
+
this.startDialogs.clear();
|
|
447
|
+
this.startRows.clear();
|
|
448
|
+
for (const [, asks] of this.pending) {
|
|
449
|
+
for (const ask of asks) {
|
|
450
|
+
ask.el.remove();
|
|
451
|
+
ask.resolve(false);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
this.pending.clear();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* The consent manager attached to a StoryMap's options object, or
|
|
460
|
+
* `undefined` when absent.
|
|
461
|
+
*/
|
|
462
|
+
export function consentManagerOf(options: unknown): ConsentManager | undefined {
|
|
463
|
+
return (options as { consent_manager?: ConsentManager })?.consent_manager;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* A localized consent string, falling back to the English default.
|
|
468
|
+
*/
|
|
469
|
+
export function consentMessage(key: string, fallback: string): string {
|
|
470
|
+
return Language.messages[key] ?? fallback;
|
|
471
|
+
}
|