- 서버 원문 조각에서 역슬래시 · 인용 머리 · 글 안 주석 벗김(원본 자료 손 안 댐) - 출처 항 · 주 번호(3-4-2 1 · 9-7-1 주⑥)를 source() 답에 실음 — 그 조각 hit 표시 - 패널이 그 항을 옅은 바탕 + 막대로 강조 · 보이게 스크롤 · 머리에 「1항」 · 「주⑥」 - 시험 3 추가 · 화면 시나리오 50-2 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
/* =============================================================================
|
|
* M01_MasterData_UI_LogicLab_Source.ts
|
|
* 원문 보기(품셈 본문 절) — 출처 글로 서버가 찾음(`GET /m01/logic/source`) · 늦게 온 답은 버림
|
|
* M01 로직 상세 「원문」 패널 · M02 일위대가 상세 「원문」 패널(47-4)이 같이 씀
|
|
* ========================================================================== */
|
|
|
|
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
|
import { el } from "@ui/ui_template_elements";
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import "../M02_MasterTemplete/DataFlow/M02_DF_Panel.css";
|
|
import "./M01_MasterData_UI_LogicLab_Side.css";
|
|
|
|
const TEXT = {
|
|
Loading: ["원문 찾는 중", "Looking up the source"],
|
|
None: ["원문 연결 없음", "No linked source"],
|
|
From: ["출처", "Source"],
|
|
NoSource: ["출처 없음", "No source"],
|
|
Near: ["{v} 절이 없어 위 절", "No {v} section — showing the parent"],
|
|
} as const;
|
|
|
|
const ts = (key: keyof typeof TEXT, v = ""): string =>
|
|
TEXT[key][currentLanguageIndex === 1 ? 1 : 0].replace("{v}", v);
|
|
|
|
interface Block {
|
|
h?: number;
|
|
text?: string;
|
|
p?: string;
|
|
table?: string[][];
|
|
/** 출처가 가리킨 항 · 주 — 강조 */
|
|
hit?: boolean;
|
|
}
|
|
interface Found {
|
|
number: string;
|
|
matched: string;
|
|
file: string;
|
|
blocks: Block[];
|
|
item?: string;
|
|
}
|
|
|
|
function blockNode(b: Block): HTMLElement {
|
|
const node = rawNode(b);
|
|
if (b.hit) node.classList.add("m01lab-src__hit");
|
|
return node;
|
|
}
|
|
|
|
function rawNode(b: Block): HTMLElement {
|
|
if (b.table)
|
|
return el("div", {
|
|
className: "m01-logic__scroll",
|
|
children: [
|
|
el("table", {
|
|
className: "m01-logic__grid m01lab-src__table",
|
|
children: [
|
|
el("tbody", {
|
|
children: b.table.map((r) =>
|
|
el("tr", { children: r.map((c) => el("td", { text: c })) }),
|
|
),
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
if (b.h) return el("h4", { className: "m01lab-src__head", text: b.text ?? "" });
|
|
return el("p", { className: "m01lab-src__p", text: b.p ?? "" });
|
|
}
|
|
|
|
export interface SourceView {
|
|
root: HTMLElement;
|
|
/** 그 출처 글의 원문을 그림 — 지금 보이는 것과 같으면 안 부름 */
|
|
show(src: string): void;
|
|
}
|
|
|
|
export function createSourceView(): SourceView {
|
|
const body = el("div", { className: "m01lab-src__body" });
|
|
const title = el("strong", { className: "m02-dfp__title" });
|
|
let shown: string | null = null;
|
|
|
|
const show = (src: string): void => {
|
|
if (shown === src) return;
|
|
shown = src;
|
|
title.textContent = src || ts("NoSource");
|
|
body.replaceChildren(el("p", { className: "m01-logic__muted", text: ts("Loading") }));
|
|
const none = (): void =>
|
|
body.replaceChildren(
|
|
el("p", { className: "m01lab-src__none", text: ts("None") }),
|
|
el("p", { className: "m01-logic__muted", text: `${ts("From")} · ${src || "—"}` }),
|
|
);
|
|
if (!src.trim()) return none();
|
|
void fetch(`${API_BASE_URL}/m01/logic/source?${new URLSearchParams({ src })}`, {
|
|
credentials: "include",
|
|
})
|
|
.then((r) => (r.ok ? (r.json() as Promise<{ found: Found[] }>) : { found: [] }))
|
|
.catch(() => ({ found: [] as Found[] }))
|
|
.then(({ found }) => {
|
|
if (shown !== src) return;
|
|
if (!found.length) return none();
|
|
body.replaceChildren(
|
|
...found.flatMap((f) => [
|
|
el("p", {
|
|
className: "m01-logic__muted m01lab-src__file",
|
|
text: [
|
|
f.file,
|
|
f.matched === f.number ? "" : ts("Near", f.number),
|
|
f.item ? (/^\d+$/.test(f.item) ? `${f.item}항` : f.item) : "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" · "),
|
|
}),
|
|
...f.blocks.map(blockNode),
|
|
]),
|
|
);
|
|
requestAnimationFrame(() =>
|
|
body.querySelector(".m01lab-src__hit")?.scrollIntoView({ block: "center" }),
|
|
);
|
|
});
|
|
};
|
|
|
|
return { root: el("div", { className: "m01lab-src", children: [title, body] }), show };
|
|
}
|