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
package/src/assets.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { copyFile, readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { copyFile, readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
3
3
|
import { dirname, extname, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import * as posix from 'node:path/posix';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
5
6
|
import { mapLimit } from './concurrency.js';
|
|
7
|
+
import {extendConsentPolicy,serializeConsentHeaders} from './consent-csp.js';
|
|
8
|
+
import {imageViewerAssets, mediaViewerAssets} from './media-assets.js';
|
|
9
|
+
import {documentViewerAssets} from '@jsw-teams/document-viewer/assets';
|
|
10
|
+
import {collectImageDimensions} from './image-dimensions.js';
|
|
11
|
+
import {collectVideoMetadata} from './video-metadata.js';
|
|
12
|
+
import {dataSaverLayout} from './data-saver.js';
|
|
13
|
+
import { build, transform } from 'esbuild';
|
|
6
14
|
|
|
7
15
|
async function walk(directory) {
|
|
8
16
|
let entries;
|
|
@@ -19,15 +27,15 @@ async function walk(directory) {
|
|
|
19
27
|
|
|
20
28
|
function isCodeAsset(path) {
|
|
21
29
|
const extension = extname(path).toLowerCase();
|
|
22
|
-
return extension === '.css' || extension === '.js';
|
|
30
|
+
return extension === '.css' || extension === '.js' || extension === '.mjs';
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
function referencedPaths(path, content, known) {
|
|
26
34
|
if (!isCodeAsset(path)) return [];
|
|
27
35
|
const text = content.toString('utf8');
|
|
28
|
-
const expression = extname(path).toLowerCase()
|
|
29
|
-
? /\b(?:from\s*|import\s*(?:\(\s*)
|
|
30
|
-
: /(?:@import\s
|
|
36
|
+
const expression = ['.js', '.mjs'].includes(extname(path).toLowerCase())
|
|
37
|
+
? /\b(?:from\s*|import\s*(?:\(\s*)?|new\s+URL\s*\(\s*)['"]([^'"]+)['"]/g
|
|
38
|
+
: /(?:@import\s*(?:url\()?\s*|url\(\s*)['"]?([^'")\s;]+)['"]?\s*\)?/gi;
|
|
31
39
|
const refs = [];
|
|
32
40
|
for (const match of text.matchAll(expression)) {
|
|
33
41
|
const specifier = match[1];
|
|
@@ -62,22 +70,22 @@ function rewriteCodeReferences(item, text, outputByOriginal) {
|
|
|
62
70
|
return rewritten + tail;
|
|
63
71
|
};
|
|
64
72
|
|
|
65
|
-
if (extension === '.js') {
|
|
66
|
-
const expression = /(\b(?:from\s*|import\s*(?:\(\s*)
|
|
73
|
+
if (extension === '.js' || extension === '.mjs') {
|
|
74
|
+
const expression = /(\b(?:from\s*|import\s*(?:\(\s*)?|new\s+URL\s*\(\s*))(['"])([^'"]+)\2/g;
|
|
67
75
|
return text.replace(expression, (match, prefix, quote, specifier) => {
|
|
68
76
|
const rewritten = rewriteSpecifier(specifier);
|
|
69
77
|
return rewritten === null ? match : prefix + quote + rewritten + quote;
|
|
70
78
|
});
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
const expression = /((?:@import\s
|
|
81
|
+
const expression = /((?:@import\s*(?:url\()?\s*|url\(\s*))(['"]?)([^)'"\s;]+)(['"]?\s*\)?)/gi;
|
|
74
82
|
return text.replace(expression, (match, prefix, quote, specifier, suffix) => {
|
|
75
83
|
const rewritten = rewriteSpecifier(specifier);
|
|
76
84
|
return rewritten === null ? match : prefix + quote + rewritten + suffix;
|
|
77
85
|
});
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
export async function collectAssets(config) {
|
|
88
|
+
export async function collectAssets(config, { documentViewer = false, mediaViewer = false } = {}) {
|
|
81
89
|
const sources = [];
|
|
82
90
|
const directories = [
|
|
83
91
|
{ root: config.resolvedPaths.static, name: 'static' },
|
|
@@ -91,6 +99,24 @@ export async function collectAssets(config) {
|
|
|
91
99
|
sources.push({ source, path, sourceName: name });
|
|
92
100
|
}
|
|
93
101
|
}
|
|
102
|
+
// Library consumers do not need to copy framework runtime source into their
|
|
103
|
+
// repositories. Project-owned overrides keep precedence for existing sites.
|
|
104
|
+
const runtimeRoot=fileURLToPath(new URL('../static/edgepress/',import.meta.url));
|
|
105
|
+
const supplied=new Set(sources.map(item=>item.path));
|
|
106
|
+
if (documentViewer) for (const item of await documentViewerAssets('edgepress/document-viewer')) if (!supplied.has(item.path)) sources.push(item);
|
|
107
|
+
for(const item of await imageViewerAssets())if(!supplied.has(item.path))sources.push(item);
|
|
108
|
+
if (mediaViewer) for (const item of await mediaViewerAssets()) if (!supplied.has(item.path)) sources.push(item);
|
|
109
|
+
for(const source of await walk(runtimeRoot)) {
|
|
110
|
+
const path='edgepress/'+relative(runtimeRoot,source).split(sep).join('/');
|
|
111
|
+
if(!supplied.has(path))sources.push({source,path,sourceName:'EdgePress runtime'});
|
|
112
|
+
}
|
|
113
|
+
// Built-in navigation and page icons live with the package's content assets,
|
|
114
|
+
// not its browser scripts. Include only this library, not example-site media.
|
|
115
|
+
const iconsRoot=fileURLToPath(new URL('../content/assets/edgepress/icons/',import.meta.url));
|
|
116
|
+
for(const source of await walk(iconsRoot)) {
|
|
117
|
+
const path='edgepress/icons/'+relative(iconsRoot,source).split(sep).join('/');
|
|
118
|
+
if(!supplied.has(path))sources.push({source,path,sourceName:'EdgePress icons'});
|
|
119
|
+
}
|
|
94
120
|
|
|
95
121
|
const headers = [];
|
|
96
122
|
const assetSources = [];
|
|
@@ -100,11 +126,71 @@ export async function collectAssets(config) {
|
|
|
100
126
|
}
|
|
101
127
|
const [headerContents, items] = await Promise.all([
|
|
102
128
|
mapLimit(headers, config.concurrency, async (item) => (await readFile(item.source)).toString('utf8')),
|
|
103
|
-
mapLimit(assetSources, config.concurrency, async (item) =>
|
|
104
|
-
...item,
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
mapLimit(assetSources, config.concurrency, async (item) => {
|
|
130
|
+
if (!isCodeAsset(item.path)) return { ...item, content: null };
|
|
131
|
+
const source = item.content ?? await readFile(item.source);
|
|
132
|
+
const result = await transform(source.toString('utf8'), {
|
|
133
|
+
loader: item.path.endsWith('.css') ? 'css' : 'js',
|
|
134
|
+
minifyWhitespace: true,
|
|
135
|
+
minifySyntax: true,
|
|
136
|
+
minifyIdentifiers: item.path === 'edgepress/data-saver.js',
|
|
137
|
+
legalComments: 'inline',
|
|
138
|
+
charset: 'utf8',
|
|
139
|
+
sourcefile: item.path
|
|
140
|
+
});
|
|
141
|
+
return { ...item, content: Buffer.from(result.code) };
|
|
142
|
+
})
|
|
107
143
|
]);
|
|
144
|
+
const coreEntries = ['edgepress/plugins/consent/manager.js', 'edgepress/external-links.js'];
|
|
145
|
+
const sourceByPath = new Map(items.map(item => [item.path, item]));
|
|
146
|
+
const bundleControl = async entry => {
|
|
147
|
+
const item = sourceByPath.get(entry);
|
|
148
|
+
if (!item) return;
|
|
149
|
+
const output = await build({
|
|
150
|
+
entryPoints: [entry],
|
|
151
|
+
bundle: true,
|
|
152
|
+
write: false,
|
|
153
|
+
format: 'esm',
|
|
154
|
+
minifyWhitespace: true,
|
|
155
|
+
minifySyntax: true,
|
|
156
|
+
minifyIdentifiers: false,
|
|
157
|
+
legalComments: 'inline',
|
|
158
|
+
charset: 'utf8',
|
|
159
|
+
plugins: [{
|
|
160
|
+
name: 'local-core-controls',
|
|
161
|
+
setup(builder) {
|
|
162
|
+
builder.onResolve({filter:/.*/}, args => {
|
|
163
|
+
const target = args.kind === 'entry-point' ? args.path : args.path.startsWith('/') ? args.path.slice(1) : posix.normalize(posix.join(posix.dirname(args.importer),args.path));
|
|
164
|
+
if (args.kind === 'dynamic-import' || !sourceByPath.has(target)) {
|
|
165
|
+
const path = sourceByPath.has(target) ? '/' + target : args.path;
|
|
166
|
+
return {path,external:true};
|
|
167
|
+
}
|
|
168
|
+
return {path:target,namespace:'local-core-controls'};
|
|
169
|
+
});
|
|
170
|
+
builder.onLoad({filter:/.*/,namespace:'local-core-controls'}, args => ({contents:sourceByPath.get(args.path).content.toString('utf8'),loader:'js'}));
|
|
171
|
+
}
|
|
172
|
+
}]
|
|
173
|
+
});
|
|
174
|
+
item.content = Buffer.from(output.outputFiles[0].contents);
|
|
175
|
+
};
|
|
176
|
+
for (const entry of coreEntries) await bundleControl(entry);
|
|
177
|
+
if (config.site.dataSaver?.enabled) {
|
|
178
|
+
const candidates = [...coreEntries, 'edgepress/language-select.js', 'edgepress/local-time.js', 'edgepress/navigation-select.js', 'edgepress/services-consent.js'];
|
|
179
|
+
const scripts = candidates.filter(path => {
|
|
180
|
+
const source = sourceByPath.get(path)?.source;
|
|
181
|
+
return source && relative(runtimeRoot,source).split(sep).join('/') === path.slice('edgepress/'.length);
|
|
182
|
+
});
|
|
183
|
+
if (scripts.length) {
|
|
184
|
+
const entry = 'edgepress/data-saver-controls.js';
|
|
185
|
+
const item = {path:entry,content:Buffer.from(scripts.map(path => 'import "/' + path + '";').join('\n')),sourceName:'Data-saving core controls'};
|
|
186
|
+
sourceByPath.set(entry,item);
|
|
187
|
+
items.push(item);
|
|
188
|
+
await bundleControl(entry);
|
|
189
|
+
config.dataSaverCore = {path:'/' + entry,scripts:scripts.map(path => '/' + path)};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const layout = await dataSaverLayout(config, items);
|
|
193
|
+
if (layout) items.push(layout);
|
|
108
194
|
const originals = new Map();
|
|
109
195
|
for (const item of items) {
|
|
110
196
|
const normalized = item.path.toLowerCase();
|
|
@@ -156,7 +242,7 @@ export async function collectAssets(config) {
|
|
|
156
242
|
const urlMap = Object.create(null);
|
|
157
243
|
for (const item of items) urlMap['/' + item.path] = '/' + outputByOriginal.get(item.path);
|
|
158
244
|
config.assetManifest = urlMap;
|
|
159
|
-
return { assets, urlMap, existingHeaders: headerContents.join('\n\n') };
|
|
245
|
+
return { assets, urlMap, imageDimensions:await collectImageDimensions(assets), videoMetadata:await collectVideoMetadata(assets), existingHeaders: extendConsentPolicy(headerContents.join('\n\n'),config) };
|
|
160
246
|
}
|
|
161
247
|
|
|
162
248
|
export async function writeAssets(bundle, output, concurrency = 8) {
|
|
@@ -166,12 +252,54 @@ export async function writeAssets(bundle, output, concurrency = 8) {
|
|
|
166
252
|
if (asset.source) await copyFile(asset.source, target);
|
|
167
253
|
else await writeFile(target, asset.content);
|
|
168
254
|
});
|
|
255
|
+
// Cloudflare already revalidates unversioned assets by default. A broad cache
|
|
256
|
+
// rule would be concatenated with exact immutable rules instead of overridden.
|
|
169
257
|
const securityRules = '/*\n X-Content-Type-Options: nosniff\n Referrer-Policy: strict-origin-when-cross-origin';
|
|
258
|
+
// Unicode fonts contain hundreds of subsets. Static hosts accept 100 header
|
|
259
|
+
// rules, so group a directory only when every WOFF2 file in it is hashed.
|
|
260
|
+
const fontsByDirectory = new Map();
|
|
261
|
+
for (const asset of bundle.assets.filter(asset => asset.path.endsWith('.woff2'))) {
|
|
262
|
+
const directory = posix.dirname(asset.path);
|
|
263
|
+
if (!fontsByDirectory.has(directory)) fontsByDirectory.set(directory, []);
|
|
264
|
+
fontsByDirectory.get(directory).push(asset.path);
|
|
265
|
+
}
|
|
266
|
+
const fontDirectories = new Set([...fontsByDirectory].filter(([, paths]) =>
|
|
267
|
+
paths.length > 1 && paths.every(path => /\.[a-f0-9]{16}\.woff2$/.test(path))
|
|
268
|
+
).map(([directory]) => directory));
|
|
170
269
|
const immutableRules = bundle.assets
|
|
171
|
-
.filter((asset) => /\.[a-f0-9]{16}\.(?:css|js)$/i.test(asset.path))
|
|
172
|
-
.
|
|
173
|
-
|
|
174
|
-
|
|
270
|
+
.filter((asset) => /\.[a-f0-9]{16}\.(?:css|m?js|json|png|jpe?g|webp|avif|svg|gif|mp4|webm|woff2?)$/i.test(asset.path))
|
|
271
|
+
.filter(asset => !asset.path.endsWith('.woff2') || !fontDirectories.has(posix.dirname(asset.path)))
|
|
272
|
+
.map((asset) => '/' + asset.path + '\n Cache-Control: public, max-age=31536000, immutable');
|
|
273
|
+
for (const directory of fontDirectories) immutableRules.push('/' + (directory === '.' ? '' : directory + '/') + '*.woff2\n Cache-Control: public, max-age=31536000, immutable');
|
|
274
|
+
const documentDirectories = new Set(bundle.assets.map(asset => asset.path.match(/^(.*\/pdf-assets\.[a-f0-9]{16})\//)?.[1]).filter(Boolean));
|
|
275
|
+
for (const directory of documentDirectories) immutableRules.push('/' + directory + '/*\n Cache-Control: public, max-age=31536000, immutable');
|
|
276
|
+
const mediaRules = await Promise.all(bundle.assets.filter(asset=>/\.(mp4|webm)$/i.test(asset.path)).map(async asset=>{
|
|
277
|
+
const {size}=await stat(resolve(output,...asset.path.split('/')));
|
|
278
|
+
return '/'+asset.path+'\n Content-Length: '+size+'\n Accept-Ranges: bytes';
|
|
279
|
+
}));
|
|
280
|
+
if (immutableRules.length + mediaRules.length > 85) {
|
|
281
|
+
const immutablePath = /\.[a-f0-9]{16}\.(?:css|m?js|json|png|jpe?g|webp|avif|svg|gif|mp4|webm|woff2?)$/i;
|
|
282
|
+
const patterns = new Map();
|
|
283
|
+
for (const asset of bundle.assets.filter(asset => immutablePath.test(asset.path))) {
|
|
284
|
+
const directory = posix.dirname(asset.path);
|
|
285
|
+
const prefix = directory === '.' ? '' : directory + '/';
|
|
286
|
+
const suffix = posix.extname(asset.path);
|
|
287
|
+
const matching = bundle.assets.filter(candidate => candidate.path.startsWith(prefix) && candidate.path.endsWith(suffix));
|
|
288
|
+
if (matching.length > 1 && matching.every(candidate => immutablePath.test(candidate.path))) {
|
|
289
|
+
patterns.set('/' + prefix + '*' + suffix, matching.map(candidate => '/' + candidate.path));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (const [pattern, paths] of [...patterns].sort((first, second) => second[1].length - first[1].length)) {
|
|
293
|
+
const exact = immutableRules.filter(rule => paths.includes(rule.split('\n')[0]));
|
|
294
|
+
if (exact.length < 2) continue;
|
|
295
|
+
for (const rule of exact) immutableRules.splice(immutableRules.indexOf(rule), 1);
|
|
296
|
+
immutableRules.push(pattern + '\n Cache-Control: public, max-age=31536000, immutable');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const headers = [bundle.existingHeaders.trim(), securityRules, ...immutableRules, ...mediaRules].filter(Boolean).join('\n\n') + '\n';
|
|
300
|
+
const count = headers.split(/\r?\n/).filter(line => line && !/^\s|^#/.test(line)).length;
|
|
301
|
+
if (count > 100) throw new Error('Static header rules exceed 100 after safe cache grouping; reduce custom rules or per-file media headers.');
|
|
302
|
+
await writeFile(resolve(output, '_headers'), serializeConsentHeaders(headers), 'utf8');
|
|
175
303
|
}
|
|
176
304
|
|
|
177
305
|
export function rewriteAssetLinks(html, urlMap) {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
export const deviceProfiles = [
|
|
2
|
+
{ name: 'desktop', width: 1440, height: 900, deviceScaleFactor: 1, mobile: false, touch: false },
|
|
3
|
+
{ name: 'phone', width: 390, height: 844, deviceScaleFactor: 3, mobile: true, touch: true },
|
|
4
|
+
{ name: 'tablet', width: 820, height: 1180, deviceScaleFactor: 2, mobile: true, touch: true }
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
// CDP input targets only the isolated headless browser, never the user's desktop.
|
|
8
|
+
async function key(send, name, modifiers = 0) {
|
|
9
|
+
const code = name === 'Enter' ? 13 : 9;
|
|
10
|
+
await send('Input.dispatchKeyEvent', { type: name === 'Enter' ? 'keyDown' : 'rawKeyDown',
|
|
11
|
+
key: name, code: name, windowsVirtualKeyCode: code, nativeVirtualKeyCode: code, modifiers,
|
|
12
|
+
...(name === 'Enter' ? { text: '\r', unmodifiedText: '\r' } : {}) });
|
|
13
|
+
await send('Input.dispatchKeyEvent', { type: 'keyUp', key: name, code: name,
|
|
14
|
+
windowsVirtualKeyCode: code, nativeVirtualKeyCode: code, modifiers });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function evaluate(send, fn) {
|
|
18
|
+
const result = await send('Runtime.evaluate', { expression: '(' + fn.toString() + ')()', returnByValue: true, awaitPromise: true });
|
|
19
|
+
if (result.exceptionDetails) throw new Error(result.exceptionDetails.text || 'Browser audit evaluation failed');
|
|
20
|
+
return result.result.value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function auditBrowser(send, profile) {
|
|
24
|
+
const checks = [];
|
|
25
|
+
const check = (group, name, passed, detail = '') => checks.push({ group, name, passed: Boolean(passed), ...(detail ? { detail } : {}) });
|
|
26
|
+
const trace = [];
|
|
27
|
+
const previous = await evaluate(send, () => {
|
|
28
|
+
const manager = document.querySelector('[data-consent-ui]');
|
|
29
|
+
const display = manager?.style.display;
|
|
30
|
+
if (manager) manager.style.display = 'none';
|
|
31
|
+
const candidates = [...document.querySelectorAll('a[href],button,input,select,textarea,summary,[tabindex]')];
|
|
32
|
+
const visual = element => {
|
|
33
|
+
const style = getComputedStyle(element);
|
|
34
|
+
return [style.backgroundColor, style.borderColor, style.color, style.textDecorationLine].join('|');
|
|
35
|
+
};
|
|
36
|
+
window.__edgepressKeyboardAudit = { candidates, elements: [...document.querySelectorAll('*')], visual,
|
|
37
|
+
before: new Map(candidates.map(element => [element, visual(element)])) };
|
|
38
|
+
document.activeElement?.blur();
|
|
39
|
+
return { display: display ?? '', href: location.href };
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
const measured = await evaluate(send, () => ({ width: innerWidth, density: devicePixelRatio, touch: navigator.maxTouchPoints,
|
|
43
|
+
overflow: Math.max(document.documentElement.scrollWidth, document.body.scrollWidth) > innerWidth + 1,
|
|
44
|
+
mainCount: document.querySelectorAll('main,[role="main"]').length,
|
|
45
|
+
h1Count: document.querySelectorAll('h1').length,
|
|
46
|
+
heading: document.querySelector('h1')?.textContent.trim() || '',
|
|
47
|
+
language: document.documentElement.lang,
|
|
48
|
+
schemes: { dark: matchMedia('(prefers-color-scheme: dark)').matches }
|
|
49
|
+
}));
|
|
50
|
+
check('device', 'Viewport matches the emulated device', measured.width === profile.width, String(measured.width));
|
|
51
|
+
check('device', 'Pixel density matches the emulated device', Math.abs(measured.density - profile.deviceScaleFactor) < .01);
|
|
52
|
+
check('device', profile.touch ? 'Mobile touch emulation exposes five touch points' : 'Native desktop touch capability is recorded',
|
|
53
|
+
profile.touch ? measured.touch === 5 : true, String(measured.touch));
|
|
54
|
+
check('device', 'No page overflow at this device size', !measured.overflow);
|
|
55
|
+
check('device', 'System color preference is emulated', measured.schemes.dark === (profile.colorScheme === 'dark'));
|
|
56
|
+
check('structure', 'One main landmark', measured.mainCount === 1);
|
|
57
|
+
check('structure', 'One page-level h1', measured.h1Count === 1);
|
|
58
|
+
check('structure', 'Document language is declared', measured.language.trim());
|
|
59
|
+
|
|
60
|
+
const { nodes } = await send('Accessibility.getFullAXTree');
|
|
61
|
+
const exposed = nodes.filter(node => !node.ignored);
|
|
62
|
+
const role = node => node.role?.value;
|
|
63
|
+
const name = node => String(node.name?.value || '').trim();
|
|
64
|
+
check('semantics', 'Main landmark is exposed to assistive technology', exposed.filter(node => role(node) === 'main').length === 1);
|
|
65
|
+
check('semantics', 'Page heading has its accessible name and level', exposed.some(node => role(node) === 'heading' &&
|
|
66
|
+
name(node) === measured.heading && node.properties?.some(property => property.name === 'level' && property.value.value === 1)));
|
|
67
|
+
const navigation = exposed.filter(node => role(node) === 'navigation');
|
|
68
|
+
check('semantics', 'Exposed navigation landmarks have names', navigation.every(node => name(node)));
|
|
69
|
+
const namedRoles = new Set(['button', 'link', 'combobox', 'textbox', 'searchbox', 'checkbox', 'radio', 'switch', 'slider', 'spinbutton', 'image']);
|
|
70
|
+
const unnamed = exposed.filter(node => namedRoles.has(role(node)) && !name(node));
|
|
71
|
+
check('semantics', 'Exposed controls and meaningful images have names', unnamed.length === 0,
|
|
72
|
+
unnamed.length ? unnamed.map(node => role(node)).join(', ') : '');
|
|
73
|
+
|
|
74
|
+
await send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
|
75
|
+
const focus = () => evaluate(send, async () => {
|
|
76
|
+
const element = document.activeElement;
|
|
77
|
+
if (!element || element === document.body || element === document.documentElement) return null;
|
|
78
|
+
// Native Tab can start a smooth scroll; observe its result without scrolling for it.
|
|
79
|
+
await new Promise(resolve=>{
|
|
80
|
+
const started=performance.now();
|
|
81
|
+
const visible=()=>{const box=element.getBoundingClientRect();if((box.bottom>0&&box.top<innerHeight&&box.right>0&&box.left<innerWidth)||performance.now()-started>1500)return resolve();requestAnimationFrame(visible);};
|
|
82
|
+
visible();
|
|
83
|
+
});
|
|
84
|
+
const { elements, before, visual } = window.__edgepressKeyboardAudit;
|
|
85
|
+
const style = getComputedStyle(element), box = element.getBoundingClientRect();
|
|
86
|
+
const transparent = value => value === 'transparent' || /rgba\([^)]*,\s*0\)$/.test(value);
|
|
87
|
+
const outline = style.outlineStyle !== 'none' && parseFloat(style.outlineWidth) > 0 && !transparent(style.outlineColor);
|
|
88
|
+
const indicator = outline || style.boxShadow !== 'none' || (before.has(element) && before.get(element) !== visual(element));
|
|
89
|
+
const hidden = element.closest('[hidden],[inert],[aria-hidden="true"]');
|
|
90
|
+
return { index: elements.indexOf(element), tag: element.tagName.toLowerCase(),
|
|
91
|
+
name: (element.getAttribute('aria-label') || element.innerText || element.labels?.[0]?.textContent || '').trim().slice(0, 160),
|
|
92
|
+
visible: !hidden && style.visibility === 'visible' && style.display !== 'none' && box.width > 0 && box.height > 0 &&
|
|
93
|
+
box.bottom > 0 && box.top < innerHeight && box.right > 0 && box.left < innerWidth,
|
|
94
|
+
indicator, skip: element.matches('a[href^="#"]') &&
|
|
95
|
+
document.getElementById(element.getAttribute('href').slice(1))?.matches('main,[role="main"]') };
|
|
96
|
+
});
|
|
97
|
+
let reverseChecked = false;
|
|
98
|
+
let unexpectedRepeat = false;
|
|
99
|
+
for (let step = 0; step < 12; step += 1) {
|
|
100
|
+
await key(send, 'Tab');
|
|
101
|
+
const current = await focus();
|
|
102
|
+
if (!current) break; // Normal wrap through browser chrome is not a focus trap.
|
|
103
|
+
if (trace.length && current.index === trace[0].index) {
|
|
104
|
+
// Mobile browser chrome may not appear as a focus stop. A natural wrap
|
|
105
|
+
// is valid only after visiting all currently tabbable page controls.
|
|
106
|
+
const expected = await evaluate(send, () => {
|
|
107
|
+
const { candidates, elements } = window.__edgepressKeyboardAudit;
|
|
108
|
+
const visible = candidates.filter(element => {
|
|
109
|
+
const style = getComputedStyle(element), box = element.getBoundingClientRect();
|
|
110
|
+
return element.tabIndex >= 0 && !element.matches(':disabled') && !element.closest('[hidden],[inert]') &&
|
|
111
|
+
style.visibility === 'visible' && box.width > 0 && box.height > 0;
|
|
112
|
+
});
|
|
113
|
+
return visible.filter(element => {
|
|
114
|
+
if (!element.matches('input[type="radio"]') || !element.name) return true;
|
|
115
|
+
const group = visible.filter(other => other.matches('input[type="radio"]') && other.name === element.name && other.form === element.form);
|
|
116
|
+
return element === (group.find(other => other.checked) || group[0]);
|
|
117
|
+
}).map(element => elements.indexOf(element));
|
|
118
|
+
});
|
|
119
|
+
const visited = new Set(trace.map(item => item.index));
|
|
120
|
+
const missing = expected.filter(index => !visited.has(index));
|
|
121
|
+
check('keyboard', 'Native page wrap does not skip tabbable controls', missing.length === 0, missing.length ? String(missing.length) + ' unvisited controls' : '');
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
if (trace.some(item => item.index === current.index)) { unexpectedRepeat = true; break; }
|
|
125
|
+
trace.push(current);
|
|
126
|
+
check('keyboard', 'Tab step ' + (step + 1) + ' lands on visible content', current.visible, current.name);
|
|
127
|
+
check('keyboard', 'Tab step ' + (step + 1) + ' has a visible focus indicator', current.indicator, current.name);
|
|
128
|
+
if (step === 1) {
|
|
129
|
+
await key(send, 'Tab', 8); // Shift modifier, scoped to CDP.
|
|
130
|
+
const reverse = await focus();
|
|
131
|
+
check('keyboard', 'Shift+Tab returns to the previous focus target', reverse?.index === trace[0].index);
|
|
132
|
+
reverseChecked = true;
|
|
133
|
+
await key(send, 'Tab');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
check('keyboard', 'Forward and backward keyboard navigation are available', trace.length >= 2 && reverseChecked);
|
|
137
|
+
check('keyboard', 'No unexpected focus cycle during the sampled Tab sequence', !unexpectedRepeat);
|
|
138
|
+
check('keyboard', 'First Tab reaches the skip-to-main link', trace[0]?.skip);
|
|
139
|
+
const hasSkip = await evaluate(send, () => {
|
|
140
|
+
const first = window.__edgepressKeyboardAudit.candidates.find(element => element.matches('a[href^="#"]') &&
|
|
141
|
+
document.getElementById(element.getAttribute('href').slice(1))?.matches('main,[role="main"]'));
|
|
142
|
+
first?.focus();
|
|
143
|
+
return Boolean(first && document.activeElement === first);
|
|
144
|
+
});
|
|
145
|
+
if (hasSkip) await key(send, 'Enter');
|
|
146
|
+
const skipReached = hasSkip && await evaluate(send, () => {
|
|
147
|
+
if (!location.hash) return false;
|
|
148
|
+
const target = document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
|
149
|
+
return Boolean(target?.matches('main,[role="main"]') && target.getBoundingClientRect().top < innerHeight);
|
|
150
|
+
});
|
|
151
|
+
check('keyboard', 'Enter on the skip link reaches the main landmark', skipReached);
|
|
152
|
+
return { checks, trace, status: checks.every(item => item.passed) ? 'pass' : 'fail' };
|
|
153
|
+
} finally {
|
|
154
|
+
await send('Runtime.evaluate', { expression: '(() => { const manager = document.querySelector("[data-consent-ui]"); if (manager) manager.style.display = ' +
|
|
155
|
+
JSON.stringify(previous.display) + '; history.replaceState(history.state, "", ' + JSON.stringify(previous.href) +
|
|
156
|
+
'); delete window.__edgepressKeyboardAudit; })()' });
|
|
157
|
+
}
|
|
158
|
+
}
|
package/src/build.js
CHANGED
|
@@ -5,14 +5,18 @@ import { hostname } from 'node:os';
|
|
|
5
5
|
import { mapLimit } from './concurrency.js';
|
|
6
6
|
import { loadConfig } from './config.js';
|
|
7
7
|
import { readDocuments, sortPostsNewest } from './content.js';
|
|
8
|
-
import { plainText } from './markdown.js';
|
|
8
|
+
import { plainText, hasMarkdownVideo } from './markdown.js';
|
|
9
9
|
import { createExtensions } from './plugin-api.js';
|
|
10
10
|
import { collectAssets, rewriteAssetLinks, writeAssets } from './assets.js';
|
|
11
11
|
import { loadLanguagePacks } from './i18n.js';
|
|
12
|
+
import { contentPermissions, extendConsentPolicy, injectConsentPolicy } from './consent-csp.js';
|
|
12
13
|
import { generateBuiltinRoutes } from './generators.js';
|
|
14
|
+
import {reserveImageDimensions,collectImageDimensions} from './image-dimensions.js';
|
|
15
|
+
import {reserveVideoMetadata} from './video-metadata.js';
|
|
16
|
+
import { dataSaverHtml, configureDataSaver } from './data-saver.js';
|
|
13
17
|
|
|
14
18
|
const RENDER_CACHE_VERSION = 3;
|
|
15
|
-
const MARKDOWN_SECURITY_POLICY_VERSION =
|
|
19
|
+
const MARKDOWN_SECURITY_POLICY_VERSION = 5;
|
|
16
20
|
|
|
17
21
|
async function readCache(file) {
|
|
18
22
|
try {
|
|
@@ -28,8 +32,9 @@ async function readCache(file) {
|
|
|
28
32
|
|
|
29
33
|
async function markdownRuntimeVersion(root) {
|
|
30
34
|
const lockPath = resolve(root, 'package-lock.json');
|
|
31
|
-
const
|
|
32
|
-
const
|
|
35
|
+
const rendererSources=await Promise.all(['markdown.js','embed.js','document.js','oembed.js','plugin-api.js','i18n.js'].map(file=>readFile(new URL('./'+file,import.meta.url))));
|
|
36
|
+
const digest=createHash('sha256');for(const source of rendererSources)digest.update(source);
|
|
37
|
+
const implementation=digest.digest('hex');
|
|
33
38
|
try {
|
|
34
39
|
const lock = JSON.parse(await readFile(lockPath, 'utf8'));
|
|
35
40
|
const marked = lock.packages?.['node_modules/marked']?.version;
|
|
@@ -143,6 +148,16 @@ export async function acquireBuildLock(directory) {
|
|
|
143
148
|
throw new Error('Unable to acquire the project build lock');
|
|
144
149
|
}
|
|
145
150
|
|
|
151
|
+
async function renameOutput(source, target) {
|
|
152
|
+
for(let attempt=0;;attempt++) {
|
|
153
|
+
try{return await rename(source,target);}
|
|
154
|
+
catch(error){
|
|
155
|
+
if(process.platform!=='win32'||!['EPERM','EBUSY'].includes(error.code)||attempt>=5)throw error;
|
|
156
|
+
await new Promise(resolve=>setTimeout(resolve,200*2**attempt));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
146
161
|
async function commitOutput(stage, output) {
|
|
147
162
|
const backup = output + '.backup-' + randomUUID();
|
|
148
163
|
let movedPrevious = false;
|
|
@@ -152,15 +167,15 @@ async function commitOutput(stage, output) {
|
|
|
152
167
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
153
168
|
throw new Error('Build output must be a real directory: ' + output);
|
|
154
169
|
}
|
|
155
|
-
await
|
|
170
|
+
await renameOutput(output, backup);
|
|
156
171
|
movedPrevious = true;
|
|
157
172
|
} catch (error) {
|
|
158
173
|
if (error.code !== 'ENOENT') throw error;
|
|
159
174
|
}
|
|
160
|
-
await
|
|
175
|
+
await renameOutput(stage, output);
|
|
161
176
|
} catch (error) {
|
|
162
177
|
if (movedPrevious) {
|
|
163
|
-
try { await
|
|
178
|
+
try { await renameOutput(backup, output); }
|
|
164
179
|
catch (restoreError) {
|
|
165
180
|
throw new Error('Build commit failed and the previous output could not be restored from ' + backup + ': ' + restoreError.message, { cause: error });
|
|
166
181
|
}
|
|
@@ -180,6 +195,10 @@ async function writeRoutes(routes, stage, assetBundle, extensions, site, config)
|
|
|
180
195
|
let body = await extensions.filter('route:body', route.body, { route, site, config });
|
|
181
196
|
if (typeof body !== 'string') throw new Error('route:body filters must return a string for ' + route.path);
|
|
182
197
|
body = rewriteAssetLinks(body, assetBundle.urlMap);
|
|
198
|
+
body = reserveImageDimensions(body, assetBundle.imageDimensions, route.path, site.config.site.url);
|
|
199
|
+
body = reserveVideoMetadata(body, assetBundle.videoMetadata, route.path, site.config.site.url);
|
|
200
|
+
body = dataSaverHtml(body, config, assetBundle.urlMap);
|
|
201
|
+
body = injectConsentPolicy(body, assetBundle.existingHeaders);
|
|
183
202
|
await writeFile(file, body, 'utf8');
|
|
184
203
|
});
|
|
185
204
|
}
|
|
@@ -187,6 +206,7 @@ async function writeRoutes(routes, stage, assetBundle, extensions, site, config)
|
|
|
187
206
|
export async function buildSite(root = process.cwd(), options = {}) {
|
|
188
207
|
const started = Date.now();
|
|
189
208
|
const config = await loadConfig(root);
|
|
209
|
+
await configureDataSaver(config);
|
|
190
210
|
if (options.preview === true) {
|
|
191
211
|
config.preview = true;
|
|
192
212
|
config.previewBuildId = randomUUID();
|
|
@@ -198,10 +218,10 @@ export async function buildSite(root = process.cwd(), options = {}) {
|
|
|
198
218
|
try {
|
|
199
219
|
await loadLanguagePacks(config);
|
|
200
220
|
const extensions = await createExtensions(config);
|
|
201
|
-
const assetBundle = await collectAssets(config);
|
|
202
221
|
const sourceDocuments = await readDocuments(config);
|
|
203
222
|
const loadedDocuments = await extensions.filter('content:loaded', sourceDocuments, { config });
|
|
204
223
|
if (!Array.isArray(loadedDocuments)) throw new Error('content:loaded filters must return an array');
|
|
224
|
+
const assetBundle = await collectAssets(config, { documentViewer: loadedDocuments.some(document => /!document\[|"type":"document"/.test(JSON.stringify(document))), mediaViewer: loadedDocuments.some(document => document.video || hasMarkdownVideo(document.markdown) || /"type":"media-viewer"/.test(JSON.stringify(document))) });
|
|
205
225
|
|
|
206
226
|
const cacheFile = resolve(config.resolvedPaths.cache, 'render-cache.json');
|
|
207
227
|
const oldCache = await readCache(cacheFile);
|
|
@@ -216,7 +236,7 @@ export async function buildSite(root = process.cwd(), options = {}) {
|
|
|
216
236
|
}
|
|
217
237
|
const signature = createHash('sha256')
|
|
218
238
|
.update(document.markdown + '\n' + JSON.stringify(config.markdown) + '\n' + document.locale + '\n' +
|
|
219
|
-
MARKDOWN_SECURITY_POLICY_VERSION + '\n' + runtimeVersion)
|
|
239
|
+
MARKDOWN_SECURITY_POLICY_VERSION + '\n' + runtimeVersion + '\n' + JSON.stringify(config.browserPlugins) + '\n' + config.site.url)
|
|
220
240
|
.digest('hex');
|
|
221
241
|
let html = renderer.cacheable ? oldCache[signature] : undefined;
|
|
222
242
|
if (html === undefined) html = await renderer.render(document.markdown, { config, document });
|
|
@@ -229,6 +249,9 @@ export async function buildSite(root = process.cwd(), options = {}) {
|
|
|
229
249
|
|
|
230
250
|
const now = new Date();
|
|
231
251
|
const visible = documents.filter((document) => !document.draft && publishedByLocalDate(document, now));
|
|
252
|
+
const revisionAssets=new Map(visible.flatMap(document=>document.revisionAssets??[]).map(asset=>[asset.path,asset]));
|
|
253
|
+
assetBundle.assets.push(...revisionAssets.values());
|
|
254
|
+
for(const [path,size] of await collectImageDimensions([...revisionAssets.values()]))assetBundle.imageDimensions.set(path,size);
|
|
232
255
|
let site = {
|
|
233
256
|
config,
|
|
234
257
|
posts: sortPostsNewest(visible.filter((document) => document.kind === 'posts')),
|
|
@@ -251,6 +274,8 @@ export async function buildSite(root = process.cwd(), options = {}) {
|
|
|
251
274
|
}
|
|
252
275
|
routes = await extensions.filter('routes:created', routes, { site, config });
|
|
253
276
|
validateRoutes(routes, output, assetBundle);
|
|
277
|
+
config.contentCsp = contentPermissions(routes);
|
|
278
|
+
assetBundle.existingHeaders = extendConsentPolicy(assetBundle.existingHeaders, config);
|
|
254
279
|
|
|
255
280
|
await mkdir(dirname(output), { recursive: true });
|
|
256
281
|
await mkdir(stage, { recursive: false });
|