edgepress 2026.10.1 → 2026.10.13
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/LICENSE +21 -0
- package/README.md +216 -16
- package/config.yml +99 -127
- package/content/assets/edgepress/brand/avatar.png +0 -0
- package/content/assets/edgepress/brand/deploy-esa.svg +1 -0
- package/content/assets/edgepress/brand/home-hero-768.webp +0 -0
- package/content/assets/edgepress/brand/home-hero.png +0 -0
- package/content/assets/edgepress/brand/home-hero.webp +0 -0
- package/content/assets/edgepress/brand/mascot.png +0 -0
- package/content/assets/edgepress/brand/mascot.webp +0 -0
- package/content/assets/edgepress/brand/og-image.png +0 -0
- package/content/assets/edgepress/brand/og-image.webp +0 -0
- package/content/assets/edgepress/brand/sources/edgepress-avatar-master.png +0 -0
- package/content/assets/edgepress/brand/sources/export-manifest.json +181 -0
- package/content/assets/edgepress/brand/sources/prompts.json +12 -0
- package/content/assets/edgepress/favicon/apple-touch-icon.png +0 -0
- package/content/assets/edgepress/favicon/favicon-16.png +0 -0
- package/content/assets/edgepress/favicon/favicon-32.png +0 -0
- package/content/assets/edgepress/favicon/favicon-48.png +0 -0
- package/content/assets/edgepress/favicon/favicon-64.png +0 -0
- package/content/assets/edgepress/favicon/favicon.ico +0 -0
- package/content/assets/edgepress/icons/LICENSE.txt +43 -0
- package/content/assets/edgepress/icons/arrow-right.svg +16 -0
- package/content/assets/edgepress/icons/arrow-up-right.svg +16 -0
- package/content/assets/edgepress/icons/book-open.svg +16 -0
- package/content/assets/edgepress/icons/check-circle.svg +16 -0
- package/content/assets/edgepress/icons/check.svg +15 -0
- package/content/assets/edgepress/icons/cloud.svg +15 -0
- package/content/assets/edgepress/icons/code.svg +16 -0
- package/content/assets/edgepress/icons/cpu.svg +28 -0
- package/content/assets/edgepress/icons/file-text.svg +19 -0
- package/content/assets/edgepress/icons/github.svg +17 -0
- package/content/assets/edgepress/icons/globe.svg +17 -0
- package/content/assets/edgepress/icons/help-circle.svg +17 -0
- package/content/assets/edgepress/icons/home.svg +16 -0
- package/content/assets/edgepress/icons/layers.svg +17 -0
- package/content/assets/edgepress/icons/layout-grid.svg +18 -0
- package/content/assets/edgepress/icons/mail.svg +16 -0
- package/content/assets/edgepress/icons/newspaper.svg +18 -0
- package/content/assets/edgepress/icons/palette.svg +19 -0
- package/content/assets/edgepress/icons/plug.svg +18 -0
- package/content/assets/edgepress/icons/provenance.json +8 -0
- package/content/assets/edgepress/icons/puzzle.svg +15 -0
- package/content/assets/edgepress/icons/rocket.svg +18 -0
- package/content/assets/edgepress/icons/rss.svg +17 -0
- package/content/assets/edgepress/icons/search.svg +16 -0
- package/content/assets/edgepress/icons/settings.svg +16 -0
- package/content/assets/edgepress/icons/shield-check.svg +16 -0
- package/content/assets/edgepress/icons/sparkles.svg +18 -0
- package/content/assets/edgepress/icons/terminal.svg +16 -0
- package/content/assets/edgepress/icons/x.svg +16 -0
- package/{static → content/assets}/edgepress-markdown-guide.svg +1 -1
- package/content/assets/images/previews/edgepress-en.webp +0 -0
- package/content/assets/images/previews/edgepress-revisions-en.png +0 -0
- package/content/assets/images/previews/edgepress-workflow-en.90f4650b21013786.gif +0 -0
- package/content/assets/images/previews/edgepress-workflow-zh.41f1e0a685f47267.gif +0 -0
- package/content/assets/images/previews/edgepress-zh.webp +0 -0
- package/content/assets/images/previews/provenance.json +49 -0
- package/content/guides/markdown-media.md +38 -0
- package/content/guides/platforms.md +15 -0
- package/content/guides/product-demos.md +37 -0
- package/content/guides/zh-cn.md +62 -0
- package/content/pages/deployment/en.json +74 -0
- package/content/pages/deployment/zh-cn.json +74 -0
- package/content/pages/home/en.json +248 -0
- package/content/pages/home/zh-cn.json +248 -0
- package/content/pages/local-tools/en.json +14 -0
- package/content/pages/local-tools/zh-cn.json +14 -0
- package/content/pages/page-authoring/en.json +107 -0
- package/content/pages/page-authoring/zh-cn.json +107 -0
- package/content/pages/plugin-development/en.json +202 -0
- package/content/pages/plugin-development/zh-cn.json +202 -0
- package/content/pages/privacy-policy/en.json +171 -0
- package/content/pages/privacy-policy/zh-cn.json +171 -0
- package/content/pages/project-introduction/en.json +158 -0
- package/content/pages/project-introduction/zh-cn.json +158 -0
- package/content/pages/quick-start/en.json +325 -0
- package/content/pages/quick-start/zh-cn.json +325 -0
- package/content/pages/theme-development/en.json +353 -0
- package/content/pages/theme-development/zh-cn.json +349 -0
- package/content/posts/2026-09-24-edgepress-release-2026-9-1/en.md +1 -1
- package/content/posts/2026-09-24-edgepress-release-2026-9-1/zh-cn.md +1 -1
- package/content/posts/2026-10-03-edgepress-release-2026-10-3/en.md +16 -0
- package/content/posts/2026-10-03-edgepress-release-2026-10-3/zh-cn.md +16 -0
- package/content/posts/2026-10-03-edgepress-release-202610-4/en.md +22 -0
- package/content/posts/2026-10-03-edgepress-release-202610-4/zh-cn.md +20 -0
- package/content/posts/2026-10-03-edgepress-release-202610-5/en.md +15 -0
- package/content/posts/2026-10-03-edgepress-release-202610-5/zh-cn.md +15 -0
- package/edgeone.json +43 -0
- package/esa.jsonc +9 -0
- package/languages/base/en.json +75 -10
- package/languages/packs/zh-CN.json +74 -9
- package/netlify.toml +6 -0
- package/package.json +37 -6
- package/plugins/consent/index.js +10 -17
- package/project-compatibility.json +142 -25
- package/schemas/pages.schema.json +74 -0
- package/src/assets.js +146 -18
- package/src/browser-audit.js +158 -0
- package/src/build.js +34 -9
- package/src/cli.js +46 -41
- package/src/config.js +114 -63
- package/src/consent-csp.js +137 -0
- package/src/content.js +55 -30
- package/src/data-saver.js +194 -0
- package/src/deploy.js +28 -0
- package/src/document.js +44 -0
- package/src/embed.js +60 -0
- package/src/generators.js +101 -59
- package/src/i18n.js +19 -3
- package/src/icons.js +5 -4
- package/src/image-dimensions.js +17 -0
- package/src/local-tool.js +29 -0
- package/src/maintenance.js +25 -8
- package/src/markdown-fixture.js +20 -0
- package/src/markdown.js +80 -16
- package/src/media-assets.js +19 -0
- package/src/media-viewer.js +47 -0
- package/src/oembed.js +53 -0
- package/src/page-blocks.js +124 -54
- package/src/page-check.js +29 -39
- package/src/plugin-api.js +7 -1
- package/src/post-details.js +60 -0
- package/src/post-template.js +5 -0
- package/src/publication-time.js +47 -0
- package/src/report-path.js +19 -0
- package/src/report-visuals.js +91 -18
- package/src/revision-cards.js +30 -0
- package/src/revision-images.js +44 -0
- package/src/revisions.js +77 -0
- package/src/rss.js +100 -0
- package/src/service-block.js +18 -0
- package/src/theme.js +59 -9
- package/src/video-metadata.js +36 -0
- package/static/edgepress/data-saver.css +12 -0
- package/static/edgepress/data-saver.js +333 -0
- package/static/edgepress/document-viewer.js +35 -0
- package/static/edgepress/external-links-policy.js +14 -0
- package/static/edgepress/external-links.css +13 -0
- package/static/edgepress/external-links.js +120 -0
- package/static/edgepress/fonts/faces.css +2217 -0
- package/static/edgepress/fonts/noto-sans-OFL.txt +93 -0
- package/static/edgepress/fonts/noto-sans-cyrillic-ext-wght-normal.82ff72b28e6610f2.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-cyrillic-wght-normal.6ab64433de6077ca.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-devanagari-wght-normal.ea6a530b75d93de7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-greek-ext-wght-normal.790b255c10c32e0b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-greek-wght-normal.7f655207c2f6c140.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-latin-ext-wght-normal.6d5a79315528df19.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-latin-wght-normal.51ca196f49a33e79.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-100-wght-normal.ad5f29028d5699a5.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-101-wght-normal.e189b81fc22ca6af.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-102-wght-normal.941c0c2971fc1d6d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-103-wght-normal.bafbd591af6493ff.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-104-wght-normal.b0bf80ced89dd61b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-105-wght-normal.8d8b4e6f4a00621d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-106-wght-normal.bae74acc629d2e74.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-107-wght-normal.a42eff5010de7b0d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-108-wght-normal.91882203459867be.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-109-wght-normal.a91f8b038e5ce108.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-110-wght-normal.bbca9081655c4523.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-111-wght-normal.372be010ac0e87cb.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-112-wght-normal.34a4580239eae8dc.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-113-wght-normal.614ab8f616352583.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-114-wght-normal.cf7b8344807a316e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-115-wght-normal.fb29957c7adccb9a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-116-wght-normal.a98dc5a726f82141.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-117-wght-normal.d94b5af0c14838af.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-118-wght-normal.76c9ddb85ae4578c.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-119-wght-normal.b3b2374881f4cd93.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-21-wght-normal.c1409c43e55ecff2.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-22-wght-normal.77037ed42ff4e7b0.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-23-wght-normal.38d0df21ac374e93.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-24-wght-normal.8fa21e7e457acf3e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-25-wght-normal.f760b7a3871d214d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-26-wght-normal.be8386e7bd7facfe.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-27-wght-normal.4895abcaddbd162e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-28-wght-normal.82540b6201a9b9ad.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-29-wght-normal.ff21d8fcc60ce391.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-30-wght-normal.1f76f6ffded00b9e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-31-wght-normal.e6c25be1bb08a63a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-32-wght-normal.0cda750d7d04a4e1.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-33-wght-normal.b9732b5606bd87c5.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-34-wght-normal.38aa326b4c84902a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-35-wght-normal.bd98226c8e0aa808.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-36-wght-normal.0db1ca695a491d37.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-37-wght-normal.8df409a4e0a237c2.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-38-wght-normal.672d2d994994a3c3.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-39-wght-normal.272d6d0e153f56e8.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-4-wght-normal.9fafd93ac77b81ff.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-40-wght-normal.5b9ef1ed48420aa7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-41-wght-normal.30aee47f660fd88a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-42-wght-normal.ec0014830db682b6.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-43-wght-normal.43df2b5dad9bb835.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-44-wght-normal.acaa6195041b1a92.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-45-wght-normal.8cdb9903f770db21.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-46-wght-normal.6590d0a6fe94571c.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-47-wght-normal.b05afdd32b3bf722.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-48-wght-normal.c9585f7d070700be.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-49-wght-normal.4943895a4dfa5dcf.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-5-wght-normal.e64f57937e3ae4fe.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-50-wght-normal.13e51063d7743cfe.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-51-wght-normal.fd4df1f02c2cb4d7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-52-wght-normal.06999a39c4c3a7ea.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-53-wght-normal.767b7b9864658d91.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-54-wght-normal.4cd00537a46f6f1a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-55-wght-normal.0118ec5d8c30b197.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-56-wght-normal.a3e4a78929826930.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-57-wght-normal.7623501009e4a6db.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-58-wght-normal.56ae1a59a32e4a25.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-59-wght-normal.7a377a40e99fd9bc.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-6-wght-normal.17e15c4f0a5bfd92.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-60-wght-normal.3657f66babdf6aff.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-61-wght-normal.23919a2eb11d25dd.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-62-wght-normal.a901edf25bc1cfae.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-63-wght-normal.92f56f713cc0b62f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-64-wght-normal.4929c9f39a5a4bd9.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-65-wght-normal.8daa61282fb62a11.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-66-wght-normal.6479166925eb462b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-67-wght-normal.a6b0e11c4c761ffd.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-68-wght-normal.65b77f1fe9d68dae.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-69-wght-normal.b34f765cbbf4d02e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-70-wght-normal.a99d55c00e4f95b7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-71-wght-normal.3298bed30dc8d233.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-72-wght-normal.6597209ca9707726.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-73-wght-normal.e7ac1d4930eabd78.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-74-wght-normal.1f0287be83a28e7a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-75-wght-normal.d9a4bb7bcc2be578.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-76-wght-normal.5a019946bcadcec6.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-77-wght-normal.7c445e85d04f5319.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-78-wght-normal.7d4afb70f641781c.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-79-wght-normal.5798753a721bb4ec.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-80-wght-normal.db887159ca6e46f3.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-81-wght-normal.71e5743bbd225b3d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-82-wght-normal.8c5094c3785fd04f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-83-wght-normal.4a60a1ccba905284.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-84-wght-normal.3ed54e59ef4758e3.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-85-wght-normal.e87e0a1342e1778c.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-86-wght-normal.6d3ac3e20959dbcb.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-87-wght-normal.0eeffd5daad5c17b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-88-wght-normal.b1d9582dfa268463.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-89-wght-normal.8b437aeb9e9fcb5a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-90-wght-normal.83b0a59e0a5e045a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-91-wght-normal.382f72b98d4bb9d7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-97-wght-normal.5e52856457c5cdab.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-98-wght-normal.ffded4e7fd560d93.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-99-wght-normal.54e302c23f11a8fd.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-OFL.txt +93 -0
- package/static/edgepress/fonts/noto-sans-sc-cyrillic-wght-normal.5932d441e7126669.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-latin-ext-wght-normal.e316201f3fc493d1.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-latin-wght-normal.c1d919c8701a86ed.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-sc-vietnamese-wght-normal.cba3d00c8db989f0.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-0-wght-normal.63c21eda5d20203f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-100-wght-normal.87a7c76301c24a28.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-101-wght-normal.c9e83fc4ffd53746.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-102-wght-normal.9a00ae0e5735852b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-103-wght-normal.7cfe93b3221f4def.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-104-wght-normal.2d5ac7497d84cb99.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-105-wght-normal.0989885bb16bcd9b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-106-wght-normal.1faa921b035064e7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-107-wght-normal.2f6006745ae16d69.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-108-wght-normal.a61e48c10a2cf4b9.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-109-wght-normal.f28c767724736d2d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-110-wght-normal.4b04bd88c3dc7c5d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-111-wght-normal.b6251e1967932a52.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-112-wght-normal.e264513b32f90660.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-113-wght-normal.46cb347d88f3338e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-114-wght-normal.9f9e1e0d0ab2aeb4.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-115-wght-normal.653dd2ab7d551b40.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-116-wght-normal.4718bf3627191297.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-117-wght-normal.b241a9409682af5f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-118-wght-normal.fda2800dc1c06852.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-119-wght-normal.948af36bd63a72c7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-19-wght-normal.62191cdc3a10e60a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-20-wght-normal.65c90892c2e01657.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-21-wght-normal.d3f59b094c334866.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-22-wght-normal.b1983a041d7e4e1f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-23-wght-normal.16e2163b66c7732a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-24-wght-normal.72c8561976116537.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-25-wght-normal.b2948d928ce4dd54.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-26-wght-normal.d583236b90abedc9.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-27-wght-normal.7d6073ba07597c9b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-28-wght-normal.517204d716b089cd.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-29-wght-normal.10189a8f8ca38099.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-30-wght-normal.695a71d72dfab3f7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-31-wght-normal.868d59473ef0e73c.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-32-wght-normal.92efa2d4b3702ec0.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-33-wght-normal.d2274bcd5636197d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-34-wght-normal.1ac8c3718ee2eed2.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-35-wght-normal.00b3df730d5a4c73.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-36-wght-normal.6963feb6c628b81f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-37-wght-normal.a1b4bd294af24cd1.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-38-wght-normal.8edb2f7f2955921b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-39-wght-normal.37bf7394eb3148c6.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-40-wght-normal.3efa655bc48f9133.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-41-wght-normal.48d47014544ebd5d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-42-wght-normal.214c733a59d423e7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-43-wght-normal.c2c9876f138bb20f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-44-wght-normal.28928bfea29db06a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-45-wght-normal.bf3743f8155fa9d2.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-46-wght-normal.06970903593781c3.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-47-wght-normal.8932ed83a91794d4.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-48-wght-normal.dfd9225628bcf234.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-49-wght-normal.c27d341ad39d44eb.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-50-wght-normal.01a0f4e7dfba0a56.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-51-wght-normal.5a30022af3d30e0f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-52-wght-normal.4884f032342a3274.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-53-wght-normal.537adcae44511270.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-54-wght-normal.6bc8c7d3e47c6fde.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-55-wght-normal.725a48711f1e1997.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-56-wght-normal.f43ecaa73e0cd8fc.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-57-wght-normal.15b24e9d42a3270f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-58-wght-normal.9b2d8a71667d3da6.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-59-wght-normal.a68535a6b3289d89.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-6-wght-normal.32cece1ae80c4f2b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-60-wght-normal.3b2c56a921d1cad4.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-61-wght-normal.54f51f80a0740878.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-62-wght-normal.ba029c5bb6c07bf1.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-63-wght-normal.380919beec4c7f3d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-64-wght-normal.f96fe0be479d201f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-65-wght-normal.7d544b80701fe678.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-66-wght-normal.fab669c07a1e3bee.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-67-wght-normal.0001bb744743fdfa.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-68-wght-normal.1a57eda03886b43c.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-69-wght-normal.017fecc749bcd6d5.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-7-wght-normal.cd0f0cac743dbbfe.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-70-wght-normal.79fb9d70b9534ec7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-71-wght-normal.04a6ea581b5f1195.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-72-wght-normal.d3a4eb35499157b7.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-73-wght-normal.d81f8484404ee465.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-74-wght-normal.4a5da9e0c2702f7d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-75-wght-normal.8b87f9c6c164513a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-76-wght-normal.a71463dbfd9fa7b5.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-77-wght-normal.d094414ea752cd52.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-78-wght-normal.04f7c97bd9cbc73b.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-79-wght-normal.1ed089d037a02049.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-8-wght-normal.d052677dfb5db5bd.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-80-wght-normal.6d41ca59cfd50442.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-81-wght-normal.f09bd0afa8a58114.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-82-wght-normal.7920841c526b1b02.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-83-wght-normal.0c6f7f82db8da0d8.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-84-wght-normal.f37c0a853b31265e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-85-wght-normal.4173bff087653b82.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-86-wght-normal.dac35605ebec15c4.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-87-wght-normal.8ac85fdd0b15a6bd.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-88-wght-normal.61625b0b21e8407f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-89-wght-normal.a1da3dccbf00698e.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-90-wght-normal.bf08f0355222a782.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-91-wght-normal.2ecf295bba2bfaa3.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-92-wght-normal.36508326bb68115f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-97-wght-normal.c712899e0fc9e947.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-98-wght-normal.13a648e521d18d7f.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-99-wght-normal.7f8c9a777e64159a.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-OFL.txt +93 -0
- package/static/edgepress/fonts/noto-sans-tc-cyrillic-wght-normal.dd0980ced8e46cb3.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-latin-ext-wght-normal.817d96335dc52ce6.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-latin-wght-normal.50cb5fb0a522cfaf.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-tc-vietnamese-wght-normal.2a77fb7b70fa304d.woff2 +0 -0
- package/static/edgepress/fonts/noto-sans-vietnamese-wght-normal.791e3f213c155a5b.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-OFL.txt +93 -0
- package/static/edgepress/fonts/noto-serif-cyrillic-ext-wght-normal.7c7afb20d33e30ec.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-cyrillic-wght-normal.54e3bd0c4a002b0f.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-greek-ext-wght-normal.9b3f4c4ff3adefd2.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-greek-wght-normal.1252037b6f1d6e86.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-latin-ext-wght-normal.2654f8d1261a420f.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-latin-wght-normal.46281456234014ce.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-math-wght-normal.d4a6aba2cb4636b3.woff2 +0 -0
- package/static/edgepress/fonts/noto-serif-vietnamese-wght-normal.fb6a5ed734c2fbe3.woff2 +0 -0
- package/static/edgepress/fonts/provenance.json +1356 -0
- package/static/edgepress/fonts.css +14 -0
- package/static/edgepress/image-viewer.js +23 -0
- package/static/edgepress/images.css +4 -0
- package/static/edgepress/link-directory.css +14 -0
- package/static/edgepress/local-time.js +12 -0
- package/static/edgepress/media-viewer.css +1 -0
- package/static/edgepress/media-viewer.js +111 -0
- package/static/edgepress/navigation-select.js +14 -0
- package/static/edgepress/oembed.js +129 -0
- package/static/edgepress/plugins/backend/external-api.js +4 -0
- package/static/edgepress/plugins/backend/external-widget.js +13 -0
- package/static/edgepress/plugins/consent/choices.js +47 -0
- package/static/edgepress/plugins/consent/manager.js +56 -74
- package/static/edgepress/project-demo.js +93 -0
- package/static/edgepress/search.js +2 -2
- package/static/edgepress/services-consent.js +3 -0
- package/static/edgepress/services.js +15 -0
- package/static/edgepress/webmcp.js +86 -0
- package/tests/assets-runtime.test.mjs +91 -0
- package/tests/audit.test.mjs +142 -0
- package/tests/brand-regression.test.mjs +110 -0
- package/tests/consent-browser.test.mjs +132 -0
- package/tests/consent-config.test.mjs +85 -0
- package/tests/consent-csp.test.mjs +90 -0
- package/tests/data-saver-loading.test.mjs +158 -0
- package/tests/data-saver-network.test.mjs +99 -0
- package/tests/data-saver.test.mjs +294 -0
- package/tests/document-package.test.mjs +28 -0
- package/tests/document-viewer.test.mjs +152 -0
- package/tests/edge-issues.test.mjs +65 -0
- package/tests/embed-syntax.test.mjs +68 -0
- package/tests/external-links.test.mjs +198 -0
- package/tests/fixtures/recording.mp4 +0 -0
- package/tests/fonts.test.mjs +99 -0
- package/tests/image-dimensions.test.mjs +15 -0
- package/tests/image-viewer.test.mjs +36 -0
- package/tests/json-pages.test.mjs +66 -0
- package/tests/link-directory.test.mjs +51 -0
- package/tests/local-tool.test.mjs +34 -0
- package/tests/markdown-media.test.mjs +119 -0
- package/tests/media-block.test.mjs +102 -0
- package/tests/navigation.test.mjs +87 -0
- package/tests/oembed-layout.test.mjs +98 -0
- package/tests/oembed.test.mjs +96 -0
- package/tests/page-authoring.test.mjs +27 -0
- package/tests/page-formatting.test.mjs +37 -0
- package/tests/post-list.test.mjs +96 -0
- package/tests/project-demo.test.mjs +73 -0
- package/tests/publication-time.test.mjs +43 -0
- package/tests/release-version.test.mjs +28 -0
- package/tests/revisions.test.mjs +93 -0
- package/tests/rss.test.mjs +54 -0
- package/tests/services.test.mjs +35 -0
- package/tests/theme-layout.test.mjs +95 -0
- package/tests/video-metadata.test.mjs +28 -0
- package/tests/webmcp.test.mjs +121 -0
- package/tests/writing.test.mjs +101 -0
- package/themes/atelier/assets/style.css +144 -22
- package/themes/atelier/layouts/partials/head.html +8 -1
- package/themes/atelier/layouts/partials/header.html +3 -1
- package/themes/default/assets/data-saver-theme.css +2 -0
- package/themes/default/assets/style.css +139 -23
- package/themes/default/layouts/partials/head.html +9 -2
- package/themes/default/layouts/partials/header.html +3 -1
- package/themes/default/theme.json +1 -0
- package/themes/folio/README.md +7 -0
- package/themes/folio/assets/data-saver-theme.css +3 -0
- package/themes/folio/assets/folio/navigation.js +10 -0
- package/themes/folio/assets/style.css +516 -0
- package/themes/folio/layouts/layout.html +15 -0
- package/themes/folio/layouts/partials/footer.html +6 -0
- package/themes/folio/layouts/partials/head.html +30 -0
- package/themes/folio/layouts/partials/header.html +8 -0
- package/themes/folio/theme.json +7 -0
- package/themes/signal/assets/style.css +143 -21
- package/themes/signal/layouts/partials/head.html +8 -1
- package/themes/signal/layouts/partials/header.html +3 -1
- package/tools/README.md +14 -0
- package/tools/assets/format-pages.mjs +56 -0
- package/tools/assets/generate-brand-assets.py +160 -0
- package/tools/assets/sync-fonts.mjs +71 -0
- package/tools/assets/sync-icons.mjs +17 -0
- package/tools/assets/video-preview.mjs +15 -0
- package/tools/recordings/capture-document-demo.mjs +147 -0
- package/tools/recordings/demo-workbench.mjs +57 -0
- package/tools/recordings/desktop-recording.mjs +92 -0
- package/tools/recordings/provision-vm.sh +18 -0
- package/tools/recordings/start-vm-desktop.sh +28 -0
- package/tools/recordings/vm-desktop.mjs +124 -0
- package/tools/verification/README.md +44 -0
- package/tools/verification/data-saver-site.mjs +405 -0
- package/tools/verification/page-performance.mjs +76 -0
- package/tools/verification/verify-brand-assets.py +52 -0
- package/tools/verification/verify-brand.mjs +180 -0
- package/vercel.json +36 -0
- package/wrangler.jsonc +5 -7
- package/content/pages/home/en.md +0 -99
- package/content/pages/home/zh-cn.md +0 -99
- package/content/pages/plugin-development/en.md +0 -109
- package/content/pages/plugin-development/zh-cn.md +0 -109
- package/content/pages/privacy-policy/en.md +0 -93
- package/content/pages/privacy-policy/zh-cn.md +0 -93
- package/content/pages/project-introduction/en.md +0 -88
- package/content/pages/project-introduction/zh-cn.md +0 -88
- package/content/pages/quick-start/en.md +0 -153
- package/content/pages/quick-start/zh-cn.md +0 -153
- package/content/pages/theme-development/en.md +0 -132
- package/content/pages/theme-development/zh-cn.md +0 -132
- package/src/worker.js +0 -139
- package/static/edgepress/brand/edgepress-mark.svg +0 -7
- package/static/edgepress/brand/mascot-209.webp +0 -0
- package/static/edgepress/brand/mascot-418.png +0 -0
- package/static/edgepress/brand/mascot-418.webp +0 -0
- package/static/edgepress/brand/mascot.png +0 -0
- package/static/edgepress/brand/og-image.png +0 -0
- package/static/edgepress/brand/og-image.svg +0 -46
- package/static/edgepress/icons.svg +0 -24
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@import url('/edgepress/fonts/faces.css');
|
|
2
|
+
|
|
3
|
+
/* Each face has a unicode-range: only characters present on a page load fonts. */
|
|
4
|
+
:root{
|
|
5
|
+
--edgepress-font-fallback:'Noto Sans SC Variable','Noto Sans TC Variable';
|
|
6
|
+
--edgepress-font-sans:'Noto Sans Variable',var(--edgepress-font-fallback),sans-serif;
|
|
7
|
+
--edgepress-font-serif:'Noto Serif Variable',var(--edgepress-font-fallback),serif;
|
|
8
|
+
--edgepress-font-mono:ui-monospace,SFMono-Regular,Consolas,var(--edgepress-font-fallback),'Noto Sans Variable',monospace;
|
|
9
|
+
}
|
|
10
|
+
:root:lang(zh-Hant),:root:lang(zh-TW),:root:lang(zh-HK),:root:lang(zh-MO){
|
|
11
|
+
--edgepress-font-fallback:'Noto Sans TC Variable','Noto Sans SC Variable';
|
|
12
|
+
}
|
|
13
|
+
/* The shared viewer's status font also needs local translated glyphs. */
|
|
14
|
+
:root .image-lightbox-status{font-family:var(--edgepress-font-sans)}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {mountImage} from './image-viewer-lib.js';
|
|
2
|
+
import {readChoice} from './plugins/consent/choices.js';
|
|
3
|
+
const root=document.querySelector('main'),mounted=new Map(),selector='.post-content img, .post-change-content img, .image-block img, .media-text-block img, [data-edgepress-oembed] img';
|
|
4
|
+
const labels=JSON.parse(document.getElementById('edgepress-image-viewer-config')?.textContent||'{}');
|
|
5
|
+
function permitted(image){
|
|
6
|
+
if(image.closest('[data-project-demo]'))return false;
|
|
7
|
+
if(!image.getAttribute('src')||image.closest('[hidden],template'))return false;
|
|
8
|
+
const host=image.closest('[data-edgepress-oembed],[data-edgepress-service]');if(!host)return true;
|
|
9
|
+
const privacy=JSON.parse(document.getElementById('edgepress-privacy-config')?.textContent||'{}'),id=host.dataset.edgepressOembed||host.dataset.edgepressService;
|
|
10
|
+
return !!readChoice(privacy)?.allowed.includes(id);
|
|
11
|
+
}
|
|
12
|
+
function refresh(){
|
|
13
|
+
for(const [image,dispose] of mounted)if(!root.contains(image)||!permitted(image)){dispose();mounted.delete(image);}
|
|
14
|
+
for(const image of root.querySelectorAll(selector)){
|
|
15
|
+
if(mounted.has(image)||!permitted(image))continue;const link=image.closest('a');
|
|
16
|
+
if(link&&link.href!==image.src&&!/\.(?:png|jpe?g|webp|avif|gif|svg)(?:[?#]|$)/i.test(link.href))continue;
|
|
17
|
+
mounted.set(image,mountImage(image,{labels,original:()=>image.dataset.original||link?.href||image.currentSrc||image.src}));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if(root){
|
|
21
|
+
const observer=new MutationObserver(refresh);observer.observe(root,{childList:true,subtree:true,attributes:true,attributeFilter:['src','hidden']});refresh();
|
|
22
|
+
window.addEventListener('pagehide',()=>{observer.disconnect();for(const dispose of mounted.values())dispose();mounted.clear();},{once:true});
|
|
23
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/* Responsive height:auto needs an explicit ratio for Edge's lazy-image audit.
|
|
2
|
+
Read the build's reserved dimensions without introducing inline styles.
|
|
3
|
+
Browsers without typed attr() retain the width/height attribute fallback. */
|
|
4
|
+
img[loading="lazy"][width][height]{aspect-ratio:attr(width type(<number>)) / attr(height type(<number>))}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.link-directory-section{min-width:0;margin-block:clamp(12px,2vw,24px)}
|
|
2
|
+
.link-directory-section>h2{margin:0 0 clamp(14px,2.5vw,24px);font-size:clamp(1.4rem,2.6vw,1.9rem);line-height:1.3}
|
|
3
|
+
.link-directory-items{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,17rem),1fr));gap:clamp(12px,2vw,20px);list-style:none;margin:0;padding:0}
|
|
4
|
+
.link-directory-entry{min-width:0;border:1px solid var(--line,#c8c8c8);border-radius:var(--radius,12px);background:var(--surface,Canvas)}
|
|
5
|
+
.link-directory-link{display:flex;flex-direction:column;gap:20px;min-width:0;height:100%;box-sizing:border-box;padding:clamp(20px,3vw,28px);color:var(--ink,CanvasText);text-decoration:none;border-radius:inherit;transition:background .16s ease}
|
|
6
|
+
.link-directory-link:hover{background:var(--surface-soft,rgba(127,127,127,.05));text-decoration:none}
|
|
7
|
+
.link-directory-link:focus-visible{outline:3px solid var(--accent,Highlight);outline-offset:3px}
|
|
8
|
+
.link-directory-main{display:flex;flex-direction:column;gap:9px;min-width:0}
|
|
9
|
+
.link-directory-name{color:var(--ink,CanvasText);font-size:clamp(1.05rem,2vw,1.22rem);font-weight:680;line-height:1.35;overflow-wrap:anywhere}
|
|
10
|
+
.link-directory-summary{color:var(--muted,#666);font-size:.95rem;line-height:1.65;overflow-wrap:anywhere}
|
|
11
|
+
.link-directory-destination{display:flex;justify-content:space-between;gap:10px;align-items:center;min-width:0;margin-top:auto;color:var(--muted,#666);font:.8rem/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere}
|
|
12
|
+
.link-directory-arrow{color:var(--accent,LinkText);font:600 1.15rem/1 sans-serif}
|
|
13
|
+
.link-directory-link:hover .link-directory-name,.link-directory-link:focus-visible .link-directory-name{color:var(--accent,LinkText)}
|
|
14
|
+
@media(prefers-reduced-motion:reduce){.link-directory-link{transition:none}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
for (const element of document.querySelectorAll('time[data-local-time]')) {
|
|
2
|
+
const value = new Date(element.dateTime);
|
|
3
|
+
if (!Number.isFinite(value.valueOf())) continue;
|
|
4
|
+
try {
|
|
5
|
+
const options = element.dataset.dateOnly === 'true'
|
|
6
|
+
? {dateStyle: 'long', timeZone: 'UTC'}
|
|
7
|
+
: {year:'numeric', month:'long', day:'numeric', hour:'2-digit', minute:'2-digit', timeZoneName:'short'};
|
|
8
|
+
const locale = element.dataset.timeLocale || document.documentElement.lang || undefined;
|
|
9
|
+
element.textContent = new Intl.DateTimeFormat(locale, options).format(value);
|
|
10
|
+
element.title = element.dataset.dateOnly === 'true' ? element.dateTime.slice(0,10) : value.toISOString();
|
|
11
|
+
} catch {}
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.media-viewer-block{margin:24px 0;min-width:0;color:var(--ink,inherit)}.media-viewer-block>figcaption{font-weight:700;margin-bottom:12px}.media-viewer-block .media-gallery{min-width:0}.media-viewer-block .gallery-stage{height:clamp(240px,68svh,660px);overflow:hidden;border-radius:12px;background:var(--panel,var(--paper,transparent));touch-action:pan-y}.media-viewer-block .gallery-stage:empty{height:auto;min-height:0}.media-viewer-block .gallery-stage figure{margin:0;height:100%}.media-viewer-block .gallery-stage img[data-media],.media-viewer-block .gallery-stage video{display:block;width:100%;height:100%;object-fit:contain}.media-viewer-block .gallery-navigation{display:flex;align-items:center;justify-content:center;gap:16px;padding:10px}.media-viewer-block .gallery-navigation[hidden]{display:none}.media-viewer-block .gallery-navigation button{appearance:none;min-width:44px;min-height:44px;border:1px solid var(--line,currentColor);background:var(--panel,transparent);color:inherit;border-radius:10px;cursor:pointer}.media-viewer-block .gallery-navigation button:disabled{opacity:.4;cursor:default}.media-viewer-block .gallery-navigation svg{width:20px;height:20px;vertical-align:middle}.media-viewer-block .gallery-count{min-width:4em;text-align:center;font-variant-numeric:tabular-nums}.media-viewer-block :focus-visible{outline:3px solid var(--accent,currentColor);outline-offset:3px}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { mountGallery } from './media-gallery-lib.js';
|
|
2
|
+
import { readChoice } from './plugins/consent/choices.js';
|
|
3
|
+
|
|
4
|
+
const settings = JSON.parse(document.getElementById('edgepress-privacy-config')?.textContent || '{}');
|
|
5
|
+
const mounted = new Map();
|
|
6
|
+
const inlineVideos = new Map();
|
|
7
|
+
let playerResources = null;
|
|
8
|
+
|
|
9
|
+
function loadPlayer() {
|
|
10
|
+
if (!playerResources) {
|
|
11
|
+
const link = document.createElement('link');
|
|
12
|
+
link.rel = 'stylesheet';
|
|
13
|
+
link.href = new URL('./media-player.css', import.meta.url).href;
|
|
14
|
+
const styles = new Promise((resolve, reject) => { link.onload = resolve; link.onerror = reject; });
|
|
15
|
+
document.head.append(link);
|
|
16
|
+
playerResources = Promise.all([import('./media-player-lib.js'), styles]).then(([library]) => library).catch(error => {
|
|
17
|
+
link.remove();
|
|
18
|
+
playerResources = null;
|
|
19
|
+
throw error;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return playerResources;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const viewing = new IntersectionObserver(entries => {
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const state = inlineVideos.get(entry.target);
|
|
28
|
+
if (!state) continue;
|
|
29
|
+
if (!entry.isIntersecting) { state.video.pause(); continue; }
|
|
30
|
+
if (state.started) continue;
|
|
31
|
+
state.started = true;
|
|
32
|
+
void loadPlayer().then(({ mountVideo }) => {
|
|
33
|
+
if (!inlineVideos.has(entry.target) || !entry.target.isConnected) return;
|
|
34
|
+
// Keep progressive native controls until the shared component is ready,
|
|
35
|
+
// then leave the actual file inert until the visitor presses Play.
|
|
36
|
+
const sources = [...state.video.querySelectorAll('source[src]')];
|
|
37
|
+
for (const source of sources) source.removeAttribute('src');
|
|
38
|
+
state.video.removeAttribute('src');
|
|
39
|
+
try {
|
|
40
|
+
state.dispose = mountVideo(state.video, { type: 'native', source: () => state.source });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (sources.length) sources[0].src = state.source;
|
|
43
|
+
else state.video.src = state.source;
|
|
44
|
+
state.video.controls = true;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}).catch(() => { state.started = false; });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function allowed(root) {
|
|
52
|
+
const id = root.dataset.mediaService;
|
|
53
|
+
if (!id) return true;
|
|
54
|
+
const service = settings.privacy?.integrations?.find(item => item.id === id);
|
|
55
|
+
return service?.provider === 'external-api' && service.enabled !== false && !!readChoice(settings)?.allowed.includes(id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function refresh() {
|
|
59
|
+
for (const [root, dispose] of mounted) {
|
|
60
|
+
if (!root.isConnected) { dispose(); mounted.delete(root); }
|
|
61
|
+
}
|
|
62
|
+
for (const [root, state] of inlineVideos) {
|
|
63
|
+
if (!root.isConnected) { viewing.unobserve(root); state.dispose?.(); inlineVideos.delete(root); }
|
|
64
|
+
}
|
|
65
|
+
for (const root of document.querySelectorAll('[data-edgepress-video]')) {
|
|
66
|
+
if (inlineVideos.has(root) || root.closest('template,[hidden],[data-edgepress-oembed],[data-edgepress-service]')) continue;
|
|
67
|
+
const video = root.matches('video') ? root : root.querySelector('video');
|
|
68
|
+
const source = video?.getAttribute('src') || video?.querySelector('source[src]')?.getAttribute('src');
|
|
69
|
+
if (!source || new URL(source, location.href).origin !== location.origin) continue;
|
|
70
|
+
inlineVideos.set(root, { video, source, started: false, dispose: null });
|
|
71
|
+
viewing.observe(root);
|
|
72
|
+
}
|
|
73
|
+
for (const root of document.querySelectorAll('[data-edgepress-media-viewer]')) {
|
|
74
|
+
if (root.closest('template,[data-edgepress-oembed],[data-edgepress-service]')) continue;
|
|
75
|
+
const gallery = root.querySelector('.media-gallery');
|
|
76
|
+
const stage = gallery.querySelector('.gallery-stage');
|
|
77
|
+
const navigation = gallery.querySelector('.gallery-navigation');
|
|
78
|
+
if (!allowed(root)) {
|
|
79
|
+
mounted.get(root)?.();
|
|
80
|
+
mounted.delete(root);
|
|
81
|
+
stage.replaceChildren();
|
|
82
|
+
navigation.hidden = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (mounted.has(root)) continue;
|
|
86
|
+
stage.replaceChildren(gallery.querySelector('template').content.cloneNode(true));
|
|
87
|
+
navigation.hidden = gallery.querySelectorAll('template').length < 2;
|
|
88
|
+
mounted.set(root, mountGallery(gallery, {
|
|
89
|
+
mountVideo: async (video, { isCurrent }) => {
|
|
90
|
+
const { mountVideo } = await loadPlayer();
|
|
91
|
+
if (!isCurrent() || !allowed(root)) return () => {};
|
|
92
|
+
return mountVideo(video);
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
refresh();
|
|
99
|
+
addEventListener('storage', refresh);
|
|
100
|
+
document.addEventListener('edgepress:service-ready', refresh);
|
|
101
|
+
document.addEventListener('edgepress:data-media', refresh);
|
|
102
|
+
document.addEventListener('visibilitychange', refresh);
|
|
103
|
+
document.addEventListener('visibilitychange', () => { if (document.hidden) for (const { video } of inlineVideos.values()) video.pause(); });
|
|
104
|
+
document.addEventListener('click', event => { if (event.target.closest('.privacy-panel button')) setTimeout(refresh, 0); });
|
|
105
|
+
addEventListener('pagehide', () => {
|
|
106
|
+
viewing.disconnect();
|
|
107
|
+
for (const dispose of mounted.values()) dispose();
|
|
108
|
+
for (const { dispose } of inlineVideos.values()) dispose?.();
|
|
109
|
+
mounted.clear();
|
|
110
|
+
inlineVideos.clear();
|
|
111
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Native selects retain keyboard interaction; noscript links provide the fallback.
|
|
2
|
+
for (const select of document.querySelectorAll('[data-navigation-select]')) {
|
|
3
|
+
select.value = '';
|
|
4
|
+
select.parentElement.hidden = false;
|
|
5
|
+
select.addEventListener('change', () => {
|
|
6
|
+
if (!select.value) return;
|
|
7
|
+
const target = new URL(select.value, location.href);
|
|
8
|
+
const newTab = select.selectedOptions[0]?.dataset.target === '_blank';
|
|
9
|
+
select.value = '';
|
|
10
|
+
if (['http:', 'https:'].includes(target.protocol) && !target.username && !target.password) {
|
|
11
|
+
if(newTab)window.open(target.href,'_blank','noopener,noreferrer');else location.assign(target.href);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {callService} from './services.js';
|
|
2
|
+
import {readChoice} from './plugins/consent/choices.js';
|
|
3
|
+
|
|
4
|
+
const scripts=new Map();
|
|
5
|
+
function reservePhotoDimensions(image,data){
|
|
6
|
+
const width=Number(data.width),height=Number(data.height);
|
|
7
|
+
if(Number.isFinite(width)&&Number.isFinite(height)&&width>0&&height>0&&width<=100000&&height<=100000){
|
|
8
|
+
image.width=Math.max(1,Math.round(width));image.height=Math.max(1,Math.round(height));
|
|
9
|
+
image.style.aspectRatio=String(image.width)+' / '+image.height;
|
|
10
|
+
}else{image.style.aspectRatio='4 / 3';image.style.objectFit='contain';}
|
|
11
|
+
return image;
|
|
12
|
+
}
|
|
13
|
+
function config(){return JSON.parse(document.getElementById('edgepress-privacy-config')?.textContent||'{}');}
|
|
14
|
+
function updateNotice(root,key){
|
|
15
|
+
const settings=config(),service=settings.privacy?.integrations?.find(item=>item.id===root.dataset.edgepressOembed);
|
|
16
|
+
if(!service)return;
|
|
17
|
+
if(!key)key=root.dataset.loaded?'embedLoaded':root.dataset.oembedUnavailable?'embedUnavailable':readChoice(settings)?.allowed.includes(service.id)?'embedReady':'embedNeedsConsent';
|
|
18
|
+
const name=typeof service.name==='string'?service.name:service.name?.[settings.siteLanguage]||service.name?.[settings.defaultLocale]||service.id;
|
|
19
|
+
const notice=root.querySelector('[data-oembed-notice]');if(notice){notice.hidden=key==='embedLoaded';notice.textContent=(settings.ui?.[key]||key).replace('{service}',name);}
|
|
20
|
+
}
|
|
21
|
+
export function trustedProviderEmbed(data,service,document) {
|
|
22
|
+
if(!data||String(data.version)!=='1.0'||!['rich','video','photo','link'].includes(data.type))throw new Error('Invalid oEmbed metadata');
|
|
23
|
+
const origins=service.embedOrigins || [new URL(service.backendUrl).origin];
|
|
24
|
+
const safe=value=>{const url=new URL(value);if(url.protocol!=='https:'||!origins.includes(url.origin)||url.username||url.password)throw new Error('Untrusted embed origin');return url.href;};
|
|
25
|
+
if(data.type==='photo'){const img=document.createElement('img');img.src=safe(data.url);img.alt=data.title||'';img.loading='lazy';img.style.cssText='width:100%;max-width:100%;height:auto';return reservePhotoDimensions(img,data);}
|
|
26
|
+
const container=document.createElement('div');container.className='oembed-content';
|
|
27
|
+
const template=document.createElement('template');template.innerHTML=data.html||'';
|
|
28
|
+
const tags=new Set(['IFRAME','BLOCKQUOTE','DIV','SECTION','A','P','SPAN','BR','STRONG','EM']);
|
|
29
|
+
function copy(source,parent){
|
|
30
|
+
if(source.nodeType===3){parent.append(document.createTextNode(source.textContent));return;}
|
|
31
|
+
if(source.nodeType!==1||!tags.has(source.tagName))return;
|
|
32
|
+
const node=document.createElement(source.tagName.toLowerCase());
|
|
33
|
+
if(source.tagName==='IFRAME'){
|
|
34
|
+
if(source.hasAttribute('class'))node.className=source.getAttribute('class');
|
|
35
|
+
node.src=safe(source.getAttribute('src'));node.title=data.title||'Media';node.loading='lazy';node.referrerPolicy='strict-origin-when-cross-origin';node.setAttribute('sandbox','allow-scripts allow-same-origin allow-presentation allow-popups');node.allow='fullscreen; picture-in-picture; encrypted-media';node.allowFullscreen=true;
|
|
36
|
+
const w=Math.max(200,Math.min(4096,Number(data.width)||640)),h=Math.max(60,Math.min(4096,Number(data.height)||480));node.style.cssText='display:block;width:100%;border:0;aspect-ratio:'+w+'/'+h+';height:auto';
|
|
37
|
+
}else{
|
|
38
|
+
if(source.tagName==='A'){
|
|
39
|
+
try{const url=new URL(source.getAttribute('href'));if(!['https:','http:'].includes(url.protocol)||url.username||url.password)throw Error();node.href=url.href;node.target='_blank';node.rel='noopener noreferrer';}catch{return;}
|
|
40
|
+
}
|
|
41
|
+
for(const name of ['class','lang','dir','cite','data-video-id','data-embed-from','data-dnt','data-theme'])if(source.hasAttribute(name))node.setAttribute(name,source.getAttribute(name));
|
|
42
|
+
for(const attribute of source.attributes)if(attribute.name.startsWith('data-'))node.setAttribute(attribute.name,attribute.value);
|
|
43
|
+
for(const child of source.childNodes)copy(child,node);
|
|
44
|
+
}
|
|
45
|
+
parent.append(node);
|
|
46
|
+
}
|
|
47
|
+
for(const child of template.content.childNodes)copy(child,container);
|
|
48
|
+
return container;
|
|
49
|
+
}
|
|
50
|
+
async function loadScripts(service,root) {
|
|
51
|
+
for(const address of service.embedScripts || []) {
|
|
52
|
+
const reusable=/^https:\/\/platform\.(?:x|twitter)\.com\/widgets\.js$/.test(address);
|
|
53
|
+
if(!scripts.has(address)||!reusable)scripts.set(address,new Promise((resolve,reject)=>{const script=document.createElement('script');script.src=address;script.async=true;script.referrerPolicy='no-referrer';script.onload=resolve;script.onerror=()=>{scripts.delete(address);script.remove();reject(new Error('Embed script unavailable'));};document.body.append(script);}));
|
|
54
|
+
await scripts.get(address);
|
|
55
|
+
}
|
|
56
|
+
globalThis.twttr?.widgets?.load(root);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function trustedEmbed(data,origin,document) {
|
|
60
|
+
if(!data||String(data.version)!=='1.0'||!['rich','video','photo'].includes(data.type))throw new Error('Invalid oEmbed metadata');
|
|
61
|
+
const safe=value=>{const url=new URL(value);if(url.protocol!=='https:'||url.origin!==origin||url.username||url.password)throw new Error('Untrusted embed origin');return url.href;};
|
|
62
|
+
if(data.type==='photo'){
|
|
63
|
+
const image=document.createElement('img');image.src=safe(data.url);image.alt=typeof data.title==='string'?data.title.slice(0,500):'';image.loading='lazy';image.style.cssText='display:block;width:100%;max-width:100%;height:auto;';return reservePhotoDimensions(image,data);
|
|
64
|
+
}
|
|
65
|
+
if(typeof data.html!=='string'||data.html.length>32768)throw new Error('Invalid embed HTML');
|
|
66
|
+
const template=document.createElement('template');template.innerHTML=data.html;
|
|
67
|
+
const nodes=template.content.children;if(nodes.length!==1||nodes[0].tagName!=='IFRAME'||nodes[0].children.length)throw new Error('Only an iframe embed is supported');
|
|
68
|
+
const frame=document.createElement('iframe');frame.src=safe(nodes[0].getAttribute('src'));frame.title=typeof data.title==='string'?data.title.slice(0,500):'Media';
|
|
69
|
+
frame.referrerPolicy='no-referrer';frame.loading='lazy';frame.setAttribute('sandbox','allow-scripts allow-same-origin allow-presentation');frame.allow='fullscreen';frame.allowFullscreen=true;
|
|
70
|
+
const width=Number(data.width),height=Number(data.height);if(!Number.isFinite(width)||!Number.isFinite(height)||width<1||height<1||width>4096||height>4096)throw new Error('Invalid embed dimensions');
|
|
71
|
+
frame.width=String(width);frame.height=String(height);frame.style.cssText='display:block;width:100%;max-width:100%;border:0;aspect-ratio:'+width+'/'+height+';height:auto;';return frame;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function show(root) {
|
|
75
|
+
if(root.dataset.loading||root.dataset.loaded)return;
|
|
76
|
+
const id=root.dataset.edgepressOembed,config=JSON.parse(document.getElementById('edgepress-privacy-config')?.textContent||'{}');
|
|
77
|
+
const integration=config.privacy?.integrations?.find(service=>service.id===id&&service.provider==='oembed');if(!integration)return;
|
|
78
|
+
delete root.dataset.oembedUnavailable;
|
|
79
|
+
root.dataset.requested='true';root.dataset.loading='true';root.setAttribute('aria-busy','true');root.classList.add('is-loading');
|
|
80
|
+
const button=root.querySelector('[data-oembed-load]');
|
|
81
|
+
const buttonHadFocus=document.activeElement===button;
|
|
82
|
+
if(button)button.disabled=true;
|
|
83
|
+
const complete=media=>{
|
|
84
|
+
const restoreFocus=buttonHadFocus&&(document.activeElement===button||document.activeElement===document.body);
|
|
85
|
+
const status=root.querySelector('[data-oembed-status]');
|
|
86
|
+
if(media.parentElement!==status)status.replaceChildren(media);
|
|
87
|
+
button?.remove();root.dataset.loaded='true';updateNotice(root,'embedLoaded');
|
|
88
|
+
if(restoreFocus)root.querySelector('figcaption a')?.focus({preventScroll:true});
|
|
89
|
+
};
|
|
90
|
+
const dimensions=data=>({...data,...(root.dataset.oembedWidth?{width:Number(root.dataset.oembedWidth)}:{}),...(root.dataset.oembedHeight?{height:Number(root.dataset.oembedHeight)}:{})});
|
|
91
|
+
try{
|
|
92
|
+
if(!readChoice(config)?.allowed.includes(id))throw new Error('Service requires visitor consent: '+id);
|
|
93
|
+
updateNotice(root,'embedLoading');
|
|
94
|
+
if(integration.oembedEndpoint||integration.embedTemplate){
|
|
95
|
+
const stored=root.querySelector('[data-oembed-data]');if(!stored)throw new Error('Embed unavailable');
|
|
96
|
+
const data=dimensions(JSON.parse(stored.content.textContent)),media=trustedProviderEmbed(data,integration,document);
|
|
97
|
+
root.querySelector('[data-oembed-status]').replaceChildren(media);
|
|
98
|
+
await loadScripts(integration,media);complete(media);return;
|
|
99
|
+
}
|
|
100
|
+
const response=await callService(id,'oembed',{headers:{'X-Service-Resource':encodeURIComponent(root.dataset.oembedUrl)}});
|
|
101
|
+
if(!response.ok||!/^application\/json(?:;|$)/i.test(response.headers.get('Content-Type')||''))throw new Error('Embed unavailable');
|
|
102
|
+
const data=dimensions(await response.json()),media=trustedEmbed(data,new URL(integration.backendUrl).origin,document);
|
|
103
|
+
complete(media);
|
|
104
|
+
}catch(error){
|
|
105
|
+
if(error.message.startsWith('Service requires visitor consent:'))document.dispatchEvent(new CustomEvent('edgepress:privacy-open'));
|
|
106
|
+
else{root.dataset.oembedUnavailable='true';root.dataset.requested='false';root.querySelector('[data-oembed-status]').replaceChildren();updateNotice(root,'embedUnavailable');}
|
|
107
|
+
}finally{delete root.dataset.loading;root.classList.remove('is-loading');root.setAttribute('aria-busy','false');if(button?.isConnected)button.disabled=false;}
|
|
108
|
+
}
|
|
109
|
+
if(typeof document!=='undefined'){
|
|
110
|
+
const roots=[...document.querySelectorAll('[data-edgepress-oembed]')],visible=new WeakSet();
|
|
111
|
+
for(const root of roots){const width=Number(root.dataset.oembedWidth)||640,height=Number(root.dataset.oembedHeight)||480;root.style.setProperty('--oembed-ratio',width+'/'+height);}
|
|
112
|
+
function frameTheme(frame){
|
|
113
|
+
const style=getComputedStyle(document.documentElement),colors=Object.fromEntries(['--accent','--ink','--muted','--line','--paper','--surface'].map(key=>[key,style.getPropertyValue(key).trim()]).filter(([,value])=>value));
|
|
114
|
+
frame.contentWindow?.postMessage({type:'edgepress:embed-theme',colors},new URL(frame.src).origin);
|
|
115
|
+
}
|
|
116
|
+
document.addEventListener('edgepress:theme-change',()=>{for(const root of roots)for(const frame of root.querySelectorAll('iframe[data-theme-ready]'))frameTheme(frame);});
|
|
117
|
+
new MutationObserver(()=>{for(const root of roots)for(const frame of root.querySelectorAll('iframe[data-theme-ready]'))frameTheme(frame);}).observe(document.documentElement,{attributes:true,attributeFilter:['data-theme','class','style']});
|
|
118
|
+
globalThis.matchMedia?.('(prefers-color-scheme:dark)').addEventListener('change',()=>{for(const root of roots)for(const frame of root.querySelectorAll('iframe[data-theme-ready]'))frameTheme(frame);});
|
|
119
|
+
globalThis.addEventListener('message',event=>{
|
|
120
|
+
const frame=roots.flatMap(root=>[...root.querySelectorAll('iframe')]).find(frame=>frame.contentWindow===event.source&&new URL(frame.src).origin===event.origin);if(!frame)return;
|
|
121
|
+
if(event.data?.type==='edgepress:embed-ready'){frame.dataset.themeReady='true';frameTheme(frame);}
|
|
122
|
+
if(event.data?.type==='edgepress:embed-size'&&Number.isFinite(event.data.height)&&event.data.height>=120&&event.data.height<=4096){frame.style.height=Math.ceil(event.data.height)+'px';frame.style.aspectRatio='auto';}
|
|
123
|
+
});
|
|
124
|
+
function loadAllowed(root){updateNotice(root);if(!root.dataset.oembedUnavailable&&readChoice(config())?.allowed.includes(root.dataset.edgepressOembed)&&(root.dataset.requested==='true'||document.documentElement.dataset.edgepressDataMode!=='text'&&visible.has(root)))void show(root);}
|
|
125
|
+
const observer=typeof IntersectionObserver==='function'?new IntersectionObserver(entries=>{for(const entry of entries)if(entry.isIntersecting){visible.add(entry.target);loadAllowed(entry.target);}else visible.delete(entry.target);},{rootMargin:'200px'}):null;
|
|
126
|
+
for(const root of roots){updateNotice(root);if(observer)observer.observe(root);else{visible.add(root);loadAllowed(root);}}
|
|
127
|
+
document.addEventListener('click',event=>{const button=event.target.closest('[data-oembed-load]');if(button)void show(button.closest('[data-edgepress-oembed]'));});
|
|
128
|
+
document.addEventListener('edgepress:service-ready',event=>{for(const root of roots)if(root.dataset.edgepressOembed===event.detail?.id)loadAllowed(root);});
|
|
129
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const mounted = new WeakSet();
|
|
2
|
+
export async function load(integration) {
|
|
3
|
+
const roots = [...document.querySelectorAll('[data-edgepress-service]')].filter(root => root.dataset.edgepressService === integration.id && !mounted.has(root));
|
|
4
|
+
if (!roots.length) return;
|
|
5
|
+
const address = new URL(integration.moduleUrl);
|
|
6
|
+
if (address.protocol !== 'https:' || address.username || address.password) throw new Error('Invalid service module');
|
|
7
|
+
const widget = await import(address.href);
|
|
8
|
+
if (typeof widget.mount !== 'function') throw new Error('Service module must export mount');
|
|
9
|
+
for (const root of roots) {
|
|
10
|
+
await widget.mount(root, {backendUrl: integration.backendUrl, thread: root.dataset.serviceThread, title: root.dataset.serviceTitle});
|
|
11
|
+
mounted.add(root);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const storageKey='edgepress-privacy-selection', cookieKey='edgepress_privacy_choice';
|
|
2
|
+
const stable=value=>Array.isArray(value)?value.map(stable):value && typeof value==='object'?Object.fromEntries(Object.keys(value).sort().map(key=>[key,stable(value[key])])):value;
|
|
3
|
+
export function choiceSnapshot(privacy, siteOrigin='') {
|
|
4
|
+
try {siteOrigin=new URL(siteOrigin).origin;}catch{}
|
|
5
|
+
const consent=privacy.consent || {};
|
|
6
|
+
const services=(privacy.integrations || []).map(service=>{
|
|
7
|
+
const presentation=new Set(['name','purpose','dataCategories','recipient','retention']);
|
|
8
|
+
const value=Object.fromEntries(Object.entries(service).filter(([key])=>!presentation.has(key)));
|
|
9
|
+
return value;
|
|
10
|
+
}).sort((a,b)=>String(a.id).localeCompare(String(b.id)));
|
|
11
|
+
return JSON.stringify(stable({controller:privacy.controller,policyUrl:privacy.policyUrl,consent,integrations:services}));
|
|
12
|
+
}
|
|
13
|
+
export function makeChoice(config, allowed) {
|
|
14
|
+
const consent=config.privacy?.consent || {};
|
|
15
|
+
return {proposedDate:consent.proposedDate || '',effectiveDate:consent.effectiveDate || '',fingerprint:config.choiceFingerprint || choiceSnapshot(config.privacy),
|
|
16
|
+
expiresAt:Date.now()+consent.expiresDays*86400000,updatedAt:Date.now(),allowed:[...new Set(allowed)]};
|
|
17
|
+
}
|
|
18
|
+
export function readChoice(config) {
|
|
19
|
+
const candidates=[];
|
|
20
|
+
for(const storage of ['localStorage','sessionStorage']) {
|
|
21
|
+
try {candidates.push(JSON.parse(globalThis[storage].getItem(storageKey)));}catch{}
|
|
22
|
+
}
|
|
23
|
+
try {const cookie=document.cookie.split(';').map(value=>value.trim()).find(value=>value.startsWith(cookieKey+'='));if(cookie)candidates.push(JSON.parse(decodeURIComponent(cookie.slice(cookieKey.length+1))));}catch{}
|
|
24
|
+
const consent=config.privacy?.consent || {}, validIds=new Set((config.privacy?.integrations || []).map(item=>item.id));
|
|
25
|
+
for(const saved of candidates.filter(Boolean).sort((a,b)=>(b.updatedAt || 0)-(a.updatedAt || 0))) {
|
|
26
|
+
if(saved.proposedDate!==(consent.proposedDate || '') || saved.effectiveDate!==(consent.effectiveDate || '') || !Number.isFinite(saved.expiresAt) || saved.expiresAt<=Date.now() || !Array.isArray(saved.allowed))continue;
|
|
27
|
+
let matches=saved.fingerprint===(config.choiceFingerprint || choiceSnapshot(config.privacy));
|
|
28
|
+
if(!matches && typeof saved.fingerprint==='string' && saved.fingerprint.startsWith('{')) {
|
|
29
|
+
try {matches=choiceSnapshot(JSON.parse(saved.fingerprint),config.siteOrigin)===config.choiceSnapshot;}catch{}
|
|
30
|
+
}
|
|
31
|
+
if(!matches)continue;
|
|
32
|
+
saved.allowed=saved.allowed.filter(id=>validIds.has(id));
|
|
33
|
+
if(saved.fingerprint!==config.choiceFingerprint){saved.fingerprint=config.choiceFingerprint;persistChoice(saved);}
|
|
34
|
+
return saved;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
export function persistChoice(choice) {
|
|
39
|
+
const value=JSON.stringify(choice);
|
|
40
|
+
let saved=false;
|
|
41
|
+
try {localStorage.setItem(storageKey,value);saved=localStorage.getItem(storageKey)===value;}catch{}
|
|
42
|
+
try {sessionStorage.setItem(storageKey,value);}catch{}
|
|
43
|
+
try {
|
|
44
|
+
// Use a small essential cookie only if persistent local storage is unavailable.
|
|
45
|
+
document.cookie=cookieKey+'='+(saved?'':encodeURIComponent(value))+'; Path=/; SameSite=Lax'+(location.protocol==='https:'?'; Secure':'')+'; Max-Age='+(saved?0:Math.max(0,Math.floor((choice.expiresAt-Date.now())/1000)));
|
|
46
|
+
}catch{}
|
|
47
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import {makeChoice,readChoice,persistChoice} from './choices.js';
|
|
1
2
|
const modules = {
|
|
3
|
+
'oembed': () => import('../backend/external-api.js'),
|
|
4
|
+
'external-widget': () => import('../backend/external-widget.js'),
|
|
5
|
+
'external-api': () => import('../backend/external-api.js'),
|
|
2
6
|
'google-tag-manager': () => import('../tracking/google-tag-manager.js'),
|
|
3
7
|
'meta-pixel': () => import('../tracking/meta-pixel.js'),
|
|
4
8
|
'cloudflare-web-analytics': () => import('../statistics/cloudflare-web-analytics.js'),
|
|
@@ -10,7 +14,8 @@ const modules = {
|
|
|
10
14
|
'hcaptcha': () => import('../captcha/hcaptcha.js')
|
|
11
15
|
};
|
|
12
16
|
|
|
13
|
-
const
|
|
17
|
+
const activeServices = new Map();
|
|
18
|
+
const requestedServices = new Set();
|
|
14
19
|
const configElement = document.getElementById('edgepress-privacy-config');
|
|
15
20
|
if (configElement) {
|
|
16
21
|
try { initialize(JSON.parse(configElement.textContent)); }
|
|
@@ -56,7 +61,6 @@ function initialize(config) {
|
|
|
56
61
|
const checkboxes = new Map();
|
|
57
62
|
|
|
58
63
|
for (const integration of integrations) {
|
|
59
|
-
const category = translate(ui, 'plugin' + capitalize(integration.category || 'statistics'));
|
|
60
64
|
const name = localized(integration.name, locale, defaultLocale) || integration.provider;
|
|
61
65
|
const purpose = localized(integration.purpose, locale, defaultLocale);
|
|
62
66
|
const dataCategories = localized(integration.dataCategories, locale, defaultLocale);
|
|
@@ -69,6 +73,7 @@ function initialize(config) {
|
|
|
69
73
|
toggle.className = 'privacy-service-toggle';
|
|
70
74
|
const checkbox = document.createElement('input');
|
|
71
75
|
checkbox.type = 'checkbox';
|
|
76
|
+
checkbox.name = 'edgepress-consent-' + integration.id;
|
|
72
77
|
checkbox.value = integration.id;
|
|
73
78
|
checkbox.setAttribute('role', 'switch');
|
|
74
79
|
checkbox.checked = false;
|
|
@@ -76,13 +81,10 @@ function initialize(config) {
|
|
|
76
81
|
toggleText.className = 'privacy-service-toggle-text';
|
|
77
82
|
const title = document.createElement('strong');
|
|
78
83
|
title.textContent = name;
|
|
79
|
-
const categoryLabel = document.createElement('span');
|
|
80
|
-
categoryLabel.className = 'privacy-category';
|
|
81
|
-
categoryLabel.textContent = category;
|
|
82
84
|
const purposeText = document.createElement('span');
|
|
83
85
|
purposeText.className = 'privacy-service-purpose';
|
|
84
86
|
purposeText.textContent = purpose;
|
|
85
|
-
toggleText.append(title,
|
|
87
|
+
toggleText.append(title, purposeText);
|
|
86
88
|
toggle.append(checkbox, toggleText);
|
|
87
89
|
const detail = document.createElement('details');
|
|
88
90
|
detail.className = 'privacy-details';
|
|
@@ -109,7 +111,7 @@ function initialize(config) {
|
|
|
109
111
|
const essential = document.createElement('p');
|
|
110
112
|
essential.className = 'privacy-essential';
|
|
111
113
|
essential.textContent = (ui.essentialStorage || 'Your choice is saved in this browser for up to {days} days.')
|
|
112
|
-
.replace('{days}', String(privacy.consent
|
|
114
|
+
.replace('{days}', String(privacy.consent.expiresDays));
|
|
113
115
|
|
|
114
116
|
const operator = privacy.controller || {};
|
|
115
117
|
const controller = document.createElement('p');
|
|
@@ -117,7 +119,7 @@ function initialize(config) {
|
|
|
117
119
|
if (operator.name || operator.contact) {
|
|
118
120
|
controller.append(document.createTextNode((ui.privacyController || 'Site operator') + ': '));
|
|
119
121
|
if (operator.name) controller.append(document.createTextNode(operator.name));
|
|
120
|
-
if (operator.name && operator.contact) controller.append(document.createTextNode('
|
|
122
|
+
if (operator.name && operator.contact) controller.append(document.createTextNode('; '));
|
|
121
123
|
if (operator.contact) {
|
|
122
124
|
controller.append(document.createTextNode((ui.privacyContact || 'Privacy contact') + ': '));
|
|
123
125
|
appendContact(controller, operator.contact);
|
|
@@ -146,13 +148,35 @@ function initialize(config) {
|
|
|
146
148
|
root.append(settingsButton, panel);
|
|
147
149
|
document.body.append(root);
|
|
148
150
|
|
|
151
|
+
document.addEventListener('edgepress:privacy-open', () => {
|
|
152
|
+
panel.hidden = false;
|
|
153
|
+
settingsButton.setAttribute('aria-expanded','true');
|
|
154
|
+
heading.focus();
|
|
155
|
+
});
|
|
149
156
|
const saved = readChoice(config);
|
|
157
|
+
document.addEventListener('click', event => {
|
|
158
|
+
const button = event.target.closest('[data-service-load]');
|
|
159
|
+
if (!button || !integrations.some(integration => integration.id === button.dataset.serviceLoad)) return;
|
|
160
|
+
requestedServices.add(button.dataset.serviceLoad);
|
|
161
|
+
const current = readChoice(config);
|
|
162
|
+
if (current?.allowed.includes(button.dataset.serviceLoad)) void activate(current.allowed);
|
|
163
|
+
else document.dispatchEvent(new CustomEvent('edgepress:privacy-open'));
|
|
164
|
+
});
|
|
150
165
|
for (const [id, checkbox] of checkboxes) checkbox.checked = Boolean(saved?.allowed.includes(id));
|
|
151
166
|
|
|
152
167
|
settingsButton.addEventListener('click', () => {
|
|
153
168
|
panel.hidden = !panel.hidden;
|
|
154
169
|
settingsButton.setAttribute('aria-expanded', String(!panel.hidden));
|
|
155
|
-
if (!panel.hidden)
|
|
170
|
+
if (!panel.hidden) {
|
|
171
|
+
document.dispatchEvent(new CustomEvent('edgepress:floating-panel-open', { detail: 'privacy' }));
|
|
172
|
+
heading.focus();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
document.addEventListener('edgepress:floating-panel-open', (event) => {
|
|
176
|
+
if (event.detail !== 'privacy') {
|
|
177
|
+
panel.hidden = true;
|
|
178
|
+
settingsButton.setAttribute('aria-expanded', 'false');
|
|
179
|
+
}
|
|
156
180
|
});
|
|
157
181
|
close.addEventListener('click', () => closePanel());
|
|
158
182
|
reject.addEventListener('click', () => saveChoice([]));
|
|
@@ -183,6 +207,7 @@ function initialize(config) {
|
|
|
183
207
|
|
|
184
208
|
if (saved) activate(saved.allowed);
|
|
185
209
|
else if (integrations.length) {
|
|
210
|
+
document.dispatchEvent(new CustomEvent('edgepress:floating-panel-open', { detail: 'privacy' }));
|
|
186
211
|
panel.hidden = false;
|
|
187
212
|
settingsButton.setAttribute('aria-expanded', 'true');
|
|
188
213
|
}
|
|
@@ -241,10 +266,6 @@ function localized(value, locale, defaultLocale) {
|
|
|
241
266
|
return value[locale] || value[defaultLocale] || Object.values(value)[0] || '';
|
|
242
267
|
}
|
|
243
268
|
|
|
244
|
-
function capitalize(value) {
|
|
245
|
-
return String(value).charAt(0).toUpperCase() + String(value).slice(1);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
269
|
function makeButton(label, className, iconName) {
|
|
249
270
|
const button = document.createElement('button');
|
|
250
271
|
button.type = 'button';
|
|
@@ -254,78 +275,39 @@ function makeButton(label, className, iconName) {
|
|
|
254
275
|
}
|
|
255
276
|
|
|
256
277
|
function makeIcon(name) {
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
};
|
|
269
|
-
path.setAttribute('d', paths[name] || paths.settings);
|
|
270
|
-
path.setAttribute('fill', 'none');
|
|
271
|
-
path.setAttribute('stroke', 'currentColor');
|
|
272
|
-
path.setAttribute('stroke-width', '2');
|
|
273
|
-
path.setAttribute('stroke-linecap', 'round');
|
|
274
|
-
path.setAttribute('stroke-linejoin', 'round');
|
|
275
|
-
svg.append(path);
|
|
276
|
-
return svg;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function makeChoice(config, allowed) {
|
|
280
|
-
const privacy = config.privacy || {};
|
|
281
|
-
const consent = privacy.consent || {};
|
|
282
|
-
const expiresAt = Date.now() + Math.max(1, Math.min(730, consent.expiresDays || 180)) * 86400000;
|
|
283
|
-
const fingerprint = config.choiceFingerprint || JSON.stringify({ controller: privacy.controller, policyUrl: privacy.policyUrl, consent, integrations: privacy.integrations });
|
|
284
|
-
return {
|
|
285
|
-
proposedDate: consent.proposedDate || '',
|
|
286
|
-
effectiveDate: consent.effectiveDate || '',
|
|
287
|
-
fingerprint,
|
|
288
|
-
expiresAt,
|
|
289
|
-
allowed: [...new Set(allowed)]
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function readChoice(config) {
|
|
294
|
-
try {
|
|
295
|
-
const saved = JSON.parse(localStorage.getItem(storageKey));
|
|
296
|
-
const privacy = config.privacy || {};
|
|
297
|
-
const consent = privacy.consent || {};
|
|
298
|
-
const fingerprint = config.choiceFingerprint || JSON.stringify({ controller: privacy.controller, policyUrl: privacy.policyUrl, consent, integrations: privacy.integrations });
|
|
299
|
-
const isLegacyFingerprint = Array.isArray(config.legacyChoiceFingerprints) && config.legacyChoiceFingerprints.includes(saved?.fingerprint);
|
|
300
|
-
if (!saved || saved.proposedDate !== (consent.proposedDate || '') || saved.effectiveDate !== (consent.effectiveDate || '') ||
|
|
301
|
-
(saved.fingerprint !== fingerprint && !isLegacyFingerprint) || saved.expiresAt <= Date.now() || !Array.isArray(saved.allowed)) {
|
|
302
|
-
localStorage.removeItem(storageKey);
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
if (isLegacyFingerprint && saved.fingerprint !== fingerprint) {
|
|
306
|
-
saved.fingerprint = fingerprint;
|
|
307
|
-
persistChoice(saved);
|
|
308
|
-
}
|
|
309
|
-
const validIds = new Set((privacy.integrations || []).map((item) => item.id));
|
|
310
|
-
saved.allowed = saved.allowed.filter((id) => validIds.has(id));
|
|
311
|
-
return saved;
|
|
312
|
-
} catch { return null; }
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function persistChoice(choice) {
|
|
316
|
-
try { localStorage.setItem(storageKey, JSON.stringify(choice)); } catch { /* Session choice still applies in memory. */ }
|
|
278
|
+
const icons = { settings: 'settings', accept: 'check', reject: 'x', close: 'x', save: 'arrow-right' };
|
|
279
|
+
if (!Object.hasOwn(icons, name)) throw new Error('Unsupported consent icon: ' + String(name));
|
|
280
|
+
const image = document.createElement('img');
|
|
281
|
+
image.className = 'icon-library';
|
|
282
|
+
image.src = '/edgepress/icons/' + icons[name] + '.svg';
|
|
283
|
+
image.width = 20;
|
|
284
|
+
image.height = 20;
|
|
285
|
+
image.alt = '';
|
|
286
|
+
image.setAttribute('aria-hidden', 'true');
|
|
287
|
+
image.decoding = 'async';
|
|
288
|
+
return image;
|
|
317
289
|
}
|
|
318
290
|
|
|
319
291
|
async function activate(allowed) {
|
|
292
|
+
const textMode = document.documentElement.dataset.edgepressDataMode === 'text';
|
|
320
293
|
const config = JSON.parse(document.getElementById('edgepress-privacy-config').textContent);
|
|
321
294
|
const permitted = new Set(allowed);
|
|
322
295
|
for (const integration of config.privacy.integrations || []) {
|
|
323
296
|
if (!permitted.has(integration.id)) continue;
|
|
297
|
+
if (textMode && !requestedServices.has(integration.id) && !['oembed', 'external-api'].includes(integration.provider)) continue;
|
|
324
298
|
const loader = modules[integration.provider];
|
|
325
299
|
if (!loader) continue;
|
|
326
300
|
try {
|
|
327
301
|
const provider = await loader();
|
|
328
|
-
|
|
302
|
+
if (!activeServices.has(integration.id)) {
|
|
303
|
+
const buttons = [...document.querySelectorAll('[data-service-load]')].filter(button => button.dataset.serviceLoad === integration.id);
|
|
304
|
+
buttons.forEach(button => { button.disabled = true; button.setAttribute('aria-busy', 'true'); });
|
|
305
|
+
const task = Promise.resolve().then(() => provider.load(integration));
|
|
306
|
+
activeServices.set(integration.id, task);
|
|
307
|
+
try { await task; buttons.forEach(button => { button.hidden = true; }); }
|
|
308
|
+
catch (error) {activeServices.delete(integration.id); throw error;}
|
|
309
|
+
finally { buttons.forEach(button => { button.disabled = false; button.removeAttribute('aria-busy'); }); }
|
|
310
|
+
}
|
|
329
311
|
} catch (error) {
|
|
330
312
|
console.error('EdgePress optional integration failed to load:', integration.provider, error);
|
|
331
313
|
}
|