1차 싱크 완료
This commit is contained in:
@@ -123,6 +123,11 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
textStyle.color = props.colorCustom;
|
||||
}
|
||||
|
||||
// 블록 배경색: backgroundColorCustom 이 설정된 경우에만 적용한다 (기본은 투명/미지정).
|
||||
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
|
||||
textStyle.backgroundColor = props.backgroundColorCustom.trim();
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
key={block.id}
|
||||
@@ -314,6 +319,41 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// 텍스트 색상: textColorCustom 이 설정되어 있으면 해당 색상을 사용하고, 없으면 기본 밝은 텍스트 색상을 사용한다.
|
||||
if (props.textColorCustom && props.textColorCustom.trim() !== "") {
|
||||
const colorValue = props.textColorCustom.trim();
|
||||
wrapperStyle.color = colorValue;
|
||||
selectStyle.color = colorValue;
|
||||
} else {
|
||||
wrapperStyle.color = "#f9fafb";
|
||||
selectStyle.color = "#f9fafb";
|
||||
}
|
||||
|
||||
// 배경/테두리 색상: fillColorCustom/strokeColorCustom 이 있으면 select 에 적용한다.
|
||||
if (props.fillColorCustom && props.fillColorCustom.trim() !== "") {
|
||||
const bgValue = props.fillColorCustom.trim();
|
||||
selectStyle.backgroundColor = bgValue;
|
||||
}
|
||||
|
||||
if (props.strokeColorCustom && props.strokeColorCustom.trim() !== "") {
|
||||
const strokeValue = props.strokeColorCustom.trim();
|
||||
selectStyle.borderColor = strokeValue;
|
||||
}
|
||||
|
||||
// 모서리 둥글기: borderRadius 토큰을 px 로 변환한다.
|
||||
const selectRadiusToken = props.borderRadius ?? "md";
|
||||
const selectRadiusPx =
|
||||
selectRadiusToken === "none"
|
||||
? 0
|
||||
: selectRadiusToken === "sm"
|
||||
? 2
|
||||
: selectRadiusToken === "lg"
|
||||
? 6
|
||||
: selectRadiusToken === "full"
|
||||
? 9999
|
||||
: 4;
|
||||
selectStyle.borderRadius = `${selectRadiusPx}px`;
|
||||
|
||||
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
|
||||
const paddingEm = pxToEm(props.paddingX);
|
||||
selectStyle.paddingInline = paddingEm;
|
||||
@@ -353,6 +393,7 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
const widthMode = props.widthMode ?? (props.fullWidth ? "full" : "auto");
|
||||
const widthClass = widthMode === "full" ? "w-full" : "";
|
||||
const groupStyle: CSSProperties = {};
|
||||
const optionContainerStyle: CSSProperties = {};
|
||||
const optionTextStyle: CSSProperties = {};
|
||||
const groupTextStyle: CSSProperties = {};
|
||||
const textSizeClass = props.fontSizeCustom && props.fontSizeCustom.trim() !== "" ? "" : "text-xs";
|
||||
@@ -404,14 +445,51 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// 텍스트 색상: textColorCustom 이 설정되어 있으면 그룹/옵션 텍스트 모두에 동일하게 적용한다.
|
||||
if (props.textColorCustom && props.textColorCustom.trim() !== "") {
|
||||
const colorValue = props.textColorCustom.trim();
|
||||
groupTextStyle.color = colorValue;
|
||||
optionTextStyle.color = colorValue;
|
||||
} else {
|
||||
groupTextStyle.color = "#e5e7eb";
|
||||
optionTextStyle.color = "#e5e7eb";
|
||||
}
|
||||
|
||||
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
|
||||
optionTextStyle.paddingInline = pxToEm(props.paddingX);
|
||||
optionContainerStyle.paddingInline = pxToEm(props.paddingX);
|
||||
}
|
||||
|
||||
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
|
||||
optionTextStyle.paddingBlock = pxToEm(props.paddingY);
|
||||
optionContainerStyle.paddingBlock = pxToEm(props.paddingY);
|
||||
}
|
||||
|
||||
// 옵션 컨테이너 배경/테두리 색상
|
||||
if (props.fillColorCustom && props.fillColorCustom.trim() !== "") {
|
||||
const bgValue = props.fillColorCustom.trim();
|
||||
optionContainerStyle.backgroundColor = bgValue;
|
||||
}
|
||||
|
||||
if (props.strokeColorCustom && props.strokeColorCustom.trim() !== "") {
|
||||
const strokeValue = props.strokeColorCustom.trim();
|
||||
optionContainerStyle.borderColor = strokeValue;
|
||||
optionContainerStyle.borderWidth = "1px";
|
||||
optionContainerStyle.borderStyle = "solid";
|
||||
}
|
||||
|
||||
// 옵션 컨테이너 모서리 둥글기
|
||||
const checkboxRadiusToken = props.borderRadius ?? "md";
|
||||
const checkboxRadiusPx =
|
||||
checkboxRadiusToken === "none"
|
||||
? 0
|
||||
: checkboxRadiusToken === "sm"
|
||||
? 2
|
||||
: checkboxRadiusToken === "lg"
|
||||
? 6
|
||||
: checkboxRadiusToken === "full"
|
||||
? 9999
|
||||
: 4;
|
||||
optionContainerStyle.borderRadius = `${checkboxRadiusPx}px`;
|
||||
|
||||
const optionsStyle: CSSProperties = {};
|
||||
if (typeof props.optionGapPx === "number" && props.optionGapPx >= 0) {
|
||||
optionsStyle.rowGap = pxToEm(props.optionGapPx);
|
||||
@@ -439,7 +517,11 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
)}
|
||||
<div className="flex flex-col" data-testid="preview-form-checkbox-options" style={optionsStyle}>
|
||||
{props.options.map((opt) => (
|
||||
<label key={opt.value} className="inline-flex items-center gap-1">
|
||||
<label
|
||||
key={opt.value}
|
||||
className="inline-flex items-center gap-1"
|
||||
style={optionContainerStyle}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 rounded border-slate-700 bg-slate-900 text-sky-500"
|
||||
@@ -469,6 +551,7 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
const widthMode = props.widthMode ?? (props.fullWidth ? "full" : "auto");
|
||||
const widthClass = widthMode === "full" ? "w-full" : "";
|
||||
const groupStyle: CSSProperties = {};
|
||||
const optionContainerStyle: CSSProperties = {};
|
||||
const optionTextStyle: CSSProperties = {};
|
||||
const groupTextStyle: CSSProperties = {};
|
||||
const textSizeClass = props.fontSizeCustom && props.fontSizeCustom.trim() !== "" ? "" : "text-xs";
|
||||
@@ -520,14 +603,51 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// 텍스트 색상: textColorCustom 이 설정되어 있으면 그룹/옵션 텍스트 모두에 동일하게 적용한다.
|
||||
if (props.textColorCustom && props.textColorCustom.trim() !== "") {
|
||||
const colorValue = props.textColorCustom.trim();
|
||||
groupTextStyle.color = colorValue;
|
||||
optionTextStyle.color = colorValue;
|
||||
} else {
|
||||
groupTextStyle.color = "#e5e7eb";
|
||||
optionTextStyle.color = "#e5e7eb";
|
||||
}
|
||||
|
||||
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
|
||||
optionTextStyle.paddingInline = pxToEm(props.paddingX);
|
||||
optionContainerStyle.paddingInline = pxToEm(props.paddingX);
|
||||
}
|
||||
|
||||
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
|
||||
optionTextStyle.paddingBlock = pxToEm(props.paddingY);
|
||||
optionContainerStyle.paddingBlock = pxToEm(props.paddingY);
|
||||
}
|
||||
|
||||
// 옵션 컨테이너 배경/테두리 색상
|
||||
if (props.fillColorCustom && props.fillColorCustom.trim() !== "") {
|
||||
const bgValue = props.fillColorCustom.trim();
|
||||
optionContainerStyle.backgroundColor = bgValue;
|
||||
}
|
||||
|
||||
if (props.strokeColorCustom && props.strokeColorCustom.trim() !== "") {
|
||||
const strokeValue = props.strokeColorCustom.trim();
|
||||
optionContainerStyle.borderColor = strokeValue;
|
||||
optionContainerStyle.borderWidth = "1px";
|
||||
optionContainerStyle.borderStyle = "solid";
|
||||
}
|
||||
|
||||
// 옵션 컨테이너 모서리 둥글기
|
||||
const radioRadiusToken = props.borderRadius ?? "md";
|
||||
const radioRadiusPx =
|
||||
radioRadiusToken === "none"
|
||||
? 0
|
||||
: radioRadiusToken === "sm"
|
||||
? 2
|
||||
: radioRadiusToken === "lg"
|
||||
? 6
|
||||
: radioRadiusToken === "full"
|
||||
? 9999
|
||||
: 4;
|
||||
optionContainerStyle.borderRadius = `${radioRadiusPx}px`;
|
||||
|
||||
const optionsStyle: CSSProperties = {};
|
||||
if (typeof props.optionGapPx === "number" && props.optionGapPx >= 0) {
|
||||
optionsStyle.rowGap = pxToEm(props.optionGapPx);
|
||||
@@ -555,7 +675,11 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
)}
|
||||
<div className="flex flex-col" data-testid="preview-form-radio-options" style={optionsStyle}>
|
||||
{props.options.map((opt) => (
|
||||
<label key={opt.value} className="inline-flex items-center gap-1">
|
||||
<label
|
||||
key={opt.value}
|
||||
className="inline-flex items-center gap-1"
|
||||
style={optionContainerStyle}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
className="h-4 w-4 border-slate-700 bg-slate-900 text-sky-500"
|
||||
@@ -753,6 +877,11 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
listStyle.color = props.textColorCustom;
|
||||
}
|
||||
|
||||
// 리스트 배경색: backgroundColorCustom 이 설정된 경우에만 적용한다.
|
||||
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
|
||||
listStyle.backgroundColor = props.backgroundColorCustom.trim();
|
||||
}
|
||||
|
||||
const bulletStyle = bulletStyleRaw;
|
||||
|
||||
const itemsTree: ListItemNode[] =
|
||||
@@ -922,6 +1051,11 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
formStyle.marginBottom = marginEm;
|
||||
}
|
||||
|
||||
// 폼 배경색: backgroundColorCustom 이 설정된 경우에만 적용한다.
|
||||
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
|
||||
formStyle.backgroundColor = props.backgroundColorCustom.trim();
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -1097,6 +1231,7 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
props.align === "left" ? "justify-start" : props.align === "right" ? "justify-end" : "justify-center";
|
||||
|
||||
const widthMode = props.widthMode ?? "auto";
|
||||
const wrapperStyle: CSSProperties = {};
|
||||
const imageStyle: CSSProperties = {
|
||||
display: "block",
|
||||
maxWidth: "100%",
|
||||
@@ -1124,8 +1259,12 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
: fallbackRadiusPx;
|
||||
imageStyle.borderRadius = radiusPx === 9999 ? "9999px" : pxToEm(radiusPx);
|
||||
|
||||
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
|
||||
wrapperStyle.backgroundColor = props.backgroundColorCustom;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={block.id} className={`w-full flex ${alignClass}`}>
|
||||
<div key={block.id} className={`w-full flex ${alignClass}`} style={wrapperStyle}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={props.src || ""} alt={props.alt} className="object-contain" style={imageStyle} />
|
||||
</div>
|
||||
@@ -1200,10 +1339,10 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col bg-slate-950 text-slate-50">
|
||||
<div className="flex-1 flex flex-col text-slate-50">
|
||||
{/* 루트 텍스트/버튼/이미지 블록들이 있다면 페이지 상단에 노출 */}
|
||||
{rootBlocks.length > 0 && (
|
||||
<section className="bg-slate-950 py-12">
|
||||
<section className="py-12">
|
||||
<div className="mx-auto max-w-3xl px-4 flex flex-col gap-4">
|
||||
{rootBlocks.map((b) => (
|
||||
<div key={b.id}>{renderBlock(b)}</div>
|
||||
|
||||
@@ -72,6 +72,9 @@ export interface TextBlockProps {
|
||||
// 커스텀 색상 값 (예: "#ff0000", "rgb(…)" 등)
|
||||
colorCustom?: string;
|
||||
|
||||
// 블록 배경색 커스텀 값 (예: "#123456")
|
||||
backgroundColorCustom?: string;
|
||||
|
||||
// 최대 너비 모드: 프리셋 또는 커스텀 값
|
||||
maxWidthMode?: "scale" | "custom";
|
||||
// 최대 너비 스케일
|
||||
@@ -127,6 +130,8 @@ export interface ImageBlockProps {
|
||||
borderRadius?: "none" | "sm" | "md" | "lg" | "full";
|
||||
// 모서리 둥글기 px 값 (이미지에 한해서는 연속적인 px 단위 조정을 지원한다)
|
||||
borderRadiusPx?: number;
|
||||
// 이미지 카드를 감싸는 배경색 커스텀 값 (예: "#123456")
|
||||
backgroundColorCustom?: string;
|
||||
// 이미지 소스 타입: 업로드된 에셋 또는 외부 URL
|
||||
sourceType?: "asset" | "externalUrl";
|
||||
// 업로드된 에셋인 경우 Asset.id 를 저장해둔다.
|
||||
@@ -450,6 +455,8 @@ export interface ListBlockProps {
|
||||
fontSizeCustom?: string;
|
||||
lineHeightCustom?: string;
|
||||
textColorCustom?: string;
|
||||
// 리스트 전체를 감싸는 배경색 커스텀 값
|
||||
backgroundColorCustom?: string;
|
||||
// 불릿/간격
|
||||
bulletStyle?:
|
||||
| "disc"
|
||||
@@ -619,6 +626,8 @@ export interface FormBlockProps {
|
||||
formWidthMode?: "auto" | "full" | "fixed";
|
||||
formWidthPx?: number;
|
||||
marginYPx?: number;
|
||||
// 폼 전체를 감싸는 배경색 커스텀 값
|
||||
backgroundColorCustom?: string;
|
||||
}
|
||||
|
||||
export type CanvasPreset = "mobile" | "tablet" | "desktop" | "full" | "custom";
|
||||
@@ -804,14 +813,32 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// Hero 템플릿 섹션 추가: 섹션 1개와 기본 텍스트/버튼 블록들을 첫 컬럼에 배치한다
|
||||
addHeroTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createHeroTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -827,14 +854,32 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// Features 템플릿 섹션 추가: 3컬럼(4/4/4) 섹션과 각 컬럼의 제목/설명 텍스트를 생성한다
|
||||
addFeaturesTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createFeaturesTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -845,14 +890,32 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// Blog 템플릿 섹션 추가: 3컬럼 포스트 카드(제목/요약)를 생성한다
|
||||
addBlogTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createBlogTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -863,14 +926,32 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// Team 템플릿 섹션 추가: 3컬럼 팀 카드(이름/역할/소개)를 생성한다
|
||||
addTeamTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createTeamTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -881,14 +962,32 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// Footer 템플릿 섹션 추가: 링크/카피라이트 텍스트가 포함된 1컬럼 섹션을 생성한다
|
||||
addFooterTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createFooterTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -899,14 +998,32 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// CTA 템플릿 섹션 추가: 텍스트와 버튼이 포함된 1컬럼 섹션을 생성한다
|
||||
addCtaTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createCtaTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -917,7 +1034,17 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// FAQ 템플릿 섹션 추가: 질문/답변 쌍이 수직으로 나열된 섹션을 생성한다
|
||||
addFaqTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createFaqTemplateBlocks({
|
||||
sectionId,
|
||||
@@ -925,7 +1052,14 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -936,7 +1070,17 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// Pricing 템플릿 섹션 추가: 3컬럼 요금제 카드(플랜 이름/가격/설명)를 생성한다
|
||||
addPricingTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createPricingTemplateBlocks({
|
||||
sectionId,
|
||||
@@ -944,7 +1088,14 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
@@ -955,7 +1106,17 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
|
||||
// Testimonials 템플릿 섹션 추가: 3컬럼 후기 카드(본문/작성자)를 생성한다
|
||||
addTestimonialsTemplateSection: () => {
|
||||
const sectionId = createId();
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
|
||||
if (selectedBlockId) {
|
||||
const target = blocks.find((b: Block) => b.id === selectedBlockId && b.type === "section");
|
||||
if (target) {
|
||||
sectionId = target.id;
|
||||
replacingSectionId = target.id;
|
||||
}
|
||||
}
|
||||
|
||||
const { blocks: templateBlocks, lastSelectedId } = createTestimonialsTemplateBlocks({
|
||||
sectionId,
|
||||
@@ -963,7 +1124,14 @@ const createEditorState = (set: any, get: any): EditorState => ({
|
||||
});
|
||||
|
||||
set((state: EditorState) => {
|
||||
const newBlocks = [...state.blocks, ...templateBlocks];
|
||||
let baseBlocks = state.blocks as Block[];
|
||||
if (replacingSectionId) {
|
||||
baseBlocks = baseBlocks.filter(
|
||||
(b) => b.id !== replacingSectionId && (b as any).sectionId !== replacingSectionId,
|
||||
);
|
||||
}
|
||||
|
||||
const newBlocks = [...baseBlocks, ...templateBlocks];
|
||||
|
||||
return {
|
||||
blocks: newBlocks,
|
||||
|
||||
Reference in New Issue
Block a user