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.
- package/README.md +12 -2
- package/config.yml +171 -171
- package/content/assets/edgepress/brand/sources/export-manifest.json +181 -181
- package/content/assets/edgepress-markdown-guide.svg +21 -21
- package/content/guides/markdown-media.md +38 -0
- package/content/guides/product-demos.md +30 -30
- package/content/pages/deployment/en.json +1 -1
- 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/languages/base/en.json +98 -98
- package/languages/packs/zh-CN.json +98 -98
- package/package.json +90 -90
- package/src/assets.js +187 -186
- package/src/browser-audit.js +158 -158
- package/src/build.js +293 -291
- package/src/cli.js +364 -364
- package/src/content.js +233 -233
- package/src/data-saver.js +3 -3
- package/src/generators.js +414 -420
- package/src/i18n.js +86 -86
- package/src/markdown.js +195 -160
- package/src/page-check.js +1 -1
- package/src/plugin-api.js +77 -77
- package/src/report-visuals.js +642 -642
- package/src/revision-cards.js +30 -30
- package/src/rss.js +100 -0
- package/src/theme.js +4 -4
- package/src/video-metadata.js +36 -0
- package/static/edgepress/fonts/faces.css +2217 -2217
- package/static/edgepress/media-viewer.js +67 -10
- package/static/edgepress/project-demo.js +83 -83
- package/static/edgepress/search.js +84 -84
- package/tests/assets-runtime.test.mjs +26 -26
- package/tests/audit.test.mjs +142 -142
- package/tests/brand-regression.test.mjs +110 -110
- package/tests/document-package.test.mjs +2 -2
- package/tests/embed-syntax.test.mjs +68 -68
- package/tests/image-viewer.test.mjs +36 -36
- package/tests/markdown-media.test.mjs +119 -0
- package/tests/oembed-layout.test.mjs +6 -1
- package/tests/project-demo.test.mjs +73 -73
- package/tests/release-version.test.mjs +28 -0
- package/tests/rss.test.mjs +54 -0
- package/tests/video-metadata.test.mjs +28 -0
- package/tests/writing.test.mjs +83 -83
- package/themes/atelier/assets/style.css +340 -340
- package/themes/default/assets/style.css +371 -371
- package/themes/default/layouts/partials/head.html +1 -1
- package/themes/folio/assets/style.css +515 -515
- package/themes/folio/theme.json +5 -5
- package/themes/signal/assets/style.css +332 -332
- package/tools/README.md +10 -10
- package/tools/assets/generate-brand-assets.py +160 -160
- package/tools/assets/video-preview.mjs +15 -0
- package/tools/recordings/demo-workbench.mjs +53 -53
- package/tools/recordings/desktop-recording.mjs +70 -70
- 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
|
+
}
|