feat(m02-df): 재료 조건 찾기 검색어 틀 — 틀 칸 · 자리별 알약 · 지금 잡히는 재료 미리 보기

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
2026-10-02 00:38:35 +09:00
co-authored by Claude Sonnet 5.5
parent 84dd9ce663
commit df2368a48f
4 changed files with 260 additions and 7 deletions
@@ -231,6 +231,16 @@ export function materialElement(
export function checkMaterial(el: CfElementRef, others: CfElementRef[]): string | null {
if (!el.name) return "자리 이름을 적어 주세요";
if (others.some((o) => o.name === el.name)) return "같은 이름의 알약이 이미 있음";
if (el.mode === "rule" && el.rule?.field === "검색어") {
if (!el.rule.template?.trim() || !templateSlots(el.rule.template).length)
return "검색어 틀에 자리 {이름} 이 하나는 있어야 함";
if (
(el.rule.slots ?? []).length < templateSlots(el.rule.template).length ||
(el.rule.slots ?? []).some((x) => !x.from.name)
)
return "틀의 자리마다 변수 알약을 골라 주세요";
return null;
}
if (el.mode === "rule" && !el.rule?.from.name) return "규칙에 쓸 변수 알약을 골라 주세요";
if (
el.mode !== "fixed" &&
@@ -626,3 +636,46 @@ export function checkGroupRule(el: CfElementRef, others: CfElementRef[]): string
if (!el.rule?.from.name) return "규칙에 쓸 변수 알약을 골라 주세요";
return null;
}
/* ── 재료 조건 찾기 field 「검색어」 — 틀(「레미콘 {굵은골재최대치수}-{강도}-{슬럼프}」) 의 자리마다 변수 알약 값을 끼운 글로 찾음 ── */
/** 틀 안 자리 이름들 — 나온 차례 · 겹침 없이 */
export function templateSlots(template: string): string[] {
return [
...new Set([...template.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1].trim()).filter(Boolean)),
];
}
/** 틀을 채운 글 — valueOf 가 알약 값을 줌 · 값이 빈 자리는 missing 에 모음(그 자리는 그대로 둠) */
export function fillTemplate(
template: string,
slots: { name: string; from: CfPort }[],
valueOf: (port: CfPort) => unknown,
): { text: string; missing: string[] } {
const missing: string[] = [];
const text = template.replace(/\{([^{}]+)\}/g, (all, raw: string) => {
const name = raw.trim();
const slot = slots.find((x) => x.name === name);
const v = slot ? valueOf(slot.from) : undefined;
if (v === null || v === undefined || v === "") {
if (!missing.includes(name)) missing.push(name);
return all;
}
return String(v);
});
return { text, missing };
}
/** 검색어 틀 규칙 — 첫 자리의 알약이 from(옛 Model 호환) */
export function templateRule(
template: string,
slots: { name: string; from: CfPort }[],
): CfMaterialRule {
return {
field: "검색어",
op: "포함",
from: slots[0]?.from ?? { box: "", name: "" },
template,
slots,
};
}