@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
package/dist/save.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { WrLangConfig } from '@webround/core/routing';
|
|
2
|
+
import type { Issue } from './check/types';
|
|
3
|
+
import type { WrCommentAddRequest, WrCommentsResponse, WrJsonPruneResponse, WrSeoGetResponse, WrSeoSuggestResponse, WrSeoSetRequest, WrSeoSiteSetRequest, WrThemeItem } from './messages';
|
|
4
|
+
export interface WrOpResult<T = any> {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
/** The HTTP status, 0 when the dev server could not be reached. */
|
|
7
|
+
status: number;
|
|
8
|
+
data?: T;
|
|
9
|
+
/** Why not: a code a caller can act on, and the words. */
|
|
10
|
+
code?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function wrOp<T = any>(name: string, input?: object): Promise<WrOpResult<T>>;
|
|
14
|
+
export declare function wrSaveFile(file: string, patch: Record<string, unknown>): Promise<void>;
|
|
15
|
+
export declare function wrSavePage(pageName: string, id: string, value: unknown): Promise<void>;
|
|
16
|
+
export declare function wrSaveRoutes(patch: Record<string, unknown>): Promise<void>;
|
|
17
|
+
export declare const wrCreatePage: (pageName: string, component: string, lazy?: boolean) => Promise<WrOpResult<any>>;
|
|
18
|
+
export interface WrPageEntry {
|
|
19
|
+
pageName: string;
|
|
20
|
+
component: string;
|
|
21
|
+
lazy?: boolean;
|
|
22
|
+
header: boolean;
|
|
23
|
+
footer: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const wrListPages: () => Promise<WrOpResult<{
|
|
26
|
+
pages: WrPageEntry[];
|
|
27
|
+
}>>;
|
|
28
|
+
export declare const wrDeletePage: (pageName: string) => Promise<WrOpResult<any>>;
|
|
29
|
+
export declare const wrUpdatePage: (pageName: string, patch: {
|
|
30
|
+
newPageName?: string;
|
|
31
|
+
newComponent?: string;
|
|
32
|
+
lazy?: boolean;
|
|
33
|
+
header?: boolean;
|
|
34
|
+
footer?: boolean;
|
|
35
|
+
}) => Promise<WrOpResult<any>>;
|
|
36
|
+
export declare function wrListLangs(): Promise<WrLangConfig | null>;
|
|
37
|
+
export declare const wrSaveLangs: (langs: WrLangConfig) => Promise<WrOpResult<any>>;
|
|
38
|
+
export declare function wrGetSeo(pageName?: string): Promise<WrSeoGetResponse>;
|
|
39
|
+
export declare function wrSuggestSeo(pageName: string, lang?: string): Promise<WrSeoSuggestResponse>;
|
|
40
|
+
/** Everything `wr-check` finds in the project (the editor's notifications). */
|
|
41
|
+
export declare function wrCheckProject(): Promise<{
|
|
42
|
+
issues: Issue[];
|
|
43
|
+
muted: string[];
|
|
44
|
+
error?: string;
|
|
45
|
+
}>;
|
|
46
|
+
/** Silence a problem the person chose not to deal with (or bring it back). */
|
|
47
|
+
export declare const wrMuteIssue: (key: string, muted: boolean) => Promise<WrOpResult<any>>;
|
|
48
|
+
/** Remove the values of elements that are no longer in the code. */
|
|
49
|
+
export declare function wrPruneJson(page?: string): Promise<WrJsonPruneResponse>;
|
|
50
|
+
/** The comments of the site (open and resolved), and how many are open. */
|
|
51
|
+
export declare function wrGetComments(): Promise<WrCommentsResponse>;
|
|
52
|
+
/** What the editor's page does with a comment: the author is always a person (the editor says who). */
|
|
53
|
+
export declare const wrAddComment: ({ page, elementId, text, authorName }: WrCommentAddRequest) => Promise<WrOpResult<any>>;
|
|
54
|
+
export declare const wrResolveComment: (id: string, resolved: boolean) => Promise<WrOpResult<any>>;
|
|
55
|
+
export declare const wrDeleteComment: (id: string) => Promise<WrOpResult<any>>;
|
|
56
|
+
export declare const wrSetSeo: ({ pageName, ...fields }: WrSeoSetRequest) => Promise<WrOpResult<any>>;
|
|
57
|
+
export declare const wrSetSeoSite: (fields: WrSeoSiteSetRequest) => Promise<WrOpResult<any>>;
|
|
58
|
+
export declare function wrGetTheme(): Promise<{
|
|
59
|
+
items: WrThemeItem[];
|
|
60
|
+
dark: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
export declare const wrSetThemeDark: (enabled: boolean) => Promise<WrOpResult<any>>;
|
|
63
|
+
export declare const wrSetThemeItem: (item: WrThemeItem) => Promise<WrOpResult<any>>;
|
|
64
|
+
export declare const wrRemoveThemeItem: (kind: WrThemeItem["kind"], key: string) => Promise<WrOpResult<any>>;
|
|
65
|
+
//# sourceMappingURL=save.d.ts.map
|
package/dist/save.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export async function wrOp(name, input = {}) {
|
|
2
|
+
try {
|
|
3
|
+
const res = await fetch(`/wr/op/${name}`, {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6
|
+
body: JSON.stringify(input),
|
|
7
|
+
});
|
|
8
|
+
const body = await res.json().catch(() => null);
|
|
9
|
+
if (res.ok && body?.ok)
|
|
10
|
+
return { ok: true, status: res.status, data: body.data };
|
|
11
|
+
return { ok: false, status: res.status, code: body?.code, error: body?.error ?? `HTTP ${res.status}` };
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
return { ok: false, status: 0, error: String(err) };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** The data of an operation that has to work: otherwise it throws what went wrong. */
|
|
18
|
+
async function must(name, input) {
|
|
19
|
+
const result = await wrOp(name, input);
|
|
20
|
+
if (!result.ok) {
|
|
21
|
+
console.error(`[wr] ${name} failed:`, result.status, result.error);
|
|
22
|
+
throw new Error(result.error ?? `HTTP ${result.status}`);
|
|
23
|
+
}
|
|
24
|
+
return result.data;
|
|
25
|
+
}
|
|
26
|
+
export async function wrSaveFile(file, patch) {
|
|
27
|
+
await must('json-patch', { file, patch });
|
|
28
|
+
}
|
|
29
|
+
export async function wrSavePage(pageName, id, value) {
|
|
30
|
+
return wrSaveFile(`config/pages/${pageName}.json`, { [id]: value });
|
|
31
|
+
}
|
|
32
|
+
export async function wrSaveRoutes(patch) {
|
|
33
|
+
return wrSaveFile('config/routes.json', patch);
|
|
34
|
+
}
|
|
35
|
+
export const wrCreatePage = (pageName, component, lazy = false) => wrOp('page-create', { pageName, component, lazy });
|
|
36
|
+
export const wrListPages = () => wrOp('page-list');
|
|
37
|
+
export const wrDeletePage = (pageName) => wrOp('page-delete', { pageName });
|
|
38
|
+
export const wrUpdatePage = (pageName, patch) => wrOp('page-update', { pageName, ...patch });
|
|
39
|
+
export async function wrListLangs() {
|
|
40
|
+
const result = await wrOp('langs-get');
|
|
41
|
+
return result.ok ? result.data?.langs ?? null : null;
|
|
42
|
+
}
|
|
43
|
+
export const wrSaveLangs = (langs) => wrOp('langs-set', { default: langs.default, available: langs.available });
|
|
44
|
+
export async function wrGetSeo(pageName) {
|
|
45
|
+
const result = await wrOp('seo-get', pageName ? { page: pageName } : {});
|
|
46
|
+
if (!result.ok || !result.data)
|
|
47
|
+
return { site: {}, siteUrlKnown: false, error: result.error ?? `HTTP ${result.status}` };
|
|
48
|
+
return result.data;
|
|
49
|
+
}
|
|
50
|
+
export async function wrSuggestSeo(pageName, lang) {
|
|
51
|
+
const result = await wrOp('seo-suggest', { page: pageName, ...(lang ? { lang } : {}) });
|
|
52
|
+
if (!result.ok || !result.data)
|
|
53
|
+
return { pageName, lang: lang ?? 'default', error: result.error ?? `HTTP ${result.status}` };
|
|
54
|
+
return result.data;
|
|
55
|
+
}
|
|
56
|
+
/** Everything `wr-check` finds in the project (the editor's notifications). */
|
|
57
|
+
export async function wrCheckProject() {
|
|
58
|
+
const result = await wrOp('project-check');
|
|
59
|
+
return result.ok && result.data ? { issues: result.data.issues, muted: result.data.muted } : { issues: [], muted: [], error: result.error ?? `HTTP ${result.status}` };
|
|
60
|
+
}
|
|
61
|
+
/** Silence a problem the person chose not to deal with (or bring it back). */
|
|
62
|
+
export const wrMuteIssue = (key, muted) => wrOp('issue-mute', { key, muted });
|
|
63
|
+
/** Remove the values of elements that are no longer in the code. */
|
|
64
|
+
export async function wrPruneJson(page) {
|
|
65
|
+
const result = await wrOp('json-prune', page ? { page } : {});
|
|
66
|
+
return result.ok && result.data ? { removed: result.data.removed } : { removed: [], error: result.error ?? `HTTP ${result.status}`, code: result.code };
|
|
67
|
+
}
|
|
68
|
+
/** The comments of the site (open and resolved), and how many are open. */
|
|
69
|
+
export async function wrGetComments() {
|
|
70
|
+
const result = await wrOp('comment-list', { status: 'all' });
|
|
71
|
+
return result.ok && result.data ? result.data : { comments: [], open: 0, error: result.error ?? `HTTP ${result.status}` };
|
|
72
|
+
}
|
|
73
|
+
/** What the editor's page does with a comment: the author is always a person (the editor says who). */
|
|
74
|
+
export const wrAddComment = ({ page, elementId, text, authorName }) => wrOp('comment-add', { page, ...(elementId ? { id: elementId } : {}), text, author: { name: authorName, kind: 'user' } });
|
|
75
|
+
export const wrResolveComment = (id, resolved) => wrOp('comment-resolve', { comment: id, resolved });
|
|
76
|
+
export const wrDeleteComment = (id) => wrOp('comment-delete', { comment: id });
|
|
77
|
+
export const wrSetSeo = ({ pageName, ...fields }) => wrOp('seo-set', { page: pageName, ...fields });
|
|
78
|
+
export const wrSetSeoSite = (fields) => wrOp('seo-site-set', { ...fields });
|
|
79
|
+
export async function wrGetTheme() {
|
|
80
|
+
const result = await wrOp('theme-get');
|
|
81
|
+
return result.ok ? { items: result.data?.items ?? [], dark: result.data?.dark === true } : { items: [], dark: false };
|
|
82
|
+
}
|
|
83
|
+
export const wrSetThemeDark = (enabled) => wrOp('theme-dark', { enabled });
|
|
84
|
+
export const wrSetThemeItem = (item) => wrOp('theme-set', { item });
|
|
85
|
+
export const wrRemoveThemeItem = (kind, key) => wrOp('theme-remove', { kind, key });
|
|
86
|
+
//# sourceMappingURL=save.js.map
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { WrElementKind, WrEditorInbound } from '@webround/editor/messages';
|
|
2
|
+
import { type WrFormMessage } from '@webround/core/form';
|
|
3
|
+
export type FieldLabelKey = 'content' | 'label' | 'href' | 'imageUrl' | 'videoUrl' | 'iframeUrl' | 'alt' | 'title' | 'allowFullscreen' | 'loading' | 'ariaLabel' | 'hidden' | 'displayMode' | 'target' | 'autoplay' | 'loop' | 'controls' | 'items' | 'themeToggle' | 'accountMenu' | 'cartMenu' | 'placeholder' | 'inputType' | 'required' | 'maxLength' | `msg-${WrFormMessage}`;
|
|
4
|
+
export type FieldBackend = 'json' | 'ast';
|
|
5
|
+
/**
|
|
6
|
+
* `multilang`: the value is translated, stored as `{ default, en… }` (see `wrLocalized` / `wrSetLocalized` in @webround/core): the
|
|
7
|
+
* ONE place that says which fields are, for the panel, the page's components and every operation that writes a value.
|
|
8
|
+
*/
|
|
9
|
+
type BaseField = {
|
|
10
|
+
key: string;
|
|
11
|
+
labelKey: FieldLabelKey;
|
|
12
|
+
backend: FieldBackend;
|
|
13
|
+
multilang?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type TextareaField = BaseField & {
|
|
16
|
+
type: 'textarea';
|
|
17
|
+
rows?: number;
|
|
18
|
+
};
|
|
19
|
+
export type UrlField = BaseField & {
|
|
20
|
+
type: 'url';
|
|
21
|
+
};
|
|
22
|
+
/** A URL that comes from the media library (or is typed): `assetKind` says which library the picker opens. */
|
|
23
|
+
export type AssetField = BaseField & {
|
|
24
|
+
type: 'asset';
|
|
25
|
+
assetKind: 'image' | 'video';
|
|
26
|
+
};
|
|
27
|
+
export type TextField = BaseField & {
|
|
28
|
+
type: 'text';
|
|
29
|
+
};
|
|
30
|
+
export type ToggleField = BaseField & {
|
|
31
|
+
type: 'toggle';
|
|
32
|
+
};
|
|
33
|
+
export type NumberField = BaseField & {
|
|
34
|
+
type: 'number';
|
|
35
|
+
min?: number;
|
|
36
|
+
};
|
|
37
|
+
export type SelectField = BaseField & {
|
|
38
|
+
type: 'select';
|
|
39
|
+
options: readonly string[];
|
|
40
|
+
};
|
|
41
|
+
/** A field of an entry of a list; `multilang` as for a field: the entry holds `{ default, en… }`. */
|
|
42
|
+
export type ListItemFieldDef = {
|
|
43
|
+
key: string;
|
|
44
|
+
labelKey: FieldLabelKey;
|
|
45
|
+
type: 'text' | 'url';
|
|
46
|
+
multilang?: boolean;
|
|
47
|
+
};
|
|
48
|
+
export type ListField = BaseField & {
|
|
49
|
+
type: 'list';
|
|
50
|
+
itemFields: ListItemFieldDef[];
|
|
51
|
+
};
|
|
52
|
+
export type FieldDef = TextareaField | UrlField | AssetField | TextField | ToggleField | NumberField | SelectField | ListField;
|
|
53
|
+
export interface FieldInputProps<F extends FieldDef> {
|
|
54
|
+
field: F;
|
|
55
|
+
draft: string;
|
|
56
|
+
setDraft: (v: string) => void;
|
|
57
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
58
|
+
}
|
|
59
|
+
export declare function getFields(kind: WrElementKind): FieldDef[];
|
|
60
|
+
/** Every field an element has, the ones edited on the page too (a text's content): what an operation can set. */
|
|
61
|
+
export declare function getAllFields(kind: WrElementKind): FieldDef[];
|
|
62
|
+
/** The fields of an element that are translated. */
|
|
63
|
+
export declare const multilangFieldsOf: (kind: WrElementKind) => string[];
|
|
64
|
+
export declare function buildUpdateMessage(kind: WrElementKind, elementId: string, key: string, value: string): WrEditorInbound | null;
|
|
65
|
+
export declare function getFieldBackend(kind: WrElementKind, key: string): FieldBackend | null;
|
|
66
|
+
/** The kind of element an update message is for (`wr:button:update` → button). */
|
|
67
|
+
export declare function kindOfUpdateMessage(type: WrEditorInbound['type']): WrElementKind | undefined;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { WR_FORM_MESSAGE_CODES, WR_INPUT_TYPES } from '@webround/core/form';
|
|
2
|
+
// Campi comuni a ogni kind. hidden/ariaLabel restano json
|
|
3
|
+
// Esigenza di accessibilità che può variare per lingua (es. aria-label tradotto).
|
|
4
|
+
// Hidden per breakpoint
|
|
5
|
+
const COMMON_FIELDS = [
|
|
6
|
+
{ key: 'ariaLabel', labelKey: 'ariaLabel', type: 'text', backend: 'json', multilang: true },
|
|
7
|
+
{ key: 'hidden', labelKey: 'hidden', type: 'toggle', backend: 'json' },
|
|
8
|
+
];
|
|
9
|
+
const TYPE_FIELDS = {
|
|
10
|
+
button: [
|
|
11
|
+
{ key: 'content', labelKey: 'label', type: 'textarea', rows: 2, backend: 'json', multilang: true },
|
|
12
|
+
],
|
|
13
|
+
link: [
|
|
14
|
+
{ key: 'content', labelKey: 'label', type: 'textarea', rows: 2, backend: 'json', multilang: true },
|
|
15
|
+
{ key: 'href', labelKey: 'href', type: 'url', backend: 'ast' },
|
|
16
|
+
{ key: 'target', labelKey: 'target', type: 'select', options: ['_self', '_blank'], backend: 'ast' },
|
|
17
|
+
],
|
|
18
|
+
image: [
|
|
19
|
+
{ key: 'src', labelKey: 'imageUrl', type: 'asset', assetKind: 'image', backend: 'ast' },
|
|
20
|
+
{ key: 'alt', labelKey: 'alt', type: 'text', backend: 'json', multilang: true },
|
|
21
|
+
{ key: 'objectFit', labelKey: 'displayMode', type: 'select', options: ['cover', 'contain', 'fill', 'none'], backend: 'ast' },
|
|
22
|
+
],
|
|
23
|
+
video: [
|
|
24
|
+
{ key: 'src', labelKey: 'videoUrl', type: 'asset', assetKind: 'video', backend: 'ast' },
|
|
25
|
+
{ key: 'autoPlay', labelKey: 'autoplay', type: 'toggle', backend: 'ast' },
|
|
26
|
+
{ key: 'loop', labelKey: 'loop', type: 'toggle', backend: 'ast' },
|
|
27
|
+
{ key: 'controls', labelKey: 'controls', type: 'toggle', backend: 'ast' },
|
|
28
|
+
],
|
|
29
|
+
iframe: [
|
|
30
|
+
{ key: 'src', labelKey: 'iframeUrl', type: 'url', backend: 'ast' },
|
|
31
|
+
{ key: 'title', labelKey: 'title', type: 'text', backend: 'ast' },
|
|
32
|
+
{ key: 'allowFullScreen', labelKey: 'allowFullscreen', type: 'toggle', backend: 'ast' },
|
|
33
|
+
{ key: 'loading', labelKey: 'loading', type: 'select', options: ['lazy', 'eager'], backend: 'ast' },
|
|
34
|
+
],
|
|
35
|
+
// Which kind of field it is and what it asks live in the code; the texts a visitor reads (label, placeholder, the form's messages) are translated, so JSON
|
|
36
|
+
input: [
|
|
37
|
+
{ key: 'label', labelKey: 'label', type: 'text', backend: 'json', multilang: true },
|
|
38
|
+
{ key: 'placeholder', labelKey: 'placeholder', type: 'text', backend: 'json', multilang: true },
|
|
39
|
+
{ key: 'type', labelKey: 'inputType', type: 'select', options: WR_INPUT_TYPES, backend: 'ast' },
|
|
40
|
+
{ key: 'required', labelKey: 'required', type: 'toggle', backend: 'ast' },
|
|
41
|
+
{ key: 'maxLength', labelKey: 'maxLength', type: 'number', min: 1, backend: 'ast' },
|
|
42
|
+
],
|
|
43
|
+
form: WR_FORM_MESSAGE_CODES.map((code) => ({ key: `msg-${code}`, labelKey: `msg-${code}`, type: 'text', backend: 'json', multilang: true })),
|
|
44
|
+
nav: [
|
|
45
|
+
{
|
|
46
|
+
key: 'items', labelKey: 'items', type: 'list', backend: 'json', // struttura + label tradotte: resta json
|
|
47
|
+
itemFields: [
|
|
48
|
+
{ key: 'label', labelKey: 'label', type: 'text', multilang: true },
|
|
49
|
+
{ key: 'href', labelKey: 'href', type: 'url' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
// The button that switches the site between its themes: a boolean in the code (it shows only if the site has a dark theme: that is the Theme panel's)
|
|
53
|
+
{ key: 'themeToggle', labelKey: 'themeToggle', type: 'toggle', backend: 'ast' },
|
|
54
|
+
// The visitor's account in the header (an icon with a sign-in, or its shortcuts): a boolean in the code; the site draws it
|
|
55
|
+
{ key: 'accountMenu', labelKey: 'accountMenu', type: 'toggle', backend: 'ast' },
|
|
56
|
+
// The visitor's cart in the header (an icon with how many things are in it): a boolean in the code; the site draws it
|
|
57
|
+
{ key: 'cartMenu', labelKey: 'cartMenu', type: 'toggle', backend: 'ast' },
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
// Fields edited on the page itself, not in the panel (a text is written in place): not in `getFields`, but an element has them all the same
|
|
61
|
+
const INLINE_FIELDS = {
|
|
62
|
+
text: [{ key: 'content', labelKey: 'content', type: 'textarea', backend: 'json', multilang: true }],
|
|
63
|
+
};
|
|
64
|
+
// Kind che non hanno i campi comuni (ariaLabel/hidden): nav ha i suoi.
|
|
65
|
+
const NO_COMMON_FIELDS = new Set(['nav']);
|
|
66
|
+
export function getFields(kind) {
|
|
67
|
+
const specific = TYPE_FIELDS[kind];
|
|
68
|
+
if (!specific)
|
|
69
|
+
return [];
|
|
70
|
+
if (NO_COMMON_FIELDS.has(kind))
|
|
71
|
+
return specific;
|
|
72
|
+
return [...specific, ...COMMON_FIELDS];
|
|
73
|
+
}
|
|
74
|
+
/** Every field an element has, the ones edited on the page too (a text's content): what an operation can set. */
|
|
75
|
+
export function getAllFields(kind) {
|
|
76
|
+
return [...getFields(kind), ...(INLINE_FIELDS[kind] ?? [])];
|
|
77
|
+
}
|
|
78
|
+
/** The fields of an element that are translated. */
|
|
79
|
+
export const multilangFieldsOf = (kind) => getAllFields(kind).filter(f => f.multilang).map(f => f.key);
|
|
80
|
+
const UPDATE_MESSAGE_TYPE = {
|
|
81
|
+
button: 'wr:button:update',
|
|
82
|
+
link: 'wr:link:update',
|
|
83
|
+
image: 'wr:image:update',
|
|
84
|
+
video: 'wr:video:update',
|
|
85
|
+
iframe: 'wr:iframe:update',
|
|
86
|
+
nav: 'wr:nav:update',
|
|
87
|
+
input: 'wr:input:update',
|
|
88
|
+
form: 'wr:form:update',
|
|
89
|
+
};
|
|
90
|
+
const BOOLEAN_FIELDS = new Set(['hidden', 'autoplay', 'loop', 'controls', 'required']);
|
|
91
|
+
const JSON_FIELDS_VALUE = new Set(['items']);
|
|
92
|
+
export function buildUpdateMessage(kind, elementId, key, value) {
|
|
93
|
+
const type = UPDATE_MESSAGE_TYPE[kind];
|
|
94
|
+
if (!type)
|
|
95
|
+
return null;
|
|
96
|
+
let resolvedValue = value;
|
|
97
|
+
if (BOOLEAN_FIELDS.has(key))
|
|
98
|
+
resolvedValue = value === 'true';
|
|
99
|
+
else if (JSON_FIELDS_VALUE.has(key))
|
|
100
|
+
resolvedValue = JSON.parse(value);
|
|
101
|
+
return { type, elementId, [key]: resolvedValue };
|
|
102
|
+
}
|
|
103
|
+
export function getFieldBackend(kind, key) {
|
|
104
|
+
return getAllFields(kind).find(f => f.key === key)?.backend ?? null;
|
|
105
|
+
}
|
|
106
|
+
/** The kind of element an update message is for (`wr:button:update` → button). */
|
|
107
|
+
export function kindOfUpdateMessage(type) {
|
|
108
|
+
return Object.keys(UPDATE_MESSAGE_TYPE).find(kind => UPDATE_MESSAGE_TYPE[kind] === type);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=schema.js.map
|
package/dist/scope.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface WrScope {
|
|
2
|
+
/** Relativo a `src/`, come lo vogliono gli endpoint /wr/*. */
|
|
3
|
+
file: string;
|
|
4
|
+
/** Nome del JSON in config/pages/ (può avere sottocartelle). */
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const WR_SCOPE_NAME_ATTR = "data-wr-scope";
|
|
8
|
+
export declare const WR_SCOPE_FILE_ATTR = "data-wr-file";
|
|
9
|
+
/** Da mettere sul wrapper (`display: contents`) di un ambito che non è la pagina. */
|
|
10
|
+
export declare const wrScopeProps: (scope: WrScope) => {
|
|
11
|
+
readonly "data-wr-scope": string;
|
|
12
|
+
readonly "data-wr-file": string;
|
|
13
|
+
};
|
|
14
|
+
export declare function setPageScope(scope: WrScope): void;
|
|
15
|
+
export declare const getPageScope: () => WrScope | null;
|
|
16
|
+
/** L'ambito che avvolge `el`, o quello della pagina. */
|
|
17
|
+
export declare function scopeOfElement(el: Element | null): WrScope | null;
|
|
18
|
+
/** L'ambito dell'elemento con questo id nella pagina (quello della pagina se non c'è). */
|
|
19
|
+
export declare function scopeOfId(id: string): WrScope | null;
|
|
20
|
+
/** L'ambito di un'ancora: il suo elemento, o la pagina (la radice è sempre della pagina). */
|
|
21
|
+
export declare const scopeOfAnchor: (anchor: {
|
|
22
|
+
id: string;
|
|
23
|
+
} | {
|
|
24
|
+
root: true;
|
|
25
|
+
}) => WrScope | null;
|
|
26
|
+
/**
|
|
27
|
+
* Where "after the header" or "before the footer" really is. The header and the footer are one element each (the root of their
|
|
28
|
+
* component): nothing can be put next to it in their file, but what a person means is the edge of the page: the first element after
|
|
29
|
+
* the header, the last one before the footer. Everything else is left as it was said.
|
|
30
|
+
*/
|
|
31
|
+
export declare function pageEdgeOf<A extends {
|
|
32
|
+
id: string;
|
|
33
|
+
} | {
|
|
34
|
+
root: true;
|
|
35
|
+
}, M extends string>(anchor: A, mode: M): {
|
|
36
|
+
anchor: A | {
|
|
37
|
+
root: true;
|
|
38
|
+
};
|
|
39
|
+
mode: M | 'first-child' | 'last-child';
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=scope.d.ts.map
|
package/dist/scope.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// L'ambito di un elemento: il FILE di codice che lo contiene e il nome del JSON dei suoi contenuti.
|
|
2
|
+
//
|
|
3
|
+
// Una pagina è il suo file e il suo JSON; header e footer sono di tutte le pagine e hanno i propri (layout/Header.tsx,
|
|
4
|
+
// config/pages/_shared/header.json). Un elemento sa a quale ambito appartiene: chi lo avvolge porta due attributi (`wrScopeProps`) e il
|
|
5
|
+
// bridge li legge risalendo dall'elemento. Un elemento fuori da ogni wrapper è della pagina. Così non esiste un "file attivo" unico
|
|
6
|
+
// e nessun componente Wr* deve conoscere gli ambiti.
|
|
7
|
+
export const WR_SCOPE_NAME_ATTR = 'data-wr-scope';
|
|
8
|
+
export const WR_SCOPE_FILE_ATTR = 'data-wr-file';
|
|
9
|
+
/** Da mettere sul wrapper (`display: contents`) di un ambito che non è la pagina. */
|
|
10
|
+
export const wrScopeProps = (scope) => ({ [WR_SCOPE_NAME_ATTR]: scope.name, [WR_SCOPE_FILE_ATTR]: scope.file });
|
|
11
|
+
let pageScope = null;
|
|
12
|
+
export function setPageScope(scope) { pageScope = scope; }
|
|
13
|
+
export const getPageScope = () => pageScope;
|
|
14
|
+
/** L'ambito che avvolge `el`, o quello della pagina. */
|
|
15
|
+
export function scopeOfElement(el) {
|
|
16
|
+
const wrapper = el?.closest(`[${WR_SCOPE_NAME_ATTR}]`);
|
|
17
|
+
if (!wrapper)
|
|
18
|
+
return pageScope;
|
|
19
|
+
return { name: wrapper.getAttribute(WR_SCOPE_NAME_ATTR), file: wrapper.getAttribute(WR_SCOPE_FILE_ATTR) };
|
|
20
|
+
}
|
|
21
|
+
/** L'ambito dell'elemento con questo id nella pagina (quello della pagina se non c'è). */
|
|
22
|
+
export function scopeOfId(id) {
|
|
23
|
+
return scopeOfElement(document.querySelector(`[data-wr-id="${CSS.escape(id)}"]`));
|
|
24
|
+
}
|
|
25
|
+
/** L'ambito di un'ancora: il suo elemento, o la pagina (la radice è sempre della pagina). */
|
|
26
|
+
export const scopeOfAnchor = (anchor) => 'id' in anchor ? scopeOfId(anchor.id) : pageScope;
|
|
27
|
+
/**
|
|
28
|
+
* Where "after the header" or "before the footer" really is. The header and the footer are one element each (the root of their
|
|
29
|
+
* component): nothing can be put next to it in their file, but what a person means is the edge of the page: the first element after
|
|
30
|
+
* the header, the last one before the footer. Everything else is left as it was said.
|
|
31
|
+
*/
|
|
32
|
+
export function pageEdgeOf(anchor, mode) {
|
|
33
|
+
if (!('id' in anchor))
|
|
34
|
+
return { anchor, mode };
|
|
35
|
+
const el = document.querySelector(`[data-wr-id="${CSS.escape(anchor.id)}"]`);
|
|
36
|
+
const wrapper = el?.parentElement;
|
|
37
|
+
const shared = wrapper?.getAttribute(WR_SCOPE_NAME_ATTR);
|
|
38
|
+
if (shared === '_shared/header' && mode === 'after')
|
|
39
|
+
return { anchor: { root: true }, mode: 'first-child' };
|
|
40
|
+
if (shared === '_shared/footer' && mode === 'before')
|
|
41
|
+
return { anchor: { root: true }, mode: 'last-child' };
|
|
42
|
+
return { anchor, mode };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Project, SyntaxKind, } from 'ts-morph';
|
|
4
|
+
import { getFields } from '@webround/editor/schema';
|
|
5
|
+
import { resolveInSrc, wrRoute } from './guard.js';
|
|
6
|
+
import { deleteJsx, findNode, insertJsx, moveJsx, } from './jsx-tree.js';
|
|
7
|
+
const ROOT = process.cwd();
|
|
8
|
+
function readLiteral(node, key) {
|
|
9
|
+
const attr = node.getAttribute(key);
|
|
10
|
+
if (!attr || attr.getKind() !== SyntaxKind.JsxAttribute)
|
|
11
|
+
return { present: false };
|
|
12
|
+
const init = attr.getInitializer?.();
|
|
13
|
+
if (!init)
|
|
14
|
+
return { present: true, value: true };
|
|
15
|
+
if (init.getKind() === SyntaxKind.StringLiteral)
|
|
16
|
+
return { present: true, value: init.getLiteralValue() };
|
|
17
|
+
if (init.getKind() === SyntaxKind.JsxExpression) {
|
|
18
|
+
const expr = init.getExpression();
|
|
19
|
+
if (!expr)
|
|
20
|
+
return { present: false };
|
|
21
|
+
if (expr.getKind() === SyntaxKind.TrueKeyword)
|
|
22
|
+
return { present: true, value: true };
|
|
23
|
+
if (expr.getKind() === SyntaxKind.FalseKeyword)
|
|
24
|
+
return { present: true, value: false };
|
|
25
|
+
if (expr.getKind() === SyntaxKind.NumericLiteral)
|
|
26
|
+
return { present: true, value: Number(expr.getText()) };
|
|
27
|
+
if (expr.getKind() === SyntaxKind.StringLiteral)
|
|
28
|
+
return { present: true, value: expr.getLiteralValue() };
|
|
29
|
+
}
|
|
30
|
+
return { present: false };
|
|
31
|
+
}
|
|
32
|
+
function writeLiteral(node, key, value) {
|
|
33
|
+
const existing = node.getAttribute(key);
|
|
34
|
+
if (value === null) {
|
|
35
|
+
if (existing && existing.getKind() === SyntaxKind.JsxAttribute)
|
|
36
|
+
existing.remove();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// JSX non ha escape con backslash dentro le stringhe-attributo: se il valore contiene `"`
|
|
40
|
+
// l'unico modo sicuro è un'espressione `{"..."}` (JSON.stringify produce un literal TS valido).
|
|
41
|
+
let text;
|
|
42
|
+
if (typeof value === 'string')
|
|
43
|
+
text = value.includes('"') ? `{${JSON.stringify(value)}}` : `"${value}"`;
|
|
44
|
+
else
|
|
45
|
+
text = `{${value}}`;
|
|
46
|
+
if (existing && existing.getKind() === SyntaxKind.JsxAttribute) {
|
|
47
|
+
existing.setInitializer(text);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
node.addAttribute({ name: key, initializer: text });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const ANCHOR_MODES = ['before', 'after', 'first-child', 'last-child'];
|
|
54
|
+
export function wrAstPlugin() {
|
|
55
|
+
const project = new Project({ tsConfigFilePath: path.resolve(ROOT, 'tsconfig.json'), skipAddingFilesFromTsConfig: true });
|
|
56
|
+
return {
|
|
57
|
+
name: 'wr-ast-plugin',
|
|
58
|
+
configureServer(server) {
|
|
59
|
+
wrRoute(server, '/wr/node', (req, res, next) => {
|
|
60
|
+
const url = new URL(req.url ?? '', 'http://localhost');
|
|
61
|
+
const file = url.searchParams.get('file');
|
|
62
|
+
const id = url.searchParams.get('id');
|
|
63
|
+
if (req.method === 'GET') {
|
|
64
|
+
if (!file || !id) {
|
|
65
|
+
res.statusCode = 400;
|
|
66
|
+
res.end('missing file/id');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const resolved = resolveInSrc(file);
|
|
70
|
+
if (!resolved || !fs.existsSync(resolved)) {
|
|
71
|
+
res.statusCode = 404;
|
|
72
|
+
res.end(JSON.stringify(null));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const found = findNode(project, resolved, id);
|
|
76
|
+
if (!found) {
|
|
77
|
+
res.statusCode = 404;
|
|
78
|
+
res.end(JSON.stringify(null));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const fields = getFields(found.kind).filter(f => f.backend === 'ast');
|
|
82
|
+
const values = {};
|
|
83
|
+
for (const f of fields) {
|
|
84
|
+
const { present, value } = readLiteral(found.node, f.key);
|
|
85
|
+
if (present && value !== undefined)
|
|
86
|
+
values[f.key] = value;
|
|
87
|
+
}
|
|
88
|
+
res.setHeader('Content-Type', 'application/json');
|
|
89
|
+
res.end(JSON.stringify({ fields: fields.map(f => f.key), values }));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (req.method === 'PUT') {
|
|
93
|
+
let body = '';
|
|
94
|
+
req.on('data', chunk => body += chunk);
|
|
95
|
+
req.on('end', () => {
|
|
96
|
+
try {
|
|
97
|
+
const { file: bodyFile, id: bodyId, key, value } = JSON.parse(body);
|
|
98
|
+
const resolved = resolveInSrc(bodyFile);
|
|
99
|
+
if (!resolved) {
|
|
100
|
+
res.statusCode = 400;
|
|
101
|
+
res.end(JSON.stringify({ error: 'invalid file' }));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const found = findNode(project, resolved, bodyId);
|
|
105
|
+
if (!found) {
|
|
106
|
+
res.statusCode = 404;
|
|
107
|
+
res.end(JSON.stringify({ error: 'node not found' }));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
writeLiteral(found.node, key, value);
|
|
111
|
+
found.node.getSourceFile().saveSync();
|
|
112
|
+
res.setHeader('Content-Type', 'application/json');
|
|
113
|
+
res.end(JSON.stringify({ ok: true }));
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
res.statusCode = 500;
|
|
117
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (req.method === 'DELETE') {
|
|
123
|
+
if (!file || !id) {
|
|
124
|
+
res.statusCode = 400;
|
|
125
|
+
res.end('missing file/id');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const resolved = resolveInSrc(file);
|
|
129
|
+
if (!resolved) {
|
|
130
|
+
res.statusCode = 400;
|
|
131
|
+
res.end(JSON.stringify({ error: 'invalid file' }));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const removed = deleteJsx(project, resolved, id);
|
|
135
|
+
if (!removed) {
|
|
136
|
+
res.statusCode = 404;
|
|
137
|
+
res.end(JSON.stringify({ error: 'node not found' }));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if ('refused' in removed) {
|
|
141
|
+
res.statusCode = 422;
|
|
142
|
+
res.setHeader('Content-Type', 'application/json');
|
|
143
|
+
res.end(JSON.stringify({ ok: false, code: removed.code, error: removed.error }));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
res.setHeader('Content-Type', 'application/json');
|
|
147
|
+
res.end(JSON.stringify({ ok: true, ...removed }));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (req.method === 'PATCH') {
|
|
151
|
+
// Spostare un elemento (con ciò che contiene) in un altro punto dello stesso file
|
|
152
|
+
let body = '';
|
|
153
|
+
req.on('data', chunk => body += chunk);
|
|
154
|
+
req.on('end', () => {
|
|
155
|
+
const send = (status, payload) => {
|
|
156
|
+
res.statusCode = status;
|
|
157
|
+
res.setHeader('Content-Type', 'application/json');
|
|
158
|
+
res.end(JSON.stringify(payload));
|
|
159
|
+
};
|
|
160
|
+
try {
|
|
161
|
+
const { file: bodyFile, id: moveId, anchor, anchorMode } = JSON.parse(body);
|
|
162
|
+
const resolved = resolveInSrc(bodyFile);
|
|
163
|
+
if (!resolved) {
|
|
164
|
+
send(400, { error: 'invalid file' });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (typeof moveId !== 'string' || !moveId) {
|
|
168
|
+
send(400, { code: 'anchor-not-found', error: 'id required' });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!anchor || typeof anchor !== 'object' || !('root' in anchor ? anchor.root === true : typeof anchor.id === 'string')) {
|
|
172
|
+
send(400, { code: 'anchor-not-found', error: 'anchor required: { id } or { root: true }' });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!ANCHOR_MODES.includes(anchorMode)) {
|
|
176
|
+
send(400, { code: 'invalid-mode', error: 'anchorMode required' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const result = moveJsx(project, resolved, { id: moveId, anchor, mode: anchorMode });
|
|
180
|
+
if (!result.ok) {
|
|
181
|
+
send(result.status, { code: result.code, error: result.error });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
send(200, { ok: true, id: result.id, from: result.from });
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
send(500, { error: String(err) });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (req.method === 'POST') {
|
|
193
|
+
let body = '';
|
|
194
|
+
req.on('data', chunk => body += chunk);
|
|
195
|
+
req.on('end', () => {
|
|
196
|
+
const send = (status, payload) => {
|
|
197
|
+
res.statusCode = status;
|
|
198
|
+
res.setHeader('Content-Type', 'application/json');
|
|
199
|
+
res.end(JSON.stringify(payload));
|
|
200
|
+
};
|
|
201
|
+
try {
|
|
202
|
+
// Il contratto è codice: JSX in ingresso, lo stesso per l'editor, per chi scrive a mano e per un'AI.
|
|
203
|
+
// `afterId` è il vecchio nome di `anchor.id`.
|
|
204
|
+
const { file: bodyFile, anchor, afterId, anchorMode, rawJsx, uniqueIds } = JSON.parse(body);
|
|
205
|
+
const resolved = resolveInSrc(bodyFile);
|
|
206
|
+
if (!resolved) {
|
|
207
|
+
send(400, { error: 'invalid file' });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (typeof rawJsx !== 'string' || !rawJsx.trim()) {
|
|
211
|
+
send(400, { code: 'invalid-jsx', error: 'rawJsx required' });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const target = anchor && typeof anchor === 'object' ? anchor : typeof afterId === 'string' ? { id: afterId } : null;
|
|
215
|
+
if (!target || !('root' in target ? target.root === true : typeof target.id === 'string')) {
|
|
216
|
+
send(400, { code: 'anchor-not-found', error: 'anchor required: { id } or { root: true }' });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const mode = ANCHOR_MODES.includes(anchorMode) ? anchorMode : 'after';
|
|
220
|
+
const result = insertJsx(project, resolved, { jsx: rawJsx, anchor: target, mode, uniqueIds });
|
|
221
|
+
if (!result.ok) {
|
|
222
|
+
send(result.status, { code: result.code, error: result.error });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
send(200, { ok: true, ids: result.ids, rootIds: result.rootIds, undoable: result.undoable, idMap: result.idMap, jsx: result.jsx });
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
send(500, { error: String(err) });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
next();
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=ast.js.map
|