@toaster-builder/site-runtime 0.1.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/README.md +38 -0
- package/dist/module.d.mts +7 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +21 -0
- package/dist/runtime/assets/editor/add-block.svg +5 -0
- package/dist/runtime/assets/editor/ai-assistant.svg +5 -0
- package/dist/runtime/assets/editor/block-settings-center.svg +3 -0
- package/dist/runtime/assets/editor/block-settings-path.svg +3 -0
- package/dist/runtime/assets/editor/copy-block.svg +10 -0
- package/dist/runtime/assets/editor/delete-block.svg +4 -0
- package/dist/runtime/assets/editor/hide-block.svg +6 -0
- package/dist/runtime/assets/editor/move-block-down.svg +3 -0
- package/dist/runtime/assets/editor/move-block-up.svg +3 -0
- package/dist/runtime/assets/editor/show-block.svg +4 -0
- package/dist/runtime/components/BlockEditorFrame.d.vue.ts +16 -0
- package/dist/runtime/components/BlockEditorFrame.vue +418 -0
- package/dist/runtime/components/BlockEditorFrame.vue.d.ts +16 -0
- package/dist/runtime/components/BlockPlacementProvider.d.vue.ts +16 -0
- package/dist/runtime/components/BlockPlacementProvider.vue +12 -0
- package/dist/runtime/components/BlockPlacementProvider.vue.d.ts +16 -0
- package/dist/runtime/components/PageRegionRenderer.d.vue.ts +16 -0
- package/dist/runtime/components/PageRegionRenderer.vue +50 -0
- package/dist/runtime/components/PageRegionRenderer.vue.d.ts +16 -0
- package/dist/runtime/components/PageRenderer.d.vue.ts +3 -0
- package/dist/runtime/components/PageRenderer.vue +65 -0
- package/dist/runtime/components/PageRenderer.vue.d.ts +3 -0
- package/dist/runtime/composables/useBuilder.d.ts +15 -0
- package/dist/runtime/composables/useBuilder.js +27 -0
- package/dist/runtime/composables/useBuilderEventService.d.ts +3 -0
- package/dist/runtime/composables/useBuilderEventService.js +14 -0
- package/dist/runtime/composables/useBuilderState.d.ts +8 -0
- package/dist/runtime/composables/useBuilderState.js +21 -0
- package/dist/runtime/composables/usePageRegistry.d.ts +6 -0
- package/dist/runtime/composables/usePageRegistry.js +76 -0
- package/dist/runtime/composables/usePageSettings.d.ts +21 -0
- package/dist/runtime/composables/usePageSettings.js +90 -0
- package/dist/runtime/events/builder-event.d.ts +9 -0
- package/dist/runtime/events/builder-event.js +14 -0
- package/dist/runtime/events/builder-form-preview-token-event.d.ts +7 -0
- package/dist/runtime/events/builder-form-preview-token-event.js +19 -0
- package/dist/runtime/events/builder-page-state-event.d.ts +9 -0
- package/dist/runtime/events/builder-page-state-event.js +62 -0
- package/dist/runtime/events/builder-ready-ping-event.d.ts +6 -0
- package/dist/runtime/events/builder-ready-ping-event.js +10 -0
- package/dist/runtime/events/site-options-updated-event.d.ts +8 -0
- package/dist/runtime/events/site-options-updated-event.js +18 -0
- package/dist/runtime/plugin.d.ts +10 -0
- package/dist/runtime/plugin.js +10 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/services/builder-event-service.d.ts +37 -0
- package/dist/runtime/services/builder-event-service.js +104 -0
- package/dist/runtime/types/assets.d.ts +5 -0
- package/dist/runtime/types/page.d.ts +37 -0
- package/dist/runtime/types/page.js +9 -0
- package/dist/runtime/utils/block-options.d.ts +2 -0
- package/dist/runtime/utils/block-options.js +9 -0
- package/dist/runtime/utils/block-placement.d.ts +2 -0
- package/dist/runtime/utils/block-placement.js +1 -0
- package/dist/runtime/utils/common.d.ts +2 -0
- package/dist/runtime/utils/common.js +6 -0
- package/dist/runtime/utils/css-variables.d.ts +7 -0
- package/dist/runtime/utils/css-variables.js +35 -0
- package/dist/runtime/utils/fonts.d.ts +8 -0
- package/dist/runtime/utils/fonts.js +80 -0
- package/dist/runtime/utils/page-config.d.ts +3 -0
- package/dist/runtime/utils/page-config.js +114 -0
- package/dist/runtime/utils/page-options.d.ts +3 -0
- package/dist/runtime/utils/page-options.js +26 -0
- package/dist/types.d.mts +3 -0
- package/package.json +65 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import BlockEditorFrame from "#toaster-site-runtime/components/BlockEditorFrame.vue";
|
|
4
|
+
import BlockPlacementProvider from "#toaster-site-runtime/components/BlockPlacementProvider.vue";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
builderReady: { type: Boolean, required: true },
|
|
7
|
+
isPlacementEnabled: { type: Function, required: true },
|
|
8
|
+
pagePlacements: { type: Function, required: true },
|
|
9
|
+
placementKey: { type: Function, required: true },
|
|
10
|
+
region: { type: null, required: true },
|
|
11
|
+
resolveBlock: { type: Function, required: true },
|
|
12
|
+
resolveBlockOptions: { type: Function, required: true }
|
|
13
|
+
});
|
|
14
|
+
const placements = computed(() => props.pagePlacements(props.region));
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<component :is="region">
|
|
19
|
+
<template
|
|
20
|
+
v-for="(placement, index) in placements"
|
|
21
|
+
:key="placementKey(region, placement)"
|
|
22
|
+
>
|
|
23
|
+
<DevOnly>
|
|
24
|
+
<BlockEditorFrame
|
|
25
|
+
v-if="isPlacementEnabled(placement) || builderReady"
|
|
26
|
+
:block-component="resolveBlock(placement.id)"
|
|
27
|
+
:block-id="placement.id"
|
|
28
|
+
:block-index="index"
|
|
29
|
+
:builder-ready="builderReady"
|
|
30
|
+
:enabled="isPlacementEnabled(placement)"
|
|
31
|
+
:options="resolveBlockOptions(placement)"
|
|
32
|
+
:placement-id="placement.placementId"
|
|
33
|
+
:placement-count="placements.length"
|
|
34
|
+
:region="region"
|
|
35
|
+
/>
|
|
36
|
+
<template #fallback>
|
|
37
|
+
<BlockPlacementProvider
|
|
38
|
+
v-if="isPlacementEnabled(placement)"
|
|
39
|
+
:placement-id="placement.placementId"
|
|
40
|
+
>
|
|
41
|
+
<component
|
|
42
|
+
:is="resolveBlock(placement.id)"
|
|
43
|
+
:options="resolveBlockOptions(placement)"
|
|
44
|
+
/>
|
|
45
|
+
</BlockPlacementProvider>
|
|
46
|
+
</template>
|
|
47
|
+
</DevOnly>
|
|
48
|
+
</template>
|
|
49
|
+
</component>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Component } from 'vue';
|
|
2
|
+
import type { BlockOptions, BlockPlacement, PageRegion } from '#toaster-site-runtime/types/page';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
builderReady: boolean;
|
|
5
|
+
isPlacementEnabled: (placement: BlockPlacement) => boolean;
|
|
6
|
+
pagePlacements: (region: PageRegion) => BlockPlacement[];
|
|
7
|
+
placementKey: (region: PageRegion, placement: BlockPlacement) => string;
|
|
8
|
+
region: PageRegion;
|
|
9
|
+
resolveBlock: (id: string) => Component;
|
|
10
|
+
resolveBlockOptions: (placement: BlockPlacement & {
|
|
11
|
+
options?: BlockOptions;
|
|
12
|
+
}) => BlockOptions;
|
|
13
|
+
};
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { onBeforeUnmount } from "vue";
|
|
3
|
+
import { useRoute, useSeoMeta } from "#imports";
|
|
4
|
+
import PageRegionRenderer from "#toaster-site-runtime/components/PageRegionRenderer.vue";
|
|
5
|
+
import { useBuilderEventService } from "#toaster-site-runtime/composables/useBuilderEventService";
|
|
6
|
+
import { useBuilderState } from "#toaster-site-runtime/composables/useBuilderState";
|
|
7
|
+
import { usePageRegistry } from "#toaster-site-runtime/composables/usePageRegistry";
|
|
8
|
+
import { usePageSettings } from "#toaster-site-runtime/composables/usePageSettings";
|
|
9
|
+
import { normalizePagePath } from "#toaster-site-runtime/utils/page-options";
|
|
10
|
+
import { pageRegions } from "#toaster-site-runtime/types/page";
|
|
11
|
+
const route = useRoute();
|
|
12
|
+
const currentPath = () => normalizePagePath(route.path);
|
|
13
|
+
const { blockDefaultOptionsRegistry, blockRegistry } = usePageRegistry();
|
|
14
|
+
const {
|
|
15
|
+
builderReady,
|
|
16
|
+
formPreviewToken,
|
|
17
|
+
pageRuntimeState,
|
|
18
|
+
siteOptions
|
|
19
|
+
} = useBuilderState();
|
|
20
|
+
const {
|
|
21
|
+
getDescription,
|
|
22
|
+
getTitle,
|
|
23
|
+
isPlacementEnabled,
|
|
24
|
+
pagePlacements,
|
|
25
|
+
placementKey,
|
|
26
|
+
resolveBlock,
|
|
27
|
+
resolveBlockOptions
|
|
28
|
+
} = usePageSettings({
|
|
29
|
+
blockDefaultOptionsRegistry,
|
|
30
|
+
blockRegistry,
|
|
31
|
+
currentPath,
|
|
32
|
+
pageRuntimeState,
|
|
33
|
+
siteOptions
|
|
34
|
+
});
|
|
35
|
+
if (import.meta.dev) {
|
|
36
|
+
const builderEvents = useBuilderEventService();
|
|
37
|
+
const clearPageContext = builderEvents.setPageContext({
|
|
38
|
+
currentPath,
|
|
39
|
+
formPreviewToken,
|
|
40
|
+
isKnownBlock: (blockId) => blockRegistry.has(blockId),
|
|
41
|
+
pageRuntimeState
|
|
42
|
+
});
|
|
43
|
+
onBeforeUnmount(clearPageContext);
|
|
44
|
+
}
|
|
45
|
+
useSeoMeta({
|
|
46
|
+
title: getTitle,
|
|
47
|
+
description: getDescription
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div class="site-shell">
|
|
53
|
+
<PageRegionRenderer
|
|
54
|
+
v-for="region in pageRegions"
|
|
55
|
+
:key="region"
|
|
56
|
+
:builder-ready="builderReady"
|
|
57
|
+
:is-placement-enabled="isPlacementEnabled"
|
|
58
|
+
:page-placements="pagePlacements"
|
|
59
|
+
:placement-key="placementKey"
|
|
60
|
+
:region="region"
|
|
61
|
+
:resolve-block="resolveBlock"
|
|
62
|
+
:resolve-block-options="resolveBlockOptions"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BlockOptions } from '#toaster-site-runtime/types/page';
|
|
2
|
+
interface SiteControl {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
}
|
|
6
|
+
interface UseBuilderOptions {
|
|
7
|
+
siteControls: readonly SiteControl[];
|
|
8
|
+
siteOptions: BlockOptions;
|
|
9
|
+
}
|
|
10
|
+
export declare function useBuilder(options: UseBuilderOptions): {
|
|
11
|
+
siteCssVariables: import("vue").ComputedRef<Record<string, string>>;
|
|
12
|
+
siteFontFacesCss: import("vue").ComputedRef<string>;
|
|
13
|
+
siteFontStyleSheets: import("vue").ComputedRef<string[]>;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { computed, onBeforeUnmount, onMounted } from "vue";
|
|
2
|
+
import { BuilderEventService } from "#toaster-site-runtime/services/builder-event-service";
|
|
3
|
+
import { collectCssVariables } from "#toaster-site-runtime/utils/css-variables";
|
|
4
|
+
import { collectFontFacesCss, collectFontStyleSheets } from "#toaster-site-runtime/utils/fonts";
|
|
5
|
+
import { provideBuilderEventService } from "./useBuilderEventService.js";
|
|
6
|
+
import { useBuilderState } from "./useBuilderState.js";
|
|
7
|
+
export function useBuilder(options) {
|
|
8
|
+
const { builderOrigin, builderReady, siteOptions } = useBuilderState(options.siteOptions);
|
|
9
|
+
if (import.meta.dev) {
|
|
10
|
+
const builderEvents = new BuilderEventService({
|
|
11
|
+
builderOrigin,
|
|
12
|
+
builderReady,
|
|
13
|
+
siteOptions
|
|
14
|
+
});
|
|
15
|
+
provideBuilderEventService(builderEvents);
|
|
16
|
+
onMounted(() => window.addEventListener("message", builderEvents.handleMessage));
|
|
17
|
+
onBeforeUnmount(() => window.removeEventListener("message", builderEvents.handleMessage));
|
|
18
|
+
}
|
|
19
|
+
const siteCssVariables = computed(() => collectCssVariables(options.siteControls, siteOptions.value));
|
|
20
|
+
const siteFontStyleSheets = computed(() => collectFontStyleSheets(options.siteControls, siteOptions.value));
|
|
21
|
+
const siteFontFacesCss = computed(() => collectFontFacesCss(options.siteControls, siteOptions.value));
|
|
22
|
+
return {
|
|
23
|
+
siteCssVariables,
|
|
24
|
+
siteFontFacesCss,
|
|
25
|
+
siteFontStyleSheets
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { inject, provide } from "vue";
|
|
2
|
+
const builderEventServiceKey = Symbol(
|
|
3
|
+
"toaster-builder-event-service"
|
|
4
|
+
);
|
|
5
|
+
export function provideBuilderEventService(service) {
|
|
6
|
+
provide(builderEventServiceKey, service);
|
|
7
|
+
}
|
|
8
|
+
export function useBuilderEventService() {
|
|
9
|
+
const service = inject(builderEventServiceKey);
|
|
10
|
+
if (!service) {
|
|
11
|
+
throw new Error("BuilderEventService was not provided by the application root.");
|
|
12
|
+
}
|
|
13
|
+
return service;
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BlockOptions, PageRuntimeState } from '#toaster-site-runtime/types/page';
|
|
2
|
+
export declare function useBuilderState(initialSiteOptions?: BlockOptions): {
|
|
3
|
+
builderOrigin: import("vue").Ref<string | null, string | null>;
|
|
4
|
+
builderReady: import("vue").Ref<boolean, boolean>;
|
|
5
|
+
formPreviewToken: import("vue").Ref<string | null, string | null>;
|
|
6
|
+
pageRuntimeState: import("vue").Ref<PageRuntimeState | null, PageRuntimeState | null>;
|
|
7
|
+
siteOptions: import("vue").Ref<BlockOptions, BlockOptions>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { useState } from "#imports";
|
|
3
|
+
export function useBuilderState(initialSiteOptions = {}) {
|
|
4
|
+
const siteOptions = useState("toaster-site-options", () => ({
|
|
5
|
+
...initialSiteOptions
|
|
6
|
+
}));
|
|
7
|
+
const builderOrigin = import.meta.dev ? useState("toaster-builder-origin", () => null) : ref(null);
|
|
8
|
+
const builderReady = import.meta.dev ? useState("toaster-builder-ready", () => false) : ref(false);
|
|
9
|
+
const formPreviewToken = useState("toaster-form-preview-token", () => null);
|
|
10
|
+
const pageRuntimeState = useState(
|
|
11
|
+
"toaster-page-runtime-state",
|
|
12
|
+
() => null
|
|
13
|
+
);
|
|
14
|
+
return {
|
|
15
|
+
builderOrigin,
|
|
16
|
+
builderReady,
|
|
17
|
+
formPreviewToken,
|
|
18
|
+
pageRuntimeState,
|
|
19
|
+
siteOptions
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { isBlockId } from "#toaster-site-runtime/types/page";
|
|
2
|
+
import { isRecord } from "#toaster-site-runtime/utils/common";
|
|
3
|
+
const blockModules = import.meta.glob("/blocks/*/*/*.vue", {
|
|
4
|
+
eager: true,
|
|
5
|
+
import: "default"
|
|
6
|
+
});
|
|
7
|
+
const blockConfigModules = import.meta.glob("/blocks/*/*/config.json", {
|
|
8
|
+
eager: true,
|
|
9
|
+
import: "default"
|
|
10
|
+
});
|
|
11
|
+
const blockSchemaModules = import.meta.glob("/blocks/*/*/schema.json", {
|
|
12
|
+
eager: true,
|
|
13
|
+
import: "default"
|
|
14
|
+
});
|
|
15
|
+
function blockIdFromSource(source) {
|
|
16
|
+
const pathParts = source.split("/");
|
|
17
|
+
const blockName = pathParts.at(-3);
|
|
18
|
+
const variantName = pathParts.at(-2);
|
|
19
|
+
return blockName && variantName ? `${blockName}/${variantName}` : null;
|
|
20
|
+
}
|
|
21
|
+
export function usePageRegistry() {
|
|
22
|
+
const blockRegistry = /* @__PURE__ */ new Map();
|
|
23
|
+
const blockDefaultOptionsRegistry = /* @__PURE__ */ new Map();
|
|
24
|
+
const blockSchemaRegistry = /* @__PURE__ */ new Set();
|
|
25
|
+
for (const [source, component] of Object.entries(blockModules)) {
|
|
26
|
+
const pathParts = source.split("/");
|
|
27
|
+
const blockName = pathParts.at(-3);
|
|
28
|
+
const fileName = pathParts.at(-1)?.replace(/\.vue$/, "");
|
|
29
|
+
const blockId = blockIdFromSource(source);
|
|
30
|
+
if (!blockId || !isBlockId(blockId) || blockName !== fileName) {
|
|
31
|
+
throw new Error(`Block path must follow blocks/<Block>/<variant>/<Block>.vue: ${source}`);
|
|
32
|
+
}
|
|
33
|
+
if (blockRegistry.has(blockId)) {
|
|
34
|
+
throw new Error(`Duplicate Block id "${blockId}"`);
|
|
35
|
+
}
|
|
36
|
+
blockRegistry.set(blockId, component);
|
|
37
|
+
}
|
|
38
|
+
for (const [source, value] of Object.entries(blockConfigModules)) {
|
|
39
|
+
const blockId = blockIdFromSource(source);
|
|
40
|
+
if (!blockId || !isBlockId(blockId) || !blockRegistry.has(blockId)) {
|
|
41
|
+
throw new Error(`Block config has no matching component: ${source}`);
|
|
42
|
+
}
|
|
43
|
+
if (!isRecord(value) || value.version !== 1 || !isRecord(value.options)) {
|
|
44
|
+
throw new Error(`Invalid Block config in ${source}`);
|
|
45
|
+
}
|
|
46
|
+
if (blockDefaultOptionsRegistry.has(blockId)) {
|
|
47
|
+
throw new Error(`Duplicate config for Block "${blockId}"`);
|
|
48
|
+
}
|
|
49
|
+
blockDefaultOptionsRegistry.set(blockId, value.options);
|
|
50
|
+
}
|
|
51
|
+
for (const [source, value] of Object.entries(blockSchemaModules)) {
|
|
52
|
+
const blockId = blockIdFromSource(source);
|
|
53
|
+
if (!blockId || !isBlockId(blockId) || !blockRegistry.has(blockId)) {
|
|
54
|
+
throw new Error(`Block schema has no matching component: ${source}`);
|
|
55
|
+
}
|
|
56
|
+
if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.tabs) || !Array.isArray(value.controls)) {
|
|
57
|
+
throw new Error(`Invalid Block schema in ${source}`);
|
|
58
|
+
}
|
|
59
|
+
if (blockSchemaRegistry.has(blockId)) {
|
|
60
|
+
throw new Error(`Duplicate schema for Block "${blockId}"`);
|
|
61
|
+
}
|
|
62
|
+
blockSchemaRegistry.add(blockId);
|
|
63
|
+
}
|
|
64
|
+
for (const blockId of blockRegistry.keys()) {
|
|
65
|
+
if (!blockDefaultOptionsRegistry.has(blockId)) {
|
|
66
|
+
throw new Error(`Missing config for Block "${blockId}"`);
|
|
67
|
+
}
|
|
68
|
+
if (!blockSchemaRegistry.has(blockId)) {
|
|
69
|
+
throw new Error(`Missing schema for Block "${blockId}"`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
blockDefaultOptionsRegistry,
|
|
74
|
+
blockRegistry
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Component, type Ref } from 'vue';
|
|
2
|
+
import { type BlockOptions, type BlockPlacement, type PageRegion, type PageRuntimeState } from '#toaster-site-runtime/types/page';
|
|
3
|
+
interface UsePageSettingsOptions {
|
|
4
|
+
blockDefaultOptionsRegistry: ReadonlyMap<string, BlockOptions>;
|
|
5
|
+
blockRegistry: ReadonlyMap<string, Component>;
|
|
6
|
+
currentPath: () => string;
|
|
7
|
+
pageRuntimeState: Ref<PageRuntimeState | null>;
|
|
8
|
+
siteOptions: Ref<BlockOptions>;
|
|
9
|
+
}
|
|
10
|
+
export declare function usePageSettings(options: UsePageSettingsOptions): {
|
|
11
|
+
getDescription: () => string | undefined;
|
|
12
|
+
getTitle: () => string | undefined;
|
|
13
|
+
isPlacementEnabled: (placement: BlockPlacement) => boolean;
|
|
14
|
+
pagePlacements: (region: PageRegion) => BlockPlacement[];
|
|
15
|
+
placementKey: (region: PageRegion, placement: BlockPlacement) => string;
|
|
16
|
+
resolveBlock: (id: string) => Component;
|
|
17
|
+
resolveBlockOptions: (placement: BlockPlacement & {
|
|
18
|
+
options?: BlockOptions;
|
|
19
|
+
}) => BlockOptions;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { computed, watch } from "vue";
|
|
2
|
+
import { createError } from "#imports";
|
|
3
|
+
import {
|
|
4
|
+
pageRegions
|
|
5
|
+
} from "#toaster-site-runtime/types/page";
|
|
6
|
+
import { getBlockElementId } from "#toaster-site-runtime/utils/block-options";
|
|
7
|
+
import { getBlockPlacementOptions, getPageConfig } from "#toaster-site-runtime/utils/page-config";
|
|
8
|
+
export function usePageSettings(options) {
|
|
9
|
+
watch(options.currentPath, () => {
|
|
10
|
+
options.pageRuntimeState.value = null;
|
|
11
|
+
});
|
|
12
|
+
const pageConfig = computed(() => {
|
|
13
|
+
const currentPath = options.currentPath();
|
|
14
|
+
const persistedConfig = getPageConfig(currentPath);
|
|
15
|
+
const config = options.pageRuntimeState.value?.page.options.path === currentPath ? options.pageRuntimeState.value.page : persistedConfig;
|
|
16
|
+
if (!config) {
|
|
17
|
+
throw createError({
|
|
18
|
+
statusCode: 404,
|
|
19
|
+
statusMessage: `Page config not found for path "${currentPath}"`
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const elementIds = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const region of pageRegions) {
|
|
24
|
+
for (const placement of config.blocks[region]) {
|
|
25
|
+
const blockId = placement.id;
|
|
26
|
+
if (!options.blockRegistry.has(blockId)) {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 500,
|
|
29
|
+
statusMessage: `Unknown Block "${blockId}" used by page "${config.options.path}"`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const elementId = getBlockElementId(resolveBlockOptions(placement), blockId);
|
|
33
|
+
const duplicateBlock = elementIds.get(elementId);
|
|
34
|
+
if (duplicateBlock !== void 0) {
|
|
35
|
+
throw createError({
|
|
36
|
+
statusCode: 500,
|
|
37
|
+
statusMessage: `Duplicate element-id "${elementId}" in Blocks "${duplicateBlock}" and "${placement.id}"`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
elementIds.set(elementId, blockId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return config;
|
|
44
|
+
});
|
|
45
|
+
function resolveBlockOptions(placement) {
|
|
46
|
+
if (placement.options) return placement.options;
|
|
47
|
+
return {
|
|
48
|
+
...options.blockDefaultOptionsRegistry.get(placement.id),
|
|
49
|
+
...getBlockPlacementOptions(placement)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function resolveBlock(id) {
|
|
53
|
+
const component = options.blockRegistry.get(id);
|
|
54
|
+
if (!component) {
|
|
55
|
+
throw createError({
|
|
56
|
+
statusCode: 500,
|
|
57
|
+
statusMessage: `Unknown Block "${id}" used by page "${pageConfig.value.options.path}"`
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return component;
|
|
61
|
+
}
|
|
62
|
+
function pagePlacements(region) {
|
|
63
|
+
return pageConfig.value.blocks[region];
|
|
64
|
+
}
|
|
65
|
+
function placementKey(region, placement) {
|
|
66
|
+
return `${region}:${placement.placementId}`;
|
|
67
|
+
}
|
|
68
|
+
function isPlacementEnabled(placement) {
|
|
69
|
+
return placement.enable;
|
|
70
|
+
}
|
|
71
|
+
function getSiteStringOption(optionId) {
|
|
72
|
+
const value = options.siteOptions.value[optionId];
|
|
73
|
+
return typeof value === "string" ? value : void 0;
|
|
74
|
+
}
|
|
75
|
+
function getTitle() {
|
|
76
|
+
return pageConfig.value.options.seoTitle ?? getSiteStringOption("name");
|
|
77
|
+
}
|
|
78
|
+
function getDescription() {
|
|
79
|
+
return pageConfig.value.options.seoDescription ?? getSiteStringOption("description");
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
getDescription,
|
|
83
|
+
getTitle,
|
|
84
|
+
isPlacementEnabled,
|
|
85
|
+
pagePlacements,
|
|
86
|
+
placementKey,
|
|
87
|
+
resolveBlock,
|
|
88
|
+
resolveBlockOptions
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare abstract class BuilderEvent {
|
|
2
|
+
readonly origin: string;
|
|
3
|
+
protected constructor(origin: string);
|
|
4
|
+
}
|
|
5
|
+
export declare abstract class BuilderMutationEvent extends BuilderEvent {
|
|
6
|
+
readonly mutationId: string;
|
|
7
|
+
protected constructor(origin: string, mutationId: string);
|
|
8
|
+
}
|
|
9
|
+
export declare function parseMutationId(payload: Record<string, unknown>): string | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class BuilderEvent {
|
|
2
|
+
constructor(origin) {
|
|
3
|
+
this.origin = origin;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export class BuilderMutationEvent extends BuilderEvent {
|
|
7
|
+
constructor(origin, mutationId) {
|
|
8
|
+
super(origin);
|
|
9
|
+
this.mutationId = mutationId;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function parseMutationId(payload) {
|
|
13
|
+
return typeof payload.mutationId === "string" && payload.mutationId.trim() !== "" ? payload.mutationId : null;
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BuilderMutationEvent } from './builder-event.js';
|
|
2
|
+
export declare class BuilderFormPreviewTokenEvent extends BuilderMutationEvent {
|
|
3
|
+
readonly token: string;
|
|
4
|
+
static readonly event = "builder-form-preview-token";
|
|
5
|
+
private constructor();
|
|
6
|
+
static fromPayload(payload: Record<string, unknown>, origin: string): BuilderFormPreviewTokenEvent | null;
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BuilderMutationEvent, parseMutationId } from "./builder-event.js";
|
|
2
|
+
export class BuilderFormPreviewTokenEvent extends BuilderMutationEvent {
|
|
3
|
+
constructor(token, mutationId, origin) {
|
|
4
|
+
super(origin, mutationId);
|
|
5
|
+
this.token = token;
|
|
6
|
+
}
|
|
7
|
+
static event = "builder-form-preview-token";
|
|
8
|
+
static fromPayload(payload, origin) {
|
|
9
|
+
const mutationId = parseMutationId(payload);
|
|
10
|
+
if (typeof payload.token !== "string" || payload.token === "" || mutationId === null) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return new BuilderFormPreviewTokenEvent(
|
|
14
|
+
payload.token,
|
|
15
|
+
mutationId,
|
|
16
|
+
origin
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PageRuntimeState } from '#toaster-site-runtime/types/page';
|
|
2
|
+
import { BuilderMutationEvent } from './builder-event.js';
|
|
3
|
+
export declare class BuilderPageStateEvent extends BuilderMutationEvent {
|
|
4
|
+
readonly state: PageRuntimeState;
|
|
5
|
+
static readonly event = "builder-page-state";
|
|
6
|
+
private constructor();
|
|
7
|
+
static fromPayload(payload: Record<string, unknown>, origin: string, isKnownBlock: (blockId: string) => boolean): BuilderPageStateEvent | null;
|
|
8
|
+
private static parseState;
|
|
9
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBlockId,
|
|
3
|
+
isPlacementId,
|
|
4
|
+
pageRegions
|
|
5
|
+
} from "#toaster-site-runtime/types/page";
|
|
6
|
+
import { isRecord } from "#toaster-site-runtime/utils/common";
|
|
7
|
+
import { parsePageOptions } from "#toaster-site-runtime/utils/page-options";
|
|
8
|
+
import { BuilderMutationEvent, parseMutationId } from "./builder-event.js";
|
|
9
|
+
export class BuilderPageStateEvent extends BuilderMutationEvent {
|
|
10
|
+
constructor(state, mutationId, origin) {
|
|
11
|
+
super(origin, mutationId);
|
|
12
|
+
this.state = state;
|
|
13
|
+
}
|
|
14
|
+
static event = "builder-page-state";
|
|
15
|
+
static fromPayload(payload, origin, isKnownBlock) {
|
|
16
|
+
const state = this.parseState(payload.state, isKnownBlock);
|
|
17
|
+
const mutationId = parseMutationId(payload);
|
|
18
|
+
if (!state || mutationId === null) return null;
|
|
19
|
+
return new BuilderPageStateEvent(
|
|
20
|
+
state,
|
|
21
|
+
mutationId,
|
|
22
|
+
origin
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
static parseState(value, isKnownBlock) {
|
|
26
|
+
if (!isRecord(value) || typeof value.revision !== "string" || !/^[a-f0-9]{64}$/.test(value.revision) || !isRecord(value.page) || value.page.version !== 1 || !isRecord(value.page.blocks)) return null;
|
|
27
|
+
let pageOptions;
|
|
28
|
+
try {
|
|
29
|
+
pageOptions = parsePageOptions(value.page.options, "builder-page-state");
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const placementIds = /* @__PURE__ */ new Set();
|
|
34
|
+
const blocks = {
|
|
35
|
+
header: [],
|
|
36
|
+
main: [],
|
|
37
|
+
footer: []
|
|
38
|
+
};
|
|
39
|
+
for (const region of pageRegions) {
|
|
40
|
+
const placements = value.page.blocks[region];
|
|
41
|
+
if (!Array.isArray(placements)) return null;
|
|
42
|
+
for (const placement of placements) {
|
|
43
|
+
if (!isRecord(placement) || typeof placement.id !== "string" || !isBlockId(placement.id) || !isKnownBlock(placement.id) || typeof placement.enable !== "boolean" || typeof placement.placementId !== "string" || !isPlacementId(placement.placementId) || placementIds.has(placement.placementId) || !isRecord(placement.options)) return null;
|
|
44
|
+
placementIds.add(placement.placementId);
|
|
45
|
+
blocks[region].push({
|
|
46
|
+
id: placement.id,
|
|
47
|
+
enable: placement.enable,
|
|
48
|
+
placementId: placement.placementId,
|
|
49
|
+
options: { ...placement.options }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
revision: value.revision,
|
|
55
|
+
page: {
|
|
56
|
+
version: 1,
|
|
57
|
+
options: pageOptions,
|
|
58
|
+
blocks
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BuilderEvent } from "./builder-event.js";
|
|
2
|
+
export class BuilderReadyPingEvent extends BuilderEvent {
|
|
3
|
+
static event = "builder-ready-ping";
|
|
4
|
+
constructor(origin) {
|
|
5
|
+
super(origin);
|
|
6
|
+
}
|
|
7
|
+
static fromOrigin(origin) {
|
|
8
|
+
return new BuilderReadyPingEvent(origin);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BlockOptions } from '#toaster-site-runtime/types/page';
|
|
2
|
+
import { BuilderMutationEvent } from './builder-event.js';
|
|
3
|
+
export declare class SiteOptionsUpdatedEvent extends BuilderMutationEvent {
|
|
4
|
+
readonly options: BlockOptions;
|
|
5
|
+
static readonly event = "site-options-updated";
|
|
6
|
+
private constructor();
|
|
7
|
+
static fromPayload(payload: Record<string, unknown>, origin: string): SiteOptionsUpdatedEvent | null;
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isRecord } from "#toaster-site-runtime/utils/common";
|
|
2
|
+
import { BuilderMutationEvent, parseMutationId } from "./builder-event.js";
|
|
3
|
+
export class SiteOptionsUpdatedEvent extends BuilderMutationEvent {
|
|
4
|
+
constructor(options, mutationId, origin) {
|
|
5
|
+
super(origin, mutationId);
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
static event = "site-options-updated";
|
|
9
|
+
static fromPayload(payload, origin) {
|
|
10
|
+
const mutationId = parseMutationId(payload);
|
|
11
|
+
if (!isRecord(payload.options) || mutationId === null) return null;
|
|
12
|
+
return new SiteOptionsUpdatedEvent(
|
|
13
|
+
{ ...payload.options },
|
|
14
|
+
mutationId,
|
|
15
|
+
origin
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|