feat(exporter): 섹션 엣지 케이스 보강(공백 제거/줄바꿈 토큰/CTA) + 테스트

This commit is contained in:
2025-11-14 23:15:05 +09:00
parent a476756a6c
commit 4b9627d4ee
3 changed files with 115 additions and 10 deletions
+26 -10
View File
@@ -5,7 +5,12 @@ export type Exported = { html: string; css: string; js: string }
export type ExportOptions = { assetManager?: AssetManager }
function renderFaq(section: { props: { items: Array<{ q: string; a: string }> } }) {
const items: Array<{ q: string; a: string }> = Array.isArray(section.props.items) ? section.props.items : []
const itemsRaw: Array<{ q: string; a: string }> = Array.isArray(section.props.items) ? section.props.items : []
const isBlank = (s: string | undefined | null) => !s || s.trim().length === 0
const items = itemsRaw
.map((it) => ({ q: (it.q || '').trim(), a: (it.a || '').trim() }))
// drop when both question and answer are blank
.filter((it) => !(isBlank(it.q) && isBlank(it.a)))
return `
<section class="faq" aria-labelledby="faq-heading">
<div class="container">
@@ -27,7 +32,9 @@ function renderFaq(section: { props: { items: Array<{ q: string; a: string }> }
}
function renderFeatures(section: { props: { items: string[] } }) {
const items: string[] = Array.isArray(section.props?.items) ? section.props.items : []
const items: string[] = (Array.isArray(section.props?.items) ? section.props.items : [])
.map((s) => String(s ?? '').trim())
.filter((s) => s.length > 0)
return `
<section class="features" aria-labelledby="features-heading">
<div class="container">
@@ -41,7 +48,9 @@ function renderFeatures(section: { props: { items: string[] } }) {
}
function renderBenefits(section: { props: { items: string[] } }) {
const items: string[] = Array.isArray(section.props?.items) ? section.props.items : []
const items: string[] = (Array.isArray(section.props?.items) ? section.props.items : [])
.map((s) => String(s ?? '').trim())
.filter((s) => s.length > 0)
return `
<section class="benefits" aria-labelledby="benefits-heading">
<div class="container">
@@ -55,9 +64,12 @@ function renderBenefits(section: { props: { items: string[] } }) {
}
function renderTestimonials(section: { props: { items: Array<{ author: string; quote: string }> } }) {
const items: Array<{ author: string; quote: string }> = Array.isArray(section.props?.items)
const items: Array<{ author: string; quote: string }> = (Array.isArray(section.props?.items)
? section.props.items
: []
: [])
.map((it) => ({ author: (it.author || '').trim(), quote: (it.quote || '').trim() }))
// drop when both are blank
.filter((it) => !(it.author.length === 0 && it.quote.length === 0))
return `
<section class="testimonials" aria-labelledby="testimonials-heading">
<div class="container">
@@ -66,8 +78,8 @@ function renderTestimonials(section: { props: { items: Array<{ author: string; q
.map(
(it) => `
<figure class="testimonial">
<blockquote>“${escapeHtml(it.quote || '')}”</blockquote>
<figcaption>— ${escapeHtml(it.author || '')}</figcaption>
<blockquote>“${escapeHtml(it.quote)}”</blockquote>
<figcaption>— ${escapeHtml(it.author)}</figcaption>
</figure>`
)
.join('')}
@@ -77,7 +89,9 @@ function renderTestimonials(section: { props: { items: Array<{ author: string; q
}
function renderCta(section: { props: { text: string; href?: string } }) {
const { text, href } = section.props
const href = section.props.href
const text = (section.props.text || '').trim()
if (text.length === 0) return ''
return `
<section class="cta" aria-labelledby="cta-heading">
<div class="container">
@@ -267,10 +281,12 @@ function buildCss(page: Page) {
.btn-cta{ background: var(--primary); color:#fff; padding:.75rem 1rem; border-radius:.5rem; display:inline-block; text-decoration:none }
.features{ background:#f8fafc }
.features-list{ list-style:none; padding:0; margin:0; display:grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: .75rem }
.feature-item{ background:#fff; border:1px solid #e2e8f0; border-radius:.5rem; padding:.75rem }
.feature-item{ background:#fff; border:1px solid #e2e8f0; border-radius:.5rem; padding:.75rem; overflow-wrap:anywhere }
.benefits-list{ list-style:none; padding:0; margin:0 }
.benefit-item{ overflow-wrap:anywhere }
.testimonials{ background:#ffffff }
.testimonial{ border-left:4px solid var(--primary); padding:.5rem 1rem; margin: .75rem 0; background:#fefefe }
.testimonial blockquote{ margin:0; font-style:italic; color:#1f2937 }
.testimonial blockquote{ margin:0; font-style:italic; color:#1f2937; overflow-wrap:anywhere }
.testimonial figcaption{ color:#475569; margin-top:.25rem }
@media (max-width: 640px){ .container{ padding: 1rem } h1{ font-size: 1.5rem } }
@media (prefers-color-scheme: dark){ body{ color:#e5e7eb; background:#0b1020 } .feature-item{ background:#0f172a; border-color:#1f2937 } a{ color:#93c5fd } a:hover{ text-decoration:underline } button{ background: var(--primary); color:#fff } .btn-cta{ background: var(--primary); color:#fff } .faq-a{ color:#cbd5e1 } }