From 8ada2d1947788f1352f84762282e8b22de6e854a Mon Sep 17 00:00:00 2001 From: Jaybe Date: Sat, 15 Nov 2025 02:35:52 +0900 Subject: [PATCH] =?UTF-8?q?feat(export-ui):=20Export=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=20UI=20=EC=B6=94=EA=B0=80(manifest=20short=5Fname/start=5Furl/?= =?UTF-8?q?display/theme=5Fcolor,=20robots=20Disallow)=20=EB=B0=8F=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/BuilderClientPage.tsx | 110 +++++++++++++++++- components/ExportOptions.manifest.ui.test.tsx | 67 +++++++++++ components/ExportOptions.ui.test.tsx | 60 ++++++++++ 3 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 components/ExportOptions.manifest.ui.test.tsx create mode 100644 components/ExportOptions.ui.test.tsx diff --git a/components/BuilderClientPage.tsx b/components/BuilderClientPage.tsx index 56a8da2..c9eec70 100644 --- a/components/BuilderClientPage.tsx +++ b/components/BuilderClientPage.tsx @@ -48,6 +48,13 @@ export default function BuilderClientPage() { const [ogImage, setOgImage] = useState('') const [ga4MeasurementId, setGa4MeasurementId] = useState('') const [metaPixelId, setMetaPixelId] = useState('') + // Export Options (manifest/robots overrides) + const [manifestName, setManifestName] = useState('') + const [manifestShortName, setManifestShortName] = useState('') + const [manifestStartUrl, setManifestStartUrl] = useState('') + const [manifestDisplay, setManifestDisplay] = useState('') + const [manifestThemeColor, setManifestThemeColor] = useState('') + const [robotsDisallow, setRobotsDisallow] = useState('') const [viewport, setViewport] = useState<'desktop' | 'mobile'>(() => { if (typeof window !== 'undefined') { try { @@ -186,7 +193,20 @@ export default function BuilderClientPage() { }, [pageData, am]) const doExport = useCallback(async () => { - const valid = pageSchema.parse(pageData) + let valid = pageData as Page + try { + valid = pageSchema.parse(pageData) + } catch { + const fixed: Page = { + ...pageData, + form: { + actionUrl: pageData.form?.actionUrl ?? '', + fields: pageData.form?.fields ?? [], + spamProtection: pageData.form?.spamProtection ?? { honeypotFieldName: '_hp', minSubmitSeconds: 2 }, + }, + } as Page + valid = pageSchema.parse(fixed) + } const exported = exportPage(valid, { assetManager: am ?? undefined }) try { if (typeof window !== 'undefined') { @@ -197,10 +217,24 @@ export default function BuilderClientPage() { } } catch {} const assets = am ? am.toZipStructure() : {} - const extras = buildExtrasForPage(valid) + // Build overrides from UI options + const manifestOverrideBase: Record = {} + if (manifestName && manifestName.trim().length > 0) manifestOverrideBase.name = manifestName.trim() + if (manifestShortName && manifestShortName.trim().length > 0) manifestOverrideBase.short_name = manifestShortName.trim() + if (manifestStartUrl && manifestStartUrl.trim().length > 0) manifestOverrideBase.start_url = manifestStartUrl.trim() + if (manifestDisplay && manifestDisplay.trim().length > 0) manifestOverrideBase.display = manifestDisplay.trim() + if (manifestThemeColor && manifestThemeColor.trim().length > 0) manifestOverrideBase.theme_color = manifestThemeColor.trim() + const manifestOverride = Object.keys(manifestOverrideBase).length > 0 ? (manifestOverrideBase as { name?: string; short_name?: string; start_url?: string; display?: string; theme_color?: string }) : undefined + const robotsTextOverride = (() => { + const lines = (robotsDisallow || '').split(/\n+/).map((s) => s.trim()).filter((s) => s.length > 0) + if (lines.length === 0) return undefined + const disallowLines = lines.map((l) => (l.startsWith('Disallow:') ? l : `Disallow: ${l}`)) + return `User-agent: *\n${disallowLines.join('\n')}` + })() + const extras = buildExtrasForPage(valid, { robotsText: robotsTextOverride, manifest: manifestOverride }) const zipBytes = await buildZip({ html: exported.html, css: exported.css, js: exported.js, assets, extras }) download('landing.zip', zipBytes) - }, [pageData, am]) + }, [pageData, am, manifestName, manifestShortName, manifestStartUrl, manifestDisplay, manifestThemeColor, robotsDisallow]) return (
@@ -263,6 +297,76 @@ export default function BuilderClientPage() { onSelect={(i) => setSelectedIndex(i)} />
+
+

Export Options

+ + + + + +