feat(804-6): 옛 수식 탭 걷음 · 줄 쌓기 = 첫 「수식」 탭(요구 686-1)

- M01 단가산출 기초 · M02 단가산출 상세 보기 = 수식(줄 쌓기 · 기본) · 단가산출 상세 둘
- 줄 쌓기에 M02 사본 [단가 갱신] · [재생성](미리 셈 → 알림 → 저장 · 고침 있으면 막음) · 판 바뀜(409) 문서 그대로면 새 판으로 다시 저장 옮김
- 옛 수식 문서 화면 걷음(formula_doc_screen · defs · edit · lines · math · math_model · rows · sub · formula_doc.css) · 이미 안 부르던 796-2 수식 나무 화면(LogicLab_Equation · Defs · Var · css · M01_Logic_Equation_Model · sheet_formula_cell · ui_template_equation_defs · edit) 걷음 — 18 파일 4,860 줄
- formula_doc_render = 줄 쌓기가 쓰는 reach 만 · 옵션 가로 CSS 는 line_stack.css 로
- 남김 = 계산기 common_util_formula_doc* · 서버 길 · 옵션 · 사본 표지 · 합계 상자 · KaTeX · 시험 페이지 tools/line-stack-test
- 시험: test_796_2 지움(걷은 화면 원본 검사뿐) · 799_1 · 800_2 걷은 화면 줄만 뺌 · 799_2 · 802_4b2 · 804_2 보기 · 화면 파일 대상 바꿈 · test_804_6 새로(걷은 파일 · 부르는 곳 없음 · 첫 탭 · 사본 할일)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-08 11:09:44 +09:00
co-authored by Claude Opus 5.5
parent 04caf618bb
commit 7480aebc96
32 changed files with 169 additions and 5199 deletions
@@ -1,123 +0,0 @@
/* =============================================================================
* M01_MasterData/logic/M01_Logic_Equation_Model.ts
* 수식 탭 계산(796-2 · 화면 · fetch 없음) — 줄 → 고침 자리 · 식 칸 아이콘 끼움 · 옵션 값 꼴 · 변수 고침 요청.
* 서버 고침 꼴 = `master_equation_edit.py` 머리(할일 식 · 더함 · 지움 · 변수 · 표시) — 여기서는 요청 목록만 만듦
* 셈 · 검사는 서버 한 벌(③ 서버 단독) — 브라우저는 모양만
* ========================================================================== */
import type { EqLine } from "@ui/ui_template_equation";
export interface EqEdit {
할일: "식" | "더함" | "지움" | "변수" | "표시" | "연결";
[key: string]: unknown;
}
/** 식 줄의 고침 자리(`중간:<열쇠>` · `결과` · `호표:<n>` · `덧줄:<n>`) */
export const spotOf = (line: EqLine): string =>
line.갈래 === "호표" || line.갈래 === "덧줄"
? `${line.갈래}:${line.차례 ?? 0}`
: line.갈래 === "결과"
? "결과"
: `중간:${line.열쇠}`;
export const formulaEdit = (line: EqLine, text: string): EqEdit => ({
할일: "식",
자리: spotOf(line),
식: text,
});
/** 아이콘 줄 — 식 칸 글에 끼우는 꼴 */
export type InsertKind = "frac" | "paren" | "sub" | "sup" | "percent";
/** 선택 영역 `[from, to)` 를 아이콘 꼴로 바꾼 글 · 커서 자리 */
export function insertInto(
kind: InsertKind,
text: string,
from: number,
to: number,
): { text: string; caret: number } {
const head = text.slice(0, from);
const pick = text.slice(from, to);
const tail = text.slice(to);
const put = (mid: string, caret: number): { text: string; caret: number } => ({
text: head + mid + tail,
caret: head.length + caret,
});
switch (kind) {
case "frac": // 분수 = (위) / (아래) — 고른 글이 위 · 커서는 아래 괄호 안
return pick ? put(`(${pick}) / ()`, pick.length + 6) : put("() / ()", 1);
case "paren":
return pick ? put(`(${pick})`, pick.length + 2) : put("()", 1);
case "sub":
return put(`${pick}_`, pick.length + 1);
case "sup":
return put(`${pick}^`, pick.length + 1);
case "percent":
return put(`${pick} / 100`, pick.length + 6);
}
}
/** 옵션 값 — 수 꼴 글은 수로(엔진 입력 꼴) · 빈 값은 안 보냄(자동값) */
export function inputsOf(values: Record<string, string>): Record<string, number | string> {
const out: Record<string, number | string> = {};
for (const [name, raw] of Object.entries(values)) {
if (raw === "") continue;
out[name] = /^-?\d+(\.\d+)?$/.test(raw) ? Number(raw) : raw;
}
return out;
}
export interface VarForm {
열쇠: string;
이름: string;
기호: string;
위: string;
아래: string;
단위: string;
갈래?: "입력" | "중간";
설명?: string;
기본값?: string;
식?: string;
/** 보이는 자리 글(중간 줄 · 빈 글 = 전체) */
표시?: string;
}
/** 변수 고침 창 → 요청 목록(바꾼 칸만) — `보이는 자리`(중간 줄) = 0~6 · 빈 칸 = 전체 자리(`표시` 지움) */
export function varEdits(before: VarForm, now: VarForm): EqEdit[] {
const out: EqEdit = { 할일: "변수", 열쇠: before.열쇠 };
let changed = false;
for (const k of ["이름", "기호", "위", "아래", "단위"] as const)
if (now[k] !== before[k]) {
out[k] = now[k];
changed = true;
}
const list: EqEdit[] = changed ? [out] : [];
if (now.표시 !== before.표시) {
const digits = now.표시 === "" || now.표시 === undefined ? null : Number(now.표시);
list.push({ 할일: "표시", 열쇠: before.열쇠, 표시: digits });
}
return list;
}
/** 변수 더함 창 → 요청 — 빈 칸은 안 보냄 */
export function addEdit(form: VarForm, after?: string): EqEdit {
const out: EqEdit = { 할일: "더함", 갈래: form.갈래 ?? "입력", 열쇠: form.열쇠.trim() };
for (const k of ["이름", "기호", "위", "아래", "단위", "설명"] as const) {
const v = (form[k] ?? "").trim();
if (v) out[k] = v;
}
if (form.갈래 === "중간") {
out.식 = form.식 || "0";
if (after !== undefined) out.뒤 = after;
} else if (form.기본값) out.기본값 = form.기본값;
return out;
}
/** 서버 까닭 「… · 모르는 기호 「A」 「B」 — …」 → 새 기호 글 목록(`master_equation_edit._set_formula` 가 같은 글을 냄) */
export function unknownOf(reasons: string[]): string[] {
for (const why of reasons) {
const hit = /모르는 기호((?:\s*「[^」]+」)+)/.exec(why);
if (hit?.[1]) return [...hit[1].matchAll(/「([^」]+)」/g)].map((m) => m[1] ?? "");
}
return [];
}