@webround/editor 0.0.0-stage → 0.3.0
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/dist/blocks.d.ts +90 -0
- package/dist/blocks.js +119 -0
- package/dist/bridge.d.ts +96 -0
- package/dist/bridge.js +970 -0
- package/dist/check/aria.d.ts +19 -0
- package/dist/check/aria.js +106 -0
- package/dist/check/cli.d.ts +3 -0
- package/dist/check/cli.js +30 -0
- package/dist/check/css.d.ts +13 -0
- package/dist/check/css.js +63 -0
- package/dist/check/html.d.ts +8 -0
- package/dist/check/html.js +29 -0
- package/dist/check/index.d.ts +4 -0
- package/dist/check/index.js +5 -0
- package/dist/check/links.d.ts +7 -0
- package/dist/check/links.js +23 -0
- package/dist/check/orphans.d.ts +20 -0
- package/dist/check/orphans.js +57 -0
- package/dist/check/producer-code.d.ts +53 -0
- package/dist/check/producer-code.js +379 -0
- package/dist/check/producer-dom.d.ts +21 -0
- package/dist/check/producer-dom.js +52 -0
- package/dist/check/project.d.ts +20 -0
- package/dist/check/project.js +51 -0
- package/dist/check/rules.d.ts +7 -0
- package/dist/check/rules.js +517 -0
- package/dist/check/types.d.ts +160 -0
- package/dist/check/types.js +22 -0
- package/dist/components/WrAltcha.d.ts +5 -0
- package/dist/components/WrAltcha.js +8 -0
- package/dist/components/WrButton.d.ts +8 -0
- package/dist/components/WrButton.js +34 -0
- package/dist/components/WrForm.d.ts +6 -0
- package/dist/components/WrForm.js +29 -0
- package/dist/components/WrFormPrivacy.d.ts +4 -0
- package/dist/components/WrFormPrivacy.js +7 -0
- package/dist/components/WrFormSuccess.d.ts +4 -0
- package/dist/components/WrFormSuccess.js +12 -0
- package/dist/components/WrIframe.d.ts +12 -0
- package/dist/components/WrIframe.js +30 -0
- package/dist/components/WrImage.d.ts +9 -0
- package/dist/components/WrImage.js +28 -0
- package/dist/components/WrInput.d.ts +8 -0
- package/dist/components/WrInput.js +29 -0
- package/dist/components/WrLink.d.ts +10 -0
- package/dist/components/WrLink.js +38 -0
- package/dist/components/WrNav.d.ts +26 -0
- package/dist/components/WrNav.js +30 -0
- package/dist/components/WrSection.d.ts +11 -0
- package/dist/components/WrSection.js +5 -0
- package/dist/components/WrShared.d.ts +12 -0
- package/dist/components/WrShared.js +20 -0
- package/dist/components/WrText.d.ts +11 -0
- package/dist/components/WrText.js +41 -0
- package/dist/components/WrVideo.d.ts +9 -0
- package/dist/components/WrVideo.js +73 -0
- package/dist/components/text-editor/CodeThemeContext.d.ts +8 -0
- package/dist/components/text-editor/CodeThemeContext.js +10 -0
- package/dist/components/text-editor/ExampleTheme.d.ts +77 -0
- package/dist/components/text-editor/ExampleTheme.js +76 -0
- package/dist/components/text-editor/ToolbarContext.d.ts +17 -0
- package/dist/components/text-editor/ToolbarContext.js +32 -0
- package/dist/components/text-editor/WrTextEditor.d.ts +13 -0
- package/dist/components/text-editor/WrTextEditor.js +48 -0
- package/dist/components/text-editor/common/constants.d.ts +109 -0
- package/dist/components/text-editor/common/constants.js +58 -0
- package/dist/components/text-editor/common/surface.d.ts +3 -0
- package/dist/components/text-editor/common/surface.js +8 -0
- package/dist/components/text-editor/common/utils.d.ts +7 -0
- package/dist/components/text-editor/common/utils.js +78 -0
- package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.d.ts +5 -0
- package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.js +41 -0
- package/dist/components/text-editor/hooks/useLexicalMarkdown.d.ts +33 -0
- package/dist/components/text-editor/hooks/useLexicalMarkdown.js +284 -0
- package/dist/components/text-editor/nodes/CodeBlockNode.d.ts +23 -0
- package/dist/components/text-editor/nodes/CodeBlockNode.js +138 -0
- package/dist/components/text-editor/nodes/HorizontalRuleNode.d.ts +13 -0
- package/dist/components/text-editor/nodes/HorizontalRuleNode.js +22 -0
- package/dist/components/text-editor/plugins/CodeBlockPlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/CodeBlockPlugin.js +38 -0
- package/dist/components/text-editor/plugins/FontPlugin.d.ts +12 -0
- package/dist/components/text-editor/plugins/FontPlugin.js +114 -0
- package/dist/components/text-editor/plugins/FontSizePlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/FontSizePlugin.js +105 -0
- package/dist/components/text-editor/plugins/FontWeightPlugin.d.ts +6 -0
- package/dist/components/text-editor/plugins/FontWeightPlugin.js +103 -0
- package/dist/components/text-editor/plugins/HTMLTagPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/HTMLTagPlugin.js +82 -0
- package/dist/components/text-editor/plugins/HorizontalRulePlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/HorizontalRulePlugin.js +35 -0
- package/dist/components/text-editor/plugins/LineSpacingPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/LineSpacingPlugin.js +83 -0
- package/dist/components/text-editor/plugins/LinkPlugin.d.ts +8 -0
- package/dist/components/text-editor/plugins/LinkPlugin.js +183 -0
- package/dist/components/text-editor/plugins/ListToolbarPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/ListToolbarPlugin.js +40 -0
- package/dist/components/text-editor/plugins/LoadContentPlugin.d.ts +4 -0
- package/dist/components/text-editor/plugins/LoadContentPlugin.js +63 -0
- package/dist/components/text-editor/plugins/MarkdownExportPlugin.d.ts +4 -0
- package/dist/components/text-editor/plugins/MarkdownExportPlugin.js +17 -0
- package/dist/components/text-editor/plugins/MarkdownPastePlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/MarkdownPastePlugin.js +45 -0
- package/dist/components/text-editor/plugins/SanitizePastePlugin.d.ts +3 -0
- package/dist/components/text-editor/plugins/SanitizePastePlugin.js +40 -0
- package/dist/components/text-editor/plugins/TextAlignPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/TextAlignPlugin.js +65 -0
- package/dist/components/text-editor/plugins/TextColorPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/TextColorPlugin.js +117 -0
- package/dist/components/text-editor/plugins/ToolbarPlugin.d.ts +10 -0
- package/dist/components/text-editor/plugins/ToolbarPlugin.js +73 -0
- package/dist/components/text-editor/theme-colors.d.ts +11 -0
- package/dist/components/text-editor/theme-colors.js +28 -0
- package/dist/context/WrAppContext.d.ts +35 -0
- package/dist/context/WrAppContext.js +113 -0
- package/dist/context/WrLayout.d.ts +15 -0
- package/dist/context/WrLayout.js +22 -0
- package/dist/context/WrPageContext.d.ts +28 -0
- package/dist/context/WrPageContext.js +282 -0
- package/dist/context/restoreValues.d.ts +2 -0
- package/dist/context/restoreValues.js +6 -0
- package/dist/contrast.d.ts +45 -0
- package/dist/contrast.js +122 -0
- package/dist/drop.d.ts +7 -0
- package/dist/drop.js +51 -0
- package/dist/gaps.d.ts +14 -0
- package/dist/gaps.js +50 -0
- package/dist/hooks/useWrMessage.d.ts +5 -0
- package/dist/hooks/useWrMessage.js +24 -0
- package/dist/hooks/useWrSave.d.ts +2 -0
- package/dist/hooks/useWrSave.js +11 -0
- package/dist/index.css +467 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/interactive.d.ts +8 -0
- package/dist/interactive.js +13 -0
- package/dist/measure-rules.d.ts +34 -0
- package/dist/measure-rules.js +54 -0
- package/dist/measure.d.ts +11 -0
- package/dist/measure.js +139 -0
- package/dist/messages.d.ts +860 -0
- package/dist/messages.js +2 -0
- package/dist/nav.d.ts +5 -0
- package/dist/nav.js +10 -0
- package/dist/operations/animations-store.d.ts +6 -0
- package/dist/operations/animations-store.js +63 -0
- package/dist/operations/cli.d.ts +3 -0
- package/dist/operations/cli.js +52 -0
- package/dist/operations/comments-store.d.ts +17 -0
- package/dist/operations/comments-store.js +62 -0
- package/dist/operations/context.d.ts +12 -0
- package/dist/operations/context.js +35 -0
- package/dist/operations/define.d.ts +81 -0
- package/dist/operations/define.js +86 -0
- package/dist/operations/index.d.ts +6 -0
- package/dist/operations/index.js +6 -0
- package/dist/operations/ops/check.d.ts +5 -0
- package/dist/operations/ops/check.js +53 -0
- package/dist/operations/ops/comments.d.ts +6 -0
- package/dist/operations/ops/comments.js +133 -0
- package/dist/operations/ops/content.d.ts +16 -0
- package/dist/operations/ops/content.js +73 -0
- package/dist/operations/ops/json.d.ts +4 -0
- package/dist/operations/ops/json.js +27 -0
- package/dist/operations/ops/langs.d.ts +4 -0
- package/dist/operations/ops/langs.js +32 -0
- package/dist/operations/ops/nodes.d.ts +8 -0
- package/dist/operations/ops/nodes.js +270 -0
- package/dist/operations/ops/pages.d.ts +8 -0
- package/dist/operations/ops/pages.js +154 -0
- package/dist/operations/ops/project.d.ts +36 -0
- package/dist/operations/ops/project.js +97 -0
- package/dist/operations/ops/seo.d.ts +17 -0
- package/dist/operations/ops/seo.js +358 -0
- package/dist/operations/ops/style.d.ts +76 -0
- package/dist/operations/ops/style.js +268 -0
- package/dist/operations/ops/stylesets.d.ts +9 -0
- package/dist/operations/ops/stylesets.js +145 -0
- package/dist/operations/ops/theme.d.ts +6 -0
- package/dist/operations/ops/theme.js +304 -0
- package/dist/operations/registry.d.ts +8 -0
- package/dist/operations/registry.js +19 -0
- package/dist/operations/stylesets-store.d.ts +50 -0
- package/dist/operations/stylesets-store.js +216 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +24 -0
- package/dist/save.d.ts +65 -0
- package/dist/save.js +86 -0
- package/dist/schema.d.ts +69 -0
- package/dist/schema.js +110 -0
- package/dist/scope.d.ts +41 -0
- package/dist/scope.js +44 -0
- package/dist/vite-plugins/ast.d.ts +3 -0
- package/dist/vite-plugins/ast.js +238 -0
- package/dist/vite-plugins/core-swap.d.ts +3 -0
- package/dist/vite-plugins/core-swap.js +57 -0
- package/dist/vite-plugins/create-page.d.ts +4 -0
- package/dist/vite-plugins/create-page.js +59 -0
- package/dist/vite-plugins/guard.d.ts +15 -0
- package/dist/vite-plugins/guard.js +35 -0
- package/dist/vite-plugins/head.d.ts +13 -0
- package/dist/vite-plugins/head.js +53 -0
- package/dist/vite-plugins/index.d.ts +7 -0
- package/dist/vite-plugins/index.js +7 -0
- package/dist/vite-plugins/jsx-tree.d.ts +118 -0
- package/dist/vite-plugins/jsx-tree.js +686 -0
- package/dist/vite-plugins/langs.d.ts +3 -0
- package/dist/vite-plugins/langs.js +44 -0
- package/dist/vite-plugins/operations.d.ts +10 -0
- package/dist/vite-plugins/operations.js +66 -0
- package/dist/vite-plugins/pages-api.d.ts +3 -0
- package/dist/vite-plugins/pages-api.js +112 -0
- package/dist/vite-plugins/pages.d.ts +3 -0
- package/dist/vite-plugins/pages.js +64 -0
- package/dist/vite-plugins/read.d.ts +3 -0
- package/dist/vite-plugins/read.js +45 -0
- package/dist/vite-plugins/seo-files.d.ts +8 -0
- package/dist/vite-plugins/seo-files.js +42 -0
- package/dist/vite-plugins/theme.d.ts +3 -0
- package/dist/vite-plugins/theme.js +207 -0
- package/dist/vite-plugins/update.d.ts +3 -0
- package/dist/vite-plugins/update.js +46 -0
- package/dist/vite-plugins/wr-plugins.d.ts +15 -0
- package/dist/vite-plugins/wr-plugins.js +34 -0
- package/dist/vite-plugins.d.ts +11 -0
- package/dist/vite-plugins.js +518 -0
- package/package.json +96 -5
- package/README.md +0 -3
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const WR_LANGS_VIRTUAL_ID = 'virtual:wr-langs';
|
|
4
|
+
const WR_LANGS_RESOLVED_ID = '\0' + WR_LANGS_VIRTUAL_ID;
|
|
5
|
+
export function wrLangsPlugin() {
|
|
6
|
+
const langsPath = path.resolve(process.cwd(), 'src', 'config', 'langs.json');
|
|
7
|
+
function generate() {
|
|
8
|
+
if (!fs.existsSync(langsPath)) {
|
|
9
|
+
return `export const langs = null;\n`;
|
|
10
|
+
}
|
|
11
|
+
const langs = JSON.parse(fs.readFileSync(langsPath, 'utf-8'));
|
|
12
|
+
return `export const langs = ${JSON.stringify(langs)};\n`;
|
|
13
|
+
}
|
|
14
|
+
function invalidate(server) {
|
|
15
|
+
const mod = server.moduleGraph.getModuleById(WR_LANGS_RESOLVED_ID);
|
|
16
|
+
if (mod) {
|
|
17
|
+
server.moduleGraph.invalidateModule(mod);
|
|
18
|
+
server.ws.send({ type: 'full-reload' });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
name: 'wr-langs',
|
|
23
|
+
resolveId(id) {
|
|
24
|
+
if (id === WR_LANGS_VIRTUAL_ID)
|
|
25
|
+
return WR_LANGS_RESOLVED_ID;
|
|
26
|
+
},
|
|
27
|
+
load(id) {
|
|
28
|
+
if (id !== WR_LANGS_RESOLVED_ID)
|
|
29
|
+
return;
|
|
30
|
+
this.addWatchFile(langsPath);
|
|
31
|
+
return generate();
|
|
32
|
+
},
|
|
33
|
+
configureServer(server) {
|
|
34
|
+
server.watcher.add(langsPath);
|
|
35
|
+
const onFsEvent = (file) => {
|
|
36
|
+
if (path.resolve(file) === langsPath)
|
|
37
|
+
invalidate(server);
|
|
38
|
+
};
|
|
39
|
+
server.watcher.on('add', onFsEvent);
|
|
40
|
+
server.watcher.on('change', onFsEvent);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=langs.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* The one door of the editor's page into the project: `POST /wr/op/<name>` with the input as JSON runs an operation (see operations/).
|
|
4
|
+
* Only the page itself may call it (the same-origin guard of every /wr/* route: the editor's parent, another site and anything that is
|
|
5
|
+
* not a browser on this page are refused) — whoever else works on the project does it through `wr-op` and the container's tools.
|
|
6
|
+
* 200 { ok: true, data } the operation's answer
|
|
7
|
+
* 4xx/5xx { ok: false, code, error } why not
|
|
8
|
+
*/
|
|
9
|
+
export declare function wrOperationsPlugin(): Plugin;
|
|
10
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createOperationContext } from '../operations/context.js';
|
|
2
|
+
import { OperationError } from '../operations/define.js';
|
|
3
|
+
import { createRegistry } from '../operations/registry.js';
|
|
4
|
+
import { wrRoute } from './guard.js';
|
|
5
|
+
const BODY_MAX_BYTES = 4 * 1024 * 1024;
|
|
6
|
+
/**
|
|
7
|
+
* The one door of the editor's page into the project: `POST /wr/op/<name>` with the input as JSON runs an operation (see operations/).
|
|
8
|
+
* Only the page itself may call it (the same-origin guard of every /wr/* route: the editor's parent, another site and anything that is
|
|
9
|
+
* not a browser on this page are refused) — whoever else works on the project does it through `wr-op` and the container's tools.
|
|
10
|
+
* 200 { ok: true, data } the operation's answer
|
|
11
|
+
* 4xx/5xx { ok: false, code, error } why not
|
|
12
|
+
*/
|
|
13
|
+
export function wrOperationsPlugin() {
|
|
14
|
+
const registry = createRegistry();
|
|
15
|
+
const ctx = createOperationContext(process.cwd());
|
|
16
|
+
return {
|
|
17
|
+
name: 'wr-operations',
|
|
18
|
+
configureServer(server) {
|
|
19
|
+
wrRoute(server, '/wr/op', (req, res) => {
|
|
20
|
+
const send = (status, payload) => {
|
|
21
|
+
res.statusCode = status;
|
|
22
|
+
res.setHeader('Content-Type', 'application/json');
|
|
23
|
+
res.end(JSON.stringify(payload));
|
|
24
|
+
};
|
|
25
|
+
const name = /^\/([a-z][a-z0-9-]*)\/?$/.exec((req.url ?? '').split('?')[0])?.[1];
|
|
26
|
+
if (req.method !== 'POST' || !name) {
|
|
27
|
+
res.statusCode = req.method === 'POST' ? 404 : 405;
|
|
28
|
+
res.end();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const chunks = [];
|
|
32
|
+
let size = 0;
|
|
33
|
+
req.on('data', (chunk) => {
|
|
34
|
+
size += chunk.length;
|
|
35
|
+
if (size <= BODY_MAX_BYTES)
|
|
36
|
+
chunks.push(chunk);
|
|
37
|
+
});
|
|
38
|
+
req.on('end', async () => {
|
|
39
|
+
if (size > BODY_MAX_BYTES) {
|
|
40
|
+
send(413, { ok: false, code: 'too-large', error: 'body too large' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
45
|
+
const input = body.trim() ? JSON.parse(body) : {};
|
|
46
|
+
const data = await registry.run(name, input, ctx);
|
|
47
|
+
send(200, { ok: true, data });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err instanceof OperationError) {
|
|
51
|
+
send(err.status, { ok: false, code: err.code, error: err.message });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (err instanceof SyntaxError) {
|
|
55
|
+
send(400, { ok: false, code: 'invalid-input', error: 'the body is not JSON' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.error(`[wr-op] ${name} failed:`, err);
|
|
59
|
+
send(500, { ok: false, code: 'internal', error: err instanceof Error ? err.message : String(err) });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { SRC_ROOT, isSafePageName, isSafeComponentName, wrRoute } from './guard.js';
|
|
4
|
+
export function wrPagesApiPlugin() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'wr-pages-api',
|
|
7
|
+
configureServer(server) {
|
|
8
|
+
wrRoute(server, '/wr/pages', (req, res) => {
|
|
9
|
+
const routesPath = path.join(SRC_ROOT, 'config', 'routes.json');
|
|
10
|
+
if (req.method === 'GET') {
|
|
11
|
+
const routes = JSON.parse(fs.readFileSync(routesPath, 'utf-8'));
|
|
12
|
+
res.setHeader('Content-Type', 'application/json');
|
|
13
|
+
res.end(JSON.stringify(routes));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (req.method === 'DELETE' || req.method === 'PATCH') {
|
|
17
|
+
let body = '';
|
|
18
|
+
req.on('data', chunk => body += chunk);
|
|
19
|
+
req.on('end', () => {
|
|
20
|
+
try {
|
|
21
|
+
const payload = body ? JSON.parse(body) : {};
|
|
22
|
+
const { pageName } = payload;
|
|
23
|
+
if (!isSafePageName(pageName)) {
|
|
24
|
+
res.statusCode = 400;
|
|
25
|
+
res.end(JSON.stringify({ error: 'invalid pageName' }));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const routes = JSON.parse(fs.readFileSync(routesPath, 'utf-8'));
|
|
29
|
+
const entry = routes[pageName];
|
|
30
|
+
if (!entry) {
|
|
31
|
+
res.statusCode = 404;
|
|
32
|
+
res.end(JSON.stringify({ error: 'page not found' }));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!isSafeComponentName(entry.component)) {
|
|
36
|
+
res.statusCode = 500;
|
|
37
|
+
res.end(JSON.stringify({ error: 'invalid component in routes.json' }));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const pagePath = path.join(SRC_ROOT, 'config', 'pages', `${pageName}.json`);
|
|
41
|
+
const componentPath = path.join(SRC_ROOT, 'pages', `${entry.component}.tsx`);
|
|
42
|
+
if (req.method === 'DELETE') {
|
|
43
|
+
if (fs.existsSync(pagePath))
|
|
44
|
+
fs.unlinkSync(pagePath);
|
|
45
|
+
if (fs.existsSync(componentPath))
|
|
46
|
+
fs.unlinkSync(componentPath);
|
|
47
|
+
delete routes[pageName];
|
|
48
|
+
fs.writeFileSync(routesPath, JSON.stringify(routes, null, 2));
|
|
49
|
+
res.end(JSON.stringify({ ok: true }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// PATCH: rename route key e/o component
|
|
53
|
+
const { newPageName, newComponent, lazy, header, footer } = payload;
|
|
54
|
+
const finalPageName = newPageName ?? pageName;
|
|
55
|
+
const finalComponent = newComponent ?? entry.component;
|
|
56
|
+
if (!isSafePageName(finalPageName) || !isSafeComponentName(finalComponent)) {
|
|
57
|
+
res.statusCode = 400;
|
|
58
|
+
res.end(JSON.stringify({ error: 'invalid newPageName or newComponent' }));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (finalPageName !== pageName && routes[finalPageName]) {
|
|
62
|
+
res.statusCode = 409;
|
|
63
|
+
res.end(JSON.stringify({ error: 'target page name already exists' }));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const newPagePath = path.join(SRC_ROOT, 'config', 'pages', `${finalPageName}.json`);
|
|
67
|
+
const newComponentPath = path.join(SRC_ROOT, 'pages', `${finalComponent}.tsx`);
|
|
68
|
+
if (fs.existsSync(pagePath) && pagePath !== newPagePath) {
|
|
69
|
+
fs.mkdirSync(path.dirname(newPagePath), { recursive: true });
|
|
70
|
+
fs.renameSync(pagePath, newPagePath);
|
|
71
|
+
}
|
|
72
|
+
if (fs.existsSync(componentPath) && componentPath !== newComponentPath) {
|
|
73
|
+
fs.mkdirSync(path.dirname(newComponentPath), { recursive: true });
|
|
74
|
+
fs.renameSync(componentPath, newComponentPath);
|
|
75
|
+
}
|
|
76
|
+
delete routes[pageName];
|
|
77
|
+
// La voce si conserva (header/footer e quello che verrà): si cambia solo ciò che la richiesta dice.
|
|
78
|
+
// `true` è il default di header e footer e non si scrive.
|
|
79
|
+
const next = { ...entry, component: finalComponent };
|
|
80
|
+
if (lazy !== undefined) {
|
|
81
|
+
if (lazy)
|
|
82
|
+
next.lazy = true;
|
|
83
|
+
else
|
|
84
|
+
delete next.lazy;
|
|
85
|
+
}
|
|
86
|
+
for (const [key, value] of [['header', header], ['footer', footer]]) {
|
|
87
|
+
if (value === undefined)
|
|
88
|
+
continue;
|
|
89
|
+
if (value === false)
|
|
90
|
+
next[key] = false;
|
|
91
|
+
else
|
|
92
|
+
delete next[key];
|
|
93
|
+
}
|
|
94
|
+
routes[finalPageName] = next;
|
|
95
|
+
fs.writeFileSync(routesPath, JSON.stringify(routes, null, 2));
|
|
96
|
+
res.end(JSON.stringify({ ok: true, pageName: finalPageName }));
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error('[wr-pages-api] failed:', err);
|
|
100
|
+
res.statusCode = 500;
|
|
101
|
+
res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
res.statusCode = 405;
|
|
107
|
+
res.end();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=pages-api.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const WR_PAGES_VIRTUAL_ID = 'virtual:wr-pages';
|
|
4
|
+
const WR_PAGES_RESOLVED_ID = '\0' + WR_PAGES_VIRTUAL_ID;
|
|
5
|
+
export function wrPagesPlugin() {
|
|
6
|
+
const routesPath = path.resolve(process.cwd(), 'src', 'config', 'routes.json');
|
|
7
|
+
function generate() {
|
|
8
|
+
const routes = JSON.parse(fs.readFileSync(routesPath, 'utf-8'));
|
|
9
|
+
const eagerImports = [];
|
|
10
|
+
const eagerEntries = [];
|
|
11
|
+
const lazyEntries = [];
|
|
12
|
+
// The modules are by component: two routes that show the same component (`shop` and `shop/[slug]`) are one module
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
for (const entry of Object.values(routes)) {
|
|
15
|
+
if (seen.has(entry.component))
|
|
16
|
+
continue;
|
|
17
|
+
seen.add(entry.component);
|
|
18
|
+
if (entry.lazy) {
|
|
19
|
+
lazyEntries.push(` ${JSON.stringify(entry.component)}: () => import('/src/pages/${entry.component}.tsx'),`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const varName = `Page_${entry.component}`;
|
|
23
|
+
eagerImports.push(`import * as ${varName} from '/src/pages/${entry.component}.tsx';`);
|
|
24
|
+
eagerEntries.push(` ${JSON.stringify(entry.component)}: ${varName},`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return `${eagerImports.join('\n')}
|
|
28
|
+
|
|
29
|
+
export const eagerModules = {
|
|
30
|
+
${eagerEntries.join('\n')}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const lazyModules = {
|
|
34
|
+
${lazyEntries.join('\n')}
|
|
35
|
+
};
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
name: 'wr-pages',
|
|
40
|
+
resolveId(id) {
|
|
41
|
+
if (id === WR_PAGES_VIRTUAL_ID)
|
|
42
|
+
return WR_PAGES_RESOLVED_ID;
|
|
43
|
+
},
|
|
44
|
+
load(id) {
|
|
45
|
+
if (id !== WR_PAGES_RESOLVED_ID)
|
|
46
|
+
return;
|
|
47
|
+
this.addWatchFile(routesPath);
|
|
48
|
+
return generate();
|
|
49
|
+
},
|
|
50
|
+
configureServer(server) {
|
|
51
|
+
server.watcher.add(routesPath);
|
|
52
|
+
server.watcher.on('change', (file) => {
|
|
53
|
+
if (path.resolve(file) === routesPath) {
|
|
54
|
+
const mod = server.moduleGraph.getModuleById(WR_PAGES_RESOLVED_ID);
|
|
55
|
+
if (mod) {
|
|
56
|
+
server.moduleGraph.invalidateModule(mod);
|
|
57
|
+
server.ws.send({ type: 'full-reload' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=pages.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { resolveInSrc, wrRoute } from './guard.js';
|
|
3
|
+
export function wrReadPlugin() {
|
|
4
|
+
return {
|
|
5
|
+
name: 'wr-read',
|
|
6
|
+
configureServer(server) {
|
|
7
|
+
wrRoute(server, '/wr/read', (req, res) => {
|
|
8
|
+
if (req.method !== 'GET') {
|
|
9
|
+
res.statusCode = 405;
|
|
10
|
+
res.end();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(req.url ?? '', 'http://localhost');
|
|
15
|
+
const file = url.searchParams.get('file');
|
|
16
|
+
if (!file) {
|
|
17
|
+
res.statusCode = 400;
|
|
18
|
+
res.end(JSON.stringify({ error: 'file query param required' }));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const filePath = resolveInSrc(file);
|
|
22
|
+
if (!filePath) {
|
|
23
|
+
res.statusCode = 400;
|
|
24
|
+
res.end(JSON.stringify({ error: 'invalid file path' }));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!fs.existsSync(filePath)) {
|
|
28
|
+
res.setHeader('Content-Type', 'application/json');
|
|
29
|
+
res.end(JSON.stringify(null));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
33
|
+
res.setHeader('Content-Type', 'application/json');
|
|
34
|
+
res.end(JSON.stringify(content));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error('[wr-read] failed:', err);
|
|
38
|
+
res.statusCode = 500;
|
|
39
|
+
res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps `public/robots.txt`, `sitemap-index.xml` and `sitemap-pages.xml` true: written when the dev server starts and again whenever the
|
|
4
|
+
* pages, the languages or the SEO data change — from the editor, from VS Code, from `wr`, from anywhere. The user never has to think
|
|
5
|
+
* about them. What to write is `wrSeoFiles` (core); this only decides when.
|
|
6
|
+
*/
|
|
7
|
+
export declare function wrSeoFilesPlugin(): Plugin;
|
|
8
|
+
//# sourceMappingURL=seo-files.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { createOperationContext } from '../operations/context.js';
|
|
3
|
+
import { writeSeoFiles } from '../operations/ops/seo.js';
|
|
4
|
+
const DEBOUNCE_MS = 250;
|
|
5
|
+
/**
|
|
6
|
+
* Keeps `public/robots.txt`, `sitemap-index.xml` and `sitemap-pages.xml` true: written when the dev server starts and again whenever the
|
|
7
|
+
* pages, the languages or the SEO data change — from the editor, from VS Code, from `wr`, from anywhere. The user never has to think
|
|
8
|
+
* about them. What to write is `wrSeoFiles` (core); this only decides when.
|
|
9
|
+
*/
|
|
10
|
+
export function wrSeoFilesPlugin() {
|
|
11
|
+
const ctx = createOperationContext(process.cwd());
|
|
12
|
+
const configDir = path.join(ctx.srcRoot, 'config') + path.sep;
|
|
13
|
+
const sync = () => {
|
|
14
|
+
try {
|
|
15
|
+
const { written } = writeSeoFiles(ctx);
|
|
16
|
+
if (written.length > 0)
|
|
17
|
+
console.log(`[wr-seo] wrote ${written.join(', ')}`);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
// A file half-saved in VS Code: the next change tries again
|
|
21
|
+
console.warn('[wr-seo] could not update the SEO files:', err instanceof Error ? err.message : err);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
name: 'wr-seo-files',
|
|
26
|
+
configureServer(server) {
|
|
27
|
+
sync();
|
|
28
|
+
let timer;
|
|
29
|
+
const onFsEvent = (file) => {
|
|
30
|
+
const resolved = path.resolve(file);
|
|
31
|
+
if (!resolved.startsWith(configDir) || !resolved.endsWith('.json'))
|
|
32
|
+
return;
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
timer = setTimeout(sync, DEBOUNCE_MS);
|
|
35
|
+
};
|
|
36
|
+
for (const event of ['add', 'change', 'unlink'])
|
|
37
|
+
server.watcher.on(event, onFsEvent);
|
|
38
|
+
server.httpServer?.once('close', () => clearTimeout(timer));
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=seo-files.js.map
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import postcss from 'postcss';
|
|
4
|
+
import { SRC_ROOT, wrRoute } from './guard.js';
|
|
5
|
+
function themeFilePath() {
|
|
6
|
+
return path.join(SRC_ROOT, 'theme.css');
|
|
7
|
+
}
|
|
8
|
+
function readTheme() {
|
|
9
|
+
const filePath = themeFilePath();
|
|
10
|
+
const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
|
|
11
|
+
return postcss.parse(content);
|
|
12
|
+
}
|
|
13
|
+
function writeTheme(root) {
|
|
14
|
+
fs.writeFileSync(themeFilePath(), root.toString());
|
|
15
|
+
}
|
|
16
|
+
function stripQuotes(value) {
|
|
17
|
+
return value.trim().replace(/^['"]|['"]$/g, '');
|
|
18
|
+
}
|
|
19
|
+
function findGoogleImport(root) {
|
|
20
|
+
let found;
|
|
21
|
+
root.walkAtRules('import', (rule) => {
|
|
22
|
+
if (rule.params.includes('fonts.googleapis.com'))
|
|
23
|
+
found = rule;
|
|
24
|
+
});
|
|
25
|
+
return found;
|
|
26
|
+
}
|
|
27
|
+
function parseGoogleImport(rule) {
|
|
28
|
+
if (!rule)
|
|
29
|
+
return [];
|
|
30
|
+
const urlMatch = rule.params.match(/url\(['"]?([^'")]+)['"]?\)/);
|
|
31
|
+
if (!urlMatch)
|
|
32
|
+
return [];
|
|
33
|
+
const url = new URL(urlMatch[1]);
|
|
34
|
+
const entries = [];
|
|
35
|
+
for (const family of url.searchParams.getAll('family')) {
|
|
36
|
+
const [name, rest] = family.split(':');
|
|
37
|
+
const weights = rest?.startsWith('wght@') ? rest.slice('wght@'.length) : rest;
|
|
38
|
+
entries.push({ family: name.replace(/\+/g, ' '), weights });
|
|
39
|
+
}
|
|
40
|
+
return entries;
|
|
41
|
+
}
|
|
42
|
+
function buildGoogleImportRule(entries) {
|
|
43
|
+
const params = entries
|
|
44
|
+
.map(({ family, weights }) => `family=${family.replace(/ /g, '+')}${weights ? `:wght@${weights}` : ''}`)
|
|
45
|
+
.join('&');
|
|
46
|
+
return `url('https://fonts.googleapis.com/css2?${params}&display=swap')`;
|
|
47
|
+
}
|
|
48
|
+
function getThemeItems(root) {
|
|
49
|
+
const items = [];
|
|
50
|
+
root.walkRules(':root', (rule) => {
|
|
51
|
+
rule.walkDecls(/^--wr-/, (decl) => {
|
|
52
|
+
items.push({ kind: 'property', key: decl.prop, value: stripQuotes(decl.value) });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
for (const { family, weights } of parseGoogleImport(findGoogleImport(root))) {
|
|
56
|
+
items.push({ kind: 'google-font', key: family, weights });
|
|
57
|
+
}
|
|
58
|
+
const customByFamily = new Map();
|
|
59
|
+
root.walkAtRules('font-face', (rule) => {
|
|
60
|
+
let family = '';
|
|
61
|
+
const face = { src: '' };
|
|
62
|
+
rule.walkDecls((decl) => {
|
|
63
|
+
const value = stripQuotes(decl.value);
|
|
64
|
+
if (decl.prop === 'font-family')
|
|
65
|
+
family = value;
|
|
66
|
+
else if (decl.prop === 'src')
|
|
67
|
+
face.src = decl.value;
|
|
68
|
+
else if (decl.prop === 'font-weight')
|
|
69
|
+
face.weight = decl.value;
|
|
70
|
+
else if (decl.prop === 'font-style')
|
|
71
|
+
face.style = decl.value;
|
|
72
|
+
else if (decl.prop === 'font-stretch')
|
|
73
|
+
face.stretch = decl.value;
|
|
74
|
+
});
|
|
75
|
+
if (!family)
|
|
76
|
+
return;
|
|
77
|
+
const faces = customByFamily.get(family) ?? [];
|
|
78
|
+
faces.push(face);
|
|
79
|
+
customByFamily.set(family, faces);
|
|
80
|
+
});
|
|
81
|
+
for (const [family, faces] of customByFamily) {
|
|
82
|
+
items.push({ kind: 'custom-font', key: family, faces });
|
|
83
|
+
}
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
function setProperty(root, key, value) {
|
|
87
|
+
let rootRule = root.nodes.find((n) => n.type === 'rule' && n.selector === ':root');
|
|
88
|
+
if (!rootRule) {
|
|
89
|
+
rootRule = postcss.rule({ selector: ':root' });
|
|
90
|
+
root.append(rootRule);
|
|
91
|
+
}
|
|
92
|
+
const existing = rootRule.nodes.find((n) => n.type === 'decl' && n.prop === key);
|
|
93
|
+
if (existing && existing.type === 'decl') {
|
|
94
|
+
existing.value = value;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
rootRule.append({ prop: key, value });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function setGoogleFont(root, family, weights) {
|
|
101
|
+
const importRule = findGoogleImport(root);
|
|
102
|
+
const entries = parseGoogleImport(importRule);
|
|
103
|
+
const idx = entries.findIndex((e) => e.family === family);
|
|
104
|
+
if (idx >= 0)
|
|
105
|
+
entries[idx] = { family, weights };
|
|
106
|
+
else
|
|
107
|
+
entries.push({ family, weights });
|
|
108
|
+
if (importRule) {
|
|
109
|
+
importRule.params = buildGoogleImportRule(entries);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const newRule = postcss.atRule({ name: 'import', params: buildGoogleImportRule(entries) });
|
|
113
|
+
root.prepend(newRule); // @import deve stare in cima al file
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function removeCustomFontBlocks(root, family) {
|
|
117
|
+
root.walkAtRules('font-face', (rule) => {
|
|
118
|
+
let ruleFamily = '';
|
|
119
|
+
rule.walkDecls('font-family', (decl) => { ruleFamily = stripQuotes(decl.value); });
|
|
120
|
+
if (ruleFamily === family)
|
|
121
|
+
rule.remove();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function setCustomFont(root, family, faces) {
|
|
125
|
+
removeCustomFontBlocks(root, family); // edit = replace, niente blocchi duplicati/stale
|
|
126
|
+
for (const face of faces) {
|
|
127
|
+
const rule = postcss.atRule({ name: 'font-face' });
|
|
128
|
+
rule.append({ prop: 'font-family', value: `'${family}'` });
|
|
129
|
+
rule.append({ prop: 'src', value: face.src });
|
|
130
|
+
if (face.weight)
|
|
131
|
+
rule.append({ prop: 'font-weight', value: face.weight });
|
|
132
|
+
if (face.style)
|
|
133
|
+
rule.append({ prop: 'font-style', value: face.style });
|
|
134
|
+
if (face.stretch)
|
|
135
|
+
rule.append({ prop: 'font-stretch', value: face.stretch });
|
|
136
|
+
root.append(rule);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function removeProperty(root, key) {
|
|
140
|
+
root.walkRules(':root', (rule) => {
|
|
141
|
+
rule.walkDecls(key, (decl) => { decl.remove(); });
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function removeGoogleFont(root, family) {
|
|
145
|
+
const importRule = findGoogleImport(root);
|
|
146
|
+
if (!importRule)
|
|
147
|
+
return;
|
|
148
|
+
const entries = parseGoogleImport(importRule).filter((e) => e.family !== family);
|
|
149
|
+
if (entries.length === 0)
|
|
150
|
+
importRule.remove();
|
|
151
|
+
else
|
|
152
|
+
importRule.params = buildGoogleImportRule(entries);
|
|
153
|
+
}
|
|
154
|
+
export function wrThemePlugin() {
|
|
155
|
+
return {
|
|
156
|
+
name: 'wr-theme',
|
|
157
|
+
configureServer(server) {
|
|
158
|
+
wrRoute(server, '/wr/theme', (req, res) => {
|
|
159
|
+
if (req.method === 'GET') {
|
|
160
|
+
const root = readTheme();
|
|
161
|
+
res.setHeader('Content-Type', 'application/json');
|
|
162
|
+
res.end(JSON.stringify({ items: getThemeItems(root) }));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let body = '';
|
|
166
|
+
req.on('data', (chunk) => body += chunk);
|
|
167
|
+
req.on('end', () => {
|
|
168
|
+
try {
|
|
169
|
+
const root = readTheme();
|
|
170
|
+
const payload = JSON.parse(body);
|
|
171
|
+
if (req.method === 'PUT') {
|
|
172
|
+
const item = payload.item;
|
|
173
|
+
if (item.kind === 'property')
|
|
174
|
+
setProperty(root, item.key, item.value);
|
|
175
|
+
else if (item.kind === 'google-font')
|
|
176
|
+
setGoogleFont(root, item.key, item.weights);
|
|
177
|
+
else if (item.kind === 'custom-font')
|
|
178
|
+
setCustomFont(root, item.key, item.faces);
|
|
179
|
+
writeTheme(root);
|
|
180
|
+
res.end(JSON.stringify({ kind: item.kind, key: item.key, ok: true }));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (req.method === 'DELETE') {
|
|
184
|
+
const { kind, key } = payload;
|
|
185
|
+
if (kind === 'property')
|
|
186
|
+
removeProperty(root, key);
|
|
187
|
+
else if (kind === 'google-font')
|
|
188
|
+
removeGoogleFont(root, key);
|
|
189
|
+
else if (kind === 'custom-font')
|
|
190
|
+
removeCustomFontBlocks(root, key);
|
|
191
|
+
writeTheme(root);
|
|
192
|
+
res.end(JSON.stringify({ kind, key, ok: true }));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
res.statusCode = 405;
|
|
196
|
+
res.end();
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
res.statusCode = 500;
|
|
200
|
+
res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=theme.js.map
|