// 원본 찾기(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)", `
${cost ? `` : ""}
${cost ? ' ' : ""}

`, ); 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 }; })();