@vigilkids/section-renderer-vue 0.2.2 → 0.5.1
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/interactions/vigilkids.d.ts +44 -1
- package/dist/interactions/vigilkids.mjs +115 -1
- package/dist/sections/article/index.mjs +60 -0
- package/dist/sections/article/prosemirror.d.ts +2 -0
- package/dist/sections/article/prosemirror.mjs +8 -0
- package/dist/sections/article/vigilkids/ArticleFaq.vue +9 -2
- package/dist/sections/article/vigilkids/ArticleFaqItem.d.vue.ts +3 -0
- package/dist/sections/article/vigilkids/ArticleFaqItem.vue +18 -18
- package/dist/sections/article/vigilkids/ArticleFaqItem.vue.d.ts +3 -0
- package/dist/sections/article/vigilkids/ArticleHeading.vue +2 -1
- package/dist/sections/article/vigilkids/ArticleHighlightParagraph.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleHighlightParagraph.vue +38 -0
- package/dist/sections/article/vigilkids/ArticleHighlightParagraph.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleProductCard.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleProductCard.vue +59 -0
- package/dist/sections/article/vigilkids/ArticleProductCard.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleProductInfoBanner.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleProductInfoBanner.vue +56 -0
- package/dist/sections/article/vigilkids/ArticleProductInfoBanner.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleRelatedArticles.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleRelatedArticles.vue +92 -0
- package/dist/sections/article/vigilkids/ArticleRelatedArticles.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleRetentionBanner.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleRetentionBanner.vue +101 -0
- package/dist/sections/article/vigilkids/ArticleRetentionBanner.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleSectionIntro.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleSectionIntro.vue +63 -0
- package/dist/sections/article/vigilkids/ArticleSectionIntro.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleStepList.vue +11 -6
- package/dist/sections/article/vigilkids/ArticleStyledHeading.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleStyledHeading.vue +48 -0
- package/dist/sections/article/vigilkids/ArticleStyledHeading.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleSubheading.vue +5 -4
- package/dist/sections/article/vigilkids/ArticleTable.d.vue.ts +13 -1
- package/dist/sections/article/vigilkids/ArticleTable.vue +26 -6
- package/dist/sections/article/vigilkids/ArticleTable.vue.d.ts +13 -1
- package/dist/sections/article/vigilkids/ArticleTipNote.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleTipNote.vue +59 -0
- package/dist/sections/article/vigilkids/ArticleTipNote.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleToc.vue +22 -7
- package/dist/sections/article/vigilkids/ArticleTocList.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleTocList.vue +63 -0
- package/dist/sections/article/vigilkids/ArticleTocList.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleTopAd.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleTopAd.vue +99 -0
- package/dist/sections/article/vigilkids/ArticleTopAd.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardCtaGroup.d.vue.ts +6 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardCtaGroup.vue +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardCtaGroup.vue.d.ts +6 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantA.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantA.vue +46 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantA.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantB.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantB.vue +46 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantB.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantC.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantC.vue +46 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantC.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantD.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantD.vue +58 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantD.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantE.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantE.vue +60 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantE.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantF.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantF.vue +65 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantF.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantG.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantG.vue +55 -0
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantG.vue.d.ts +21 -0
- package/dist/styles/products/vigilkids.css +1 -1
- package/dist/styles/products/visiva.css +1 -1
- package/package.json +13 -10
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vigilkids 产品专属 DOM 交互
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* 使用场景:
|
|
5
|
+
* - admin preview-host:走 Vue 组件渲染路径,不调用本模块
|
|
6
|
+
* - marketing 站点(Nuxt):innerHTML 直出 HTML 后 onMounted 激活交互
|
|
7
|
+
*
|
|
8
|
+
* 设计要点:
|
|
9
|
+
* - 幂等绑定:重复调用 setupInteractions 不会重复绑定事件
|
|
10
|
+
* - 可销毁:提供 destroyInteractions 清理 embla 实例和绑定标记,避免卸载后内存泄漏
|
|
11
|
+
* - 不破坏 vDOM:banner 隐藏用 style.display,而非 el.remove(),避免外层 Vue 重渲染冲突
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 推荐文章轮播(Module 4)
|
|
15
|
+
* - 桌面 ≥1025px:保持 grid 布局,仅做 dot 点击滚动定位
|
|
16
|
+
* - 移动端 ≤1024px:动态加载 embla-carousel,同步 dot / card 高亮
|
|
17
|
+
*
|
|
18
|
+
* 幂等:容器级 data-embla-init + dot 级 data-bound,重复调用不会重复绑定或重复 new 实例
|
|
19
|
+
* 内存:embla 实例存入 WeakMap,destroyInteractions 时调用 .destroy() 释放监听器和 DOM 引用
|
|
20
|
+
*/
|
|
21
|
+
export declare function setupCarousel(root?: Document | HTMLElement): void;
|
|
22
|
+
/**
|
|
23
|
+
* 留存 Banner(Module 5)
|
|
24
|
+
*
|
|
25
|
+
* 关闭策略:style.display = 'none'
|
|
26
|
+
* - 不调用 el.remove():marketing 侧 Nuxt 在 v-html 容器下,Vue diff 会检测 DOM 差异导致警告
|
|
27
|
+
* - admin 侧走 Vue 组件路径不进入本函数;innerHTML 场景靠本函数统一管理显隐
|
|
28
|
+
*
|
|
29
|
+
* localStorage key 规则:`retention-banner-dismissed:{dismissKey}`
|
|
30
|
+
*/
|
|
31
|
+
export declare function setupRetentionBanner(root?: Document | HTMLElement): void;
|
|
32
|
+
/**
|
|
33
|
+
* 激活所有产品交互
|
|
34
|
+
* 幂等:所有子函数都有各自的绑定保护,重复调用安全
|
|
4
35
|
*/
|
|
5
36
|
export declare function setupInteractions(): void;
|
|
37
|
+
/**
|
|
38
|
+
* 销毁产品交互
|
|
39
|
+
*
|
|
40
|
+
* 必须在组件卸载前调用,避免:
|
|
41
|
+
* - embla 实例持有 viewport 引用 + resize/scroll 监听器导致内存泄漏
|
|
42
|
+
* - data-bound 标记残留导致重挂载后事件绑定失败
|
|
43
|
+
*
|
|
44
|
+
* 不强制解绑 FAQ document delegate:
|
|
45
|
+
* - document 级委托不持有 root 引用,卸载不会产生泄漏
|
|
46
|
+
* - 跨多次 mount/unmount 复用同一 listener 反而更省
|
|
47
|
+
*/
|
|
48
|
+
export declare function destroyInteractions(root?: Document | HTMLElement): void;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
const emblaInstances = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
let faqDelegateBound = false;
|
|
3
|
+
function setupFaqAccordion() {
|
|
4
|
+
if (faqDelegateBound)
|
|
5
|
+
return;
|
|
6
|
+
faqDelegateBound = true;
|
|
2
7
|
document.addEventListener("click", (e) => {
|
|
3
8
|
const faqTitle = e.target.closest?.(".faq-item-title");
|
|
4
9
|
if (!faqTitle)
|
|
@@ -24,3 +29,112 @@ export function setupInteractions() {
|
|
|
24
29
|
body.style.maxHeight = isActive ? "0" : `${body.scrollHeight}px`;
|
|
25
30
|
});
|
|
26
31
|
}
|
|
32
|
+
function wireDesktopDotClicks(root) {
|
|
33
|
+
const dots = root.querySelectorAll(".recommend-dot");
|
|
34
|
+
const cards = root.querySelectorAll(".recommend-card");
|
|
35
|
+
dots.forEach((dot, i) => {
|
|
36
|
+
if (dot.dataset.bound === "1")
|
|
37
|
+
return;
|
|
38
|
+
dot.dataset.bound = "1";
|
|
39
|
+
dot.addEventListener("click", () => {
|
|
40
|
+
const target = cards[i];
|
|
41
|
+
if (target)
|
|
42
|
+
target.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function setupCarousel(root = document) {
|
|
47
|
+
const els = root.querySelectorAll('[data-component="related-articles-carousel"]');
|
|
48
|
+
els.forEach((el) => {
|
|
49
|
+
if (el.dataset.emblaInit === "1")
|
|
50
|
+
return;
|
|
51
|
+
el.dataset.emblaInit = "1";
|
|
52
|
+
const viewport = el.querySelector(".recommend-grid");
|
|
53
|
+
if (!viewport)
|
|
54
|
+
return;
|
|
55
|
+
const mq = window.matchMedia("(max-width: 1024px)");
|
|
56
|
+
if (!mq.matches) {
|
|
57
|
+
wireDesktopDotClicks(el);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
import("embla-carousel").then(({ default: EmblaCarousel }) => {
|
|
61
|
+
if (el.dataset.emblaInit !== "1")
|
|
62
|
+
return;
|
|
63
|
+
const embla = EmblaCarousel(viewport, {
|
|
64
|
+
loop: false,
|
|
65
|
+
align: "start",
|
|
66
|
+
containScroll: "trimSnaps"
|
|
67
|
+
});
|
|
68
|
+
emblaInstances.set(el, embla);
|
|
69
|
+
viewport.classList.add("recommend-slider-ready");
|
|
70
|
+
const dots = el.querySelectorAll(".recommend-dot");
|
|
71
|
+
const cards = el.querySelectorAll(".recommend-card");
|
|
72
|
+
const syncActive = () => {
|
|
73
|
+
const idx = embla.selectedScrollSnap();
|
|
74
|
+
dots.forEach((d, i) => d.classList.toggle("active", i === idx));
|
|
75
|
+
cards.forEach((c, i) => c.classList.toggle("is-active", i === idx));
|
|
76
|
+
};
|
|
77
|
+
embla.on("select", syncActive);
|
|
78
|
+
embla.on("init", syncActive);
|
|
79
|
+
syncActive();
|
|
80
|
+
dots.forEach((dot, i) => {
|
|
81
|
+
if (dot.dataset.bound === "1")
|
|
82
|
+
return;
|
|
83
|
+
dot.dataset.bound = "1";
|
|
84
|
+
dot.addEventListener("click", () => embla.scrollTo(i));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export function setupRetentionBanner(root = document) {
|
|
90
|
+
const els = root.querySelectorAll('[data-component="retention-banner"]');
|
|
91
|
+
els.forEach((el) => {
|
|
92
|
+
const key = el.dataset.dismissKey;
|
|
93
|
+
if (!key)
|
|
94
|
+
return;
|
|
95
|
+
const storageKey = `retention-banner-dismissed:${key}`;
|
|
96
|
+
if (localStorage.getItem(storageKey)) {
|
|
97
|
+
el.style.display = "none";
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const closeBtn = el.querySelector(".retention-banner-close");
|
|
101
|
+
if (!closeBtn)
|
|
102
|
+
return;
|
|
103
|
+
if (closeBtn.dataset.bound === "1")
|
|
104
|
+
return;
|
|
105
|
+
closeBtn.dataset.bound = "1";
|
|
106
|
+
closeBtn.addEventListener("click", () => {
|
|
107
|
+
localStorage.setItem(storageKey, "1");
|
|
108
|
+
el.style.display = "none";
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
export function setupInteractions() {
|
|
113
|
+
setupFaqAccordion();
|
|
114
|
+
setupCarousel();
|
|
115
|
+
setupRetentionBanner();
|
|
116
|
+
}
|
|
117
|
+
export function destroyInteractions(root = document) {
|
|
118
|
+
const carousels = root.querySelectorAll('[data-component="related-articles-carousel"]');
|
|
119
|
+
carousels.forEach((el) => {
|
|
120
|
+
const embla = emblaInstances.get(el);
|
|
121
|
+
if (embla) {
|
|
122
|
+
embla.destroy();
|
|
123
|
+
emblaInstances.delete(el);
|
|
124
|
+
}
|
|
125
|
+
delete el.dataset.emblaInit;
|
|
126
|
+
el.querySelectorAll('.recommend-dot[data-bound="1"]').forEach((dot) => {
|
|
127
|
+
delete dot.dataset.bound;
|
|
128
|
+
});
|
|
129
|
+
const viewport = el.querySelector(".recommend-grid");
|
|
130
|
+
if (viewport)
|
|
131
|
+
viewport.classList.remove("recommend-slider-ready");
|
|
132
|
+
});
|
|
133
|
+
const banners = root.querySelectorAll('[data-component="retention-banner"]');
|
|
134
|
+
banners.forEach((el) => {
|
|
135
|
+
el.style.display = "";
|
|
136
|
+
const closeBtn = el.querySelector(".retention-banner-close");
|
|
137
|
+
if (closeBtn)
|
|
138
|
+
delete closeBtn.dataset.bound;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -93,6 +93,66 @@ export function registerArticleSections() {
|
|
|
93
93
|
componentName: "ArticleToc",
|
|
94
94
|
component: () => import("./vigilkids/ArticleToc.vue")
|
|
95
95
|
});
|
|
96
|
+
registerSection({
|
|
97
|
+
name: "article-related-articles",
|
|
98
|
+
productCode: "vigilkids",
|
|
99
|
+
componentName: "ArticleRelatedArticles",
|
|
100
|
+
component: () => import("./vigilkids/ArticleRelatedArticles.vue")
|
|
101
|
+
});
|
|
102
|
+
registerSection({
|
|
103
|
+
name: "article-retention-banner",
|
|
104
|
+
productCode: "vigilkids",
|
|
105
|
+
componentName: "ArticleRetentionBanner",
|
|
106
|
+
component: () => import("./vigilkids/ArticleRetentionBanner.vue")
|
|
107
|
+
});
|
|
108
|
+
registerSection({
|
|
109
|
+
name: "article-tip-note",
|
|
110
|
+
productCode: "vigilkids",
|
|
111
|
+
componentName: "ArticleTipNote",
|
|
112
|
+
component: () => import("./vigilkids/ArticleTipNote.vue")
|
|
113
|
+
});
|
|
114
|
+
registerSection({
|
|
115
|
+
name: "article-top-ad",
|
|
116
|
+
productCode: "vigilkids",
|
|
117
|
+
componentName: "ArticleTopAd",
|
|
118
|
+
component: () => import("./vigilkids/ArticleTopAd.vue")
|
|
119
|
+
});
|
|
120
|
+
registerSection({
|
|
121
|
+
name: "article-highlight-paragraph",
|
|
122
|
+
productCode: "vigilkids",
|
|
123
|
+
componentName: "ArticleHighlightParagraph",
|
|
124
|
+
component: () => import("./vigilkids/ArticleHighlightParagraph.vue")
|
|
125
|
+
});
|
|
126
|
+
registerSection({
|
|
127
|
+
name: "article-section-intro",
|
|
128
|
+
productCode: "vigilkids",
|
|
129
|
+
componentName: "ArticleSectionIntro",
|
|
130
|
+
component: () => import("./vigilkids/ArticleSectionIntro.vue")
|
|
131
|
+
});
|
|
132
|
+
registerSection({
|
|
133
|
+
name: "article-toc-list",
|
|
134
|
+
productCode: "vigilkids",
|
|
135
|
+
componentName: "ArticleTocList",
|
|
136
|
+
component: () => import("./vigilkids/ArticleTocList.vue")
|
|
137
|
+
});
|
|
138
|
+
registerSection({
|
|
139
|
+
name: "article-styled-heading",
|
|
140
|
+
productCode: "vigilkids",
|
|
141
|
+
componentName: "ArticleStyledHeading",
|
|
142
|
+
component: () => import("./vigilkids/ArticleStyledHeading.vue")
|
|
143
|
+
});
|
|
144
|
+
registerSection({
|
|
145
|
+
name: "article-product-card",
|
|
146
|
+
productCode: "vigilkids",
|
|
147
|
+
componentName: "ArticleProductCard",
|
|
148
|
+
component: () => import("./vigilkids/ArticleProductCard.vue")
|
|
149
|
+
});
|
|
150
|
+
registerSection({
|
|
151
|
+
name: "article-product-info-banner",
|
|
152
|
+
productCode: "vigilkids",
|
|
153
|
+
componentName: "ArticleProductInfoBanner",
|
|
154
|
+
component: () => import("./vigilkids/ArticleProductInfoBanner.vue")
|
|
155
|
+
});
|
|
96
156
|
registerSection({
|
|
97
157
|
name: "article-heading",
|
|
98
158
|
productCode: "visiva",
|
|
@@ -67,3 +67,11 @@ export function renderContent(content) {
|
|
|
67
67
|
}
|
|
68
68
|
return "";
|
|
69
69
|
}
|
|
70
|
+
const singleParagraphWrapperPattern = /^\s*<p(?:\s[^>]*)?>([\s\S]*)<\/p>\s*$/i;
|
|
71
|
+
export function renderInlineContent(content) {
|
|
72
|
+
const html = renderContent(content);
|
|
73
|
+
const matched = html.match(singleParagraphWrapperPattern);
|
|
74
|
+
if (!matched)
|
|
75
|
+
return html;
|
|
76
|
+
return matched[1] ?? "";
|
|
77
|
+
}
|
|
@@ -19,7 +19,8 @@ const emit = defineEmits<{
|
|
|
19
19
|
(e: 'undo-redo', action: 'redo' | 'undo'): void
|
|
20
20
|
}>()
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// FAQ 的可编辑粒度在 block 维度(每个 Q/A pair),透传给子项
|
|
23
|
+
const { blockEditableAttrs } = useInlineEdit({
|
|
23
24
|
editorMode: () => !!props.editorMode,
|
|
24
25
|
onSettingUpdate: (key, value) => emit('update:setting', key, value),
|
|
25
26
|
onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
|
|
@@ -32,7 +33,13 @@ useInlineEdit({
|
|
|
32
33
|
<template>
|
|
33
34
|
<div class="faq-list">
|
|
34
35
|
<template v-for="blockId in blockOrder" :key="blockId">
|
|
35
|
-
<ArticleFaqItem
|
|
36
|
+
<ArticleFaqItem
|
|
37
|
+
v-if="blocks[blockId]"
|
|
38
|
+
:block-id="blockId"
|
|
39
|
+
:editor-mode="editorMode"
|
|
40
|
+
:block-editable-attrs="blockEditableAttrs"
|
|
41
|
+
:settings="blocks[blockId]!.settings"
|
|
42
|
+
/>
|
|
36
43
|
</template>
|
|
37
44
|
</div>
|
|
38
45
|
</template>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
+
blockId: string;
|
|
3
|
+
editorMode?: boolean;
|
|
2
4
|
settings: Record<string, unknown>;
|
|
5
|
+
blockEditableAttrs: (blockId: string, key: string) => Record<string, unknown>;
|
|
3
6
|
};
|
|
4
7
|
declare const _default: 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>;
|
|
5
8
|
export default _default;
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
// 展开/收起状态完全由 interactions/vigilkids.ts 的 document delegate 接管
|
|
3
|
+
// 这里不再自持 active/answer ref,避免与 delegate 双重触发
|
|
4
4
|
defineProps<{
|
|
5
|
+
blockId: string
|
|
6
|
+
editorMode?: boolean
|
|
5
7
|
settings: Record<string, unknown>
|
|
8
|
+
// 从父 Section 透传的 block-scope 可编辑属性工厂
|
|
9
|
+
blockEditableAttrs: (blockId: string, key: string) => Record<string, unknown>
|
|
6
10
|
}>()
|
|
7
|
-
|
|
8
|
-
const active = ref(false)
|
|
9
|
-
const answer = ref<HTMLElement>()
|
|
10
|
-
|
|
11
|
-
function toggle() {
|
|
12
|
-
const el = answer.value
|
|
13
|
-
if (el) {
|
|
14
|
-
el.style.maxHeight = active.value ? '0' : `${el.scrollHeight}px`
|
|
15
|
-
}
|
|
16
|
-
active.value = !active.value
|
|
17
|
-
}
|
|
18
11
|
</script>
|
|
19
12
|
|
|
20
13
|
<template>
|
|
21
|
-
<div class="faq-item"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
<div class="faq-item">
|
|
15
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
16
|
+
<p
|
|
17
|
+
class="faq-item-title"
|
|
18
|
+
v-bind="blockEditableAttrs(blockId, 'question')"
|
|
19
|
+
v-html="settings.question"
|
|
20
|
+
/>
|
|
25
21
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
26
|
-
<div
|
|
22
|
+
<div
|
|
23
|
+
class="faq-item-answer"
|
|
24
|
+
v-bind="blockEditableAttrs(blockId, 'answer')"
|
|
25
|
+
v-html="settings.answer"
|
|
26
|
+
/>
|
|
27
27
|
</div>
|
|
28
28
|
</template>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
+
blockId: string;
|
|
3
|
+
editorMode?: boolean;
|
|
2
4
|
settings: Record<string, unknown>;
|
|
5
|
+
blockEditableAttrs: (blockId: string, key: string) => Record<string, unknown>;
|
|
3
6
|
};
|
|
4
7
|
declare const _default: 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>;
|
|
5
8
|
export default _default;
|
|
@@ -38,6 +38,7 @@ const { editableAttrs } = useInlineEdit({
|
|
|
38
38
|
:id="String(s.anchor || '')"
|
|
39
39
|
:class="{ 'black-h2': variant === 'plain' }"
|
|
40
40
|
>
|
|
41
|
-
|
|
41
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
42
|
+
<span v-bind="editableAttrs('title')" v-html="s.title" />
|
|
42
43
|
</h2>
|
|
43
44
|
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BlockData } from '@vigilkids/section-core';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
blockOrder: string[];
|
|
4
|
+
blocks: Record<string, BlockData>;
|
|
5
|
+
editorMode?: boolean;
|
|
6
|
+
settings: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
"update:setting": (key: string, value: unknown) => any;
|
|
10
|
+
"update:block-setting": (blockId: string, key: string, value: unknown) => any;
|
|
11
|
+
"inline-edit-start": (key: string) => any;
|
|
12
|
+
"inline-edit-end": () => any;
|
|
13
|
+
"undo-redo": (action: "redo" | "undo") => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
|
+
"onUpdate:setting"?: ((key: string, value: unknown) => any) | undefined;
|
|
16
|
+
"onUpdate:block-setting"?: ((blockId: string, key: string, value: unknown) => any) | undefined;
|
|
17
|
+
"onInline-edit-start"?: ((key: string) => any) | undefined;
|
|
18
|
+
"onInline-edit-end"?: (() => any) | undefined;
|
|
19
|
+
"onUndo-redo"?: ((action: "redo" | "undo") => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BlockData } from '@vigilkids/section-core'
|
|
3
|
+
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
|
|
6
|
+
import { useInlineEdit } from '../../../composables/useInlineEdit'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
blockOrder: string[]
|
|
10
|
+
blocks: Record<string, BlockData>
|
|
11
|
+
editorMode?: boolean
|
|
12
|
+
settings: Record<string, unknown>
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
(e: 'update:setting', key: string, value: unknown): void
|
|
17
|
+
(e: 'update:block-setting', blockId: string, key: string, value: unknown): void
|
|
18
|
+
(e: 'inline-edit-start', key: string): void
|
|
19
|
+
(e: 'inline-edit-end'): void
|
|
20
|
+
(e: 'undo-redo', action: 'redo' | 'undo'): void
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const s = computed(() => props.settings)
|
|
24
|
+
|
|
25
|
+
const { editableAttrs } = useInlineEdit({
|
|
26
|
+
editorMode: () => !!props.editorMode,
|
|
27
|
+
onSettingUpdate: (key, value) => emit('update:setting', key, value),
|
|
28
|
+
onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
|
|
29
|
+
onEditStart: key => emit('inline-edit-start', key),
|
|
30
|
+
onEditEnd: () => emit('inline-edit-end'),
|
|
31
|
+
onUndoRedo: action => emit('undo-redo', action),
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
37
|
+
<p class="highlight-paragraph" v-bind="editableAttrs('content')" v-html="s.content" />
|
|
38
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BlockData } from '@vigilkids/section-core';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
blockOrder: string[];
|
|
4
|
+
blocks: Record<string, BlockData>;
|
|
5
|
+
editorMode?: boolean;
|
|
6
|
+
settings: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
"update:setting": (key: string, value: unknown) => any;
|
|
10
|
+
"update:block-setting": (blockId: string, key: string, value: unknown) => any;
|
|
11
|
+
"inline-edit-start": (key: string) => any;
|
|
12
|
+
"inline-edit-end": () => any;
|
|
13
|
+
"undo-redo": (action: "redo" | "undo") => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
|
+
"onUpdate:setting"?: ((key: string, value: unknown) => any) | undefined;
|
|
16
|
+
"onUpdate:block-setting"?: ((blockId: string, key: string, value: unknown) => any) | undefined;
|
|
17
|
+
"onInline-edit-start"?: ((key: string) => any) | undefined;
|
|
18
|
+
"onInline-edit-end"?: (() => any) | undefined;
|
|
19
|
+
"onUndo-redo"?: ((action: "redo" | "undo") => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BlockData } from '@vigilkids/section-core';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
blockOrder: string[];
|
|
4
|
+
blocks: Record<string, BlockData>;
|
|
5
|
+
editorMode?: boolean;
|
|
6
|
+
settings: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
"update:setting": (key: string, value: unknown) => any;
|
|
10
|
+
"update:block-setting": (blockId: string, key: string, value: unknown) => any;
|
|
11
|
+
"inline-edit-start": (key: string) => any;
|
|
12
|
+
"inline-edit-end": () => any;
|
|
13
|
+
"undo-redo": (action: "redo" | "undo") => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
|
+
"onUpdate:setting"?: ((key: string, value: unknown) => any) | undefined;
|
|
16
|
+
"onUpdate:block-setting"?: ((blockId: string, key: string, value: unknown) => any) | undefined;
|
|
17
|
+
"onInline-edit-start"?: ((key: string) => any) | undefined;
|
|
18
|
+
"onInline-edit-end"?: (() => any) | undefined;
|
|
19
|
+
"onUndo-redo"?: ((action: "redo" | "undo") => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BlockData } from '@vigilkids/section-core'
|
|
3
|
+
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
|
|
6
|
+
import ProductCardVariantA from './product-card/ProductCardVariantA.vue'
|
|
7
|
+
import ProductCardVariantB from './product-card/ProductCardVariantB.vue'
|
|
8
|
+
import ProductCardVariantC from './product-card/ProductCardVariantC.vue'
|
|
9
|
+
import ProductCardVariantD from './product-card/ProductCardVariantD.vue'
|
|
10
|
+
import ProductCardVariantE from './product-card/ProductCardVariantE.vue'
|
|
11
|
+
import ProductCardVariantF from './product-card/ProductCardVariantF.vue'
|
|
12
|
+
import ProductCardVariantG from './product-card/ProductCardVariantG.vue'
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
blockOrder: string[]
|
|
16
|
+
blocks: Record<string, BlockData>
|
|
17
|
+
editorMode?: boolean
|
|
18
|
+
settings: Record<string, unknown>
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
(e: 'update:setting', key: string, value: unknown): void
|
|
23
|
+
(e: 'update:block-setting', blockId: string, key: string, value: unknown): void
|
|
24
|
+
(e: 'inline-edit-start', key: string): void
|
|
25
|
+
(e: 'inline-edit-end'): void
|
|
26
|
+
(e: 'undo-redo', action: 'redo' | 'undo'): void
|
|
27
|
+
}>()
|
|
28
|
+
|
|
29
|
+
const variant = computed(() => String(props.settings.variant || 'A').toUpperCase())
|
|
30
|
+
|
|
31
|
+
const VARIANT_MAP = {
|
|
32
|
+
A: { class: 'product-card', component: ProductCardVariantA },
|
|
33
|
+
B: { class: 'product-card-compact', component: ProductCardVariantB },
|
|
34
|
+
C: { class: 'product-card-image', component: ProductCardVariantC },
|
|
35
|
+
D: { class: 'product-card-features', component: ProductCardVariantD },
|
|
36
|
+
E: { class: 'product-card-split', component: ProductCardVariantE },
|
|
37
|
+
F: { class: 'product-card-vertical', component: ProductCardVariantF },
|
|
38
|
+
G: { class: 'product-card-checklist', component: ProductCardVariantG },
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
const resolved = computed(() => VARIANT_MAP[variant.value as keyof typeof VARIANT_MAP] || VARIANT_MAP.A)
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div :class="resolved.class">
|
|
46
|
+
<component
|
|
47
|
+
:is="resolved.component"
|
|
48
|
+
:block-order="blockOrder"
|
|
49
|
+
:blocks="blocks"
|
|
50
|
+
:editor-mode="editorMode"
|
|
51
|
+
:settings="settings"
|
|
52
|
+
@update:setting="(key, value) => emit('update:setting', key, value)"
|
|
53
|
+
@update:block-setting="(blockId, key, value) => emit('update:block-setting', blockId, key, value)"
|
|
54
|
+
@inline-edit-start="key => emit('inline-edit-start', key)"
|
|
55
|
+
@inline-edit-end="() => emit('inline-edit-end')"
|
|
56
|
+
@undo-redo="action => emit('undo-redo', action)"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BlockData } from '@vigilkids/section-core';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
blockOrder: string[];
|
|
4
|
+
blocks: Record<string, BlockData>;
|
|
5
|
+
editorMode?: boolean;
|
|
6
|
+
settings: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
"update:setting": (key: string, value: unknown) => any;
|
|
10
|
+
"update:block-setting": (blockId: string, key: string, value: unknown) => any;
|
|
11
|
+
"inline-edit-start": (key: string) => any;
|
|
12
|
+
"inline-edit-end": () => any;
|
|
13
|
+
"undo-redo": (action: "redo" | "undo") => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
|
+
"onUpdate:setting"?: ((key: string, value: unknown) => any) | undefined;
|
|
16
|
+
"onUpdate:block-setting"?: ((blockId: string, key: string, value: unknown) => any) | undefined;
|
|
17
|
+
"onInline-edit-start"?: ((key: string) => any) | undefined;
|
|
18
|
+
"onInline-edit-end"?: (() => any) | undefined;
|
|
19
|
+
"onUndo-redo"?: ((action: "redo" | "undo") => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BlockData } from '@vigilkids/section-core';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
blockOrder: string[];
|
|
4
|
+
blocks: Record<string, BlockData>;
|
|
5
|
+
editorMode?: boolean;
|
|
6
|
+
settings: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
"update:setting": (key: string, value: unknown) => any;
|
|
10
|
+
"update:block-setting": (blockId: string, key: string, value: unknown) => any;
|
|
11
|
+
"inline-edit-start": (key: string) => any;
|
|
12
|
+
"inline-edit-end": () => any;
|
|
13
|
+
"undo-redo": (action: "redo" | "undo") => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
|
+
"onUpdate:setting"?: ((key: string, value: unknown) => any) | undefined;
|
|
16
|
+
"onUpdate:block-setting"?: ((blockId: string, key: string, value: unknown) => any) | undefined;
|
|
17
|
+
"onInline-edit-start"?: ((key: string) => any) | undefined;
|
|
18
|
+
"onInline-edit-end"?: (() => any) | undefined;
|
|
19
|
+
"onUndo-redo"?: ((action: "redo" | "undo") => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BlockData } from '@vigilkids/section-core'
|
|
3
|
+
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
|
|
6
|
+
import { useInlineEdit } from '../../../composables/useInlineEdit'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
blockOrder: string[]
|
|
10
|
+
blocks: Record<string, BlockData>
|
|
11
|
+
editorMode?: boolean
|
|
12
|
+
settings: Record<string, unknown>
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
(e: 'update:setting', key: string, value: unknown): void
|
|
17
|
+
(e: 'update:block-setting', blockId: string, key: string, value: unknown): void
|
|
18
|
+
(e: 'inline-edit-start', key: string): void
|
|
19
|
+
(e: 'inline-edit-end'): void
|
|
20
|
+
(e: 'undo-redo', action: 'redo' | 'undo'): void
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const s = computed(() => props.settings)
|
|
24
|
+
|
|
25
|
+
const { editableAttrs } = useInlineEdit({
|
|
26
|
+
editorMode: () => !!props.editorMode,
|
|
27
|
+
onSettingUpdate: (key, value) => emit('update:setting', key, value),
|
|
28
|
+
onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
|
|
29
|
+
onEditStart: key => emit('inline-edit-start', key),
|
|
30
|
+
onEditEnd: () => emit('inline-edit-end'),
|
|
31
|
+
onUndoRedo: action => emit('undo-redo', action),
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<section>
|
|
37
|
+
<div class="product-info-banner">
|
|
38
|
+
<img class="banner-logo" :src="(s.logo_url as string) || ''" :alt="(s.logo_alt as string) || ''">
|
|
39
|
+
<div class="banner-body">
|
|
40
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
41
|
+
<h3 class="banner-title" v-bind="editableAttrs('title')" v-html="s.title" />
|
|
42
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
43
|
+
<p class="banner-desc" v-bind="editableAttrs('desc')" v-html="s.desc" />
|
|
44
|
+
<div class="banner-platform-row">
|
|
45
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
46
|
+
<span class="banner-available" v-bind="editableAttrs('available_text')" v-html="s.available_text" />
|
|
47
|
+
<span class="banner-platforms">
|
|
48
|
+
<img :src="(s.platforms_image_url as string) || ''" :alt="(s.platforms_image_alt as string) || ''">
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
53
|
+
<a class="product-info-banner-btn" :href="(s.cta_url as string) || '#'" v-bind="editableAttrs('cta_label')" v-html="s.cta_label" />
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BlockData } from '@vigilkids/section-core';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
blockOrder: string[];
|
|
4
|
+
blocks: Record<string, BlockData>;
|
|
5
|
+
editorMode?: boolean;
|
|
6
|
+
settings: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
"update:setting": (key: string, value: unknown) => any;
|
|
10
|
+
"update:block-setting": (blockId: string, key: string, value: unknown) => any;
|
|
11
|
+
"inline-edit-start": (key: string) => any;
|
|
12
|
+
"inline-edit-end": () => any;
|
|
13
|
+
"undo-redo": (action: "redo" | "undo") => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
|
+
"onUpdate:setting"?: ((key: string, value: unknown) => any) | undefined;
|
|
16
|
+
"onUpdate:block-setting"?: ((blockId: string, key: string, value: unknown) => any) | undefined;
|
|
17
|
+
"onInline-edit-start"?: ((key: string) => any) | undefined;
|
|
18
|
+
"onInline-edit-end"?: (() => any) | undefined;
|
|
19
|
+
"onUndo-redo"?: ((action: "redo" | "undo") => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|