edgepress 2026.10.12 → 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.
Files changed (57) hide show
  1. package/README.md +12 -2
  2. package/config.yml +171 -171
  3. package/content/assets/edgepress/brand/sources/export-manifest.json +181 -181
  4. package/content/assets/edgepress-markdown-guide.svg +21 -21
  5. package/content/guides/markdown-media.md +38 -0
  6. package/content/guides/product-demos.md +30 -30
  7. package/content/pages/deployment/en.json +1 -1
  8. package/content/posts/2026-09-24-edgepress-release-2026-9-1/en.md +1 -1
  9. package/content/posts/2026-09-24-edgepress-release-2026-9-1/zh-cn.md +1 -1
  10. package/languages/base/en.json +98 -98
  11. package/languages/packs/zh-CN.json +98 -98
  12. package/package.json +90 -90
  13. package/src/assets.js +187 -186
  14. package/src/browser-audit.js +158 -158
  15. package/src/build.js +293 -291
  16. package/src/cli.js +364 -364
  17. package/src/content.js +233 -233
  18. package/src/data-saver.js +3 -3
  19. package/src/generators.js +414 -420
  20. package/src/i18n.js +86 -86
  21. package/src/markdown.js +195 -160
  22. package/src/page-check.js +1 -1
  23. package/src/plugin-api.js +77 -77
  24. package/src/report-visuals.js +642 -642
  25. package/src/revision-cards.js +30 -30
  26. package/src/rss.js +100 -0
  27. package/src/theme.js +4 -4
  28. package/src/video-metadata.js +36 -0
  29. package/static/edgepress/fonts/faces.css +2217 -2217
  30. package/static/edgepress/media-viewer.js +67 -10
  31. package/static/edgepress/project-demo.js +83 -83
  32. package/static/edgepress/search.js +84 -84
  33. package/tests/assets-runtime.test.mjs +26 -26
  34. package/tests/audit.test.mjs +142 -142
  35. package/tests/brand-regression.test.mjs +110 -110
  36. package/tests/document-package.test.mjs +2 -2
  37. package/tests/embed-syntax.test.mjs +68 -68
  38. package/tests/image-viewer.test.mjs +36 -36
  39. package/tests/markdown-media.test.mjs +119 -0
  40. package/tests/oembed-layout.test.mjs +6 -1
  41. package/tests/project-demo.test.mjs +73 -73
  42. package/tests/release-version.test.mjs +28 -0
  43. package/tests/rss.test.mjs +54 -0
  44. package/tests/video-metadata.test.mjs +28 -0
  45. package/tests/writing.test.mjs +83 -83
  46. package/themes/atelier/assets/style.css +340 -340
  47. package/themes/default/assets/style.css +371 -371
  48. package/themes/default/layouts/partials/head.html +1 -1
  49. package/themes/folio/assets/style.css +515 -515
  50. package/themes/folio/theme.json +5 -5
  51. package/themes/signal/assets/style.css +332 -332
  52. package/tools/README.md +10 -10
  53. package/tools/assets/generate-brand-assets.py +160 -160
  54. package/tools/assets/video-preview.mjs +15 -0
  55. package/tools/recordings/demo-workbench.mjs +53 -53
  56. package/tools/recordings/desktop-recording.mjs +70 -70
  57. package/tools/recordings/vm-desktop.mjs +59 -59
package/src/plugin-api.js CHANGED
@@ -1,81 +1,81 @@
1
- import { isAbsolute, relative, resolve, sep } from 'node:path';
2
- import { realpath } from 'node:fs/promises';
3
- import { pathToFileURL } from 'node:url';
4
- import { renderMarkdown } from './markdown.js';
1
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
2
+ import { realpath } from 'node:fs/promises';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { renderMarkdown } from './markdown.js';
5
5
  import {renderEmbed} from './embed.js';
6
6
  import {renderDocument} from './document.js';
7
- import {localizedContentUrl} from './i18n.js';
8
-
9
- export async function createExtensions(config) {
10
- const filters = new Map();
11
- const generators = new Map();
12
- const renderers = new Map([['.md', { render: (source, context) => {
13
- const locale = context.document.locale;
14
- const base = context.config.i18n.translationsByLocale?.[context.config.i18n.defaultLocale];
15
- const selected = context.config.i18n.translationsByLocale?.[locale];
16
- return renderMarkdown(source, {
17
- ...context.config.markdown,
18
- linkResolver:value=>localizedContentUrl(context.config,locale,value),
19
- taskLabels: {
20
- complete: selected?.markdownTaskComplete ?? base?.markdownTaskComplete ?? 'Task complete',
21
- incomplete: selected?.markdownTaskIncomplete ?? base?.markdownTaskIncomplete ?? 'Task incomplete'
22
- },
7
+ import {localizedContentUrl} from './i18n.js';
8
+
9
+ export async function createExtensions(config) {
10
+ const filters = new Map();
11
+ const generators = new Map();
12
+ const renderers = new Map([['.md', { render: (source, context) => {
13
+ const locale = context.document.locale;
14
+ const base = context.config.i18n.translationsByLocale?.[context.config.i18n.defaultLocale];
15
+ const selected = context.config.i18n.translationsByLocale?.[locale];
16
+ return renderMarkdown(source, {
17
+ ...context.config.markdown,
18
+ linkResolver:value=>localizedContentUrl(context.config,locale,value),
19
+ taskLabels: {
20
+ complete: selected?.markdownTaskComplete ?? base?.markdownTaskComplete ?? 'Task complete',
21
+ incomplete: selected?.markdownTaskIncomplete ?? base?.markdownTaskIncomplete ?? 'Task incomplete'
22
+ },
23
23
  embedRenderer: block => renderEmbed(block, {config: context.config, locale}),
24
24
  documentRenderer: block => renderDocument(block, {config: context.config, locale})
25
- });
26
- }, cacheable: true }]]);
27
-
28
- function registerFilter(name, fn) {
29
- if (typeof fn !== 'function') throw new TypeError('A filter must be a function');
30
- const list = filters.get(name) ?? [];
31
- list.push(fn);
32
- filters.set(name, list);
33
- }
34
- function registerGenerator(name, fn) {
35
- if (generators.has(name)) throw new Error('Generator already registered: ' + name);
36
- if (typeof fn !== 'function') throw new TypeError('A generator must be a function');
37
- generators.set(name, fn);
38
- }
39
- function registerRenderer(extension, fn) {
40
- if (typeof fn !== 'function') throw new TypeError('A renderer must be a function');
41
- const ext = extension.startsWith('.') ? extension.toLowerCase() : '.' + extension.toLowerCase();
42
- renderers.set(ext, { render: fn, cacheable: false });
43
- }
44
-
45
- const api = { registerFilter, registerGenerator, registerRenderer };
46
- const realRoot = await realpath(config.root);
47
- for (const pluginPath of config.plugins) {
48
- if (typeof pluginPath !== 'string' || !pluginPath.trim() || isAbsolute(pluginPath)) {
49
- throw new Error('Each plugin must be a non-empty relative module path');
50
- }
51
- const pluginFile = resolve(config.root, pluginPath);
52
- const pluginRelative = relative(config.root, pluginFile);
53
- if (pluginRelative === '..' || pluginRelative.startsWith('..' + sep) || isAbsolute(pluginRelative)) {
54
- throw new Error('Plugin paths must stay inside the project root: ' + pluginPath);
55
- }
56
- const realPluginFile = await realpath(pluginFile);
57
- const realPluginRelative = relative(realRoot, realPluginFile);
58
- if (realPluginRelative === '..' || realPluginRelative.startsWith('..' + sep) || isAbsolute(realPluginRelative)) {
59
- throw new Error('Plugin paths must resolve inside the project root, including through symbolic links: ' + pluginPath);
60
- }
61
- const url = pathToFileURL(pluginFile).href;
62
- const plugin = (await import(url)).default;
63
- if (typeof plugin !== 'function') throw new Error('Plugin must have a default function export: ' + pluginPath);
64
- await plugin(api);
65
- }
66
-
67
- return {
68
- generators,
69
- renderer(extension) {
70
- return renderers.get(extension.toLowerCase()) ?? null;
71
- },
72
- async filter(name, value, context = {}) {
73
- let current = value;
74
- for (const fn of filters.get(name) ?? []) {
75
- const next = await fn(current, context);
76
- if (next !== undefined) current = next;
77
- }
78
- return current;
79
- }
80
- };
81
- }
25
+ });
26
+ }, cacheable: true }]]);
27
+
28
+ function registerFilter(name, fn) {
29
+ if (typeof fn !== 'function') throw new TypeError('A filter must be a function');
30
+ const list = filters.get(name) ?? [];
31
+ list.push(fn);
32
+ filters.set(name, list);
33
+ }
34
+ function registerGenerator(name, fn) {
35
+ if (generators.has(name)) throw new Error('Generator already registered: ' + name);
36
+ if (typeof fn !== 'function') throw new TypeError('A generator must be a function');
37
+ generators.set(name, fn);
38
+ }
39
+ function registerRenderer(extension, fn) {
40
+ if (typeof fn !== 'function') throw new TypeError('A renderer must be a function');
41
+ const ext = extension.startsWith('.') ? extension.toLowerCase() : '.' + extension.toLowerCase();
42
+ renderers.set(ext, { render: fn, cacheable: false });
43
+ }
44
+
45
+ const api = { registerFilter, registerGenerator, registerRenderer };
46
+ const realRoot = await realpath(config.root);
47
+ for (const pluginPath of config.plugins) {
48
+ if (typeof pluginPath !== 'string' || !pluginPath.trim() || isAbsolute(pluginPath)) {
49
+ throw new Error('Each plugin must be a non-empty relative module path');
50
+ }
51
+ const pluginFile = resolve(config.root, pluginPath);
52
+ const pluginRelative = relative(config.root, pluginFile);
53
+ if (pluginRelative === '..' || pluginRelative.startsWith('..' + sep) || isAbsolute(pluginRelative)) {
54
+ throw new Error('Plugin paths must stay inside the project root: ' + pluginPath);
55
+ }
56
+ const realPluginFile = await realpath(pluginFile);
57
+ const realPluginRelative = relative(realRoot, realPluginFile);
58
+ if (realPluginRelative === '..' || realPluginRelative.startsWith('..' + sep) || isAbsolute(realPluginRelative)) {
59
+ throw new Error('Plugin paths must resolve inside the project root, including through symbolic links: ' + pluginPath);
60
+ }
61
+ const url = pathToFileURL(pluginFile).href;
62
+ const plugin = (await import(url)).default;
63
+ if (typeof plugin !== 'function') throw new Error('Plugin must have a default function export: ' + pluginPath);
64
+ await plugin(api);
65
+ }
66
+
67
+ return {
68
+ generators,
69
+ renderer(extension) {
70
+ return renderers.get(extension.toLowerCase()) ?? null;
71
+ },
72
+ async filter(name, value, context = {}) {
73
+ let current = value;
74
+ for (const fn of filters.get(name) ?? []) {
75
+ const next = await fn(current, context);
76
+ if (next !== undefined) current = next;
77
+ }
78
+ return current;
79
+ }
80
+ };
81
+ }