@projektemacher/storymapjs 0.10.7 → 0.11.0
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 +431 -19
- package/README.md +168 -17
- package/dist/assets/demo-bWnOyfUw.js +1 -0
- package/dist/assets/harness-C3JUv5Ii.js +1 -0
- package/dist/assets/harness-multi-XZNex3tb.js +1 -0
- package/dist/assets/harness-support-CjfZIVeb.js +1 -0
- package/dist/assets/index-wHPqGrBS.js +1 -0
- package/dist/assets/main-DQ32hWBM.js +17 -0
- package/dist/context.json +23 -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 +84 -0
- 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 +3 -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 +2112 -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/isSlideshowCollection.html +51 -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/slideshowToStorymapData.html +51 -0
- package/dist/docs/api/functions/slideshowVersion.html +48 -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 +300 -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/MapEvents.html +116 -0
- package/dist/docs/api/interfaces/MapMarkerEvents.html +66 -0
- package/dist/docs/api/interfaces/MarkerEventPayload.html +57 -0
- package/dist/docs/api/interfaces/MediaEvents.html +78 -0
- package/dist/docs/api/interfaces/MenuBarEvents.html +114 -0
- package/dist/docs/api/interfaces/MessageEvents.html +72 -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/SlideBackgroundState.html +66 -0
- package/dist/docs/api/interfaces/SlideEvents.html +78 -0
- package/dist/docs/api/interfaces/SlideNavEvents.html +72 -0
- package/dist/docs/api/interfaces/SlideshowPlayerSettings.html +182 -0
- package/dist/docs/api/interfaces/SlideshowToStorymapResult.html +60 -0
- package/dist/docs/api/interfaces/SlideshowTranslatorOptions.html +65 -0
- package/dist/docs/api/interfaces/SlideshowWarning.html +72 -0
- package/dist/docs/api/interfaces/StoryMapEvents.html +146 -0
- package/dist/docs/api/interfaces/StoryMapMarker.html +763 -0
- package/dist/docs/api/interfaces/StorySliderEvents.html +102 -0
- package/dist/docs/api/interfaces/StorymapBasemap.html +69 -0
- package/dist/docs/api/interfaces/StorymapData.html +77 -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 +137 -0
- package/dist/docs/api/interfaces/StorymapManifestBackground.html +81 -0
- package/dist/docs/api/interfaces/StorymapManifestCanvas.html +188 -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 +734 -0
- package/dist/docs/api/interfaces/StorymapOverlayLayer.html +157 -0
- package/dist/docs/api/interfaces/StorymapSlide.html +186 -0
- package/dist/docs/api/interfaces/StorymapSlideBackground.html +72 -0
- package/dist/docs/api/interfaces/StorymapSlideLocation.html +161 -0
- package/dist/docs/api/interfaces/StorymapSlideMarker.html +103 -0
- package/dist/docs/api/interfaces/StorymapSlideMedia.html +149 -0
- package/dist/docs/api/interfaces/StorymapSlideNarration.html +117 -0
- package/dist/docs/api/interfaces/StorymapSlideText.html +72 -0
- package/dist/docs/api/interfaces/StorymapTilejson.html +105 -0
- package/dist/docs/api/interfaces/SwipableEvents.html +102 -0
- package/dist/docs/api/interfaces/TextEvents.html +66 -0
- package/dist/docs/api/media/es.json +31 -0
- package/dist/docs/api/media/events.md +66 -0
- package/dist/docs/api/media/migration-from-knightlab.md +399 -0
- package/dist/docs/api/media/slideshow.md +109 -0
- package/dist/docs/api/media/storymap-as-iiif-manifest.md +1007 -0
- package/dist/docs/api/modules.html +50 -0
- package/dist/docs/api/types/ContentState.html +70 -0
- package/dist/docs/api/types/FiredEvent.html +43 -0
- package/dist/docs/api/types/ImagereadyKind.html +34 -0
- package/dist/docs/api/types/MediaTypeMatch.html +78 -0
- package/dist/docs/api/types/SwipeDirection.html +36 -0
- package/dist/docs/api/types/TileSourceFactory.html +53 -0
- package/dist/docs/authoring.html +306 -0
- package/dist/docs/events.html +263 -0
- package/dist/docs/iiif.html +738 -69
- package/dist/docs/migration.html +360 -11
- package/dist/docs/readme.html +149 -17
- package/dist/embed/index.html +8 -1
- package/dist/examples/font-css-false.json +33 -0
- 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/layers-control.json +60 -0
- package/dist/examples/map-area-overlays.json +68 -0
- package/dist/examples/map-template-provider.json +36 -0
- package/dist/examples/multi-media.json +71 -0
- package/dist/examples/no-map.json +44 -0
- package/dist/examples-iiif/annotated-image.json +204 -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/font-css-false.json +114 -0
- 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/layers-control.json +226 -0
- package/dist/examples-iiif/map-area-overlays.json +289 -0
- package/dist/examples-iiif/map-template-provider.json +133 -0
- package/dist/examples-iiif/marktwain.json +255 -58
- package/dist/examples-iiif/multi-media.json +237 -0
- 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/examples-slideshow/single.json +79 -0
- package/dist/examples-slideshow/static/pixel.png +0 -0
- package/dist/examples-slideshow/static.json +44 -0
- package/dist/harness-multi.html +17 -0
- package/dist/harness.html +4 -2
- package/dist/index.html +19 -12
- package/dist/js/storymap.d.ts +2669 -307
- package/dist/js/storymap.js +10820 -6196
- package/dist/js/storymap.js.map +1 -1
- package/dist/navplace-properties.json +54 -0
- package/dist/site.css +54 -36
- package/docs/migration-from-knightlab.md +279 -19
- package/docs/storymap-as-iiif-manifest.md +633 -136
- package/package.json +12 -7
- package/schema/storymap.schema.json +733 -80
- package/src/animation/easings.ts +8 -1
- package/src/animation/tween.ts +186 -0
- package/src/core/Browser.ts +6 -5
- package/src/core/Load.ts +54 -2
- package/src/core/Util.ts +257 -193
- package/src/core/mixins.ts +66 -35
- package/src/core/viewers.ts +94 -0
- package/src/dom/DomEvent.ts +27 -8
- package/src/globals.d.ts +8 -0
- package/src/language/Language.ts +206 -25
- package/src/language/locale/.expected-gaps.json +346 -0
- package/src/language/locale/be.json +15 -2
- package/src/language/locale/bg.json +15 -2
- package/src/language/locale/cs.json +15 -2
- package/src/language/locale/de.json +23 -2
- package/src/language/locale/el.json +15 -2
- package/src/language/locale/en.json +17 -3
- package/src/language/locale/es.json +15 -2
- package/src/language/locale/et.json +5 -1
- package/src/language/locale/fr.json +15 -2
- package/src/language/locale/he.json +16 -3
- package/src/language/locale/hu.json +15 -2
- package/src/language/locale/is.json +5 -1
- package/src/language/locale/it.json +15 -2
- package/src/language/locale/jp.json +15 -2
- package/src/language/locale/ko.json +15 -2
- package/src/language/locale/nl.json +15 -2
- package/src/language/locale/nn.json +5 -1
- package/src/language/locale/no.json +15 -2
- package/src/language/locale/pl.json +15 -2
- package/src/language/locale/pt.json +15 -2
- package/src/language/locale/ru.json +15 -2
- package/src/language/locale/sk.json +15 -2
- package/src/language/locale/sr.json +5 -1
- package/src/language/locale/sv.json +15 -2
- package/src/language/locale/tr.json +15 -2
- package/src/language/locale/uk.json +15 -2
- package/src/language/locale/ur.json +5 -1
- package/src/language/locale/zh-cn.json +15 -2
- package/src/language/locale/zh-tw.json +15 -2
- package/src/main.ts +143 -0
- package/src/map/Map.ts +227 -112
- package/src/map/MapMarker.ts +44 -8
- package/src/map/georeference.ts +325 -0
- package/src/map/openlayers/Map.OpenLayers.ts +1601 -263
- package/src/map/openlayers/MapMarker.OpenLayers.ts +348 -24
- package/src/map/openlayers/zoomifyTiles.ts +1 -4
- package/src/map/slideView.ts +149 -0
- package/src/map/types.ts +2 -0
- package/src/media/EmbedUtil.ts +333 -24
- package/src/media/Media.ts +270 -59
- package/src/media/embedId.ts +110 -0
- package/src/media/types/Audio.ts +9 -82
- package/src/media/types/Blockquote.ts +2 -3
- package/src/media/types/DailyMotion.ts +5 -10
- package/src/media/types/DocumentCloud.ts +6 -35
- package/src/media/types/Facebook.ts +3 -4
- package/src/media/types/Flickr.ts +23 -12
- package/src/media/types/GoogleDoc.ts +3 -4
- package/src/media/types/HtmlMedia.ts +233 -0
- package/src/media/types/IFrame.ts +3 -4
- package/src/media/types/Image.ts +87 -9
- package/src/media/types/Juxtapose.ts +14 -5
- package/src/media/types/SoundCloud.ts +28 -6
- package/src/media/types/Text.ts +46 -21
- package/src/media/types/Twitter.ts +35 -20
- package/src/media/types/Video.ts +9 -79
- package/src/media/types/Vimeo.ts +11 -11
- package/src/media/types/Website.ts +23 -13
- package/src/media/types/Wikipedia.ts +18 -8
- package/src/media/types/YouTube.ts +72 -40
- package/src/scss/Typography.scss +16 -18
- package/src/scss/VCO.StoryMap.scss +208 -12
- package/src/scss/Variables.scss +3 -2
- package/src/scss/core/Mixins.scss +5 -63
- package/src/scss/core/Reset.scss +5 -18
- package/src/scss/fonts/font.amatic-andika.scss +3 -0
- package/src/scss/fonts/font.bitter-raleway.scss +6 -0
- package/src/scss/fonts/font.cairo.scss +79 -0
- package/src/scss/fonts/font.clicker-garamond.scss +5 -0
- package/src/scss/fonts/font.dancing-ledger.scss +1 -0
- package/src/scss/fonts/font.knightlab.scss +14 -0
- package/src/scss/fonts/font.oldstandard.scss +6 -0
- package/src/scss/fonts/font.opensans-gentiumbook.scss +5 -0
- package/src/scss/fonts/font.playfair-faunaone.scss +2 -0
- package/src/scss/fonts/font.playfair.scss +4 -0
- package/src/scss/fonts/font.pt.scss +6 -0
- package/src/scss/fonts/font.ubuntu.scss +4 -0
- package/src/scss/fonts/font.unicaone-vollkorn.scss +5 -0
- package/src/scss/map/VCO.Map.scss +1 -1
- package/src/scss/map/VCO.MapMarker.scss +114 -8
- package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +7 -4
- package/src/scss/media/VCO.Media.scss +14 -17
- package/src/scss/media/types/VCO.Media.Blockquote.scss +1 -1
- package/src/scss/media/types/VCO.Media.Text.scss +13 -5
- package/src/scss/media/types/VCO.Media.Twitter.scss +4 -4
- package/src/scss/media/types/VCO.Media.Wikipedia.scss +3 -19
- package/src/scss/site/site.scss +55 -36
- package/src/scss/slider/VCO.Slide.scss +61 -13
- package/src/scss/slider/VCO.SlideNav.scss +34 -24
- package/src/scss/slider/VCO.StorySlider.scss +53 -1
- package/src/scss/snapmap/VCO.SnapMap.scss +3 -3
- package/src/scss/ui/VCO.Consent.scss +63 -13
- package/src/scss/ui/VCO.Layers.scss +119 -0
- package/src/scss/ui/VCO.MenuBar.Button.scss +143 -14
- package/src/scss/ui/VCO.MenuBar.scss +9 -2
- package/src/scss/ui/VCO.Message.scss +16 -5
- package/src/site/site.ts +2 -1
- package/src/slider/Slide.ts +326 -74
- package/src/slider/SlideNav.ts +54 -9
- package/src/slider/StorySlider.ts +352 -60
- package/src/storymap/Consent.ts +429 -82
- package/src/storymap/StoryMap.ts +2163 -340
- package/src/storymap/content-state.ts +180 -0
- package/src/storymap/font-css.ts +72 -0
- package/src/storymap/from-slideshow.ts +785 -0
- package/src/storymap/iiif-shared.ts +228 -0
- package/src/storymap/iiif.ts +1742 -95
- package/src/storymap/to-iiif.ts +1008 -0
- package/src/storymap/validate.ts +80 -7
- package/src/types.ts +591 -29
- package/src/ui/LayersControl.ts +302 -0
- package/src/ui/MenuBar.ts +325 -61
- package/src/ui/Message.ts +44 -2
- package/src/ui/Swipable.ts +170 -85
- package/dist/assets/demo-D6cTmzn6.js +0 -1
- package/dist/assets/harness-BPFlzutp.js +0 -1
- package/dist/assets/index-DDsPBPZA.js +0 -1
- package/dist/assets/main-Y-MYUSJ4.js +0 -17
- package/src/animation/morpheus.d.ts +0 -41
- package/src/scss/VCO.StoryMap.Dark.scss +0 -138
- package/src/scss/Variables.Dark.scss +0 -56
- package/src/scss/media/types/VCO.Media.Instagram.scss +0 -7
- package/src/scss/media/types/VCO.Media.Profile.scss +0 -5
- package/src/ui/Draggable.ts +0 -374
package/src/media/Media.ts
CHANGED
|
@@ -1,16 +1,65 @@
|
|
|
1
|
-
import { mergeData } from "../core/Util";
|
|
1
|
+
import { mergeData, ratio, clearTimer } from "../core/Util";
|
|
2
2
|
import { Evented, type EventedInstance } from "../core/mixins";
|
|
3
3
|
import Dom from "../dom/Dom";
|
|
4
4
|
import Message from "../ui/Message";
|
|
5
5
|
import { Browser } from "../core/Browser";
|
|
6
6
|
import { MediaState, StorymapSlideMedia } from "../types";
|
|
7
|
-
import {
|
|
7
|
+
import { Language } from "../language/Language";
|
|
8
|
+
import { consentManagerOf, consentMessage, mediaService } from "../storymap/Consent";
|
|
9
|
+
import { validateWebURL, sanitizeSlideText } from "./EmbedUtil";
|
|
8
10
|
import { loadJS } from "../core/Load";
|
|
9
11
|
/* VCO.Media
|
|
10
12
|
Main media template for media assets.
|
|
11
13
|
Takes a data object and populates a dom object
|
|
12
14
|
================================================== */
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The icon glyph each media type shows in its error state.
|
|
18
|
+
*
|
|
19
|
+
* The icon font does not carry a glyph for every registered type, and the
|
|
20
|
+
* class used to be built straight from `media_type`, so dailymotion, audio,
|
|
21
|
+
* googledocs, iframe, documentcloud, juxtapose and website rendered an empty
|
|
22
|
+
* box next to the error text. The mapping is explicit rather than derived, so
|
|
23
|
+
* a type added later falls back to a real glyph instead of a blank one —
|
|
24
|
+
* `tests/embed-util-media.test.ts` checks the whole table against the font.
|
|
25
|
+
*/
|
|
26
|
+
const LOAD_ERROR_ICONS: Record<string, string> = {
|
|
27
|
+
youtube: "youtube",
|
|
28
|
+
vimeo: "vimeo",
|
|
29
|
+
dailymotion: "video",
|
|
30
|
+
soundcloud: "soundcloud",
|
|
31
|
+
twitter: "twitter",
|
|
32
|
+
flickr: "flickr",
|
|
33
|
+
image: "image",
|
|
34
|
+
video: "video",
|
|
35
|
+
audio: "music",
|
|
36
|
+
googledocs: "doc",
|
|
37
|
+
wikipedia: "wikipedia",
|
|
38
|
+
iframe: "web",
|
|
39
|
+
facebook: "facebook",
|
|
40
|
+
documentcloud: "doc",
|
|
41
|
+
juxtapose: "image",
|
|
42
|
+
blockquote: "blockquote",
|
|
43
|
+
website: "web",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const GENERIC_LOAD_ERROR_ICON = "web";
|
|
47
|
+
|
|
48
|
+
export function loadErrorIcon(media_type: unknown): string {
|
|
49
|
+
const type = typeof media_type === "string" ? media_type : "";
|
|
50
|
+
return LOAD_ERROR_ICONS[type] ?? GENERIC_LOAD_ERROR_ICON;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether media captions/credits render: `show_info: false` hides them, but
|
|
55
|
+
* only for slideshow tours (`slideshow_source`) — internal documents always
|
|
56
|
+
* show their captions. Pure for testability.
|
|
57
|
+
*/
|
|
58
|
+
export function shouldShowInfo(options: MediaOptions): boolean {
|
|
59
|
+
if (options.slideshow_source !== true) return true;
|
|
60
|
+
return options.show_info !== false;
|
|
61
|
+
}
|
|
62
|
+
|
|
14
63
|
/* Options for Media and its subclasses: the fields Media itself sets
|
|
15
64
|
or reads. Everything else merged in via mergeData is absorbed by the
|
|
16
65
|
index signature. */
|
|
@@ -29,29 +78,57 @@ export interface MediaOptions {
|
|
|
29
78
|
/* Data for media assets: the shared exchange format plus the link
|
|
30
79
|
fields the base layout renders. */
|
|
31
80
|
export interface MediaData extends StorymapSlideMedia {
|
|
32
|
-
uniqueid?: string;
|
|
33
|
-
link?: string;
|
|
34
|
-
link_target?: string;
|
|
81
|
+
uniqueid?: string | null;
|
|
82
|
+
link?: string | null;
|
|
83
|
+
link_target?: string | null;
|
|
35
84
|
[key: string]: unknown;
|
|
36
85
|
}
|
|
37
86
|
|
|
87
|
+
export interface MediaEvents {
|
|
88
|
+
loaded: MediaData;
|
|
89
|
+
media_loaded: MediaData;
|
|
90
|
+
media_ended: MediaData;
|
|
91
|
+
added: MediaData;
|
|
92
|
+
removed: MediaData;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The media element cache.
|
|
97
|
+
*
|
|
98
|
+
* `content_item`, `content_link`, `caption` and `credit` are only created
|
|
99
|
+
* once `_loadMedia()` / `showMeta()` have run, so they start out null rather
|
|
100
|
+
* than as an empty element — that is what the "did the load happen?" check is
|
|
101
|
+
* for, and pretending otherwise with `{} as HTMLElement` is what let the
|
|
102
|
+
* consent-denied path dereference `undefined.style`.
|
|
103
|
+
*/
|
|
104
|
+
type MediaElements = {
|
|
105
|
+
container: HTMLElement;
|
|
106
|
+
content_container: HTMLElement;
|
|
107
|
+
content: HTMLElement;
|
|
108
|
+
content_item: HTMLElement | null;
|
|
109
|
+
content_link: HTMLElement | null;
|
|
110
|
+
source_item: HTMLSourceElement | null;
|
|
111
|
+
caption: HTMLElement | null;
|
|
112
|
+
credit: HTMLElement | null;
|
|
113
|
+
parent: HTMLElement;
|
|
114
|
+
link: HTMLElement | null;
|
|
115
|
+
};
|
|
116
|
+
|
|
38
117
|
export class MediaBase {
|
|
39
|
-
declare "_el":
|
|
118
|
+
declare "_el": MediaElements;
|
|
40
119
|
declare "player": unknown;
|
|
41
|
-
declare "timer": ReturnType<typeof setTimeout
|
|
42
|
-
declare "load_timer": ReturnType<typeof setTimeout
|
|
120
|
+
declare "timer": ReturnType<typeof setTimeout> | null;
|
|
121
|
+
declare "load_timer": ReturnType<typeof setTimeout> | null;
|
|
43
122
|
declare "load_controller": AbortController | null;
|
|
44
|
-
declare "message": Message;
|
|
123
|
+
declare "message": Message | null;
|
|
45
124
|
declare "media_id": unknown;
|
|
46
125
|
declare "_state": MediaState;
|
|
126
|
+
declare "_disposed": boolean;
|
|
47
127
|
declare "data": MediaData;
|
|
48
128
|
declare "options": MediaOptions;
|
|
49
129
|
declare "animator": unknown;
|
|
50
130
|
declare "_media": unknown;
|
|
51
|
-
declare "
|
|
52
|
-
declare "fire": EventedInstance["fire"];
|
|
53
|
-
|
|
54
|
-
//_el: {},
|
|
131
|
+
declare "fire": EventedInstance<MediaEvents>["fire"];
|
|
55
132
|
|
|
56
133
|
/* Constructor
|
|
57
134
|
================================================== */
|
|
@@ -61,8 +138,9 @@ export class MediaBase {
|
|
|
61
138
|
container: {} as HTMLElement,
|
|
62
139
|
content_container: {} as HTMLElement,
|
|
63
140
|
content: {} as HTMLElement,
|
|
64
|
-
content_item:
|
|
65
|
-
content_link:
|
|
141
|
+
content_item: null,
|
|
142
|
+
content_link: null,
|
|
143
|
+
source_item: null,
|
|
66
144
|
caption: null,
|
|
67
145
|
credit: null,
|
|
68
146
|
parent: {} as HTMLElement,
|
|
@@ -89,6 +167,7 @@ export class MediaBase {
|
|
|
89
167
|
show_meta: false,
|
|
90
168
|
media_loaded: false,
|
|
91
169
|
};
|
|
170
|
+
this._disposed = false;
|
|
92
171
|
|
|
93
172
|
// Data
|
|
94
173
|
this.data = {
|
|
@@ -132,8 +211,14 @@ export class MediaBase {
|
|
|
132
211
|
const manager = consentManagerOf(this.options);
|
|
133
212
|
if (this.options.consent_required && manager && this.options.media_type) {
|
|
134
213
|
// GDPR consent mode: ask before loading anything from this
|
|
135
|
-
// external service
|
|
136
|
-
|
|
214
|
+
// external service. media_type is the stable identity (the
|
|
215
|
+
// MediaType slug) and media_name is the display name — using
|
|
216
|
+
// the slug as the label made this panel ask "Load content from
|
|
217
|
+
// youtube?" where the start-of-story dialog said "YouTube".
|
|
218
|
+
const service = mediaService(
|
|
219
|
+
this.options.media_type,
|
|
220
|
+
(this.options.media_name as string | undefined) ?? "",
|
|
221
|
+
);
|
|
137
222
|
let host = "";
|
|
138
223
|
try {
|
|
139
224
|
host = new URL((this.data as { url?: string })?.url ?? "").host;
|
|
@@ -163,8 +248,7 @@ export class MediaBase {
|
|
|
163
248
|
this._state.loaded = true;
|
|
164
249
|
this._updateDisplay();
|
|
165
250
|
} catch (e) {
|
|
166
|
-
console.
|
|
167
|
-
console.log(e);
|
|
251
|
+
console.warn("StoryMapJS: error loading media.", e);
|
|
168
252
|
this.loadErrorDisplay("Error loading media.");
|
|
169
253
|
}
|
|
170
254
|
}, 1200);
|
|
@@ -175,49 +259,65 @@ export class MediaBase {
|
|
|
175
259
|
const target = (this._el.content_container ?? this._el.container) as HTMLElement;
|
|
176
260
|
const blocked = document.createElement("div");
|
|
177
261
|
blocked.className = "vco-consent-blocked";
|
|
262
|
+
// the display name, not the slug — see loadMedia()
|
|
263
|
+
const service_name =
|
|
264
|
+
(this.options.media_name as string | undefined) ||
|
|
265
|
+
(this.options.media_type as string | undefined) ||
|
|
266
|
+
"";
|
|
178
267
|
blocked.textContent = (
|
|
179
268
|
consentMessage("consent_blocked", "Content from {service} is blocked.") as string
|
|
180
|
-
).replace("{service}",
|
|
269
|
+
).replace("{service}", service_name);
|
|
181
270
|
target.append(blocked);
|
|
182
271
|
this.onLoaded(true);
|
|
183
272
|
}
|
|
184
273
|
|
|
185
|
-
|
|
186
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Look up a UI string for the active language, falling back to the
|
|
276
|
+
* English default (which `setLanguage` already merges in) and finally to
|
|
277
|
+
* the key itself, so a missing translation degrades to something visible
|
|
278
|
+
* rather than "undefined".
|
|
279
|
+
*/
|
|
280
|
+
_(key: string): string {
|
|
281
|
+
return Language.messages[key] ?? key;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Show the loading overlay for this media item. */
|
|
285
|
+
loadingMessage(): void {
|
|
286
|
+
this.message?.updateMessage(this._("loading") + " " + this.options.media_name);
|
|
187
287
|
}
|
|
188
288
|
|
|
189
289
|
updateMediaDisplay(layout?: string) {
|
|
190
|
-
|
|
191
|
-
|
|
290
|
+
// `content_item` only exists once a media type's `_loadMedia()` has
|
|
291
|
+
// run. It stays null when a consent decision blocked the load, so the
|
|
292
|
+
// sizing below has to be skipped rather than dereference a
|
|
293
|
+
// placeholder — that was the crash on the denied path.
|
|
294
|
+
const content_item = this._el.content_item;
|
|
295
|
+
if (!this._state.loaded || !content_item) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
192
298
|
|
|
193
|
-
|
|
194
|
-
this._el.content_item.style.maxHeight = this.options.height / 2 + "px";
|
|
195
|
-
}
|
|
299
|
+
this._updateMediaDisplay(layout);
|
|
196
300
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
if (this._el.caption) {
|
|
202
|
-
this._el.caption.style.width = "auto";
|
|
203
|
-
}
|
|
204
|
-
}
|
|
301
|
+
if (!Browser.mobile && layout !== "portrait") {
|
|
302
|
+
content_item.style.maxHeight = Number(this.options.height ?? 0) / 2 + "px";
|
|
303
|
+
}
|
|
205
304
|
|
|
206
|
-
|
|
207
|
-
|
|
305
|
+
if (layout === "portrait") {
|
|
306
|
+
content_item.style.maxHeight = "none";
|
|
307
|
+
}
|
|
308
|
+
if (this._state.media_loaded) {
|
|
309
|
+
const width = content_item.offsetWidth + "px";
|
|
310
|
+
if (this._el.credit) {
|
|
311
|
+
this._el.credit.style.width = width;
|
|
208
312
|
}
|
|
209
|
-
if (this.
|
|
210
|
-
|
|
211
|
-
this._el.credit.style.width = this._el.content_item.offsetWidth + "px";
|
|
212
|
-
}
|
|
213
|
-
if (this._el.caption) {
|
|
214
|
-
this._el.caption.style.width = this._el.content_item.offsetWidth + "px";
|
|
215
|
-
}
|
|
313
|
+
if (this._el.caption) {
|
|
314
|
+
this._el.caption.style.width = width;
|
|
216
315
|
}
|
|
217
316
|
}
|
|
218
317
|
}
|
|
219
318
|
|
|
220
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Media Specific
|
|
221
321
|
================================================== */
|
|
222
322
|
_loadMedia() {}
|
|
223
323
|
|
|
@@ -225,6 +325,24 @@ export class MediaBase {
|
|
|
225
325
|
//this._el.content_item.style.maxHeight = (this.options.height - this.options.credit_height - this.options.caption_height - 16) + "px";
|
|
226
326
|
}
|
|
227
327
|
|
|
328
|
+
/** Size the media box to the `height` option, in pixels. */
|
|
329
|
+
_sizeContentItemToOptionHeight(): void {
|
|
330
|
+
if (this._el.content_item) {
|
|
331
|
+
this._el.content_item.style.height = Number(this.options.height ?? 0) + "px";
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Size the media box to a 16:9 box matching its current width — the
|
|
337
|
+
* aspect ratio every video provider embeds at.
|
|
338
|
+
*/
|
|
339
|
+
_sizeContentItemTo16x9(): void {
|
|
340
|
+
const item = this._el.content_item;
|
|
341
|
+
if (item) {
|
|
342
|
+
item.style.height = ratio.r16_9({ w: item.offsetWidth }) + "px";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
228
346
|
/* Public
|
|
229
347
|
================================================== */
|
|
230
348
|
show() {}
|
|
@@ -241,6 +359,45 @@ export class MediaBase {
|
|
|
241
359
|
this.onRemove();
|
|
242
360
|
}
|
|
243
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Terminal teardown: cancel pending work, stop playback, release the
|
|
364
|
+
* media-specific player and drop the DOM. Called by `Slide.dispose()`;
|
|
365
|
+
* the media must not be used afterwards.
|
|
366
|
+
*/
|
|
367
|
+
dispose() {
|
|
368
|
+
if (this._disposed) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
this._disposed = true;
|
|
372
|
+
|
|
373
|
+
// a load queued by loadMedia() that never started
|
|
374
|
+
if (this.load_timer) {
|
|
375
|
+
clearTimer(this.load_timer);
|
|
376
|
+
this.load_timer = null;
|
|
377
|
+
}
|
|
378
|
+
if (this.timer) {
|
|
379
|
+
clearTimer(this.timer);
|
|
380
|
+
this.timer = null;
|
|
381
|
+
}
|
|
382
|
+
// an external script still in flight (YouTube, Vimeo, Twitter, ...)
|
|
383
|
+
this.load_controller?.abort();
|
|
384
|
+
this.load_controller = null;
|
|
385
|
+
|
|
386
|
+
// let the concrete type tear down its player before the DOM goes away
|
|
387
|
+
this._disposeMedia();
|
|
388
|
+
|
|
389
|
+
this._el.container?.getAnimations?.().forEach((a) => a.cancel());
|
|
390
|
+
this.message?.dispose?.();
|
|
391
|
+
this.message = null;
|
|
392
|
+
this._el.container?.remove();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Media-specific teardown. Overridden by types that own a player, an
|
|
397
|
+
* embedded document or a listening widget.
|
|
398
|
+
*/
|
|
399
|
+
_disposeMedia() {}
|
|
400
|
+
|
|
244
401
|
// Update Display
|
|
245
402
|
updateDisplay(w?: number, h?: number, l?: string) {
|
|
246
403
|
this._updateDisplay(w, h, l);
|
|
@@ -251,7 +408,8 @@ export class MediaBase {
|
|
|
251
408
|
// load delay): avoids building iframes and injecting scripts for a
|
|
252
409
|
// slide the visitor already skipped past
|
|
253
410
|
if (!this._state.loaded && this.load_timer) {
|
|
254
|
-
|
|
411
|
+
clearTimer(this.load_timer);
|
|
412
|
+
this.load_timer = null;
|
|
255
413
|
this.load_timer = null;
|
|
256
414
|
}
|
|
257
415
|
// Abort an in-flight external script load, if any
|
|
@@ -288,8 +446,21 @@ export class MediaBase {
|
|
|
288
446
|
"vco-media-item vco-media-loaderror",
|
|
289
447
|
this._el.content,
|
|
290
448
|
);
|
|
291
|
-
|
|
292
|
-
|
|
449
|
+
// The icon font has no glyph for every media type (dailymotion, audio,
|
|
450
|
+
// googledocs, iframe, documentcloud, juxtapose and website had none),
|
|
451
|
+
// and the class was built by string concatenation, so those error
|
|
452
|
+
// states rendered an empty box. Map the missing ones to a glyph that
|
|
453
|
+
// exists, and fall back to a generic "web" icon for anything added
|
|
454
|
+
// later.
|
|
455
|
+
this._el.content_item.appendChild(
|
|
456
|
+
Dom.create("div", `vco-icon-${loadErrorIcon(this.options.media_type)}`),
|
|
457
|
+
);
|
|
458
|
+
const text = Dom.create("p", "");
|
|
459
|
+
text.appendChild(document.createTextNode(message));
|
|
460
|
+
this._el.content_item.appendChild(text);
|
|
461
|
+
// announced, not just shown: load failures otherwise pass silently
|
|
462
|
+
// for screen-reader visitors
|
|
463
|
+
this._el.content_item.setAttribute("role", "alert");
|
|
293
464
|
|
|
294
465
|
// After Loaded
|
|
295
466
|
this.onLoaded(true);
|
|
@@ -312,27 +483,38 @@ export class MediaBase {
|
|
|
312
483
|
onMediaLoaded(e?: unknown) {
|
|
313
484
|
this._state.media_loaded = true;
|
|
314
485
|
this.fire("media_loaded", this.data);
|
|
486
|
+
const width = (this._el.content_item?.offsetWidth ?? 0) + "px";
|
|
315
487
|
if (this._el.credit) {
|
|
316
|
-
this._el.credit.style.width =
|
|
488
|
+
this._el.credit.style.width = width;
|
|
317
489
|
}
|
|
318
490
|
if (this._el.caption) {
|
|
319
|
-
this._el.caption.style.width =
|
|
491
|
+
this._el.caption.style.width = width;
|
|
320
492
|
}
|
|
321
493
|
}
|
|
322
494
|
|
|
323
|
-
|
|
495
|
+
/** Render the media credit and caption, if the slide has them. */
|
|
496
|
+
showMeta(): void {
|
|
497
|
+
// slideshow tours may hide the info block (show_info: false);
|
|
498
|
+
// internal documents always render it
|
|
499
|
+
if (!shouldShowInfo(this.options)) return;
|
|
324
500
|
this._state.show_meta = true;
|
|
325
|
-
// Credit
|
|
326
|
-
|
|
501
|
+
// Credit and caption are author-supplied strings that may legitimately
|
|
502
|
+
// contain formatting (they are documented as HTML), so they go through
|
|
503
|
+
// the same sanitizer as the slide text rather than straight to
|
|
504
|
+
// innerHTML — the JSON is untrusted input, and these were the two
|
|
505
|
+
// easiest stored-XSS sinks in the viewer.
|
|
506
|
+
const credit = this._credit();
|
|
507
|
+
if (credit && credit !== "" && !this._el.credit) {
|
|
327
508
|
this._el.credit = Dom.create("div", "vco-credit", this._el.content_container);
|
|
328
|
-
this._el.credit.
|
|
509
|
+
this._el.credit.appendChild(sanitizeSlideText(credit));
|
|
329
510
|
this.options.credit_height = this._el.credit.offsetHeight;
|
|
330
511
|
}
|
|
331
512
|
|
|
332
513
|
// Caption
|
|
333
|
-
|
|
514
|
+
const caption = this._caption();
|
|
515
|
+
if (caption && caption !== "" && !this._el.caption) {
|
|
334
516
|
this._el.caption = Dom.create("div", "vco-caption", this._el.content_container);
|
|
335
|
-
this._el.caption.
|
|
517
|
+
this._el.caption.appendChild(sanitizeSlideText(caption));
|
|
336
518
|
this.options.caption_height = this._el.caption.offsetHeight;
|
|
337
519
|
}
|
|
338
520
|
}
|
|
@@ -347,6 +529,21 @@ export class MediaBase {
|
|
|
347
529
|
|
|
348
530
|
/* Private Methods
|
|
349
531
|
================================================== */
|
|
532
|
+
/** The media URL, normalised to a string. */
|
|
533
|
+
_url(): string {
|
|
534
|
+
return typeof this.data.url === "string" ? this.data.url : "";
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** The media caption, or null when the slide has none. */
|
|
538
|
+
_caption(): string | null {
|
|
539
|
+
return typeof this.data.caption === "string" ? this.data.caption : null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** The media credit, or null when the slide has none. */
|
|
543
|
+
_credit(): string | null {
|
|
544
|
+
return typeof this.data.credit === "string" ? this.data.credit : null;
|
|
545
|
+
}
|
|
546
|
+
|
|
350
547
|
_initLayout() {
|
|
351
548
|
// Message
|
|
352
549
|
this.message = new Message({}, this.options);
|
|
@@ -360,15 +557,29 @@ export class MediaBase {
|
|
|
360
557
|
);
|
|
361
558
|
|
|
362
559
|
// Link
|
|
363
|
-
|
|
560
|
+
// The link target comes from the storymap JSON, so it has to pass the
|
|
561
|
+
// same protocol check as every other author-supplied URL — otherwise a
|
|
562
|
+
// `javascript:` href is a click-through XSS. A rejected link falls
|
|
563
|
+
// through to the unlinked layout below rather than rendering a
|
|
564
|
+
// dead anchor.
|
|
565
|
+
// (`_el.content` starts as a truthy `{}` placeholder, so the branch is
|
|
566
|
+
// tracked with a local flag rather than a truthiness test.)
|
|
567
|
+
const raw_link = this.data.link;
|
|
568
|
+
const href = typeof raw_link === "string" ? validateWebURL(raw_link) : null;
|
|
569
|
+
if (href) {
|
|
364
570
|
this._el.link = Dom.create("a", "vco-media-link", this._el.content_container);
|
|
365
571
|
const link = this._el.link as HTMLAnchorElement;
|
|
366
|
-
link.href =
|
|
572
|
+
link.href = href;
|
|
367
573
|
if (this.data.link_target && this.data.link_target !== "") {
|
|
368
574
|
link.target = this.data.link_target;
|
|
369
575
|
} else {
|
|
370
576
|
link.target = "_blank";
|
|
371
577
|
}
|
|
578
|
+
if (link.target === "_blank") {
|
|
579
|
+
// rel=noopener so the opened page cannot reach back through
|
|
580
|
+
// window.opener
|
|
581
|
+
link.rel = "noopener noreferrer";
|
|
582
|
+
}
|
|
372
583
|
|
|
373
584
|
this._el.content = Dom.create("div", "vco-media-content", this._el.link);
|
|
374
585
|
} else {
|
|
@@ -402,7 +613,7 @@ export class MediaBase {
|
|
|
402
613
|
_stopMedia() {}
|
|
403
614
|
}
|
|
404
615
|
|
|
405
|
-
export class Media extends Evented(MediaBase) {
|
|
616
|
+
export class Media extends Evented<MediaEvents, typeof MediaBase>(MediaBase) {
|
|
406
617
|
constructor(...args: ConstructorParameters<typeof MediaBase>) {
|
|
407
618
|
super(...args);
|
|
408
619
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* embedId
|
|
2
|
+
Provider id extraction.
|
|
3
|
+
|
|
4
|
+
Each media type used to roll its own: six different ways of stripping the
|
|
5
|
+
query string, three different failure modes (throw an Error, throw a bare
|
|
6
|
+
string, or log a warning and carry on with `undefined`), and a fourth
|
|
7
|
+
divergence between the URL shapes the code accepted and the ones
|
|
8
|
+
`MediaType`'s routing regex actually matched.
|
|
9
|
+
|
|
10
|
+
These helpers are the single implementation. Each returns null when the
|
|
11
|
+
url does not carry that provider's id, and the caller decides how to
|
|
12
|
+
report it.
|
|
13
|
+
================================================== */
|
|
14
|
+
|
|
15
|
+
/** Strip the query string and fragment. */
|
|
16
|
+
function withoutQuery(url: string): string {
|
|
17
|
+
return url.split(/[?#]/)[0];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The first path segment after any of `markers`, with the query string
|
|
22
|
+
* removed. Returns null when none of the markers is present.
|
|
23
|
+
*/
|
|
24
|
+
export function segmentAfter(url: string, ...markers: RegExp[]): string | null {
|
|
25
|
+
for (const marker of markers) {
|
|
26
|
+
const match = url.match(marker);
|
|
27
|
+
if (match) {
|
|
28
|
+
const rest = url.slice((match.index ?? 0) + match[0].length);
|
|
29
|
+
const segment = withoutQuery(rest).split("/")[0];
|
|
30
|
+
if (segment) {
|
|
31
|
+
return segment;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A query parameter, via the platform parser rather than a hand-rolled split. */
|
|
39
|
+
export function queryParam(url: string, name: string): string | null {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(url).searchParams.get(name);
|
|
42
|
+
} catch {
|
|
43
|
+
// not an absolute URL; fall back to the raw query string
|
|
44
|
+
const query = url.split("?")[1];
|
|
45
|
+
if (!query) return null;
|
|
46
|
+
for (const pair of query.split("&")) {
|
|
47
|
+
const [key, value] = pair.split("=");
|
|
48
|
+
if (key === name) {
|
|
49
|
+
return value ?? "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** YouTube video id: watch, embed, short, shorts, live and youtu.be links. */
|
|
57
|
+
export function youtubeId(url: string): string | null {
|
|
58
|
+
const fromQuery = queryParam(url, "v");
|
|
59
|
+
if (fromQuery) {
|
|
60
|
+
return fromQuery;
|
|
61
|
+
}
|
|
62
|
+
return segmentAfter(
|
|
63
|
+
url,
|
|
64
|
+
/youtu\.be\//i,
|
|
65
|
+
/\/embed\//i,
|
|
66
|
+
/\/shorts\//i,
|
|
67
|
+
/\/live\//i,
|
|
68
|
+
/\/v\//i,
|
|
69
|
+
/[?&]v=/i,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Vimeo video id. Watch URLs are the bare `vimeo.com/<id>` form, so the
|
|
75
|
+
* explicit `/video/` path alone is not enough — the previous implementation
|
|
76
|
+
* split on `video\/|\/\/vimeo\.com\//` and relied on the second alternative.
|
|
77
|
+
* Non-numeric pages (channels, albums, showcases) are rejected.
|
|
78
|
+
*/
|
|
79
|
+
export function vimeoId(url: string): string | null {
|
|
80
|
+
const explicit = segmentAfter(url, /vimeo\.com\/video\//i, /player\.vimeo\.com\/video\//i);
|
|
81
|
+
if (explicit) {
|
|
82
|
+
return explicit;
|
|
83
|
+
}
|
|
84
|
+
return url.match(/vimeo\.com\/(\d+)(?:$|[/?#])/i)?.[1] ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Dailymotion video id from a watch or embed link. */
|
|
88
|
+
export function dailymotionId(url: string): string | null {
|
|
89
|
+
return segmentAfter(url, /dailymotion\.com\/video\//i, /dai\.ly\//i, /\/embed\/video\//i);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Flickr photo id from a photo page URL. */
|
|
93
|
+
export function flickrId(url: string): string | null {
|
|
94
|
+
const id = segmentAfter(url, /flickr\.com\/photos\/[^/]+\//i);
|
|
95
|
+
if (id && /^\d+$/.test(id)) {
|
|
96
|
+
return id;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Tweet id from a twitter.com or x.com status URL. */
|
|
102
|
+
export function tweetId(url: string): string | null {
|
|
103
|
+
return segmentAfter(url, /\/status(?:es)?\//i);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Tweet author (the profile segment before `/status/<id>`), or null. */
|
|
107
|
+
export function tweetAuthor(url: string): string | null {
|
|
108
|
+
const match = /(?:twitter\.com|x\.com)\/([^\s/?#]+)\/status(?:es)?\//i.exec(url);
|
|
109
|
+
return match ? match[1] : null;
|
|
110
|
+
}
|