Files
Aislo/tools/est-test/native-find.js
eomsangdonandClaude Opus 5.5 982ba07fea feat(est-test): 찾기 창 다음찾기와 한 건 바꾸기
- 원본 찾기 창처럼 처음부터/현재위치부터 다음찾기로 행 이동, 호표 자동 전환
- 단가산출 찾은 행 한 건 바꾸기 뒤 다음 위치로 이동
- 편집 메뉴 찾기와 Ctrl+F 로 열기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 14:56:47 +09:00

122 lines
5.0 KiB
JavaScript

// 원본 찾기(TFRMESTSERCH: 검색어·처음부터/현재위치부터·다음찾기)와
// 산출근거 바꾸기(TFRMCOSTWORDCHANGE: 다음찾기·바꾸기)를 옮긴 찾기 창. 모두 바꾸기는 기존 `산출근거 문구 바꾸기`.
const EstFind = (() => {
const grouped = ["HeadMainDat", "CostMainDat", "MachMainDat"];
const fold = (value) =>
String(value ?? "")
.normalize("NFKC")
.toLowerCase();
function text(row) {
return Object.entries(row)
.filter(([key, value]) => typeof value === "string" && !key.endsWith("_"))
.map(([key, value]) => (key === "LineData" ? EstText.clean(value) : value))
.join("\n");
}
function next(rows, query, from = 0) {
const q = fold(query).trim();
if (!q) throw Error("검색어를 입력하세요.");
for (let index = Math.max(0, from); index < rows.length; index++)
if (fold(text(rows[index])).includes(q)) return index;
return -1;
}
function groupOf(rows, index) {
for (const [code, group] of EstNative.groups(rows))
if (index >= group.start && index < group.start + group.rows.length)
return { code, position: index - group.start };
return { code: "", position: index };
}
function open(state) {
const rows = state.project.tables[state.view];
if (!Array.isArray(rows)) throw Error("표 화면에서 찾기를 사용하세요.");
state.find ||= { query: "", replacement: "", from: "current" };
const { esc } = EstApp,
cost = state.view === "CostMainDat";
EstApp.showDialog(
"찾기(Ctrl+F)",
`<div class="form-fields"><label>검색어<input name="find-query" value="${esc(state.find.query)}"></label>${cost ? `<label>바꿀 내용<input name="find-replacement" value="${esc(state.find.replacement)}"></label>` : ""}<label>시작<select name="find-from"><option value="current" ${state.find.from === "current" ? "selected" : ""}>현재위치부터 검색</option><option value="first" ${state.find.from === "first" ? "selected" : ""}>처음부터 검색</option></select></label></div><button data-action="find-next">다음찾기</button>${cost ? ' <button data-action="find-replace">바꾸기</button>' : ""}<p id="find-result" role="status"></p>`,
);
document.querySelector('#dialog-content [name="find-query"]').focus();
}
function read(state) {
const value = (name) => document.querySelector(`#dialog-content [name="${name}"]`)?.value;
state.find.query = value("find-query") ?? "";
state.find.replacement = value("find-replacement") ?? state.find.replacement;
state.find.from = value("find-from") || "current";
}
function show(state, index) {
const rows = state.project.tables[state.view],
{ code, position } = grouped.includes(state.view)
? groupOf(rows, index)
: { code: state.group, position: index };
state.group = code;
state.query = "";
EstApp.byId("search").value = "";
state.page = Math.floor(position / 100);
state.selection = new Set([index]);
EstApp.render();
}
function findNext(state) {
read(state);
const rows = state.project.tables[state.view],
start =
state.find.from === "first"
? 0
: Math.max(-1, ...[...state.selection].map(Number).filter(Number.isFinite)) + 1;
const index = next(rows, state.find.query, start);
const result = EstApp.byId("find-result");
if (index < 0) {
result.textContent = "끝까지 찾았습니다. 더 찾을 항목이 없습니다.";
return;
}
state.find.from = "current";
document.querySelector('#dialog-content [name="find-from"]').value = "current";
show(state, index);
result.textContent = `${index + 1}번째 행을 찾았습니다.`;
}
function replaceCurrent(state) {
read(state);
if (state.view !== "CostMainDat") throw Error("단가산출 화면에서 바꾸기를 사용하세요.");
const rows = state.project.tables.CostMainDat,
index = [...state.selection][0];
if (state.selection.size !== 1 || !rows[index]) throw Error("먼저 다음찾기로 행을 찾으세요.");
const updates = EstText.replaceWords(
[{ row: rows[index], index }],
state.find.query,
state.find.replacement,
);
if (!updates.length) throw Error("이 행에는 바꿀 문구가 없습니다. 다음찾기를 누르세요.");
EstApp.change((project) => (project.tables.CostMainDat[index].LineData = updates[0].value));
findNext(state);
}
function handle(name, state) {
if (name === "find") open(state);
else if (name === "find-next") findNext(state);
else if (name === "find-replace") replaceCurrent(state);
else return false;
return true;
}
if (typeof document !== "undefined" && document.addEventListener)
document.addEventListener("keydown", (event) => {
if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "f") return;
event.preventDefault();
try {
open(EstApp.state);
} catch (error) {
EstApp.state.notice = error.message;
EstApp.render();
}
});
return { text, next, groupOf, handle };
})();