v4:코드모듈화_20260123

This commit is contained in:
2026-02-20 11:34:02 +09:00
parent a990081287
commit 17e639ed40
24 changed files with 5412 additions and 1054 deletions

View File

@@ -1074,6 +1074,8 @@
}
</style>
<link rel="stylesheet" href="/static/css/editor.css">
<script src="/static/js/editor.js"></script>
</head>
<body>
<!-- 상단 툴바 -->
@@ -1217,17 +1219,7 @@
<!-- 가운데 뷰어 -->
<div class="main">
<!-- 서식 바 (편집 모드) -->
<div class="format-bar" id="formatBar">
<button class="format-btn" onclick="formatText('bold')"><b>B</b></button>
<button class="format-btn" onclick="formatText('italic')"><i>I</i></button>
<button class="format-btn" onclick="formatText('underline')"><u>U</u></button>
<div class="format-divider"></div>
<button class="format-btn" onclick="formatText('justifyLeft')"></button>
<button class="format-btn" onclick="formatText('justifyCenter')"></button>
<button class="format-btn" onclick="formatText('justifyRight')"></button>
</div>
<div class="viewer" id="viewer">
<div class="a4-wrapper" id="a4Wrapper">
<div class="a4-preview" id="a4Preview">
@@ -1531,7 +1523,8 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
html: html,
doc_type: currentDocType
doc_type: currentDocType,
style_grouping: true
})
});
@@ -1628,7 +1621,8 @@
body: JSON.stringify({
current_html: generatedHTML,
selected_text: selectedText,
request: request
request: request,
doc_type: currentDocType // ← 추가
})
});
@@ -1696,10 +1690,38 @@
if (targetEl.nodeType === 3) {
targetEl = targetEl.parentElement;
}
// 적절한 부모 찾기 (div, section 등)
const parent = targetEl.closest('.lead-box, .section, .info-table, .data-table, .process-flow') || targetEl.closest('div');
if (parent) {
parent.outerHTML = modifiedContent;
if (currentDocType === 'report') {
// ===== 보고서: 안전한 부모만 교체 =====
// sheet, body-content 등 페이지 구조는 절대 건드리지 않음
const dangerousClasses = ['sheet', 'body-content', 'page-header', 'page-footer', 'a4-preview'];
// p, li, td, h1~h6 등 안전한 요소 찾기
const safeParent = targetEl.closest('p, li, td, th, h1, h2, h3, h4, h5, h6, ul, ol, table, figure');
if (safeParent && !dangerousClasses.some(cls => safeParent.classList.contains(cls))) {
// 선택된 요소 뒤에 새 구조 삽입
safeParent.insertAdjacentHTML('afterend', modifiedContent);
// 기존 요소는 유지하거나 숨김 처리
// safeParent.style.display = 'none'; // 선택: 기존 숨기기
safeParent.remove(); // 또는 삭제
console.log('보고서 - 안전한 구조 교체:', safeParent.tagName);
} else {
// 안전한 부모를 못 찾으면 텍스트 앞에 구조 삽입
console.log('보고서 - 안전한 부모 없음, 선택 위치에 삽입');
const range = selectedRange.cloneRange();
range.deleteContents();
const temp = document.createElement('div');
temp.innerHTML = modifiedContent;
range.insertNode(temp.firstElementChild || temp);
}
} else {
// ===== 기획서: 기존 로직 =====
const parent = targetEl.closest('.lead-box, .section, .info-table, .data-table, .process-flow') || targetEl.closest('div');
if (parent) {
parent.outerHTML = modifiedContent;
}
}
}
}
@@ -2196,6 +2218,93 @@
URL.revokeObjectURL(url);
}
// ===== HWP 다운로드 (스타일 그루핑) =====
async function downloadHwpStyled() {
if (!generatedHTML) {
alert('먼저 문서를 생성해주세요.');
return;
}
// 편집된 내용 가져오기
const frame = document.getElementById('previewFrame');
const html = frame.contentDocument ?
'<!DOCTYPE html>' + frame.contentDocument.documentElement.outerHTML :
generatedHTML;
try {
setStatus('HWP 변환 중...', true);
const response = await fetch('/export-hwp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
html: html,
doc_type: currentDocType || 'report',
style_grouping: true // ★ 스타일 그루핑 활성화
})
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.error || 'HWP 변환 실패');
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `report_${new Date().toISOString().slice(0,10)}.hwp`;
a.click();
URL.revokeObjectURL(url);
setStatus('HWP 저장 완료', true);
} catch (error) {
alert('HWP 변환 오류: ' + error.message);
setStatus('오류 발생', false);
}
}
// ===== 스타일 분석 미리보기 (선택사항) =====
async function analyzeStyles() {
if (!generatedHTML) {
alert('먼저 문서를 생성해주세요.');
return;
}
const frame = document.getElementById('previewFrame');
const html = frame.contentDocument ?
'<!DOCTYPE html>' + frame.contentDocument.documentElement.outerHTML :
generatedHTML;
try {
const response = await fetch('/analyze-styles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: html })
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// 결과 표시
let summary = `📊 스타일 분석 결과\n\n${data.total_elements}개 요소\n\n`;
summary += Object.entries(data.summary)
.map(([k, v]) => `${k}: ${v}`)
.join('\n');
alert(summary);
console.log('스타일 분석 상세:', data);
} catch (error) {
alert('분석 오류: ' + error.message);
}
}
function printDoc() {
const frame = document.getElementById('previewFrame');
if (frame.contentWindow) {