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/generators.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import {renderPostServices,hasServiceBlock} from './service-block.js';
|
|
2
|
+
import {generateRssRoute} from './rss.js';
|
|
3
|
+
import {renderRevisionCards} from './revision-cards.js';
|
|
4
|
+
import { authorForPost, readingMinutes, filterPostsByCategory, categorySlug, normalizeCategory } from './post-details.js';
|
|
1
5
|
import { plainText, renderMarkdownExcerpt } from './markdown.js';
|
|
2
6
|
import { renderBlocks } from './page-blocks.js';
|
|
3
7
|
import { renderLayout } from './theme.js';
|
|
4
|
-
import { localizedUrl, translate } from './i18n.js';
|
|
8
|
+
import { localizedContentUrl, localizedUrl, translate } from './i18n.js';
|
|
5
9
|
import { postsForLocale, slugify, sortPostsNewest } from './content.js';
|
|
6
10
|
import { mapLimit } from './concurrency.js';
|
|
7
11
|
import { renderIcon } from './icons.js';
|
|
@@ -23,13 +27,8 @@ function urlFor(path) {
|
|
|
23
27
|
return '/' + parts.join('/') + (String(path).endsWith('/') && parts.length ? '/' : '');
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
return slugify(tag);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function dateLabel(date, language) {
|
|
32
|
-
return new Intl.DateTimeFormat(language, { dateStyle: 'long', timeZone: 'UTC' }).format(date);
|
|
30
|
+
function dateLabel(date, language, timeZone = 'UTC', dateOnly = true) {
|
|
31
|
+
return new Intl.DateTimeFormat(language, dateOnly?{dateStyle:'long',timeZone}:{year:'numeric',month:'long',day:'numeric',hour:'2-digit',minute:'2-digit',timeZoneName:'short',timeZone}).format(date);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
function fileRoute(path, body, contentType = 'text/html; charset=utf-8') {
|
|
@@ -87,7 +86,7 @@ function addPostContents(html, locale, config) {
|
|
|
87
86
|
function renderPostVideo(video, locale) {
|
|
88
87
|
if (!video) return '';
|
|
89
88
|
const videoTitle = escapeHtml(video.title);
|
|
90
|
-
return '<figure class="post-video"><video controls playsinline preload="
|
|
89
|
+
return '<figure class="post-video"><video data-edgepress-video controls playsinline preload="none" poster="' + escapeHtml(video.poster) + '" aria-label="' +
|
|
91
90
|
escapeHtml(video.alt) + '"><source src="' + escapeHtml(video.src) + '" type="video/mp4"><track kind="captions" src="' +
|
|
92
91
|
escapeHtml(video.captions) + '" srclang="' + escapeHtml(video.captionLanguage) + '" label="' + escapeHtml(video.captionLabel) + '" default>' +
|
|
93
92
|
'<a href="' + escapeHtml(video.src) + '">' + (locale.toLowerCase().startsWith('zh') ? '下载 MP4 视频' : 'Download the MP4 video') +
|
|
@@ -128,16 +127,50 @@ function renderPostAuthor(post, locale, config) {
|
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
async function renderPostCard(post, locale, config, showLanguage = false) {
|
|
131
|
-
const summary = await renderMarkdownExcerpt(post.description || post.markdown, config.markdown);
|
|
130
|
+
const summary = await renderMarkdownExcerpt(post.description || post.markdown, {...config.markdown,linkResolver:value=>localizedContentUrl(config,post.locale,value)});
|
|
131
|
+
const postLanguage = showLanguage && post.locale !== locale ? translate(config, post.locale, 'languageName') : '';
|
|
132
132
|
return '<article class="post-card" lang="' + escapeHtml(post.locale) + '"><h2><a href="' + escapeHtml(urlFor(post.path)) + '">' + escapeHtml(post.title) + '</a></h2>' +
|
|
133
|
-
'<p class="meta"><time datetime="' + post.date.toISOString() + '">' + escapeHtml(dateLabel(post.date, post.locale)) + '</time>' +
|
|
134
|
-
(
|
|
135
|
-
(post.author ? '
|
|
133
|
+
'<p class="meta"><time data-local-time data-time-locale="' + escapeHtml(post.locale || locale) + '" data-date-only="' + (post.dateOnly === true) + '" datetime="' + (post.dateOnly ? post.date.toISOString().slice(0,10) : post.date.toISOString()) + '">' + escapeHtml(dateLabel(post.date, post.locale || locale, post.dateOnly ? 'UTC' : config.site.timeZone, post.dateOnly)) + '</time>' +
|
|
134
|
+
(postLanguage ? ' <span class="post-language" lang="' + escapeHtml(post.locale) + '">' + escapeHtml(postLanguage) + '</span>' : '') +
|
|
135
|
+
(post.author ? ' ' + renderPostAuthor(post, locale, config) : '') + '</p><div class="post-excerpt" lang="' + escapeHtml(post.locale) + '">' +
|
|
136
136
|
summary + '</div></article>';
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
function paginationPageNumbers(currentPage, totalPages) {
|
|
140
|
+
const visible = new Set([1, totalPages]);
|
|
141
|
+
for (let page = Math.max(1, currentPage - 2); page <= Math.min(totalPages, currentPage + 2); page += 1) visible.add(page);
|
|
142
|
+
const pages = [...visible].sort((left, right) => left - right);
|
|
143
|
+
const entries = [];
|
|
144
|
+
for (let index = 0; index < pages.length; index += 1) {
|
|
145
|
+
const page = pages[index];
|
|
146
|
+
const previous = pages[index - 1];
|
|
147
|
+
if (previous !== undefined && page - previous > 1) {
|
|
148
|
+
if (page - previous === 2) entries.push(previous + 1);
|
|
149
|
+
else entries.push(null);
|
|
150
|
+
}
|
|
151
|
+
entries.push(page);
|
|
152
|
+
}
|
|
153
|
+
return entries;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function renderPagination(locale, currentPage, totalPages, config, pageUrl) {
|
|
157
|
+
const label = (key) => translate(config, locale, key);
|
|
158
|
+
const pageLinks = paginationPageNumbers(currentPage, totalPages).map((page) => {
|
|
159
|
+
if (page === null) return '<li class="pagination-ellipsis" aria-hidden="true">…</li>';
|
|
160
|
+
if (page === currentPage) return '<li><span class="pagination-current" aria-current="page">' + page + '</span></li>';
|
|
161
|
+
const pageLabel = label('pageNumber').replace('{page}', String(page));
|
|
162
|
+
return '<li><a href="' + escapeHtml(pageUrl(page)) + '" aria-label="' + escapeHtml(pageLabel) + '">' + page + '</a></li>';
|
|
163
|
+
}).join('');
|
|
164
|
+
const previous = currentPage > 1
|
|
165
|
+
? '<a rel="prev" href="' + escapeHtml(pageUrl(currentPage - 1)) + '">' + escapeHtml(label('newer')) + '</a>' : '';
|
|
166
|
+
const next = currentPage < totalPages
|
|
167
|
+
? '<a rel="next" href="' + escapeHtml(pageUrl(currentPage + 1)) + '">' + escapeHtml(label('older')) + '</a>' : '';
|
|
168
|
+
return '<nav class="pagination" aria-label="' + escapeHtml(label('pagination')) + '">' + previous +
|
|
169
|
+
'<ol class="pagination-pages">' + pageLinks + '</ol>' + next + '</nav>';
|
|
170
|
+
}
|
|
171
|
+
|
|
139
172
|
async function pageBodyForDocument(page, site, config, locale, isHomepage = false, renderContext = {}) {
|
|
140
|
-
const content = await renderBlocks(page.blocks, { config, locale, site, isHomepage, ...renderContext });
|
|
173
|
+
const content = await renderBlocks(page.blocks, { config, locale, site, document: page, isHomepage, ...renderContext });
|
|
141
174
|
const firstElement = page.blocks[0]?.cells?.[0]?.[0];
|
|
142
175
|
const needsTitle = !isHomepage || firstElement?.type !== 'hero';
|
|
143
176
|
const title = needsTitle ? '<header class="page-title"><h1>' + escapeHtml(page.title) + '</h1></header>' : '';
|
|
@@ -179,7 +212,8 @@ export async function generateBuiltinRoutes(site, config, extensions) {
|
|
|
179
212
|
const allPosts = sortPostsNewest(site.posts);
|
|
180
213
|
for (const locale of config.i18n.locales) {
|
|
181
214
|
const posts = allPosts.filter((post) => post.locale === locale);
|
|
182
|
-
const
|
|
215
|
+
const localizedPosts = postsForLocale(allPosts, locale, defaultLocale);
|
|
216
|
+
const archivePosts = filterPostsByCategory(localizedPosts, config.site.archive?.categories);
|
|
183
217
|
const pages = site.pages.filter((page) => page.locale === locale && !page.homepage);
|
|
184
218
|
const homePage = homepageDocument(site.pages, locale, defaultLocale);
|
|
185
219
|
const latestPostsPageSize = paginatedLatestPostsSize(homePage);
|
|
@@ -220,12 +254,11 @@ export async function generateBuiltinRoutes(site, config, extensions) {
|
|
|
220
254
|
const cards = await Promise.all(subset.map((post) => renderPostCard(post, locale, config, true)));
|
|
221
255
|
const body = '<section class="intro"><h1>' + escapeHtml(config.site.title) + '</h1><p>' + escapeHtml(config.site.description) +
|
|
222
256
|
'</p></section><section class="post-list" aria-label="' + escapeHtml(label('latestPosts')) + '">' +
|
|
223
|
-
(cards.length ? cards.join('') : '<p>' + escapeHtml(label('noPosts')) + '</p>') +
|
|
257
|
+
(cards.length ? '<div class="post-list archive-post-list">' + cards.join('') + '</div>' : '<p>' + escapeHtml(label('noPosts')) + '</p>') +
|
|
224
258
|
'</section>' + (pageNumber === 1 && pages.length ? '<section class="page-links"><h2>' + escapeHtml(label('pages')) + '</h2><ul>' +
|
|
225
259
|
pages.map((page) => '<li><a href="' + escapeHtml(urlFor(page.path)) + '">' + escapeHtml(page.title) + '</a></li>').join('') + '</ul></section>' : '') +
|
|
226
|
-
(totalPages > 1 ?
|
|
227
|
-
(
|
|
228
|
-
(pageNumber < totalPages ? '<a href="' + escapeHtml(localizedUrl(config, locale, 'page/' + (pageNumber + 1) + '/')) + '">' + escapeHtml(label('older')) + '</a>' : '') + '</nav>' : '');
|
|
260
|
+
(totalPages > 1 ? renderPagination(locale, pageNumber, totalPages, config, (page) =>
|
|
261
|
+
localizedUrl(config, locale, page === 1 ? '' : 'page/' + page + '/')) : '');
|
|
229
262
|
const routePath = pageNumber === 1 ? prefix + 'index.html' : prefix + 'page/' + pageNumber + '/index.html';
|
|
230
263
|
routes.push(await pageRoute(routePath, pageNumber === 1 ? config.site.title : 'Page ' + pageNumber,
|
|
231
264
|
config.site.description, body, locale, config, extensions));
|
|
@@ -235,12 +268,18 @@ export async function generateBuiltinRoutes(site, config, extensions) {
|
|
|
235
268
|
for (const post of posts) allPagePaths.add(urlFor(post.path));
|
|
236
269
|
const postRoutes = await mapLimit(posts, config.concurrency, async (post) => {
|
|
237
270
|
const renderedMarkdown = addPostContents(post.html, locale, config);
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
(
|
|
242
|
-
|
|
243
|
-
|
|
271
|
+
const author = authorForPost(post, config);
|
|
272
|
+
const minutes = post.readingMinutes || readingMinutes(post.markdown);
|
|
273
|
+
const authorHtml = author.name ? '<span class="post-author">' +
|
|
274
|
+
(author.avatar ? '<img class="author-avatar" src="' + escapeHtml(author.avatar) + '" width="40" height="40" alt="" decoding="async">' : '') +
|
|
275
|
+
escapeHtml(label('postAuthor').replace('{author}', author.name)) + '</span>' : '';
|
|
276
|
+
const tagsHtml = post.tags.length ? '<div class="post-tags" role="group" aria-label="' + escapeHtml(label('tags')) + '"><span class="post-tags-label">' +
|
|
277
|
+
escapeHtml(label('tags')) + ':</span>' + post.tags.map(tag => '<span class="post-tag">' + escapeHtml(tag) + '</span>').join('') + '</div>' : '';
|
|
278
|
+
const body = '<article class="post"><header><h1>' + escapeHtml(post.title) + '</h1><div class="meta post-byline">' + authorHtml +
|
|
279
|
+
'<time data-local-time data-time-locale="' + escapeHtml(post.locale || locale) + '" data-date-only="' + (post.dateOnly === true) + '" datetime="' + (post.dateOnly ? post.date.toISOString().slice(0,10) : post.date.toISOString()) + '">' + escapeHtml(dateLabel(post.date, post.locale || locale, post.dateOnly ? 'UTC' : config.site.timeZone, post.dateOnly)) + '</time>' +
|
|
280
|
+
'<span class="post-reading-time" data-reading-minutes="' + minutes + '">' + escapeHtml(label('postReadingTime').replace('{minutes}', minutes)) + '</span>' +
|
|
281
|
+
(post.updated ? '<span class="post-updated">' + escapeHtml(label('postUpdated')) + ' <time data-local-time data-time-locale="' + escapeHtml(locale) + '" datetime="' + post.updated.toISOString() + '">' + escapeHtml(dateLabel(post.updated, locale, config.site.timeZone, false)) + '</time></span>' : '') +
|
|
282
|
+
'</div>' + tagsHtml + '</header>' + (post.showChanges && post.changes ? await renderRevisionCards(post.changes,label,post.revisionImages,{linkResolver:value=>localizedContentUrl(config,locale,value)}) : '') + renderPostVideo(post.video, locale) + renderedMarkdown.toc + '<div class="post-content">' + renderedMarkdown.html + '</div></article>' + renderPostServices(post, locale, config);
|
|
244
283
|
const canonicalPath = urlFor(post.path);
|
|
245
284
|
return pageRoute(post.path + 'index.html', post.title, post.description || plainText(post.markdown).slice(0, 160), body,
|
|
246
285
|
locale, config, extensions, {
|
|
@@ -248,8 +287,8 @@ export async function generateBuiltinRoutes(site, config, extensions) {
|
|
|
248
287
|
structuredData: {
|
|
249
288
|
'@context': 'https://schema.org', '@type': 'BlogPosting', headline: post.title,
|
|
250
289
|
description: post.description || plainText(post.markdown).slice(0, 160),
|
|
251
|
-
datePublished: post.date.toISOString(), dateModified: post.date.toISOString(),
|
|
252
|
-
author: { '@type': 'Person', name:
|
|
290
|
+
datePublished: post.dateOnly ? post.date.toISOString().slice(0,10) : post.date.toISOString(), dateModified: post.updated ? post.updated.toISOString() : post.dateOnly ? post.date.toISOString().slice(0,10) : post.date.toISOString(),
|
|
291
|
+
author: { '@type': 'Person', name: author.name || config.site.title },
|
|
253
292
|
mainEntityOfPage: config.site.url ? config.site.url + canonicalPath : undefined,
|
|
254
293
|
inLanguage: locale
|
|
255
294
|
}
|
|
@@ -275,36 +314,41 @@ export async function generateBuiltinRoutes(site, config, extensions) {
|
|
|
275
314
|
const cards = await Promise.all(subset.map((post) => renderPostCard(post, locale, config, true)));
|
|
276
315
|
const archiveUrl = pageNumber === 1 ? 'archives/' : 'archives/page/' + pageNumber + '/';
|
|
277
316
|
const pagination = archivePageCount > 1
|
|
278
|
-
?
|
|
279
|
-
|
|
280
|
-
localizedUrl(config, locale, 'archives/page/' + (pageNumber - 1) + '/')) + '">' + escapeHtml(label('newer')) + '</a>' : '') +
|
|
281
|
-
(pageNumber < archivePageCount ? '<a rel="next" href="' + escapeHtml(localizedUrl(config, locale, 'archives/page/' + (pageNumber + 1) + '/')) + '">' +
|
|
282
|
-
escapeHtml(label('older')) + '</a>' : '') + '</nav>' : '';
|
|
317
|
+
? renderPagination(locale, pageNumber, archivePageCount, config, (page) => localizedUrl(config, locale,
|
|
318
|
+
page === 1 ? 'archives/' : 'archives/page/' + page + '/')) : '';
|
|
283
319
|
const archiveBody = '<section><h1>' + escapeHtml(label('archives')) + '</h1>' +
|
|
284
|
-
(cards.length ? cards.join('') : '<p>' + escapeHtml(label('noPosts')) + '</p>') + pagination + '</section>';
|
|
320
|
+
(cards.length ? '<div class="post-list archive-post-list">' + cards.join('') + '</div>' : '<p>' + escapeHtml(label('noPosts')) + '</p>') + pagination + '</section>';
|
|
285
321
|
routes.push(await pageRoute(prefix + (pageNumber === 1 ? 'archives/index.html' : 'archives/page/' + pageNumber + '/index.html'),
|
|
286
|
-
pageNumber === 1 ? label('archives') : label('archives') + '
|
|
322
|
+
pageNumber === 1 ? label('archives') : label('archives') + ' ' + pageNumber, label('allPosts'), archiveBody, locale, config, extensions,
|
|
287
323
|
{ urlPath: localizedUrl(config, locale, archiveUrl) }));
|
|
288
324
|
allPagePaths.add(localizedUrl(config, locale, archiveUrl));
|
|
289
325
|
}
|
|
290
326
|
|
|
291
|
-
const
|
|
292
|
-
for (const post of
|
|
293
|
-
const
|
|
294
|
-
if (!
|
|
295
|
-
|
|
327
|
+
const categories = new Map();
|
|
328
|
+
for (const post of localizedPosts) {
|
|
329
|
+
const category = normalizeCategory(post.category);
|
|
330
|
+
if (!categories.has(category)) categories.set(category, []);
|
|
331
|
+
categories.get(category).push(post);
|
|
332
|
+
}
|
|
333
|
+
for (const [category, categoryPosts] of categories) {
|
|
334
|
+
const slug = categorySlug(category);
|
|
335
|
+
const title = category === 'blog' ? label('categoryBlog') : category === 'uncategorized' ? label('categoryUncategorized') : category;
|
|
336
|
+
const totalPages = Math.ceil(categoryPosts.length / config.pagination.perPage);
|
|
337
|
+
const categoryPath = number => 'categories/' + slug + '/' + (number === 1 ? '' : 'page/' + number + '/');
|
|
338
|
+
for (let number = 1; number <= totalPages; number += 1) {
|
|
339
|
+
const cards = await Promise.all(categoryPosts.slice((number - 1) * config.pagination.perPage, number * config.pagination.perPage)
|
|
340
|
+
.map(post => renderPostCard(post, locale, config, true)));
|
|
341
|
+
const pagination = totalPages > 1 ? renderPagination(locale, number, totalPages, config,
|
|
342
|
+
page => localizedUrl(config, locale, categoryPath(page))) : '';
|
|
343
|
+
const body = '<section><h1>' + escapeHtml(title) + '</h1><div class="post-list archive-post-list">' + cards.join('') + '</div>' + pagination + '</section>';
|
|
344
|
+
const urlPath = localizedUrl(config, locale, categoryPath(number));
|
|
345
|
+
allPagePaths.add(urlPath);
|
|
346
|
+
routes.push(await pageRoute(prefix + categoryPath(number) + 'index.html', title, title, body, locale, config, extensions, { urlPath }));
|
|
347
|
+
}
|
|
296
348
|
}
|
|
297
|
-
const tagRoutes = await mapLimit([...tags.entries()], config.concurrency, async ([slug, value]) => {
|
|
298
|
-
const cards = await Promise.all(value.posts.map((post) => renderPostCard(post, locale, config)));
|
|
299
|
-
const body = '<section><h1>' + escapeHtml(label('tag')) + ': ' + escapeHtml(value.label) + '</h1>' +
|
|
300
|
-
cards.join('') + '</section>';
|
|
301
|
-
return pageRoute(prefix + 'tags/' + slug + '/index.html', label('tag') + ': ' + value.label,
|
|
302
|
-
label('tag') + ' ' + value.label, body, locale, config, extensions);
|
|
303
|
-
});
|
|
304
|
-
routes.push(...tagRoutes);
|
|
305
349
|
|
|
306
350
|
const searchable = [
|
|
307
|
-
...posts.map((post) => ({ title: post.title, author: post.author, date: post.date.toISOString(), url: urlFor(post.path), summary: plainText(post.description || post.markdown).slice(0, 220), content: plainText(post.markdown), type: 'post' })),
|
|
351
|
+
...posts.map((post) => ({ title: post.title, author: post.author, date: post.dateOnly ? post.date.toISOString().slice(0,10) : post.date.toISOString(), dateOnly: post.dateOnly, url: urlFor(post.path), summary: plainText(post.description || post.markdown).slice(0, 220), content: plainText(post.markdown), type: 'post' })),
|
|
308
352
|
...pages.map((page) => ({ title: page.title, url: urlFor(page.path), summary: page.description || plainText(page.markdown).slice(0, 220), type: 'page' }))
|
|
309
353
|
];
|
|
310
354
|
routes.push(fileRoute(prefix + 'search.json', JSON.stringify(searchable), 'application/json; charset=utf-8'));
|
|
@@ -313,21 +357,14 @@ export async function generateBuiltinRoutes(site, config, extensions) {
|
|
|
313
357
|
'<p>' + escapeHtml(label('searchIntro')) + '</p><form class="local-search-form" data-edgepress-search data-index="' +
|
|
314
358
|
escapeHtml(localizedUrl(config, locale, 'search.json')) + '"><label for="edgepress-search-query">' + escapeHtml(label('searchLabel')) +
|
|
315
359
|
'</label><div class="local-search-controls"><input id="edgepress-search-query" name="q" type="search" autocomplete="off" ' +
|
|
316
|
-
'
|
|
317
|
-
'
|
|
318
|
-
|
|
360
|
+
'><button type="submit">' + escapeHtml(label('searchButton')) + '</button></div>' +
|
|
361
|
+
'</form><p id="edgepress-search-status" role="status" aria-live="polite">' +
|
|
362
|
+
'' + '</p><ol id="edgepress-search-results" class="local-search-results"></ol>' +
|
|
319
363
|
'<noscript><p>' + escapeHtml(label('searchNeedsJavaScript')) + '</p></noscript></section>';
|
|
320
364
|
routes.push(await pageRoute(prefix + 'search/index.html', label('searchPosts'), label('searchIntro'), searchBody,
|
|
321
365
|
locale, config, extensions));
|
|
322
366
|
|
|
323
|
-
|
|
324
|
-
const entries = posts.slice(0, 20).map((post) => '<entry><title>' + escapeXml(post.title) + '</title>' +
|
|
325
|
-
(post.author ? '<author><name>' + escapeXml(post.author) + '</name></author>' : '') + '<link href="' +
|
|
326
|
-
escapeXml(base + urlFor(post.path)) + '"/><id>' + escapeXml(base + urlFor(post.path)) + '</id><updated>' + post.date.toISOString() +
|
|
327
|
-
'</updated><summary>' + escapeXml(plainText(post.description || post.markdown).slice(0, 220)) + '</summary></entry>').join('');
|
|
328
|
-
routes.push(fileRoute(prefix + 'feed.xml', '<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>' +
|
|
329
|
-
escapeXml(config.site.title) + '</title><id>' + escapeXml(base || 'urn:edgepress:site') + '</id><updated>' +
|
|
330
|
-
(posts[0]?.date.toISOString() || new Date(0).toISOString()) + '</updated>' + entries + '</feed>', 'application/atom+xml; charset=utf-8'));
|
|
367
|
+
routes.push(await generateRssRoute(posts, config, locale));
|
|
331
368
|
}
|
|
332
369
|
|
|
333
370
|
const base = config.site.url;
|
|
@@ -368,5 +405,10 @@ export async function generateBuiltinRoutes(site, config, extensions) {
|
|
|
368
405
|
escapeHtml(translate(config, locale, 'returnHome')) + '</a></p></section>';
|
|
369
406
|
routes.push(await pageRoute(prefix + '403.html', forbiddenTitle, forbiddenText, forbiddenBody, locale, config, extensions, { robots: 'noindex,nofollow' }));
|
|
370
407
|
}
|
|
408
|
+
const discussions = config.browserPlugins.services.some(service => service.enabled !== false && service.provider === 'external-widget') ? [
|
|
409
|
+
...site.posts.filter(post => !post.draft).map(post => ({thread:post.bundlePath,title:post.title})),
|
|
410
|
+
...site.pages.filter(page => !page.draft && hasServiceBlock(page.blocks)).map(page => ({thread:'page:' + page.bundlePath,title:page.title}))
|
|
411
|
+
] : [];
|
|
412
|
+
routes.push(fileRoute('edgepress/service-contexts.json', JSON.stringify([...new Map(discussions.map(item => [item.thread,item])).values()]), 'application/json; charset=utf-8'));
|
|
371
413
|
return routes;
|
|
372
414
|
}
|
package/src/i18n.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFile, realpath } from 'node:fs/promises';
|
|
1
|
+
import { readFile, realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
2
3
|
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
4
|
|
|
4
5
|
const MAX_LANGUAGE_FILE_BYTES = 64 * 1024;
|
|
@@ -39,9 +40,16 @@ async function loadDictionary(root, directory, locale, label) {
|
|
|
39
40
|
export async function loadLanguagePacks(config) {
|
|
40
41
|
const baseLocale = config.i18n.defaultLocale;
|
|
41
42
|
const translationsByLocale = Object.create(null);
|
|
42
|
-
|
|
43
|
+
const builtinRoot=fileURLToPath(new URL('../',import.meta.url)),english=await loadDictionary(builtinRoot,'languages/base','en','Built-in');
|
|
44
|
+
async function defaults(locale){
|
|
45
|
+
if(locale==='en')return english;
|
|
46
|
+
const file=resolve(builtinRoot,'languages/packs',locale+'.json');
|
|
47
|
+
const exists=await stat(file).then(()=>true,error=>{if(error.code==='ENOENT')return false;throw error;});
|
|
48
|
+
return exists?{...english,...await loadDictionary(builtinRoot,'languages/packs',locale,'Built-in')}:english;
|
|
49
|
+
}
|
|
50
|
+
translationsByLocale[baseLocale] = {...await defaults(baseLocale),...await loadDictionary(config.root, 'languages/base', baseLocale, 'Base')};
|
|
43
51
|
for (const locale of config.i18n.languagePacks) {
|
|
44
|
-
translationsByLocale[locale] = await loadDictionary(config.root, 'languages/packs', locale, 'Language pack');
|
|
52
|
+
translationsByLocale[locale] = {...await defaults(locale),...await loadDictionary(config.root, 'languages/packs', locale, 'Language pack')};
|
|
45
53
|
}
|
|
46
54
|
config.i18n.translationsByLocale = translationsByLocale;
|
|
47
55
|
return translationsByLocale;
|
|
@@ -68,3 +76,11 @@ export function localizedUrl(config, locale, path) {
|
|
|
68
76
|
const suffix = String(path ?? '').replace(/^\/+/, '');
|
|
69
77
|
return prefix + '/' + suffix;
|
|
70
78
|
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
export function localizedContentUrl(config, locale, value, label = 'Content URL') {
|
|
82
|
+
const url=String(value),token=/^\/(?:\[launge\]|%5blaunge%5d)(?=\/|$)/i;
|
|
83
|
+
if(!url.startsWith('/'))return url;
|
|
84
|
+
if(/\[launge\]|%5blaunge%5d/i.test(url)&&!token.test(url))throw new Error(label+' may use [launge] only as the first site-path segment');
|
|
85
|
+
return token.test(url)?localePrefix(config,locale)+(url.replace(token,'')||'/'):url;
|
|
86
|
+
}
|
package/src/icons.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const iconNames = new Set([
|
|
2
2
|
'home', 'book-open', 'rocket', 'newspaper', 'search', 'shield-check', 'rss', 'file-text',
|
|
3
3
|
'layers', 'code', 'puzzle', 'plug', 'cloud', 'palette', 'globe', 'help-circle', 'arrow-up-right',
|
|
4
|
-
'check-circle', 'layout-grid', 'terminal', 'settings', 'sparkles'
|
|
4
|
+
'check-circle', 'layout-grid', 'terminal', 'settings', 'sparkles', 'cpu', 'mail', 'github', 'check', 'x', 'arrow-right'
|
|
5
5
|
]);
|
|
6
6
|
|
|
7
7
|
export function isIconName(name) {
|
|
@@ -10,7 +10,8 @@ export function isIconName(name) {
|
|
|
10
10
|
|
|
11
11
|
export function renderIcon(name, className = 'icon') {
|
|
12
12
|
if (!isIconName(name)) throw new Error('Unsupported EdgePress icon: ' + String(name));
|
|
13
|
-
if (!/^[a-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
if (typeof className !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_-]*(?: [a-zA-Z_][a-zA-Z0-9_-]*)*$/.test(className)) {
|
|
14
|
+
throw new Error('Icon class must contain space-separated CSS identifiers');
|
|
15
|
+
}
|
|
16
|
+
return '<img class="' + className + ' icon-library" src="/edgepress/icons/' + name + '.svg" width="24" height="24" alt="" aria-hidden="true" decoding="async">';
|
|
16
17
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {readFile} from 'node:fs/promises';
|
|
2
|
+
import {imageSize} from 'image-size';
|
|
3
|
+
import {mapLimit} from './concurrency.js';
|
|
4
|
+
export async function collectImageDimensions(assets){
|
|
5
|
+
const result=new Map();await mapLimit(assets.filter(item=>/\.(?:png|jpe?g|gif|webp|avif|svg)$/i.test(item.path)),8,async item=>{
|
|
6
|
+
try{let {width,height,orientation}=imageSize(item.content??await readFile(item.source));if([5,6,7,8].includes(orientation))[width,height]=[height,width];if(width>0&&height>0)result.set('/'+item.path,{width,height});}catch{}
|
|
7
|
+
});return result;
|
|
8
|
+
}
|
|
9
|
+
export function reserveImageDimensions(html,dimensions,path,origin){
|
|
10
|
+
return html.replace(/<img\b[^>]*>/gi,tag=>{
|
|
11
|
+
const value=name=>tag.match(new RegExp('\\s'+name+'\\s*=\\s*["\']([^"\']+)["\']','i'))?.[1],src=value('src');if(!src)return tag;
|
|
12
|
+
let url,key;try{url=new URL(src,new URL('/'+path,origin));key=decodeURI(url.pathname);}catch{return tag;}
|
|
13
|
+
if(url.origin!==new URL(origin).origin)return tag;const size=dimensions.get(key);if(!size)return tag;
|
|
14
|
+
const w=Number(value('width')),h=Number(value('height'));if(w>0&&h>0)return tag;
|
|
15
|
+
return tag.replace(/\s*\/?>$/,end=>(value('width')?'':' width="'+Math.max(1,Math.round(h>0?h*size.width/size.height:size.width))+'"')+(value('height')?'':' height="'+Math.max(1,Math.round(w>0?w*size.height/size.width:size.height))+'"')+end);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Local tool modules are trusted site source, like theme scripts. External
|
|
2
|
+
// providers belong to consent-controlled services, never this element.
|
|
3
|
+
const escape = value => String(value).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
4
|
+
|
|
5
|
+
export function renderLocalTool(block, context) {
|
|
6
|
+
for (const key of Object.keys(block)) if (!['type','id','title','text','script','stylesheet','data','fallback'].includes(key)) throw new Error('Unsupported local-tool option: ' + key);
|
|
7
|
+
if (typeof block.id !== 'string' || !/^[a-z][a-z0-9-]{0,79}$/.test(block.id)) throw new Error('local-tool.id must be a lowercase HTML identifier');
|
|
8
|
+
if (context.localToolIds?.has(block.id)) throw new Error('Duplicate local-tool.id: ' + block.id);
|
|
9
|
+
context.localToolIds?.add(block.id);
|
|
10
|
+
const string = (key, required = false) => {
|
|
11
|
+
const value = block[key];
|
|
12
|
+
if (value === undefined && !required) return '';
|
|
13
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 2000) throw new Error('local-tool.' + key + ' must be a non-empty string of at most 2000 characters');
|
|
14
|
+
return escape(value);
|
|
15
|
+
};
|
|
16
|
+
const asset = (key, extension, required = false) => {
|
|
17
|
+
if (block[key] === undefined && !required) return '';
|
|
18
|
+
const value = block[key];
|
|
19
|
+
if (typeof value !== 'string' || !/^\/(?!\/)[a-zA-Z0-9_./-]+$/.test(value) || value.split('/').some(p => p==='.' || p==='..') || !extension.test(value)) throw new Error('local-tool.' + key + ' must be a same-origin local asset path');
|
|
20
|
+
if (!context.config.assetManifest?.[value]) throw new Error('local-tool.' + key + ' asset does not exist: ' + value);
|
|
21
|
+
return escape(value);
|
|
22
|
+
};
|
|
23
|
+
const script = asset('script', /\.m?js$/, true);
|
|
24
|
+
const stylesheet = asset('stylesheet', /\.css$/);
|
|
25
|
+
const data = asset('data', /\.json$/);
|
|
26
|
+
const title = string('title', true), description = string('text'), fallback = string('fallback', true);
|
|
27
|
+
const heading = block.id + '-title';
|
|
28
|
+
return '<section class="local-tool" id="' + block.id + '" data-edgepress-local-tool' + (data ? ' data-tool-data="' + data + '"' : '') + ' aria-labelledby="' + heading + '"><h2 id="' + heading + '">' + title + '</h2>' + (description ? '<p class="local-tool-description">' + description + '</p>' : '') + '<div data-tool-mount></div><noscript><p>' + fallback + '</p></noscript></section>' + (stylesheet ? '<link rel="stylesheet" href="' + stylesheet + '">' : '') + '<script type="module" src="' + script + '"></script>';
|
|
29
|
+
}
|
package/src/maintenance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
1
|
+
import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
3
|
|
|
4
4
|
async function readJson(file) {
|
|
5
5
|
return JSON.parse(await readFile(file, 'utf8'));
|
|
@@ -34,16 +34,33 @@ export async function checkCompatibility(config) {
|
|
|
34
34
|
} else if (workerDate < manifest.requirements.minimumWorkersDate) {
|
|
35
35
|
issues.push({ severity: 'error', message: 'Workers compatibility_date is older than the supported minimum.' });
|
|
36
36
|
}
|
|
37
|
-
if (!wrangler.assets || !wrangler.assets.directory
|
|
37
|
+
if (!wrangler.assets || !wrangler.assets.directory) {
|
|
38
38
|
issues.push({ severity: 'error', message: 'Configure the ASSETS binding and static assets directory in Wrangler.' });
|
|
39
39
|
}
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
issues.push({
|
|
40
|
+
const entry = wrangler.main;
|
|
41
|
+
if (entry !== undefined && (typeof entry !== 'string' || !entry.trim())) {
|
|
42
|
+
issues.push({severity:'error',message:'The optional Worker entry must be a non-empty string.'});
|
|
43
|
+
} else if (entry !== undefined) {
|
|
44
|
+
try {
|
|
45
|
+
const realRoot = await realpath(root);
|
|
46
|
+
const workerFile = await realpath(resolve(root, entry));
|
|
47
|
+
const rel = relative(realRoot, workerFile);
|
|
48
|
+
if (rel === '..' || rel.startsWith('..' + sep) || isAbsolute(rel)) {
|
|
49
|
+
issues.push({ severity: 'error', message: 'The configured Worker entry must stay inside the project root.' });
|
|
50
|
+
} else {
|
|
51
|
+
const workerSource = await readFile(workerFile, 'utf8');
|
|
52
|
+
if (/from\s+['"]node:|require\s*\(\s*['"]node:/i.test(workerSource)) {
|
|
53
|
+
issues.push({ severity: 'error', message: 'The configured Worker entry imports a Node.js built-in; keep build-only APIs out of the Worker.' });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (!['ENOENT', 'EISDIR', 'EACCES', 'EPERM'].includes(error.code)) throw error;
|
|
58
|
+
issues.push({ severity: 'error', message: 'Cannot read the configured Worker entry: ' + entry + ' (' + error.code + ').' });
|
|
59
|
+
}
|
|
43
60
|
}
|
|
44
61
|
const first = wrangler.assets?.run_worker_first;
|
|
45
|
-
if (
|
|
46
|
-
issues.push({ severity: 'warning', message: '
|
|
62
|
+
if (first === true || (Array.isArray(first) && first.length)) {
|
|
63
|
+
issues.push({ severity: 'warning', message: 'Static websites should use assets-only hosting; deploy backend services separately.' });
|
|
47
64
|
}
|
|
48
65
|
|
|
49
66
|
const report = {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Renderer verification only: never added to a site's content or generated routes.
|
|
2
|
+
export const markdownFixture = [
|
|
3
|
+
'## Heading two', '### Heading three', '#### Heading four', '##### Heading five', '###### Heading six', '',
|
|
4
|
+
'Setext heading level two', '------------------------', '',
|
|
5
|
+
'A paragraph with a hard break. ', 'The next line.', '',
|
|
6
|
+
'---', '', '***', '', '___', '',
|
|
7
|
+
'*Emphasis* and **strong emphasis** with ~~strikethrough~~.', '',
|
|
8
|
+
'Inline `code`.', '', '```js', 'console.log("fixture");', '```', '',
|
|
9
|
+
'~~~text', 'Plain fenced code.', '~~~', '', ' const indented = true;', '',
|
|
10
|
+
'`` ` ``', '', '\\*escaped punctuation\\* < &', '',
|
|
11
|
+
'[Inline](/quick-start/) and [reference][guide].', '', '[guide]: /quick-start/', '',
|
|
12
|
+
'https://www.markdownguide.org/', '', '<https://commonmark.org> www.example.com <team@example.com>', '',
|
|
13
|
+
'', '',
|
|
14
|
+
'', '',
|
|
15
|
+
'- **Formatted** item', ' 1. Nested ordered item', '',
|
|
16
|
+
'- [x] Completed task', '- [ ] Pending task', '',
|
|
17
|
+
'> - A list in a quotation', '> - Another item', '', '> Outer quotation', '>', '> > Nested quotation', '',
|
|
18
|
+
'| Heading | Value |', '| --- | --- |', '| *Emphasis* | Detail |', '',
|
|
19
|
+
'Use <kbd>Ctrl</kbd>.', '', '<script>alert("unsafe")</script>'
|
|
20
|
+
].join('\n');
|