1차 싱크 완료
CI / test (push) Failing after 41m13s
CI / pr_and_merge (push) Has been skipped

This commit is contained in:
2025-11-24 21:32:37 +09:00
parent 7a8ad7c057
commit 672cca5271
83 changed files with 6681 additions and 325 deletions
+309 -20
View File
@@ -119,7 +119,9 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
? weightMap[fontWeightScale]
: "";
let colorClass = "";
let colorClass = "pb-text-color-strong";
const inlineStyles: string[] = [];
if (props.colorMode === "palette") {
const palette = props.colorPalette ?? "default";
const paletteMap: Record<NonNullable<TextBlockProps["colorPalette"]>, string> = {
@@ -133,7 +135,18 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
info: "pb-text-color-info",
neutral: "pb-text-color-neutral",
};
colorClass = paletteMap[palette] ?? "";
colorClass = paletteMap[palette] ?? colorClass;
} else if (
props.colorMode === "custom" &&
typeof props.colorCustom === "string" &&
props.colorCustom.trim() !== ""
) {
inlineStyles.push(`color:${props.colorCustom.trim()}`);
}
// 블록 배경색: backgroundColorCustom 이 설정된 경우에만 적용한다.
if (typeof (props as any).backgroundColorCustom === "string" && (props as any).backgroundColorCustom.trim() !== "") {
inlineStyles.push(`background-color:${(props as any).backgroundColorCustom.trim()}`);
}
let maxWidthClass = "";
@@ -159,12 +172,15 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
weightClass,
colorClass,
maxWidthClass,
"pb-whitespace-pre-wrap",
...decoClasses,
]
.filter(Boolean)
.join(" ");
return `<p class="${classes}">${escapeHtml(text)}</p>`;
const styleAttr = inlineStyles.length > 0 ? ` style="${inlineStyles.join(";")}"` : "";
return `<p class="${classes}"${styleAttr}>${escapeHtml(text)}</p>`;
}
if (block.type === "button") {
@@ -238,7 +254,42 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const props: any = block.props ?? {};
const src = typeof props.src === "string" ? props.src : "";
const alt = typeof props.alt === "string" ? props.alt : "";
return `<div><img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}" /></div>`;
const wrapperStyleParts: string[] = [];
const imgStyleParts: string[] = [];
if (typeof props.backgroundColorCustom === "string" && props.backgroundColorCustom.trim() !== "") {
wrapperStyleParts.push(`background-color:${escapeAttr(props.backgroundColorCustom.trim())}`);
}
const widthMode = props.widthMode ?? "auto";
if (widthMode === "fixed" && typeof props.widthPx === "number" && props.widthPx > 0) {
imgStyleParts.push(`width:${props.widthPx}px`);
}
const radiusToken = props.borderRadius ?? "md";
const fallbackRadiusPx =
radiusToken === "none"
? 0
: radiusToken === "sm"
? 4
: radiusToken === "lg"
? 16
: radiusToken === "full"
? 9999
: 8;
const radiusPx =
typeof props.borderRadiusPx === "number" && props.borderRadiusPx >= 0
? props.borderRadiusPx
: fallbackRadiusPx;
if (typeof radiusPx === "number" && radiusPx >= 0) {
imgStyleParts.push(`border-radius:${radiusPx === 9999 ? "9999px" : `${radiusPx}px`}`);
}
const wrapperStyleAttr = wrapperStyleParts.length > 0 ? ` style="${wrapperStyleParts.join(";")}"` : "";
const imgStyleAttr = imgStyleParts.length > 0 ? ` style="${imgStyleParts.join(";")}"` : "";
return `<div${wrapperStyleAttr}><img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}"${imgStyleAttr} /></div>`;
}
if (block.type === "form") {
@@ -261,7 +312,13 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const fields = controllerFields.length > 0 ? controllerFields : null;
const formParts: string[] = [];
formParts.push(`<form class="pb-form" method="post">`);
const formStyleParts: string[] = [];
if (typeof props.backgroundColorCustom === "string" && props.backgroundColorCustom.trim() !== "") {
formStyleParts.push(`background-color:${props.backgroundColorCustom.trim()}`);
}
const formStyleAttr = formStyleParts.length > 0 ? ` style="${formStyleParts.join(";")}"` : "";
formParts.push(`<form class="pb-form" method="post"${formStyleAttr}>`);
if (fields) {
for (const fieldBlock of fields) {
@@ -272,8 +329,15 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
? (anyProps.label ?? anyProps.groupLabel)
: name;
const textColorRaw =
typeof anyProps.textColorCustom === "string" && anyProps.textColorCustom.trim() !== ""
? anyProps.textColorCustom.trim()
: "";
const labelStyleAttr = textColorRaw ? ` style="color:${escapeAttr(textColorRaw)}"` : "";
const fieldTextStyleAttr = textColorRaw ? ` style="color:${escapeAttr(textColorRaw)}"` : "";
formParts.push(`<div class="pb-form-field">`);
formParts.push(`<label class="pb-form-label">${escapeHtml(label ?? "")}</label>`);
formParts.push(`<label class="pb-form-label"${labelStyleAttr}>${escapeHtml(label ?? "")}</label>`);
if (fieldBlock.type === "formInput") {
const type = anyProps.inputType === "email" ? "email" : "text";
@@ -281,12 +345,12 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
formParts.push(
`<input class="pb-input" type="${type}" name="${escapeAttr(name)}" placeholder="${escapeAttr(
label ?? "",
)}"${required} />`,
)}"${required}${fieldTextStyleAttr} />`,
);
} else if (fieldBlock.type === "formSelect") {
const required = anyProps.required ? " required" : "";
const options: FormSelectOption[] = Array.isArray(anyProps.options) ? anyProps.options : [];
formParts.push(`<select class="pb-select" name="${escapeAttr(name)}"${required}>`);
formParts.push(`<select class="pb-select" name="${escapeAttr(name)}"${required}${fieldTextStyleAttr}>`);
for (const opt of options) {
formParts.push(
`<option value="${escapeAttr(opt.value)}">${escapeHtml(opt.label)}</option>`,
@@ -297,7 +361,7 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const options = Array.isArray(anyProps.options) ? anyProps.options : [];
for (const opt of options) {
formParts.push(
`<label class="pb-form-option"><input type="checkbox" name="${escapeAttr(
`<label class="pb-form-option"${fieldTextStyleAttr}><input type="checkbox" name="${escapeAttr(
name,
)}" value="${escapeAttr(opt.value)}" /> ${escapeHtml(opt.label)}</label>`,
);
@@ -306,7 +370,7 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const options = Array.isArray(anyProps.options) ? anyProps.options : [];
for (const opt of options) {
formParts.push(
`<label class="pb-form-option"><input type="radio" name="${escapeAttr(
`<label class="pb-form-option"${fieldTextStyleAttr}><input type="radio" name="${escapeAttr(
name,
)}" value="${escapeAttr(opt.value)}" /> ${escapeHtml(opt.label)}</label>`,
);
@@ -386,7 +450,14 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const align = props.align === "center" ? "center" : props.align === "right" ? "right" : "left";
const lis = items.map((text) => `<li>${escapeHtml(text)}</li>`).join("");
return `<${Tag} class="pb-list" style="text-align:${align};">${lis}</${Tag}>`;
const listStyleParts: string[] = [`text-align:${align}`];
if (typeof props.backgroundColorCustom === "string" && props.backgroundColorCustom.trim() !== "") {
listStyleParts.push(`background-color:${props.backgroundColorCustom.trim()}`);
}
const listStyleAttr = listStyleParts.join(";");
return `<${Tag} class="pb-list" style="${listStyleAttr}">${lis}</${Tag}>`;
}
if (block.type === "formInput") {
@@ -397,12 +468,69 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const type = props.inputType === "email" ? "email" : "text";
const required = props.required ? " required" : "";
const textColorRaw =
typeof props.textColorCustom === "string" && props.textColorCustom.trim() !== ""
? props.textColorCustom.trim()
: "";
const labelStyleAttr = textColorRaw ? ` style=\"color:${escapeAttr(textColorRaw)}\"` : "";
const inputStyleParts: string[] = [];
if (textColorRaw) {
inputStyleParts.push(`color:${escapeAttr(textColorRaw)}`);
}
if (typeof props.fillColorCustom === "string" && props.fillColorCustom.trim() !== "") {
inputStyleParts.push(`background-color:${escapeAttr(props.fillColorCustom.trim())}`);
}
if (typeof props.strokeColorCustom === "string" && props.strokeColorCustom.trim() !== "") {
inputStyleParts.push(`border-color:${escapeAttr(props.strokeColorCustom.trim())}`);
}
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
const px = props.paddingX;
inputStyleParts.push(`padding-left:${px}px`);
inputStyleParts.push(`padding-right:${px}px`);
}
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
const py = props.paddingY;
inputStyleParts.push(`padding-top:${py}px`);
inputStyleParts.push(`padding-bottom:${py}px`);
}
const widthMode =
typeof props.widthMode === "string"
? props.widthMode
: props.fullWidth
? "full"
: "auto";
if (widthMode === "fixed" && typeof props.widthPx === "number" && props.widthPx > 0) {
inputStyleParts.push(`width:${props.widthPx}px`);
}
const radiusToken = typeof props.borderRadius === "string" ? props.borderRadius : "md";
const radiusPx =
radiusToken === "none"
? 0
: radiusToken === "sm"
? 2
: radiusToken === "lg"
? 6
: radiusToken === "full"
? 9999
: 4;
inputStyleParts.push(`border-radius:${radiusPx}px`);
const inputStyleAttr =
inputStyleParts.length > 0 ? ` style=\"${inputStyleParts.join(";")}\"` : "";
return [
'<div class="pb-form-field">',
`<label class="pb-form-label">${escapeHtml(label)}</label>`,
`<label class="pb-form-label"${labelStyleAttr}>${escapeHtml(label)}</label>`,
`<input class="pb-input" type="${type}" name="${escapeAttr(name)}" placeholder="${escapeAttr(
label,
)}"${required} />`,
)}"${required}${inputStyleAttr} />`,
"</div>",
].join("");
}
@@ -415,10 +543,67 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const required = props.required ? " required" : "";
const options: FormSelectOption[] = Array.isArray(props.options) ? props.options : [];
const textColorRaw =
typeof props.textColorCustom === "string" && props.textColorCustom.trim() !== ""
? props.textColorCustom.trim()
: "";
const labelStyleAttr = textColorRaw ? ` style=\"color:${escapeAttr(textColorRaw)}\"` : "";
const selectStyleParts: string[] = [];
if (textColorRaw) {
selectStyleParts.push(`color:${escapeAttr(textColorRaw)}`);
}
const widthMode =
typeof props.widthMode === "string"
? props.widthMode
: props.fullWidth
? "full"
: "auto";
if (widthMode === "fixed" && typeof props.widthPx === "number" && props.widthPx > 0) {
selectStyleParts.push(`width:${props.widthPx}px`);
}
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
const px = props.paddingX;
selectStyleParts.push(`padding-left:${px}px`);
selectStyleParts.push(`padding-right:${px}px`);
}
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
const py = props.paddingY;
selectStyleParts.push(`padding-top:${py}px`);
selectStyleParts.push(`padding-bottom:${py}px`);
}
if (typeof props.fillColorCustom === "string" && props.fillColorCustom.trim() !== "") {
selectStyleParts.push(`background-color:${escapeAttr(props.fillColorCustom.trim())}`);
}
if (typeof props.strokeColorCustom === "string" && props.strokeColorCustom.trim() !== "") {
selectStyleParts.push(`border-color:${escapeAttr(props.strokeColorCustom.trim())}`);
}
const radiusToken = typeof props.borderRadius === "string" ? props.borderRadius : "md";
const radiusPx =
radiusToken === "none"
? 0
: radiusToken === "sm"
? 2
: radiusToken === "lg"
? 6
: radiusToken === "full"
? 9999
: 4;
selectStyleParts.push(`border-radius:${radiusPx}px`);
const selectStyleAttr =
selectStyleParts.length > 0 ? ` style=\"${selectStyleParts.join(";")}\"` : "";
const parts: string[] = [];
parts.push('<div class="pb-form-field">');
parts.push(`<label class="pb-form-label">${escapeHtml(label)}</label>`);
parts.push(`<select class="pb-select" name="${escapeAttr(name)}"${required}>`);
parts.push(`<label class="pb-form-label"${labelStyleAttr}>${escapeHtml(label)}</label>`);
parts.push(`<select class="pb-select" name="${escapeAttr(name)}"${required}${selectStyleAttr}>`);
for (const opt of options) {
parts.push(
`<option value="${escapeAttr(opt.value)}">${escapeHtml(opt.label)}</option>`,
@@ -439,12 +624,61 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
: name;
const options = Array.isArray(props.options) ? props.options : [];
const textColorRaw =
typeof props.textColorCustom === "string" && props.textColorCustom.trim() !== ""
? props.textColorCustom.trim()
: "";
const labelStyleAttr = textColorRaw ? ` style=\"color:${escapeAttr(textColorRaw)}\"` : "";
const optionStyleParts: string[] = [];
if (textColorRaw) {
optionStyleParts.push(`color:${escapeAttr(textColorRaw)}`);
}
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
const px = props.paddingX;
optionStyleParts.push(`padding-left:${px}px`);
optionStyleParts.push(`padding-right:${px}px`);
}
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
const py = props.paddingY;
optionStyleParts.push(`padding-top:${py}px`);
optionStyleParts.push(`padding-bottom:${py}px`);
}
if (typeof props.fillColorCustom === "string" && props.fillColorCustom.trim() !== "") {
optionStyleParts.push(`background-color:${escapeAttr(props.fillColorCustom.trim())}`);
}
if (typeof props.strokeColorCustom === "string" && props.strokeColorCustom.trim() !== "") {
optionStyleParts.push(`border-color:${escapeAttr(props.strokeColorCustom.trim())}`);
optionStyleParts.push("border-width:1px");
optionStyleParts.push("border-style:solid");
}
const checkboxRadiusToken = typeof props.borderRadius === "string" ? props.borderRadius : "md";
const checkboxRadiusPx =
checkboxRadiusToken === "none"
? 0
: checkboxRadiusToken === "sm"
? 2
: checkboxRadiusToken === "lg"
? 6
: checkboxRadiusToken === "full"
? 9999
: 4;
optionStyleParts.push(`border-radius:${checkboxRadiusPx}px`);
const optionStyleAttr =
optionStyleParts.length > 0 ? ` style=\"${optionStyleParts.join(";")}\"` : "";
const parts: string[] = [];
parts.push('<div class="pb-form-field">');
parts.push(`<label class="pb-form-label">${escapeHtml(label)}</label>`);
parts.push(`<label class="pb-form-label"${labelStyleAttr}>${escapeHtml(label)}</label>`);
for (const opt of options) {
parts.push(
`<label class="pb-form-option"><input type="checkbox" name="${escapeAttr(
`<label class="pb-form-option"${optionStyleAttr}><input type="checkbox" name="${escapeAttr(
name,
)}" value="${escapeAttr(opt.value)}" /> ${escapeHtml(opt.label)}</label>`,
);
@@ -463,12 +697,61 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
: name;
const options = Array.isArray(props.options) ? props.options : [];
const textColorRaw =
typeof props.textColorCustom === "string" && props.textColorCustom.trim() !== ""
? props.textColorCustom.trim()
: "";
const labelStyleAttr = textColorRaw ? ` style=\"color:${escapeAttr(textColorRaw)}\"` : "";
const optionStyleParts: string[] = [];
if (textColorRaw) {
optionStyleParts.push(`color:${escapeAttr(textColorRaw)}`);
}
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
const px = props.paddingX;
optionStyleParts.push(`padding-left:${px}px`);
optionStyleParts.push(`padding-right:${px}px`);
}
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
const py = props.paddingY;
optionStyleParts.push(`padding-top:${py}px`);
optionStyleParts.push(`padding-bottom:${py}px`);
}
if (typeof props.fillColorCustom === "string" && props.fillColorCustom.trim() !== "") {
optionStyleParts.push(`background-color:${escapeAttr(props.fillColorCustom.trim())}`);
}
if (typeof props.strokeColorCustom === "string" && props.strokeColorCustom.trim() !== "") {
optionStyleParts.push(`border-color:${escapeAttr(props.strokeColorCustom.trim())}`);
optionStyleParts.push("border-width:1px");
optionStyleParts.push("border-style:solid");
}
const radioRadiusToken = typeof props.borderRadius === "string" ? props.borderRadius : "md";
const radioRadiusPx =
radioRadiusToken === "none"
? 0
: radioRadiusToken === "sm"
? 2
: radioRadiusToken === "lg"
? 6
: radioRadiusToken === "full"
? 9999
: 4;
optionStyleParts.push(`border-radius:${radioRadiusPx}px`);
const optionStyleAttr =
optionStyleParts.length > 0 ? ` style=\"${optionStyleParts.join(";")}\"` : "";
const parts: string[] = [];
parts.push('<div class="pb-form-field">');
parts.push(`<label class="pb-form-label">${escapeHtml(label)}</label>`);
parts.push(`<label class="pb-form-label"${labelStyleAttr}>${escapeHtml(label)}</label>`);
for (const opt of options) {
parts.push(
`<label class="pb-form-option"><input type="radio" name="${escapeAttr(
`<label class="pb-form-option"${optionStyleAttr}><input type="radio" name="${escapeAttr(
name,
)}" value="${escapeAttr(opt.value)}" /> ${escapeHtml(opt.label)}</label>`,
);
@@ -515,8 +798,14 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const pyClass = pyClassMap[pyToken] ?? "pb-section-py-md";
const sectionClasses = ["pb-section", bgClass, pyClass].filter(Boolean).join(" ");
const sectionStyleParts: string[] = [];
// 섹션 커스텀 배경색: backgroundColorCustom 이 설정된 경우에만 인라인 스타일로 적용하여 토큰 기반 배경 클래스를 오버라이드한다.
if (typeof props.backgroundColorCustom === "string" && props.backgroundColorCustom.trim() !== "") {
sectionStyleParts.push(`background-color:${escapeAttr(props.backgroundColorCustom.trim())}`);
}
const sectionStyleAttr = sectionStyleParts.length > 0 ? ` style="${sectionStyleParts.join(";")}"` : "";
bodyParts.push(`<section class="${sectionClasses}"><div class="pb-section-inner"><div class="pb-section-columns">`);
bodyParts.push(`<section class="${sectionClasses}"${sectionStyleAttr}><div class="pb-section-inner"><div class="pb-section-columns">`);
for (const col of columns) {
const columnBlocks = blocks.filter((b) => b.sectionId === section.id && b.columnId === col.id);
bodyParts.push('<div class="pb-section-column">');