@vigilkids/section-renderer-vue 0.0.1 → 0.1.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.
Files changed (56) hide show
  1. package/dist/composables/useInlineEdit.d.ts +1 -1
  2. package/dist/composables/useInlineEdit.mjs +4 -2
  3. package/dist/composables/useLazyRender.mjs +2 -1
  4. package/dist/composables/useRegistry.d.ts +1 -1
  5. package/dist/composables/useRegistry.mjs +6 -3
  6. package/dist/composables/useSectionSEO.mjs +10 -5
  7. package/dist/composables/useSectionStyle.d.ts +1 -1
  8. package/dist/composables/useSectionStyle.mjs +10 -6
  9. package/dist/editor.d.ts +2 -2
  10. package/dist/editor.mjs +2 -2
  11. package/dist/index.d.ts +12 -12
  12. package/dist/index.mjs +8 -8
  13. package/dist/interactions/common.d.ts +5 -0
  14. package/dist/interactions/common.mjs +10 -0
  15. package/dist/interactions/vigilkids.d.ts +5 -0
  16. package/dist/interactions/vigilkids.mjs +26 -0
  17. package/dist/preview/createPreviewApp.mjs +33 -6
  18. package/dist/renderer/FallbackSection.vue +9 -3
  19. package/dist/renderer/LazySection.vue +25 -22
  20. package/dist/renderer/SectionErrorBoundary.vue +3 -1
  21. package/dist/renderer/SectionRenderer.vue +19 -6
  22. package/dist/renderer/SectionWrapper.vue +4 -5
  23. package/dist/sections/RichTextSection.vue +12 -12
  24. package/dist/sections/article/prosemirror.mjs +8 -4
  25. package/dist/sections/article/shared/ArticleCustomHtml.vue +46 -1
  26. package/dist/sections/article/shared/ArticleImage.vue +3 -3
  27. package/dist/sections/article/vigilkids/ArticleBulletList.vue +5 -10
  28. package/dist/sections/article/vigilkids/ArticleCta.vue +25 -39
  29. package/dist/sections/article/vigilkids/ArticleFaq.vue +4 -28
  30. package/dist/sections/article/vigilkids/ArticleFaqItem.vue +6 -7
  31. package/dist/sections/article/vigilkids/ArticleFeature.vue +8 -38
  32. package/dist/sections/article/vigilkids/ArticleHeading.vue +4 -14
  33. package/dist/sections/article/vigilkids/ArticleNotice.vue +7 -42
  34. package/dist/sections/article/vigilkids/ArticleProsCons.vue +11 -19
  35. package/dist/sections/article/vigilkids/ArticleQuestion.vue +9 -21
  36. package/dist/sections/article/vigilkids/ArticleQuote.vue +11 -13
  37. package/dist/sections/article/vigilkids/ArticleStepList.vue +9 -9
  38. package/dist/sections/article/vigilkids/ArticleSubheading.vue +8 -17
  39. package/dist/sections/article/vigilkids/ArticleTable.vue +10 -13
  40. package/dist/sections/article/vigilkids/ArticleToc.vue +14 -14
  41. package/dist/sections/article/visiva/ArticleBulletList.vue +4 -5
  42. package/dist/sections/article/visiva/ArticleCta.vue +127 -30
  43. package/dist/sections/article/visiva/ArticleFaq.vue +24 -11
  44. package/dist/sections/article/visiva/ArticleFeature.vue +22 -10
  45. package/dist/sections/article/visiva/ArticleHeading.vue +2 -2
  46. package/dist/sections/article/visiva/ArticleNotice.vue +19 -17
  47. package/dist/sections/article/visiva/ArticleProsCons.vue +41 -29
  48. package/dist/sections/article/visiva/ArticleQuestion.vue +20 -10
  49. package/dist/sections/article/visiva/ArticleQuote.vue +9 -5
  50. package/dist/sections/article/visiva/ArticleStepList.vue +9 -4
  51. package/dist/sections/article/visiva/ArticleSubheading.vue +32 -27
  52. package/dist/sections/article/visiva/ArticleTable.vue +79 -60
  53. package/dist/sections/article/visiva/ArticleToc.vue +42 -12
  54. package/dist/styles/products/vigilkids.css +1 -1
  55. package/dist/styles/products/visiva.css +1 -1
  56. package/package.json +18 -3
@@ -27,11 +27,13 @@ function renderNode(node) {
27
27
  }
28
28
  }
29
29
  function renderChildren(node) {
30
- if (!node.content) return node.text ?? "";
30
+ if (!node.content)
31
+ return node.text ?? "";
31
32
  return node.content.map(renderNode).join("");
32
33
  }
33
34
  function applyMarks(text, marks) {
34
- if (!marks) return text;
35
+ if (!marks)
36
+ return text;
35
37
  let result = text;
36
38
  for (const mark of marks) {
37
39
  switch (mark.type) {
@@ -55,10 +57,12 @@ function applyMarks(text, marks) {
55
57
  return result;
56
58
  }
57
59
  export function renderContent(content) {
58
- if (typeof content === "string") return content;
60
+ if (typeof content === "string")
61
+ return content;
59
62
  if (content && typeof content === "object" && content.type === "doc") {
60
63
  const doc = content;
61
- if (!doc.content) return "";
64
+ if (!doc.content)
65
+ return "";
62
66
  return doc.content.map(renderNode).join("");
63
67
  }
64
68
  return "";
@@ -25,8 +25,53 @@ const htmlContent = computed(() => {
25
25
  const raw = props.settings.html_content ?? props.settings.content ?? ''
26
26
  return renderContent(raw)
27
27
  })
28
+
29
+ // ── v-html 内容的交互处理 ──
30
+ // SectionWrapper 的 @click.stop 阻止事件冒泡到 document,
31
+ // document 级事件委托无法捕获 v-html 内的点击,
32
+ // 所以直接在组件根元素上用 @click 处理
33
+ function handleContentClick(e: MouseEvent) {
34
+ // FAQ 手风琴
35
+ const faqTitle = (e.target as HTMLElement).closest?.('.faq-item-title')
36
+ if (faqTitle) {
37
+ e.stopPropagation()
38
+ handleFaqToggle(faqTitle)
39
+ return
40
+ }
41
+
42
+ // 链接拦截:阻止非锚点链接在 iframe 内导航
43
+ const anchor = (e.target as HTMLElement).closest?.('a')
44
+ if (anchor) {
45
+ const href = anchor.getAttribute('href')
46
+ if (!href?.startsWith('#')) e.preventDefault()
47
+ }
48
+ }
49
+
50
+ function handleFaqToggle(faqTitle: Element) {
51
+ const faqItem = faqTitle.closest('.faq-item')
52
+ if (!faqItem) return
53
+
54
+ const faqList = faqItem.closest('.faq-list')
55
+ const isActive = faqItem.classList.contains('active')
56
+
57
+ // 手风琴互斥:收起同组其他项
58
+ if (faqList) {
59
+ faqList.querySelectorAll('.faq-item.active').forEach((item) => {
60
+ if (item !== faqItem) {
61
+ item.classList.remove('active')
62
+ const body = item.querySelector('.faq-item-answer') as HTMLElement | null
63
+ if (body) body.style.maxHeight = '0'
64
+ }
65
+ })
66
+ }
67
+
68
+ // 切换当前项
69
+ faqItem.classList.toggle('active', !isActive)
70
+ const body = faqItem.querySelector('.faq-item-answer') as HTMLElement | null
71
+ if (body) body.style.maxHeight = isActive ? '0' : `${body.scrollHeight}px`
72
+ }
28
73
  </script>
29
74
 
30
75
  <template>
31
- <div :class="['article-custom-html', 'article-content', productClass]" v-html="htmlContent" />
76
+ <div class="article-custom-html article-content" :class="[productClass]" v-html="htmlContent" @click="handleContentClick" />
32
77
  </template>
@@ -27,9 +27,9 @@ const { editableAttrs } = useInlineEdit({
27
27
  editorMode: () => !!props.editorMode,
28
28
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
29
29
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
30
- onEditStart: (key) => emit('inline-edit-start', key),
30
+ onEditStart: key => emit('inline-edit-start', key),
31
31
  onEditEnd: () => emit('inline-edit-end'),
32
- onUndoRedo: (action) => emit('undo-redo', action),
32
+ onUndoRedo: action => emit('undo-redo', action),
33
33
  })
34
34
  </script>
35
35
 
@@ -41,7 +41,7 @@ const { editableAttrs } = useInlineEdit({
41
41
  :src="safeUrl(String(s.src))"
42
42
  class="article-image__img"
43
43
  loading="lazy"
44
- />
44
+ >
45
45
  <figcaption v-if="s.caption" class="article-image__caption" v-bind="editableAttrs('caption')">
46
46
  {{ s.caption }}
47
47
  </figcaption>
@@ -27,22 +27,17 @@ const { blockEditableAttrs } = useInlineEdit({
27
27
  editorMode: () => !!props.editorMode,
28
28
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
29
29
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
30
- onEditStart: (key) => emit('inline-edit-start', key),
30
+ onEditStart: key => emit('inline-edit-start', key),
31
31
  onEditEnd: () => emit('inline-edit-end'),
32
- onUndoRedo: (action) => emit('undo-redo', action),
32
+ onUndoRedo: action => emit('undo-redo', action),
33
33
  })
34
34
  </script>
35
35
 
36
36
  <template>
37
- <ul class="article-bullet-list">
37
+ <ul class="disc-list">
38
38
  <template v-for="blockId in blockOrder" :key="blockId">
39
- <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">
40
- {{ blocks[blockId]!.settings.text }}
41
- </li>
39
+ <!-- eslint-disable-next-line vue/no-v-html -->
40
+ <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')" v-html="blocks[blockId]!.settings.text" />
42
41
  </template>
43
42
  </ul>
44
43
  </template>
45
-
46
- <style scoped>
47
- .article-bullet-list{color:var(--article-text,#3a4259);list-style-type:disc;margin-top:10px;padding-left:20px}
48
- </style>
@@ -28,17 +28,17 @@ const { editableAttrs } = useInlineEdit({
28
28
  editorMode: () => !!props.editorMode,
29
29
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
30
30
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
31
- onEditStart: (key) => emit('inline-edit-start', key),
31
+ onEditStart: key => emit('inline-edit-start', key),
32
32
  onEditEnd: () => emit('inline-edit-end'),
33
- onUndoRedo: (action) => emit('undo-redo', action),
33
+ onUndoRedo: action => emit('undo-redo', action),
34
34
  })
35
35
  </script>
36
36
 
37
37
  <template>
38
- <!-- button variant: 单个居中按钮 -->
38
+ <!-- button variant: 单个居中按钮 -->
39
39
  <a
40
40
  v-if="variant === 'button'"
41
- :href="safeUrl(String(s.button_url || ''))"
41
+ :href="editorMode ? undefined : safeUrl(String(s.button_url || ''))"
42
42
  class="btn btn-primary"
43
43
  v-bind="editableAttrs('button_text')"
44
44
  >
@@ -48,7 +48,7 @@ const { editableAttrs } = useInlineEdit({
48
48
  <!-- button-group variant: 两个按钮并排 -->
49
49
  <div v-else-if="variant === 'button-group'" class="btn-group">
50
50
  <a
51
- :href="safeUrl(String(s.button_url || ''))"
51
+ :href="editorMode ? undefined : safeUrl(String(s.button_url || ''))"
52
52
  class="btn btn-primary"
53
53
  v-bind="editableAttrs('button_text')"
54
54
  >
@@ -56,7 +56,7 @@ const { editableAttrs } = useInlineEdit({
56
56
  </a>
57
57
  <a
58
58
  v-if="s.secondary_button_text"
59
- :href="safeUrl(String(s.secondary_button_url || ''))"
59
+ :href="editorMode ? undefined : safeUrl(String(s.secondary_button_url || ''))"
60
60
  class="btn outline-btn"
61
61
  v-bind="editableAttrs('secondary_button_text')"
62
62
  >
@@ -67,12 +67,16 @@ const { editableAttrs } = useInlineEdit({
67
67
  <!-- card variant: 左文字右按钮 -->
68
68
  <div v-else-if="variant === 'card'" class="article-try">
69
69
  <div class="article-try-content">
70
- <p v-if="s.title" class="article-try-title" v-bind="editableAttrs('title')">{{ s.title }}</p>
71
- <p v-if="s.description" class="article-try-description" v-bind="editableAttrs('description')">{{ s.description }}</p>
70
+ <p v-if="s.title" class="article-try-title" v-bind="editableAttrs('title')">
71
+ {{ s.title }}
72
+ </p>
73
+ <p v-if="s.description" class="article-try-description" v-bind="editableAttrs('description')">
74
+ {{ s.description }}
75
+ </p>
72
76
  </div>
73
77
  <a
74
78
  v-if="s.button_text"
75
- :href="safeUrl(String(s.button_url || ''))"
79
+ :href="editorMode ? undefined : safeUrl(String(s.button_url || ''))"
76
80
  class="btn btn-primary"
77
81
  v-bind="editableAttrs('button_text')"
78
82
  >
@@ -83,44 +87,26 @@ const { editableAttrs } = useInlineEdit({
83
87
  <!-- showcase variant: 左文字+按钮,右图片 -->
84
88
  <div v-else-if="variant === 'showcase'" class="article-show">
85
89
  <div class="article-show-content">
86
- <p v-if="s.title" class="article-show-title" v-bind="editableAttrs('title')">{{ s.title }}</p>
87
- <p v-if="s.description" class="article-show-description" v-bind="editableAttrs('description')">{{ s.description }}</p>
90
+ <p v-if="s.title" class="article-show-title" v-bind="editableAttrs('title')">
91
+ {{ s.title }}
92
+ </p>
93
+ <p
94
+ v-if="s.description"
95
+ class="article-show-description"
96
+ v-bind="editableAttrs('description')"
97
+ >
98
+ {{ s.description }}
99
+ </p>
88
100
  <a
89
101
  v-if="s.button_text"
90
- :href="safeUrl(String(s.button_url || ''))"
102
+ :href="editorMode ? undefined : safeUrl(String(s.button_url || ''))"
91
103
  class="btn btn-primary"
92
104
  v-bind="editableAttrs('button_text')"
93
105
  >
94
106
  {{ s.button_text }}
95
107
  </a>
96
108
  </div>
97
- <img
98
- v-if="s.image_src"
99
- :src="String(s.image_src)"
100
- alt=""
101
- loading="lazy"
102
- >
109
+ <img v-if="s.image_src" :src="String(s.image_src)" alt="" loading="lazy">
103
110
  </div>
104
111
 
105
- <!-- button-group-dark variant: 黑底容器+白边主按钮+渐变次按钮 (Visiva) -->
106
- <div v-else-if="variant === 'button-group-dark'" class="btn-group-dark">
107
- <div class="btn-group-dark__buttons">
108
- <div v-if="s.button_text" class="btn-group-dark__item">
109
- <a :href="safeUrl(String(s.button_url || ''))" class="btn btn-outline-white" v-bind="editableAttrs('button_text')">
110
- {{ s.button_text }}
111
- </a>
112
- <p v-if="s.button_caption" class="btn-group-dark__caption" v-bind="editableAttrs('button_caption')">{{ s.button_caption }}</p>
113
- </div>
114
- <div v-if="s.secondary_button_text" class="btn-group-dark__item">
115
- <a :href="safeUrl(String(s.secondary_button_url || ''))" class="btn btn-gradient" v-bind="editableAttrs('secondary_button_text')">
116
- {{ s.secondary_button_text }}
117
- </a>
118
- <p v-if="s.secondary_button_caption" class="btn-group-dark__caption" v-bind="editableAttrs('secondary_button_caption')">{{ s.secondary_button_caption }}</p>
119
- </div>
120
- </div>
121
- </div>
122
112
  </template>
123
-
124
- <style scoped>
125
- .btn{align-items:center;border:none;border-radius:var(--article-btn-radius,10px);box-sizing:border-box;cursor:pointer;display:flex;font-size:16px;font-weight:700;height:var(--article-btn-height,52px);justify-content:center;line-height:24px;padding:10px 20px;text-decoration:none;transition:all .3s ease;width:220px}.btn.btn-primary{background:var(--article-primary,#24c790);color:var(--article-btn-color,#fff);margin:20px auto 30px}.btn.btn-primary:active,.btn.btn-primary:hover{background:var(--article-primary-hover,#1ba97a)}.btn.outline-btn{background:#fff;border:2px solid var(--article-primary,#24c790);border-radius:var(--article-btn-radius,10px);color:var(--article-primary-hover,#1ba97a);height:var(--article-btn-height,52px);margin:20px auto 30px}.btn.outline-btn:hover{background:var(--article-primary,#24c790);color:#fff}.btn.outline-btn:active{background:var(--article-primary-hover,#1ba97a);color:#fff}.btn-group{align-items:center;display:flex;gap:40px;justify-content:center;margin-bottom:30px}.btn-group .btn{margin:0}.article-try{align-items:center;background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:var(--article-text,#3a4259);margin-top:30px;padding:40px 60px}.article-try,.article-try-content{display:flex;justify-content:space-between}.article-try-content{flex-direction:column}.article-try-title{font-size:20px;font-weight:700;line-height:23px;margin-bottom:10px;margin-top:0}.article-try-description{line-height:32px;margin-bottom:0;margin-top:0}.article-try .btn{margin:0 0 0 10px;min-width:220px}.article-show{align-items:center;background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:var(--article-text,#3a4259);margin-top:30px;padding:40px 30px 40px 60px}.article-show,.article-show-content{display:flex;justify-content:space-between}.article-show-content{flex-direction:column}.article-show-title{font-size:24px;font-weight:700;line-height:28px;margin-bottom:14px;margin-top:0}.article-show-description{line-height:30px;margin-bottom:0;margin-top:0}.article-show .btn{margin-left:0;margin-top:20px;min-width:220px}.article-show img{border-radius:10px;height:auto;margin-left:32px;max-width:368px;-o-object-fit:cover;object-fit:cover;width:100%}.btn-group-dark{background:#000;border-radius:20px;margin-top:40px;padding:16px 40px}.btn-group-dark__buttons{align-items:center;display:flex;flex-direction:column;gap:16px;justify-content:center}.btn-group-dark__item{text-align:center}.btn-group-dark__caption{color:#9ca3af;font-size:14px;font-weight:500;margin:16px 0 0}.btn.btn-outline-white{background:transparent;border:1px solid #fff;color:#fff;margin:0}.btn.btn-outline-white:hover{background:#fff;color:#000}.btn.btn-gradient{background:linear-gradient(to right,var(--article-gradient-start,#24c790),var(--article-gradient-end,#1ba97a));border:none;color:#000;margin:0}.btn.btn-gradient:hover{opacity:.9}@media (min-width:769px){.btn-group-dark{padding:40px}.btn-group-dark__buttons{flex-direction:row;gap:24px}}@media (max-width:768px){.btn-group{flex-direction:column;gap:10px}.article-try{align-items:center;flex-direction:column;padding:20px 16px;text-align:center}.article-try-description{line-height:21px}.article-try .btn{margin:20px 0}.article-show{flex-direction:column;padding:20px 16px;text-align:center}.article-show,.article-show-content{align-items:center}.article-show-description{line-height:21px}.article-show .btn,.article-show img{margin:20px 0}.btn-group-dark__buttons{gap:16px}}
126
- </style>
@@ -1,8 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { BlockData } from '@vigilkids/section-core'
3
3
 
4
- import { computed } from 'vue'
5
-
6
4
  import { useInlineEdit } from '../../../composables/useInlineEdit'
7
5
  import ArticleFaqItem from './ArticleFaqItem.vue'
8
6
 
@@ -21,42 +19,20 @@ const emit = defineEmits<{
21
19
  (e: 'undo-redo', action: 'redo' | 'undo'): void
22
20
  }>()
23
21
 
24
- const s = computed(() => props.settings)
25
-
26
- const { editableAttrs, blockEditableAttrs } = useInlineEdit({
22
+ useInlineEdit({
27
23
  editorMode: () => !!props.editorMode,
28
24
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
29
25
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
30
- onEditStart: (key) => emit('inline-edit-start', key),
26
+ onEditStart: key => emit('inline-edit-start', key),
31
27
  onEditEnd: () => emit('inline-edit-end'),
32
- onUndoRedo: (action) => emit('undo-redo', action),
28
+ onUndoRedo: action => emit('undo-redo', action),
33
29
  })
34
30
  </script>
35
31
 
36
32
  <template>
37
- <!-- Visiva 风格:浅绿卡片 + 绿色标题 + 静态列表 -->
38
- <div v-if="s.title" class="faq-card">
39
- <div class="faq-card__header">
40
- <h2 class="faq-card__title" v-bind="editableAttrs('title')">{{ s.title }}</h2>
41
- </div>
42
- <div class="faq-card__list">
43
- <template v-for="blockId in blockOrder" :key="blockId">
44
- <div v-if="blocks[blockId]" class="faq-card__item">
45
- <h3 class="faq-card__question" v-bind="blockEditableAttrs(blockId, 'question')">{{ blocks[blockId]!.settings.question }}</h3>
46
- <p class="faq-card__answer" v-bind="blockEditableAttrs(blockId, 'answer')">{{ blocks[blockId]!.settings.answer }}</p>
47
- </div>
48
- </template>
49
- </div>
50
- </div>
51
-
52
- <!-- VigilKids 风格:手风琴折叠 -->
53
- <div v-else class="faq-list">
33
+ <div class="faq-list">
54
34
  <template v-for="blockId in blockOrder" :key="blockId">
55
35
  <ArticleFaqItem v-if="blocks[blockId]" :settings="blocks[blockId]!.settings" />
56
36
  </template>
57
37
  </div>
58
38
  </template>
59
-
60
- <style scoped>
61
- .faq-list{background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:#333;margin-bottom:50px;margin-top:20px;padding:26px 20px}.faq-list :deep(.faq-item:not(:last-child)){margin-bottom:10px}.faq-card{background:var(--article-bg-faq,#f9fafb);border-radius:10px;font-size:14px;margin-top:40px;padding:28px 16px}.faq-card__header{align-items:center;display:flex;gap:8px;margin-bottom:16px}.faq-card__title{color:var(--article-primary,#24c790);font-size:18px;font-weight:700;margin:0}.faq-card__list{display:flex;flex-direction:column;gap:16px}.faq-card__question{font-size:14px;font-weight:700;margin:0 0 6px}.faq-card__answer{line-height:1.6;margin:0}@media (min-width:769px){.faq-card{border-radius:15px;font-size:16px;padding:28px 20px}.faq-card__title{font-size:20px}.faq-card__list{gap:24px}.faq-card__question{font-size:16px;margin-bottom:12px}}
62
- </style>
@@ -13,12 +13,11 @@ function toggle() {
13
13
  </script>
14
14
 
15
15
  <template>
16
- <div class="faq-item" :class="{ active }" @click="toggle">
17
- <p class="faq-item-title">{{ settings.question }}</p>
18
- <p class="faq-item-answer">{{ settings.answer }}</p>
16
+ <div class="faq-item" :class="{ active }" @click="toggle">
17
+ <p class="faq-item-title">
18
+ {{ settings.question }}
19
+ </p>
20
+ <!-- eslint-disable-next-line vue/no-v-html -->
21
+ <div class="faq-item-answer" v-html="settings.answer" />
19
22
  </div>
20
23
  </template>
21
-
22
- <style scoped>
23
- .faq-item{background:#fff;border-radius:10px;cursor:pointer;padding:32px 26px 32px 18px;position:relative;transition:all .3s ease}.faq-item:hover{transform:translateY(-2px)}.faq-item-title{cursor:pointer;font-size:18px;font-weight:700;line-height:26px;margin-bottom:0;margin-top:0;padding-right:74px;transition:margin-bottom .3s ease}.faq-item-title:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23333' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:cover;content:"";height:26px;position:absolute;right:26px;top:30px;transition:transform .3s ease;width:26px}.faq-item.active .faq-item-title{margin-bottom:18px}.faq-item.active .faq-item-title:before{transform:rotate(180deg)}.faq-item-answer{color:#666;font-size:18px;line-height:27px;margin:0;max-height:0;opacity:0;overflow:hidden;padding:0 74px 0 22px;transition:max-height .4s ease,opacity .3s ease,padding .3s ease}.faq-item.active .faq-item-answer{max-height:500px;opacity:1;padding-bottom:0;padding-top:0}@media (max-width:768px){.faq-item{padding:20px 16px}.faq-item-title{font-size:14px;line-height:16px;padding-right:40px}.faq-item-title:before{height:16px;right:16px;top:20px;width:16px}.faq-item-answer{font-size:14px;line-height:16px;padding-left:0;padding-right:0}.faq-item.active .faq-item-title{margin-bottom:10px}}
24
- </style>
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { BlockData } from '@vigilkids/section-core'
3
3
 
4
- import { safeUrl } from '@vigilkids/section-core'
5
4
  import { computed } from 'vue'
6
5
 
7
6
  import { useInlineEdit } from '../../../composables/useInlineEdit'
@@ -22,56 +21,27 @@ const emit = defineEmits<{
22
21
  }>()
23
22
 
24
23
  const s = computed(() => props.settings)
25
- const variant = computed(() => String(s.value.variant || 'default'))
26
24
 
27
25
  const { editableAttrs, blockEditableAttrs } = useInlineEdit({
28
26
  editorMode: () => !!props.editorMode,
29
27
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
30
28
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
31
- onEditStart: (key) => emit('inline-edit-start', key),
29
+ onEditStart: key => emit('inline-edit-start', key),
32
30
  onEditEnd: () => emit('inline-edit-end'),
33
- onUndoRedo: (action) => emit('undo-redo', action),
31
+ onUndoRedo: action => emit('undo-redo', action),
34
32
  })
35
33
  </script>
36
34
 
37
35
  <template>
38
- <!-- light / dark 变体:图片+特性列表+CTA (Visiva) -->
39
- <div
40
- v-if="variant === 'light' || variant === 'dark'"
41
- :class="['feature-card', { 'feature-card--dark': variant === 'dark' }]"
42
- >
43
- <div class="feature-card__text">
44
- <h2 v-if="s.title" class="feature-card__title" v-bind="editableAttrs('title')">{{ s.title }}</h2>
45
- <ul class="feature-card__list">
46
- <template v-for="blockId in blockOrder" :key="blockId">
47
- <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">{{ blocks[blockId]!.settings.text }}</li>
48
- </template>
49
- </ul>
50
- <a
51
- v-if="s.button_text"
52
- :href="safeUrl(String(s.button_url || ''))"
53
- class="feature-card__btn"
54
- v-bind="editableAttrs('button_text')"
55
- >
56
- {{ s.button_text }}
57
- </a>
58
- </div>
59
- <div v-if="s.image_src" class="feature-card__image">
60
- <img :src="String(s.image_src)" alt="" loading="lazy" />
61
- </div>
62
- </div>
63
-
64
- <!-- default 变体:原有灰色特性框 (VigilKids,完全不动) -->
65
- <div v-else class="article-feature">
66
- <p v-if="s.title" class="article-feature-title" v-bind="editableAttrs('title')">{{ s.title }}</p>
36
+ <div class="article-feature">
37
+ <p v-if="s.title" class="article-feature-title" v-bind="editableAttrs('title')">
38
+ {{ s.title }}
39
+ </p>
67
40
  <ul class="article-feature-list">
68
41
  <template v-for="blockId in blockOrder" :key="blockId">
69
- <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">{{ blocks[blockId]!.settings.text }}</li>
42
+ <!-- eslint-disable-next-line vue/no-v-html -->
43
+ <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')" v-html="blocks[blockId]!.settings.text" />
70
44
  </template>
71
45
  </ul>
72
46
  </div>
73
47
  </template>
74
-
75
- <style scoped>
76
- .article-feature{background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:var(--article-text,#3a4259);margin-top:30px;padding:30px 60px}.article-feature-title{font-size:20px;font-weight:700;line-height:23px;margin-bottom:20px;margin-top:0}.article-feature-list{line-height:32px;list-style-type:disc;padding-left:20px}@media (max-width:768px){.article-feature{padding:20px 16px}}.feature-card{align-items:center;background:var(--article-bg-light,#f3f4f6);border-radius:10px;display:flex;flex-direction:column;gap:20px;margin-top:40px;padding:28px 12px}.feature-card--dark{background:var(--article-bg-dark,#1f2937);color:#fff}.feature-card__text{max-width:426px}.feature-card__title{font-size:18px;font-weight:700;margin:0 0 16px}.feature-card__list{font-size:14px;font-weight:300;line-height:1.8;list-style-type:disc;margin:0 0 20px;padding-left:16px}.feature-card--dark .feature-card__list{color:#d1d5db}.feature-card:not(.feature-card--dark) .feature-card__list{color:var(--article-text-muted,#6b7280)}.feature-card__btn{align-items:center;background:linear-gradient(to right,var(--article-gradient-start,#24c790),var(--article-gradient-end,#1ba97a));border-radius:40px;color:#000;display:inline-flex;font-size:16px;font-weight:700;height:60px;justify-content:center;min-width:260px;text-decoration:none;transition:opacity .2s}.feature-card__btn:hover{opacity:.9}.feature-card__image img{border-radius:10px;height:auto;max-width:411px;-o-object-fit:cover;object-fit:cover;width:100%}@media (min-width:769px){.feature-card{border-radius:20px;flex-direction:row;justify-content:space-between;padding:40px}.feature-card__title{font-size:20px}.feature-card__list{margin-bottom:28px}.feature-card__image img{border-radius:20px}}
77
- </style>
@@ -27,27 +27,17 @@ const { editableAttrs } = useInlineEdit({
27
27
  editorMode: () => !!props.editorMode,
28
28
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
29
29
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
30
- onEditStart: (key) => emit('inline-edit-start', key),
30
+ onEditStart: key => emit('inline-edit-start', key),
31
31
  onEditEnd: () => emit('inline-edit-end'),
32
- onUndoRedo: (action) => emit('undo-redo', action),
32
+ onUndoRedo: action => emit('undo-redo', action),
33
33
  })
34
34
  </script>
35
35
 
36
36
  <template>
37
- <h2
37
+ <h2
38
38
  :id="String(s.anchor || '')"
39
- :class="[
40
- 'article-heading',
41
- {
42
- 'article-heading--plain': variant === 'plain',
43
- 'article-heading--accent': variant === 'accent',
44
- },
45
- ]"
39
+ :class="{ 'black-h2': variant === 'plain' }"
46
40
  >
47
41
  <span v-bind="editableAttrs('title')">{{ s.title }}</span>
48
42
  </h2>
49
43
  </template>
50
-
51
- <style scoped>
52
- .article-heading{background:var(--article-bg-light,#f5f9fc);font-size:28px;font-weight:var(--article-heading-weight,700);line-height:36px;margin-bottom:30px;margin-top:var(--article-heading-mt,30px);padding:14px 30px;position:relative}.article-heading:not(.article-heading--plain):not(.article-heading--accent):before{background:var(--article-primary,#24c790);content:"";height:100%;left:0;position:absolute;top:0;width:10px}.article-heading--plain{font-size:28px;line-height:36px;padding:0}.article-heading--accent,.article-heading--plain{background:transparent;color:var(--article-text,#3a4259)}.article-heading--accent{font-size:30px;font-weight:var(--article-heading-weight,700);line-height:1.2;padding:0 0 0 16px}.article-heading--accent:before{background:var(--article-primary,#24c790);border-radius:0;content:"";height:25px;left:0;position:absolute;top:4px;width:5px}.article-heading[id]{scroll-margin-top:80px}@media (max-width:768px){.article-heading{font-size:20px;line-height:23px;margin-bottom:20px;margin-top:20px;padding:16px}.article-heading:not(.article-heading--plain):not(.article-heading--accent):before{height:100%;width:4px}.article-heading--plain{font-size:20px;line-height:23px}.article-heading--accent{font-size:22px;line-height:1.2;padding-left:10px}.article-heading--accent:before{height:18px;width:4px}}
53
- </style>
@@ -21,61 +21,26 @@ const emit = defineEmits<{
21
21
  }>()
22
22
 
23
23
  const s = computed(() => props.settings)
24
- const variant = computed(() => String(s.value.variant || 'default'))
25
- const isWarning = computed(() => variant.value === 'warning')
26
- const isRow = computed(() => String(s.value.layout) === 'row')
24
+ const isWarning = computed(() => String(s.value.variant) === 'warning')
27
25
 
28
- const { editableAttrs, blockEditableAttrs } = useInlineEdit({
26
+ const { blockEditableAttrs } = useInlineEdit({
29
27
  editorMode: () => !!props.editorMode,
30
28
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
31
29
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
32
- onEditStart: (key) => emit('inline-edit-start', key),
30
+ onEditStart: key => emit('inline-edit-start', key),
33
31
  onEditEnd: () => emit('inline-edit-end'),
34
- onUndoRedo: (action) => emit('undo-redo', action),
32
+ onUndoRedo: action => emit('undo-redo', action),
35
33
  })
36
34
  </script>
37
35
 
38
36
  <template>
39
- <!-- tips / note 变体:角标样式 (Visiva) -->
40
- <div v-if="variant === 'tips' || variant === 'note'" class="notice-badge">
41
- <div class="notice-badge__tag">
42
- <span class="notice-badge__label" v-bind="editableAttrs('title')">{{ s.title || 'Tips' }}</span>
43
- </div>
44
- <div class="notice-badge__content">
45
- <template v-for="blockId in blockOrder" :key="blockId">
46
- <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">{{ blocks[blockId]!.settings.text }}</p>
47
- </template>
48
- </div>
49
- </div>
50
-
51
- <!-- info 变体:圆角卡片+图标标题 (Visiva) -->
52
- <div v-else-if="variant === 'info'" class="notice-card">
53
- <div v-if="s.title" class="notice-card__header">
54
- <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
55
- <h3 class="notice-card__title" v-bind="editableAttrs('title')">{{ s.title }}</h3>
56
- </div>
57
- <div class="notice-card__content">
58
- <template v-for="blockId in blockOrder" :key="blockId">
59
- <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">{{ blocks[blockId]!.settings.text }}</p>
60
- </template>
61
- </div>
62
- </div>
63
-
64
- <!-- default / warning 变体:虚线框样式 (VigilKids 原有,完全不动) -->
65
37
  <div
66
- v-else
67
38
  class="notice-info"
68
- :class="{
69
- warning: isWarning,
70
- 'notice-info--row-child': isRow,
71
- }"
39
+ :class="{ 'warning': isWarning }"
72
40
  >
73
41
  <template v-for="blockId in blockOrder" :key="blockId">
74
- <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">{{ blocks[blockId]!.settings.text }}</p>
42
+ <!-- eslint-disable-next-line vue/no-v-html -->
43
+ <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')" v-html="blocks[blockId]!.settings.text" />
75
44
  </template>
76
45
  </div>
77
46
  </template>
78
-
79
- <style scoped>
80
- .notice-info{background:color-mix(in srgb,var(--article-primary-hover,#1ba97a) 10%,transparent);border:2px dashed var(--article-primary-hover,#1ba97a);border-radius:10px;font-size:18px;line-height:21px;margin-bottom:20px;margin-top:30px;padding:60px 40px 40px 108px;position:relative}.notice-info:before{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='142' height='40' fill='none' viewBox='0 0 142 40'%3E%3Cpath fill='%2324c790' d='M0 0h142l-9.079 27.62a18 18 0 0 1-17.1 12.38H26.179a18 18 0 0 1-17.1-12.38z'/%3E%3Cpath fill='%23fff' d='M51.153 30.957h-.033c-1.017-.011-2.037-.011-3.057-.011h-1.556c-1.039 0-2.078 0-3.12-.014a4.8 4.8 0 0 1-1.681-.355c-.91-.356-1.43-1.149-1.428-2.18l.003-3.866c0-2.141 0-4.285.02-6.426.002-.582.377-1.129.697-1.416 1.238-1.132 2.584-2.543 3.147-4.446.156-.53.216-1.116.279-1.734.126-1.23.924-2.031 1.99-2.031q.63.002 1.247.369c.823.495 1.373 1.271 1.676 2.379.487 1.758.238 3.464-.035 4.927v.005a.555.555 0 0 0 .544.66h3.555c.612 0 1.49.076 2.031.732.394.479.506 1.124.34 1.977-.51 2.622-1.133 5.267-1.729 7.73-.186.769-.495 1.48-.793 2.169l-.128.295c-.34.798-1.04 1.236-1.969 1.236M35.91 30.7h-.325a2.145 2.145 0 0 1-2.139-2.139v-9.529c0-1.176.963-2.138 2.139-2.138h.325c1.176 0 2.138.962 2.138 2.138v9.527a2.14 2.14 0 0 1-2.138 2.14M76.807 21.932h-3.623v-2.285h3.623q.84 0 1.367-.274.527-.284.771-.781.245-.498.245-1.123 0-.634-.245-1.182a1.97 1.97 0 0 0-.771-.879q-.528-.332-1.367-.332h-2.608V27h-2.93V12.781h5.538q1.67 0 2.861.606 1.2.595 1.836 1.65.635 1.054.635 2.412 0 1.377-.635 2.383-.634 1.005-1.836 1.553-1.191.547-2.861.547m9.99-3.194V27h-2.813V16.434h2.647zm3.184-2.373-.05 2.608a7 7 0 0 0-.497-.05 5 5 0 0 0-.518-.028 3.1 3.1 0 0 0-1.035.156 1.85 1.85 0 0 0-.723.44 1.9 1.9 0 0 0-.43.712q-.136.42-.156.957l-.566-.176q0-1.025.205-1.884.205-.87.596-1.514.4-.645.976-.996a2.5 2.5 0 0 1 1.319-.352q.234 0 .478.04.244.028.4.087m.585 5.46v-.206q0-1.162.332-2.139a4.95 4.95 0 0 1 .967-1.709 4.3 4.3 0 0 1 1.563-1.123q.927-.41 2.129-.41 1.2 0 2.138.41.938.4 1.573 1.124a4.9 4.9 0 0 1 .976 1.709q.332.975.332 2.138v.205q0 1.152-.332 2.139a5 5 0 0 1-.976 1.709 4.3 4.3 0 0 1-1.563 1.123q-.927.4-2.129.4-1.2 0-2.138-.4a4.4 4.4 0 0 1-1.573-1.123 5.1 5.1 0 0 1-.967-1.71 6.7 6.7 0 0 1-.332-2.138m2.813-.206v.205q0 .664.117 1.24.117.577.371 1.016.264.43.684.674t1.025.244q.585 0 1.006-.244.42-.245.674-.674.255-.44.371-1.015.127-.577.127-1.24v-.206q0-.645-.127-1.21a3.2 3.2 0 0 0-.38-1.016q-.255-.45-.675-.704t-1.015-.253-1.016.253q-.41.255-.674.704a3.4 3.4 0 0 0-.37 1.015 6 6 0 0 0-.118 1.211m14.639 2.461a.9.9 0 0 0-.176-.547q-.175-.244-.654-.449-.469-.215-1.358-.39a10.6 10.6 0 0 1-1.494-.44 5.2 5.2 0 0 1-1.191-.654 2.8 2.8 0 0 1-.782-.899 2.5 2.5 0 0 1-.283-1.201q0-.665.283-1.25.294-.586.83-1.035a3.9 3.9 0 0 1 1.328-.713 5.6 5.6 0 0 1 1.778-.264q1.377 0 2.363.44.996.44 1.524 1.21.537.762.537 1.739h-2.813q0-.41-.176-.732a1.16 1.16 0 0 0-.527-.518q-.351-.195-.918-.195-.469 0-.81.166a1.25 1.25 0 0 0-.528.43 1.03 1.03 0 0 0-.176.585q0 .245.098.44.108.185.342.341t.605.294q.381.126.938.234a10.8 10.8 0 0 1 2.041.615q.898.372 1.426 1.016.527.635.527 1.67 0 .702-.313 1.289-.312.585-.898 1.025a4.6 4.6 0 0 1-1.406.674 6.6 6.6 0 0 1-1.826.234q-1.475 0-2.5-.527-1.016-.528-1.543-1.338-.518-.82-.518-1.68h2.666q.02.577.293.928.283.352.713.508.44.156.947.156.548 0 .908-.146.362-.157.547-.41a1 1 0 0 0 .196-.606'/%3E%3C/svg%3E") no-repeat 50%;background-size:cover;content:"";height:40px;left:40px;position:absolute;top:0;width:142px}.notice-info :deep(p){color:var(--article-text,#3a4259);margin-top:0;position:relative}.notice-info :deep(p:not(:last-child)){margin-bottom:16px}.notice-info :deep(p:before){background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' fill='none' viewBox='0 0 30 30'%3E%3Cpath fill='%2324c790' d='M14.947 1.897C7.722 1.897 1.844 7.775 1.844 15S7.72 28.103 14.947 28.103c7.225 0 13.103-5.878 13.103-13.103S22.172 1.897 14.946 1.897m6.323 10.942-7.528 7.609-.007.005q-.003.004-.006.008c-.06.058-.134.094-.204.133-.034.02-.063.05-.1.064a.95.95 0 0 1-.706-.002c-.038-.016-.068-.048-.104-.067-.07-.04-.142-.075-.202-.134l-.005-.007-.007-.006-3.702-3.805a.94.94 0 1 1 1.348-1.31l3.034 3.117 6.853-6.928a.94.94 0 1 1 1.336 1.322'/%3E%3C/svg%3E") no-repeat 50%;content:"";height:30px;left:-38px;position:absolute;top:-4.5px;width:30px}.notice-info.warning{background:color-mix(in srgb,var(--article-warning,#f1631c) 10%,transparent);border-color:var(--article-warning,#f1631c)}.notice-info.warning:before{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='142' height='40' fill='none' viewBox='0 0 142 40'%3E%3Cpath fill='%23f1631c' d='M0 0h142l-9.079 27.62a18 18 0 0 1-17.1 12.38H26.179a18 18 0 0 1-17.1-12.38z'/%3E%3Cpath fill='%23fff' d='M38.14 8.478h.033c1.017.01 2.037.01 3.057.01h1.556c1.039 0 2.078 0 3.12.014a4.8 4.8 0 0 1 1.681.356c.91.355 1.43 1.148 1.427 2.18l-.002 3.865c0 2.141 0 4.285-.02 6.426-.002.583-.377 1.13-.697 1.417-1.238 1.132-2.584 2.543-3.147 4.446-.156.53-.216 1.115-.279 1.733-.126 1.23-.924 2.032-1.99 2.032-.419 0-.837-.126-1.247-.37-.823-.494-1.373-1.27-1.676-2.378-.487-1.758-.238-3.465.035-4.927v-.006a.555.555 0 0 0-.544-.659h-3.555c-.612 0-1.49-.076-2.032-.733-.393-.478-.505-1.123-.338-1.977.508-2.622 1.132-5.266 1.728-7.73.185-.768.495-1.479.793-2.168l.128-.295c.34-.799 1.04-1.236 1.969-1.236m15.244.257h.325c1.176 0 2.139.962 2.139 2.138v9.53a2.145 2.145 0 0 1-2.139 2.138h-.325a2.145 2.145 0 0 1-2.138-2.139v-9.526a2.14 2.14 0 0 1 2.138-2.141M79.512 22.283h2.92q-.089 1.436-.791 2.549-.694 1.114-1.944 1.738-1.24.625-2.988.625-1.367 0-2.451-.468a5.2 5.2 0 0 1-1.856-1.368q-.762-.888-1.162-2.148t-.4-2.822v-.987q0-1.562.41-2.822.42-1.27 1.191-2.158a5.3 5.3 0 0 1 1.866-1.367q1.084-.48 2.421-.479 1.778 0 2.999.645 1.23.645 1.904 1.777.684 1.133.82 2.578h-2.93q-.048-.86-.341-1.455a1.94 1.94 0 0 0-.889-.908q-.585-.313-1.562-.313-.733 0-1.28.274-.546.273-.918.83-.371.557-.556 1.406-.177.84-.176 1.973v1.006q0 1.103.166 1.943.166.83.508 1.406.351.567.898.86.556.283 1.338.283.918 0 1.514-.293t.908-.87q.323-.575.38-1.435m4.219-.459v-.205q0-1.162.332-2.139a4.95 4.95 0 0 1 .966-1.709 4.3 4.3 0 0 1 1.563-1.123q.927-.41 2.129-.41 1.2 0 2.138.41.938.4 1.573 1.124.645.721.976 1.709.332.975.332 2.138v.205q0 1.152-.332 2.139a5 5 0 0 1-.976 1.709 4.3 4.3 0 0 1-1.563 1.123q-.927.4-2.129.4-1.2 0-2.138-.4a4.4 4.4 0 0 1-1.573-1.123 5.1 5.1 0 0 1-.966-1.71 6.7 6.7 0 0 1-.332-2.138m2.812-.205v.205q0 .664.117 1.24.117.577.371 1.016.264.43.684.674t1.025.244q.585 0 1.006-.244.42-.245.674-.674a3.4 3.4 0 0 0 .371-1.015q.127-.577.127-1.24v-.206q0-.645-.127-1.21a3.2 3.2 0 0 0-.38-1.016 1.9 1.9 0 0 0-.675-.704q-.42-.253-1.015-.253-.596 0-1.016.253-.41.255-.674.704-.254.44-.37 1.015a6 6 0 0 0-.118 1.211m11.68-2.93V27H95.41V16.434h2.637zm-.41 2.657h-.762q0-1.173.302-2.11.304-.947.85-1.611a3.65 3.65 0 0 1 1.299-1.025 3.9 3.9 0 0 1 1.699-.362q.742 0 1.358.215.615.214 1.054.684.45.468.684 1.24.244.771.244 1.885V27h-2.832v-6.748q0-.703-.195-1.094a1.1 1.1 0 0 0-.576-.547q-.372-.166-.918-.166-.567 0-.987.225-.41.225-.683.625a3 3 0 0 0-.4.918q-.138.527-.138 1.133m14.58 2.734a.9.9 0 0 0-.176-.547q-.175-.244-.655-.449-.468-.215-1.357-.39a10.6 10.6 0 0 1-1.494-.44 5.2 5.2 0 0 1-1.191-.654 2.85 2.85 0 0 1-.782-.899 2.5 2.5 0 0 1-.283-1.201q0-.665.283-1.25.294-.586.83-1.035a3.9 3.9 0 0 1 1.328-.713 5.6 5.6 0 0 1 1.778-.264q1.377 0 2.363.44.996.44 1.524 1.21.537.762.537 1.739h-2.813q0-.41-.176-.732a1.16 1.16 0 0 0-.527-.518q-.351-.195-.918-.195-.469 0-.81.166a1.25 1.25 0 0 0-.528.43 1.03 1.03 0 0 0-.176.585q0 .245.098.44.108.185.342.341t.605.294q.381.126.938.234a10.8 10.8 0 0 1 2.041.615q.898.372 1.426 1.016.527.635.527 1.67 0 .702-.313 1.289-.312.585-.898 1.025a4.6 4.6 0 0 1-1.406.674 6.6 6.6 0 0 1-1.826.234q-1.475 0-2.5-.527-1.016-.528-1.543-1.338-.518-.82-.518-1.68h2.666q.02.577.293.928.283.352.713.508.44.156.947.156.548 0 .908-.146.362-.157.547-.41a1 1 0 0 0 .196-.606'/%3E%3C/svg%3E") no-repeat 50%;background-size:cover}.notice-info.warning :deep(p:before){background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' fill='none' viewBox='0 0 30 30'%3E%3Cg clip-path='url(%23a)'%3E%3Cpath fill='%23f1631c' d='M15 1.875C7.751 1.875 1.875 7.751 1.875 15S7.751 28.125 15 28.125 28.125 22.249 28.125 15 22.249 1.875 15 1.875m6.299 17.931a1.06 1.06 0 0 1 0 1.493 1.06 1.06 0 0 1-1.492 0L15 16.49 10.195 21.3a1.06 1.06 0 0 1-1.494 0 1.06 1.06 0 0 1 0-1.492l4.805-4.804L8.7 10.195a1.058 1.058 0 0 1 .747-1.803c.28 0 .549.111.747.31L15 13.508l4.805-4.805a1.06 1.06 0 0 1 1.494-.003 1.06 1.06 0 0 1 0 1.494L16.49 15z'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath fill='%23fff' d='M0 0h30v30H0z'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E") no-repeat 50%}@media (max-width:768px){.notice-info{line-height:19px;padding:44px 16px 30px 44px}.notice-info:before{background-size:cover!important;height:32px;left:16px;width:114px}.notice-info :deep(p:before){background-size:cover;height:24px;left:-30px;top:-2px;width:24px}}.notice-badge{background:var(--article-bg-light,#f3f4f6);border-radius:0 0 5px 5px;margin-top:40px;padding:36px 16px 20px;position:relative}.notice-badge__tag{align-items:center;background:var(--article-badge,#24c790);border-radius:5px 0 10px 0;display:flex;gap:4px;height:24px;left:0;padding:0 8px;position:absolute;top:0}.notice-badge__label{color:#fff;font-size:14px;font-weight:700}.notice-badge__content{color:var(--article-text-muted,#6b7280);font-size:14px;font-weight:300;line-height:1.6}.notice-badge__content p{margin:0 0 8px}.notice-badge__content p:last-child{margin-bottom:0}.notice-card{background:var(--article-bg-light,#f3f4f6);border-radius:10px;margin-top:40px;padding:20px 16px}.notice-card__header{align-items:center;display:flex;gap:12px;margin-bottom:12px}.notice-card__title{color:var(--article-text,#1f2937);font-size:16px;font-weight:600;margin:0}.notice-card__content{color:var(--article-text-muted,#6b7280);font-size:14px;font-weight:300;line-height:1.6}.notice-card__content p{margin:0 0 8px}.notice-card__content p:last-child{margin-bottom:0}@media (min-width:769px){.notice-badge{padding:36px 16px 20px}.notice-card{padding:24px}.notice-badge__content,.notice-card__content{font-size:16px}}
81
- </style>
@@ -24,14 +24,14 @@ const s = computed(() => props.settings)
24
24
 
25
25
  const pros = computed(() =>
26
26
  props.blockOrder
27
- .filter((id) => props.blocks[id]?.type === 'pro')
28
- .map((id) => ({ id, block: props.blocks[id]! })),
27
+ .filter(id => props.blocks[id]?.type === 'pro')
28
+ .map(id => ({ id, block: props.blocks[id]! })),
29
29
  )
30
30
 
31
31
  const cons = computed(() =>
32
32
  props.blockOrder
33
- .filter((id) => props.blocks[id]?.type === 'con')
34
- .map((id) => ({ id, block: props.blocks[id]! })),
33
+ .filter(id => props.blocks[id]?.type === 'con')
34
+ .map(id => ({ id, block: props.blocks[id]! })),
35
35
  )
36
36
 
37
37
  const isDashed = computed(() => String(s.value.variant) === 'dashed')
@@ -40,35 +40,27 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
40
40
  editorMode: () => !!props.editorMode,
41
41
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
42
42
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
43
- onEditStart: (key) => emit('inline-edit-start', key),
43
+ onEditStart: key => emit('inline-edit-start', key),
44
44
  onEditEnd: () => emit('inline-edit-end'),
45
- onUndoRedo: (action) => emit('undo-redo', action),
45
+ onUndoRedo: action => emit('undo-redo', action),
46
46
  })
47
47
  </script>
48
48
 
49
49
  <template>
50
- <div :class="isDashed ? 'props-dashed' : 'props-solid'">
51
- <!-- Pros -->
50
+ <div :class="isDashed ? 'props-dashed' : 'props-solid'">
52
51
  <div class="props-left">
53
52
  <span v-bind="editableAttrs('pros_title')">{{ s.pros_title || 'Pros' }}</span>
54
53
  <ul>
55
- <li v-for="(item, idx) in pros" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')">
56
- {{ idx + 1 }}. {{ item.block.settings.text }}
57
- </li>
54
+ <!-- eslint-disable-next-line vue/no-v-html -->
55
+ <li v-for="item in pros" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.block.settings.text" />
58
56
  </ul>
59
57
  </div>
60
- <!-- Cons -->
61
58
  <div class="props-right">
62
59
  <span v-bind="editableAttrs('cons_title')">{{ s.cons_title || 'Cons' }}</span>
63
60
  <ul>
64
- <li v-for="(item, idx) in cons" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')">
65
- {{ idx + 1 }}. {{ item.block.settings.text }}
66
- </li>
61
+ <!-- eslint-disable-next-line vue/no-v-html -->
62
+ <li v-for="item in cons" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.block.settings.text" />
67
63
  </ul>
68
64
  </div>
69
65
  </div>
70
66
  </template>
71
-
72
- <style scoped>
73
- .props-dashed,.props-solid{background:var(--article-bg-light,#f5f9fc);border-radius:10px;display:grid;gap:60px;grid-template-columns:1fr 1fr;margin:20px 0;padding:20px 30px}.props-dashed{background:#fff;border:1px dashed #ccc}.props-dashed span,.props-solid span{display:block;font-size:20px;font-weight:700;line-height:28px;padding-left:14px;position:relative}.props-dashed span:before,.props-solid span:before{background:var(--article-primary,#24c790);content:"";height:14px;left:0;position:absolute;top:50%;transform:translateY(-50%);width:6px}.props-dashed ul,.props-solid ul{color:var(--article-text,#3a4259);font-size:16px;line-height:32px;margin-top:14px;padding-left:14px}.props-dashed .props-right span:before,.props-solid .props-right span:before{background:var(--article-cons,#f44242)}@media (max-width:768px){.props-dashed,.props-solid{gap:20px;grid-template-columns:1fr;margin:16px 0;padding:16px}.props-dashed span,.props-solid span{font-size:18px;line-height:21px;padding-left:10px}.props-dashed span:before,.props-solid span:before{height:10px;width:4px}.props-dashed ul,.props-solid ul{font-size:14px;line-height:21px;margin-top:10px;padding-left:10px}}
74
- </style>