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,180 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { readFile, writeFile, readdir, mkdir, stat } from 'node:fs/promises';
|
|
4
|
+
import { resolve, relative, sep, extname } from 'node:path';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { chromium } from 'playwright';
|
|
7
|
+
|
|
8
|
+
const site = resolve(process.argv[2] || process.cwd());
|
|
9
|
+
const output = resolve(site, 'dist');
|
|
10
|
+
const evidence = resolve(site, 'tools/reports/brand-verification');
|
|
11
|
+
await mkdir(evidence, { recursive: true });
|
|
12
|
+
const configFile = resolve(site, 'edgepress.config.mjs');
|
|
13
|
+
const siteFile = resolve(site, 'config.yml');
|
|
14
|
+
const originalConfig = await readFile(configFile, 'utf8');
|
|
15
|
+
const originalSite = await readFile(siteFile, 'utf8');
|
|
16
|
+
const configuredTheme = originalSite.match(/^theme:\s*(\S+)/m)?.[1];
|
|
17
|
+
const defaultLocale = originalConfig.match(/defaultLocale:\s*["']([^"']+)/)?.[1];
|
|
18
|
+
assert.ok(defaultLocale, 'Default language must be declared.');
|
|
19
|
+
const themes = (await readdir(resolve(site, 'themes'), { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
20
|
+
const locales = (await Promise.all((await readdir(resolve(site, 'content/pages/home'))).filter((name) => /\.(?:md|json)$/.test(name)).map(async (name) => {
|
|
21
|
+
const text = await readFile(resolve(site, 'content/pages/home', name), 'utf8');
|
|
22
|
+
return name.endsWith('.json') ? JSON.parse(text).lang : text.match(/^lang:\s*(\S+)/m)?.[1];
|
|
23
|
+
}))).filter(Boolean);
|
|
24
|
+
const cli = await stat(resolve(site, 'src/cli.js')).then(() => resolve(site, 'src/cli.js')).catch(() => resolve(site, 'node_modules/edgepress/src/cli.js'));
|
|
25
|
+
async function build() {
|
|
26
|
+
await new Promise((done, reject) => {
|
|
27
|
+
const child = spawn(process.execPath, [cli, 'build'], { cwd: site, windowsHide: true });
|
|
28
|
+
let text = '';
|
|
29
|
+
child.stdout.on('data', (chunk) => { text += chunk; });
|
|
30
|
+
child.stderr.on('data', (chunk) => { text += chunk; });
|
|
31
|
+
child.once('error', reject);
|
|
32
|
+
child.once('exit', (code) => code === 0 ? done() : reject(new Error(text)));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function filesIn(directory) {
|
|
36
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
37
|
+
return (await Promise.all(entries.map((entry) => entry.isDirectory() ? filesIn(resolve(directory, entry.name)) : resolve(directory, entry.name)))).flat();
|
|
38
|
+
}
|
|
39
|
+
const server = createServer(async (request, response) => {
|
|
40
|
+
try {
|
|
41
|
+
const pathname = decodeURIComponent(new URL(request.url, 'http://local.test').pathname);
|
|
42
|
+
let path = resolve(output, '.' + pathname);
|
|
43
|
+
const rel = relative(output, path);
|
|
44
|
+
if (rel === '..' || rel.startsWith('..' + sep)) throw new Error('Invalid path');
|
|
45
|
+
if ((await stat(path)).isDirectory()) path = resolve(path, 'index.html');
|
|
46
|
+
const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.png': 'image/png', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.json': 'application/json', '.ico': 'image/x-icon' };
|
|
47
|
+
response.writeHead(200, { 'Content-Type': types[extname(path)] || 'application/octet-stream', 'Cache-Control': 'no-store' });
|
|
48
|
+
response.end(await readFile(path));
|
|
49
|
+
} catch { response.writeHead(404); response.end('Not found'); }
|
|
50
|
+
});
|
|
51
|
+
await new Promise((done) => server.listen(0, '127.0.0.1', done));
|
|
52
|
+
const base = 'http://127.0.0.1:' + server.address().port;
|
|
53
|
+
const browser = await chromium.launch({ executablePath: 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe', headless: true });
|
|
54
|
+
const results = [];
|
|
55
|
+
const failures = [];
|
|
56
|
+
try {
|
|
57
|
+
for (const theme of themes) {
|
|
58
|
+
if (configuredTheme) await writeFile(siteFile, originalSite.replace(/^theme:\s*\S+/m, 'theme: ' + theme));
|
|
59
|
+
else await writeFile(configFile, originalConfig.replace(/(theme:\s*["'])themes\/[^"']+(["'])/, '$1themes/' + theme + '$2'));
|
|
60
|
+
await build();
|
|
61
|
+
const htmlFiles = (await filesIn(output)).filter((file) => file.endsWith('.html'));
|
|
62
|
+
for (const file of htmlFiles) {
|
|
63
|
+
const html = await readFile(file, 'utf8');
|
|
64
|
+
assert.doesNotMatch(html, /<use\b|\/icons\.svg(?:#|["'])/i, 'Old sprite remains in ' + file);
|
|
65
|
+
for (const match of html.matchAll(/<img\b[^>]*\bsrc="(\/[^"]+)"/g)) {
|
|
66
|
+
const path = resolve(output, '.' + new URL(match[1], base).pathname);
|
|
67
|
+
await stat(path);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const article = await (async () => {
|
|
71
|
+
for (const file of htmlFiles) if ((await readFile(file, 'utf8')).includes('<article class="post">')) return '/' + relative(output, file).split(sep).join('/').replace(/index\.html$/, '');
|
|
72
|
+
return null;
|
|
73
|
+
})();
|
|
74
|
+
for (const scheme of ['light', 'dark']) {
|
|
75
|
+
for (const width of [390, 1440]) {
|
|
76
|
+
const context = await browser.newContext({ viewport: { width, height: 920 }, colorScheme: scheme });
|
|
77
|
+
await context.route('**/*', (route) => route.request().url().startsWith(base) ? route.continue() : route.abort());
|
|
78
|
+
const page = await context.newPage();
|
|
79
|
+
for (const locale of locales) {
|
|
80
|
+
const prefix = locale === defaultLocale ? '/' : '/' + locale + '/';
|
|
81
|
+
const paths = [prefix, prefix + 'archives/'];
|
|
82
|
+
if (await stat(resolve(output, '.' + prefix + 'archives/page/2/index.html')).then(() => true).catch(() => false)) paths.push(prefix + 'archives/page/2/');
|
|
83
|
+
if (await stat(resolve(output, '.' + prefix + 'edgepress/index.html')).then(() => true).catch(() => false)) paths.push(prefix + 'edgepress/');
|
|
84
|
+
for (const path of paths) {
|
|
85
|
+
await page.goto(base + path, { waitUntil: 'networkidle' });
|
|
86
|
+
const measured = await page.evaluate(async () => {
|
|
87
|
+
const images = [...document.querySelectorAll('img')];
|
|
88
|
+
for (const image of images) image.loading = 'eager';
|
|
89
|
+
await Promise.all(images.map((image) => image.decode().catch(() => {})));
|
|
90
|
+
const icons = [...document.querySelectorAll('.icon-library')];
|
|
91
|
+
return {
|
|
92
|
+
colorScheme: getComputedStyle(document.documentElement).colorScheme,
|
|
93
|
+
overflow: document.documentElement.scrollWidth > innerWidth + 1,
|
|
94
|
+
brokenImages: images.filter((image) => !image.complete || !image.naturalWidth).map((image) => image.src),
|
|
95
|
+
inlineSvg: document.querySelectorAll('svg,use').length,
|
|
96
|
+
iconSemantics: icons.every((image) => image.tagName === 'IMG' && image.alt === '' && image.getAttribute('aria-hidden') === 'true' && /\/edgepress\/icons\/[a-z-]+\.svg$/.test(image.src)),
|
|
97
|
+
rawLanguageCodes: [...document.querySelectorAll('.post-language')].some((element) => /^(?:en|zh-(?:CN|TW|SG))$/.test(element.textContent.trim())),
|
|
98
|
+
cardGap: document.querySelector('.post-list') ? parseFloat(getComputedStyle(document.querySelector('.post-list')).rowGap) : null,
|
|
99
|
+
cellGap: document.querySelector('.page-builder-cell') ? parseFloat(getComputedStyle(document.querySelector('.page-builder-cell')).rowGap) : null,
|
|
100
|
+
filter: icons.length ? getComputedStyle(icons[0]).filter : null,
|
|
101
|
+
h1: document.querySelectorAll('h1').length,
|
|
102
|
+
ogImage: document.querySelector('meta[property="og:image"]')?.content,
|
|
103
|
+
currentPage: document.querySelector('.pagination-current')?.getAttribute('aria-current')
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
assert.equal(measured.overflow, false, theme + ' ' + path + ' overflows at ' + width);
|
|
107
|
+
assert.deepEqual(measured.brokenImages, [], theme + ' ' + path + ' has broken images');
|
|
108
|
+
assert.equal(measured.inlineSvg, 0);
|
|
109
|
+
assert.equal(measured.iconSemantics, true);
|
|
110
|
+
assert.equal(measured.rawLanguageCodes, false);
|
|
111
|
+
assert.equal(measured.h1, 1);
|
|
112
|
+
assert.match(measured.ogImage || '', /^https:\/\/.+\.png$/);
|
|
113
|
+
if (measured.cardGap !== null) assert.ok(measured.cardGap >= 16);
|
|
114
|
+
if (measured.cellGap !== null) assert.ok(measured.cellGap >= 16);
|
|
115
|
+
if (measured.currentPage) assert.equal(measured.currentPage, 'page');
|
|
116
|
+
if (theme === 'signal' && measured.colorScheme === 'dark' || theme === 'lumen' && scheme === 'dark') assert.notEqual(measured.filter, 'none');
|
|
117
|
+
results.push({ theme, scheme, width, locale, path, ...measured });
|
|
118
|
+
if (path === prefix && locale === defaultLocale) {
|
|
119
|
+
await page.screenshot({ path: resolve(evidence, 'home-' + theme + '-' + width + '-' + scheme + '.png'), fullPage: true });
|
|
120
|
+
await page.keyboard.press('Tab');
|
|
121
|
+
assert.equal(await page.evaluate(() => document.activeElement.classList.contains('skip-link')), true, 'Skip link must be first keyboard target');
|
|
122
|
+
if (await page.locator('.privacy-settings-button').count()) {
|
|
123
|
+
if (await page.locator('.privacy-panel').isVisible()) await page.locator('.privacy-close').click();
|
|
124
|
+
await page.locator('.privacy-settings-button').click();
|
|
125
|
+
assert.equal(await page.locator('.privacy-settings-button').getAttribute('aria-expanded'), 'true');
|
|
126
|
+
assert.equal(await page.locator('.privacy-panel').isVisible(), true);
|
|
127
|
+
assert.equal(await page.locator('.privacy-panel svg').count(), 0);
|
|
128
|
+
const contrasts = await page.evaluate(() => {
|
|
129
|
+
const luminance = (color) => {
|
|
130
|
+
const values = color.match(/[\d.]+/g).slice(0, 3).map(Number).map((value) => {
|
|
131
|
+
value /= 255;
|
|
132
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
133
|
+
});
|
|
134
|
+
return values[0] * 0.2126 + values[1] * 0.7152 + values[2] * 0.0722;
|
|
135
|
+
};
|
|
136
|
+
return [...document.querySelectorAll('.privacy-settings-button,.privacy-accept,.privacy-reject')].map((button) => {
|
|
137
|
+
const style = getComputedStyle(button);
|
|
138
|
+
const foreground = luminance(style.color);
|
|
139
|
+
const background = luminance(style.backgroundColor);
|
|
140
|
+
return (Math.max(foreground, background) + 0.05) / (Math.min(foreground, background) + 0.05);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
assert.ok(contrasts.every((ratio) => ratio >= 4.5), theme + ' privacy button contrast: ' + contrasts.join(', '));
|
|
144
|
+
await page.locator('.privacy-close').click();
|
|
145
|
+
assert.equal(await page.locator('.privacy-panel').isVisible(), false);
|
|
146
|
+
await page.screenshot({ path: resolve(evidence, 'home-' + theme + '-' + width + '-' + scheme + '.png'), fullPage: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (article) {
|
|
152
|
+
await page.goto(base + article, { waitUntil: 'networkidle' });
|
|
153
|
+
if (await page.locator('.privacy-panel').isVisible()) await page.locator('.privacy-close').click();
|
|
154
|
+
if (await page.locator('.post-toc-toggle').count()) {
|
|
155
|
+
await page.evaluate(() => window.scrollTo(0, 400));
|
|
156
|
+
await page.locator('.post-toc-toggle').waitFor({ state: 'visible' });
|
|
157
|
+
assert.equal(await page.locator('.post-toc-toggle img').count(), 1);
|
|
158
|
+
await page.locator('.post-toc-toggle').click();
|
|
159
|
+
assert.equal(await page.locator('.post-toc-panel').isVisible(), true);
|
|
160
|
+
await page.locator('.post-toc-close').click();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
await context.close();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
console.log(theme + ': image references, layouts, locales, light/dark, keyboard and library icon controls passed.');
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
failures.push(error.message);
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
console.error(error.stack);
|
|
172
|
+
} finally {
|
|
173
|
+
await writeFile(configFile, originalConfig);
|
|
174
|
+
await writeFile(siteFile, originalSite);
|
|
175
|
+
await build();
|
|
176
|
+
await browser.close();
|
|
177
|
+
await new Promise((done) => server.close(done));
|
|
178
|
+
await writeFile(resolve(evidence, 'browser-report.json'), JSON.stringify({ status: failures.length ? 'fail' : 'pass', cases: results.length, results, failures,
|
|
179
|
+
limitations: ['Browser measurements use desktop Edge and simulated viewports/color schemes. Physical devices and assistive screen readers were not tested.'] }, null, 2) + '\n');
|
|
180
|
+
}
|
package/vercel.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://openapi.vercel.sh/vercel.json",
|
|
3
|
+
"framework": null,
|
|
4
|
+
"installCommand": "npm ci",
|
|
5
|
+
"buildCommand": "npm run build",
|
|
6
|
+
"outputDirectory": "dist",
|
|
7
|
+
"trailingSlash": true,
|
|
8
|
+
"headers": [
|
|
9
|
+
{
|
|
10
|
+
"source": "/(.*)",
|
|
11
|
+
"headers": [
|
|
12
|
+
{
|
|
13
|
+
"key": "Cache-Control",
|
|
14
|
+
"value": "public, max-age=0, must-revalidate"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"key": "X-Content-Type-Options",
|
|
18
|
+
"value": "nosniff"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"key": "Referrer-Policy",
|
|
22
|
+
"value": "strict-origin-when-cross-origin"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"source": "/:asset(.*\\.[a-f0-9]{16}\\..*)",
|
|
28
|
+
"headers": [
|
|
29
|
+
{
|
|
30
|
+
"key": "Cache-Control",
|
|
31
|
+
"value": "public, max-age=31536000, immutable"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
package/wrangler.jsonc
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
// For the optional URL/token backend, add vars.BACKEND_URL (for example, https://api.example.org) and run `edgepress secret put BACKEND_TOKEN`.
|
|
2
1
|
{
|
|
3
|
-
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
4
2
|
"name": "edgepress",
|
|
5
|
-
"
|
|
6
|
-
"compatibility_date": "2026-09-01",
|
|
3
|
+
"compatibility_date": "2026-10-03",
|
|
7
4
|
"assets": {
|
|
8
5
|
"directory": "./dist",
|
|
9
|
-
"binding": "ASSETS",
|
|
10
6
|
"html_handling": "auto-trailing-slash",
|
|
11
|
-
"
|
|
12
|
-
}
|
|
7
|
+
"not_found_handling": "404-page"
|
|
8
|
+
},
|
|
9
|
+
"workers_dev": false,
|
|
10
|
+
"preview_urls": false
|
|
13
11
|
}
|
package/content/pages/home/en.md
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: EdgePress website builder
|
|
3
|
-
lang: en
|
|
4
|
-
slug: home
|
|
5
|
-
homepage: true
|
|
6
|
-
description: Build fast, secure websites on Cloudflare Workers with Markdown posts and editable page elements.
|
|
7
|
-
keywords:
|
|
8
|
-
- website builder
|
|
9
|
-
- Cloudflare Workers
|
|
10
|
-
- page elements
|
|
11
|
-
- website plugins
|
|
12
|
-
blocks:
|
|
13
|
-
- columns: 1
|
|
14
|
-
cells:
|
|
15
|
-
-
|
|
16
|
-
- type: hero
|
|
17
|
-
eyebrow: A project built for the edge
|
|
18
|
-
title: Build a fast website from clear, editable source files.
|
|
19
|
-
text: EdgePress turns Markdown posts, page layouts, themes, and consent-aware integrations into a static site served by Cloudflare Workers.
|
|
20
|
-
mascotSrc: /edgepress/brand/mascot-418.png
|
|
21
|
-
mascotAlt: A curious sparrow folded from layered paper pages, EdgePress's project mascot.
|
|
22
|
-
mascotWidth: 418
|
|
23
|
-
mascotHeight: 440
|
|
24
|
-
mascotSizes: 209px
|
|
25
|
-
mascotWebpSrcset:
|
|
26
|
-
- src: /edgepress/brand/mascot-209.webp
|
|
27
|
-
width: 209
|
|
28
|
-
- src: /edgepress/brand/mascot-418.webp
|
|
29
|
-
width: 418
|
|
30
|
-
cta:
|
|
31
|
-
label: Start with the quick guide
|
|
32
|
-
url: /quick-start/
|
|
33
|
-
highlights:
|
|
34
|
-
- Editable pages in localized Markdown front matter
|
|
35
|
-
- Articles written in Markdown
|
|
36
|
-
- Shared layouts and theme-defined palettes
|
|
37
|
-
- columns: 1
|
|
38
|
-
cells:
|
|
39
|
-
-
|
|
40
|
-
- type: feature-grid
|
|
41
|
-
title: A clear place for each part of a site
|
|
42
|
-
items:
|
|
43
|
-
- title: Pages use editable elements
|
|
44
|
-
icon: layout-grid
|
|
45
|
-
text: Set the number of columns first, then choose elements and edit their content in each page file.
|
|
46
|
-
url: /theme-development/
|
|
47
|
-
- title: Posts use Markdown
|
|
48
|
-
icon: file-text
|
|
49
|
-
text: Write long-form articles in Markdown and let the built-in renderer sanitize and format them.
|
|
50
|
-
url: /project-introduction/
|
|
51
|
-
- title: Themes own the design
|
|
52
|
-
icon: palette
|
|
53
|
-
text: Build layouts with HTML partials and keep each theme's color palette in its stylesheet.
|
|
54
|
-
url: /theme-development/
|
|
55
|
-
- title: Integrations wait for consent
|
|
56
|
-
icon: shield-check
|
|
57
|
-
text: Add tracking, statistics, advertising, or CAPTCHA services with provider details in configuration.
|
|
58
|
-
url: /plugin-development/
|
|
59
|
-
- columns: 2
|
|
60
|
-
cells:
|
|
61
|
-
-
|
|
62
|
-
- type: section
|
|
63
|
-
title: Learn the project
|
|
64
|
-
tone: soft
|
|
65
|
-
blocks:
|
|
66
|
-
- type: text
|
|
67
|
-
paragraphs:
|
|
68
|
-
- Start with the project introduction and quick start. The guides cover the feature lifecycle, supported runtimes, and the recommended publishing workflow.
|
|
69
|
-
- type: link-list
|
|
70
|
-
title: Project guides
|
|
71
|
-
items:
|
|
72
|
-
- label: Project introduction
|
|
73
|
-
url: /project-introduction/
|
|
74
|
-
- label: Quick start
|
|
75
|
-
url: /quick-start/
|
|
76
|
-
-
|
|
77
|
-
- type: section
|
|
78
|
-
title: Extend the project
|
|
79
|
-
tone: soft
|
|
80
|
-
blocks:
|
|
81
|
-
- type: text
|
|
82
|
-
paragraphs:
|
|
83
|
-
- Use theme partials for shared head metadata, navigation, footer, and error-page layout. Page-specific rows and element content stay in localized page files.
|
|
84
|
-
- Build plugins run in Node. Browser services wait for explicit consent before requesting vendor code.
|
|
85
|
-
- type: link-list
|
|
86
|
-
title: Development guides
|
|
87
|
-
items:
|
|
88
|
-
- label: Theme development
|
|
89
|
-
url: /theme-development/
|
|
90
|
-
- label: Plugin development
|
|
91
|
-
url: /plugin-development/
|
|
92
|
-
- columns: 1
|
|
93
|
-
cells:
|
|
94
|
-
-
|
|
95
|
-
- type: latest-posts
|
|
96
|
-
title: Latest project updates
|
|
97
|
-
count: 3
|
|
98
|
-
paginate: true
|
|
99
|
-
---
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: EdgePress 网站构建器
|
|
3
|
-
lang: zh-CN
|
|
4
|
-
slug: home
|
|
5
|
-
homepage: true
|
|
6
|
-
description: 使用 Markdown 文章和可编辑页面元素,在 Cloudflare Workers 上构建快速、安全的网站。
|
|
7
|
-
keywords:
|
|
8
|
-
- 网站构建器
|
|
9
|
-
- Cloudflare Workers
|
|
10
|
-
- 页面元素
|
|
11
|
-
- 网站插件
|
|
12
|
-
blocks:
|
|
13
|
-
- columns: 1
|
|
14
|
-
cells:
|
|
15
|
-
-
|
|
16
|
-
- type: hero
|
|
17
|
-
eyebrow: 为边缘网络构建
|
|
18
|
-
title: 用清晰、可编辑的源文件构建快速网站。
|
|
19
|
-
text: EdgePress 将 Markdown 文章、页面布局、主题和遵循同意状态的集成构建为由 Cloudflare Workers 提供服务的静态网站。
|
|
20
|
-
mascotSrc: /edgepress/brand/mascot-418.png
|
|
21
|
-
mascotAlt: EdgePress 吉祥物,一只由层叠折页构成、神情好奇的小鸟。
|
|
22
|
-
mascotWidth: 418
|
|
23
|
-
mascotHeight: 440
|
|
24
|
-
mascotSizes: 209px
|
|
25
|
-
mascotWebpSrcset:
|
|
26
|
-
- src: /edgepress/brand/mascot-209.webp
|
|
27
|
-
width: 209
|
|
28
|
-
- src: /edgepress/brand/mascot-418.webp
|
|
29
|
-
width: 418
|
|
30
|
-
cta:
|
|
31
|
-
label: 从快速入门开始
|
|
32
|
-
url: /[launge]/quick-start/
|
|
33
|
-
highlights:
|
|
34
|
-
- 在本地化 Markdown 前置数据中编辑页面
|
|
35
|
-
- 使用 Markdown 撰写文章
|
|
36
|
-
- 复用布局,配色由主题定义
|
|
37
|
-
- columns: 1
|
|
38
|
-
cells:
|
|
39
|
-
-
|
|
40
|
-
- type: feature-grid
|
|
41
|
-
title: 网站各部分各有清晰位置
|
|
42
|
-
items:
|
|
43
|
-
- title: 页面使用可编辑元素
|
|
44
|
-
icon: layout-grid
|
|
45
|
-
text: 先设置栏数,再选择元素类型,并在各页面文件中编辑对应内容。
|
|
46
|
-
url: /[launge]/theme-development/
|
|
47
|
-
- title: 文章使用 Markdown
|
|
48
|
-
icon: file-text
|
|
49
|
-
text: 使用 Markdown 撰写长文,由内置渲染器进行清理和排版。
|
|
50
|
-
url: /[launge]/project-introduction/
|
|
51
|
-
- title: 主题负责视觉设计
|
|
52
|
-
icon: palette
|
|
53
|
-
text: 使用 HTML 局部模板构建布局,并将各主题的配色保留在样式表中。
|
|
54
|
-
url: /[launge]/theme-development/
|
|
55
|
-
- title: 集成遵循用户同意
|
|
56
|
-
icon: shield-check
|
|
57
|
-
text: 配置跟踪、统计、广告或人机验证服务及其供应商信息。
|
|
58
|
-
url: /[launge]/plugin-development/
|
|
59
|
-
- columns: 2
|
|
60
|
-
cells:
|
|
61
|
-
-
|
|
62
|
-
- type: section
|
|
63
|
-
title: 了解项目
|
|
64
|
-
tone: soft
|
|
65
|
-
blocks:
|
|
66
|
-
- type: text
|
|
67
|
-
paragraphs:
|
|
68
|
-
- 从项目介绍和快速入门开始。指南涵盖功能生命周期、支持的运行环境和建议的发布流程。
|
|
69
|
-
- type: link-list
|
|
70
|
-
title: 项目指南
|
|
71
|
-
items:
|
|
72
|
-
- label: 项目介绍
|
|
73
|
-
url: /[launge]/project-introduction/
|
|
74
|
-
- label: 快速入门
|
|
75
|
-
url: /[launge]/quick-start/
|
|
76
|
-
-
|
|
77
|
-
- type: section
|
|
78
|
-
title: 扩展项目
|
|
79
|
-
tone: soft
|
|
80
|
-
blocks:
|
|
81
|
-
- type: text
|
|
82
|
-
paragraphs:
|
|
83
|
-
- 使用主题局部模板复用 head 元数据、导航、页脚和错误页布局。每个页面的行布局和元素内容保存在本地化页面文件中。
|
|
84
|
-
- 构建插件在 Node 中运行。浏览器服务会等到访客明确同意后才请求供应商代码。
|
|
85
|
-
- type: link-list
|
|
86
|
-
title: 开发指南
|
|
87
|
-
items:
|
|
88
|
-
- label: 主题开发
|
|
89
|
-
url: /[launge]/theme-development/
|
|
90
|
-
- label: 插件开发
|
|
91
|
-
url: /[launge]/plugin-development/
|
|
92
|
-
- columns: 1
|
|
93
|
-
cells:
|
|
94
|
-
-
|
|
95
|
-
- type: latest-posts
|
|
96
|
-
title: 最新项目动态
|
|
97
|
-
count: 3
|
|
98
|
-
paginate: true
|
|
99
|
-
---
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Plugin development
|
|
3
|
-
lang: en
|
|
4
|
-
slug: plugin-development
|
|
5
|
-
description: Extend EdgePress with trusted build plugins, consent-gated browser integrations, and private backend forwarding.
|
|
6
|
-
blocks:
|
|
7
|
-
- columns: 1
|
|
8
|
-
cells:
|
|
9
|
-
-
|
|
10
|
-
- type: hero
|
|
11
|
-
eyebrow: Extension guide
|
|
12
|
-
title: Connect trusted build code, browser providers, and backend services.
|
|
13
|
-
text: Build-time plugins run in Node. Browser integrations wait for visitor consent. Runtime backend requests stay behind the Worker.
|
|
14
|
-
- columns: 2
|
|
15
|
-
cells:
|
|
16
|
-
-
|
|
17
|
-
- type: section
|
|
18
|
-
title: Build-time plugins
|
|
19
|
-
tone: soft
|
|
20
|
-
blocks:
|
|
21
|
-
- type: text
|
|
22
|
-
paragraphs:
|
|
23
|
-
- Local JavaScript modules in plugins/ may transform source documents, register hooks or renderers, and generate routes. They execute with the build process permissions, so only load code reviewed by the project owner.
|
|
24
|
-
- The starter consent plugin adds a privacy panel after the shared page layout. Build plugins are trusted code and are not deployed as browser scripts.
|
|
25
|
-
- type: code
|
|
26
|
-
title: Register a route generator
|
|
27
|
-
language: js
|
|
28
|
-
code: |
|
|
29
|
-
export default function projectMeta(api) {
|
|
30
|
-
api.registerGenerator('project-meta', ({ site }) => ({
|
|
31
|
-
path: 'project-meta.json',
|
|
32
|
-
body: JSON.stringify({
|
|
33
|
-
title: site.config.site.title,
|
|
34
|
-
posts: site.posts.length
|
|
35
|
-
}),
|
|
36
|
-
contentType: 'application/json; charset=utf-8'
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
-
|
|
40
|
-
- type: section
|
|
41
|
-
title: Tracking, statistics, advertising, and CAPTCHA
|
|
42
|
-
tone: soft
|
|
43
|
-
blocks:
|
|
44
|
-
- type: text
|
|
45
|
-
paragraphs:
|
|
46
|
-
- Add browser providers in config.yml under plugins.consent.tracking, plugins.consent.statistics, plugins.consent.advertising, or plugins.consent.captcha. Each service needs a unique ID, provider, purpose, dataCategories, recipient, retention, a privacyUrl pointing to the provider's current privacy information, and its public site identifier. privacyUrl must be an HTTPS URL. Localized fields are maps keyed by every enabled locale, such as en and zh-CN.
|
|
47
|
-
- Available providers include Google Tag Manager and Meta Pixel; Cloudflare Web Analytics, Google Analytics, and Baidu Tongji; Google AdSense; and Cloudflare Turnstile, Google reCAPTCHA, and hCaptcha.
|
|
48
|
-
- Set plugins.consent.proposedDate to the YYYY-MM-DD date the notice was drafted. Set effectiveDate when the notice takes effect; omit it while no effective date has been set. Updating either date makes saved consent stale, so visitors are asked again.
|
|
49
|
-
- Provider code is imported only after the visitor accepts that integration. The first layer offers accept and reject together; service data and individual switches are available in its details. Keep CAPTCHA secret keys out of config.yml and client code. The server must submit each CAPTCHA response to the provider verification API before accepting a form.
|
|
50
|
-
- type: notice
|
|
51
|
-
title: Operator details
|
|
52
|
-
text: Fill privacy.controller.name, privacy.controller.contact, and privacy.policyUrl before configuring any browser provider. The build rejects provider services when the controller name or contact is blank.
|
|
53
|
-
tone: warning
|
|
54
|
-
- columns: 1
|
|
55
|
-
cells:
|
|
56
|
-
-
|
|
57
|
-
- type: section
|
|
58
|
-
title: Forward requests to a URL and token backend
|
|
59
|
-
blocks:
|
|
60
|
-
- type: text
|
|
61
|
-
paragraphs:
|
|
62
|
-
- The Worker exposes /api/v1/* for GET, POST, PUT, PATCH, and DELETE. With BACKEND_URL and BACKEND_TOKEN configured, it appends the request path and query to the backend URL, sends a Bearer token, forwards a small allowlist of request headers, and marks responses no-store.
|
|
63
|
-
- BACKEND_URL must use HTTPS. Plain HTTP is accepted only for localhost development. Request bodies are limited to 1 MB. The backend still needs to authenticate the scoped token, validate user input, authorize every action, and enforce rate and size limits.
|
|
64
|
-
- Prefer a Cloudflare BACKEND Service Binding for private Worker-to-Worker calls. A browser must never receive BACKEND_TOKEN. This generic proxy does not create application accounts, forms, or data models by itself.
|
|
65
|
-
- type: steps
|
|
66
|
-
items:
|
|
67
|
-
- title: Set the backend base URL
|
|
68
|
-
text: Add vars.BACKEND_URL in wrangler.jsonc. Use an HTTPS base URL without credentials or a token in its URL.
|
|
69
|
-
- title: Store the secret
|
|
70
|
-
text: Wrangler prompts for the secret value. Use a narrowly scoped backend token and rotate it under the operator's process.
|
|
71
|
-
command: edgepress secret put BACKEND_TOKEN
|
|
72
|
-
- title: Configure and deploy
|
|
73
|
-
text: Confirm the backend accepts the forwarded path and Bearer token, then deploy the Worker.
|
|
74
|
-
command: edgepress deploy
|
|
75
|
-
- type: code
|
|
76
|
-
title: Local development
|
|
77
|
-
language: sh
|
|
78
|
-
code: |
|
|
79
|
-
# .dev.vars is local and must not be committed
|
|
80
|
-
BACKEND_URL=http://127.0.0.1:8788
|
|
81
|
-
BACKEND_TOKEN=replace-with-a-local-token
|
|
82
|
-
- type: text
|
|
83
|
-
text: The Worker returns 503 when no backend binding or URL and token are configured. It rejects unsupported methods and oversized request bodies.
|
|
84
|
-
- type: link-list
|
|
85
|
-
title: Provider validation reference
|
|
86
|
-
items:
|
|
87
|
-
- label: Cloudflare Turnstile Siteverify
|
|
88
|
-
url: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
|
|
89
|
-
- label: EDPB consent summary
|
|
90
|
-
url: https://www.edpb.europa.eu/system/files/2026-04/edpb-summary-consent_en.pdf
|
|
91
|
-
- columns: 1
|
|
92
|
-
cells:
|
|
93
|
-
-
|
|
94
|
-
- type: section
|
|
95
|
-
title: Content security and maintenance
|
|
96
|
-
blocks:
|
|
97
|
-
- type: text
|
|
98
|
-
paragraphs:
|
|
99
|
-
- The built-in Markdown renderer sanitizes post output. Custom renderers, plugins, themes, and files in static/ are trusted project code; review changes before building or deploying. Do not put secrets in posts, themes, or public assets.
|
|
100
|
-
- edgepress iterate writes a report-only review plan under .edgepress/reports/. It does not edit source files, update dependencies, or deploy. edgepress security checks dependency advisories. Review the lockfile and generated page report before deployment, and keep a known-good deployment for rollback.
|
|
101
|
-
- type: code
|
|
102
|
-
title: Review project changes
|
|
103
|
-
language: sh
|
|
104
|
-
code: |
|
|
105
|
-
edgepress security
|
|
106
|
-
edgepress iterate
|
|
107
|
-
edgepress check
|
|
108
|
-
edgepress server
|
|
109
|
-
---
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 插件开发
|
|
3
|
-
lang: zh-CN
|
|
4
|
-
slug: plugin-development
|
|
5
|
-
description: 使用可信构建插件、遵循同意设置的浏览器集成和私有后端转发扩展 EdgePress。
|
|
6
|
-
blocks:
|
|
7
|
-
- columns: 1
|
|
8
|
-
cells:
|
|
9
|
-
-
|
|
10
|
-
- type: hero
|
|
11
|
-
eyebrow: 扩展指南
|
|
12
|
-
title: 连接可信构建代码、浏览器供应商和后端服务。
|
|
13
|
-
text: 构建插件在 Node 中运行。浏览器集成需等待访客同意。运行时后端请求由 Worker 转发。
|
|
14
|
-
- columns: 2
|
|
15
|
-
cells:
|
|
16
|
-
-
|
|
17
|
-
- type: section
|
|
18
|
-
title: 构建插件
|
|
19
|
-
tone: soft
|
|
20
|
-
blocks:
|
|
21
|
-
- type: text
|
|
22
|
-
paragraphs:
|
|
23
|
-
- plugins/ 中的本地 JavaScript 模块可以转换源文档、注册钩子或渲染器,并生成路由。它们拥有构建进程的权限,因此只应加载项目负责人审查过的代码。
|
|
24
|
-
- 起始同意插件会在共享页面布局后添加隐私面板。构建插件属于可信代码,不会作为浏览器脚本发布。
|
|
25
|
-
- type: code
|
|
26
|
-
title: 注册路由生成器
|
|
27
|
-
language: js
|
|
28
|
-
code: |
|
|
29
|
-
export default function projectMeta(api) {
|
|
30
|
-
api.registerGenerator('project-meta', ({ site }) => ({
|
|
31
|
-
path: 'project-meta.json',
|
|
32
|
-
body: JSON.stringify({
|
|
33
|
-
title: site.config.site.title,
|
|
34
|
-
posts: site.posts.length
|
|
35
|
-
}),
|
|
36
|
-
contentType: 'application/json; charset=utf-8'
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
-
|
|
40
|
-
- type: section
|
|
41
|
-
title: 跟踪、统计、广告和人机验证
|
|
42
|
-
tone: soft
|
|
43
|
-
blocks:
|
|
44
|
-
- type: text
|
|
45
|
-
paragraphs:
|
|
46
|
-
- 在 config.yml 的 plugins.consent.tracking、plugins.consent.statistics、plugins.consent.advertising 或 plugins.consent.captcha 下添加浏览器供应商。每项需要唯一 ID、provider、purpose、dataCategories、recipient、retention、指向供应商当前隐私说明的 privacyUrl,以及公开站点标识。privacyUrl 必须是 HTTPS 地址。多语言字段需使用语言映射,覆盖已启用的每种语言,例如 en 与 zh-CN。
|
|
47
|
-
- 可用供应商包括 Google Tag Manager 和 Meta Pixel;Cloudflare Web Analytics、Google Analytics 和百度统计;Google AdSense;以及 Cloudflare Turnstile、Google reCAPTCHA 和 hCaptcha。
|
|
48
|
-
- plugins.consent.proposedDate 必须设置为 YYYY-MM-DD 格式的告知拟定日期。告知生效后再设置 effectiveDate;尚未生效时可以省略。修改任一日期都会使旧的同意记录失效,并重新询问访客。
|
|
49
|
-
- 只有访客接受对应集成后才会导入供应商代码。首层同时提供接受和拒绝,数据详情与逐项开关位于展开内容中。不要把 CAPTCHA 密钥放入 config.yml 或客户端代码。服务器必须在接受表单前将每个 CAPTCHA 响应提交给供应商验证 API。
|
|
50
|
-
- type: notice
|
|
51
|
-
title: 运营者信息
|
|
52
|
-
text: 配置浏览器供应商前,请填写 privacy.controller.name、privacy.controller.contact 和 privacy.policyUrl。控制者名称或联系方式为空时,构建器会拒绝启用供应商服务。
|
|
53
|
-
tone: warning
|
|
54
|
-
- columns: 1
|
|
55
|
-
cells:
|
|
56
|
-
-
|
|
57
|
-
- type: section
|
|
58
|
-
title: 通过 URL 和令牌转发后端请求
|
|
59
|
-
blocks:
|
|
60
|
-
- type: text
|
|
61
|
-
paragraphs:
|
|
62
|
-
- Worker 为 GET、POST、PUT、PATCH 和 DELETE 提供 /api/v1/* 接口。配置 BACKEND_URL 和 BACKEND_TOKEN 后,Worker 会将请求路径及查询参数附加到后端 URL,发送 Bearer 令牌,转发有限的请求头,并为响应设置 no-store。
|
|
63
|
-
- BACKEND_URL 必须使用 HTTPS。只有 localhost 开发环境允许普通 HTTP。请求正文最大为 1 MB。后端仍需验证受限令牌、校验用户输入、授权每项操作,并执行自己的请求频率和大小限制。
|
|
64
|
-
- 私有 Worker 间调用优先使用 Cloudflare BACKEND Service Binding。浏览器绝不能获得 BACKEND_TOKEN。此通用代理本身不会创建应用账户、表单或数据模型。
|
|
65
|
-
- type: steps
|
|
66
|
-
items:
|
|
67
|
-
- title: 设置后端基础 URL
|
|
68
|
-
text: 在 wrangler.jsonc 中添加 vars.BACKEND_URL。基础 URL 应使用 HTTPS,且不能包含凭据或令牌。
|
|
69
|
-
- title: 保存密钥
|
|
70
|
-
text: Wrangler 会提示输入密钥值。请使用权限范围有限的后端令牌,并依照运营者流程定期轮换。
|
|
71
|
-
command: edgepress secret put BACKEND_TOKEN
|
|
72
|
-
- title: 配置并部署
|
|
73
|
-
text: 确认后端接受转发路径和 Bearer 令牌,再部署 Worker。
|
|
74
|
-
command: edgepress deploy
|
|
75
|
-
- type: code
|
|
76
|
-
title: 本地开发
|
|
77
|
-
language: sh
|
|
78
|
-
code: |
|
|
79
|
-
# .dev.vars 仅供本地使用,不要提交到版本库
|
|
80
|
-
BACKEND_URL=http://127.0.0.1:8788
|
|
81
|
-
BACKEND_TOKEN=replace-with-a-local-token
|
|
82
|
-
- type: text
|
|
83
|
-
text: 未配置后端绑定,或 URL 和令牌时,Worker 会返回 503。它会拒绝不支持的方法和过大的请求正文。
|
|
84
|
-
- type: link-list
|
|
85
|
-
title: 供应商验证参考
|
|
86
|
-
items:
|
|
87
|
-
- label: Cloudflare Turnstile Siteverify
|
|
88
|
-
url: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
|
|
89
|
-
- label: EDPB 同意摘要
|
|
90
|
-
url: https://www.edpb.europa.eu/system/files/2026-04/edpb-summary-consent_en.pdf
|
|
91
|
-
- columns: 1
|
|
92
|
-
cells:
|
|
93
|
-
-
|
|
94
|
-
- type: section
|
|
95
|
-
title: 内容安全与维护
|
|
96
|
-
blocks:
|
|
97
|
-
- type: text
|
|
98
|
-
paragraphs:
|
|
99
|
-
- 内置 Markdown 渲染器会清理文章输出。自定义渲染器、插件、主题和 static/ 中的文件都属于可信项目代码;构建或部署前应审查变更。不要将密钥放在文章、主题或公开资源中。
|
|
100
|
-
- edgepress iterate 会在 .edgepress/reports/ 下生成只读维护计划,不会修改源文件、更新依赖或部署。edgepress security 检查依赖安全公告。部署前请审查锁文件和页面报告,并保留可用于回滚的已知稳定部署。
|
|
101
|
-
- type: code
|
|
102
|
-
title: 审查项目变更
|
|
103
|
-
language: sh
|
|
104
|
-
code: |
|
|
105
|
-
edgepress security
|
|
106
|
-
edgepress iterate
|
|
107
|
-
edgepress check
|
|
108
|
-
edgepress server
|
|
109
|
-
---
|