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

@@ -11,6 +11,7 @@ let redoStack = [];
const MAX_HISTORY = 50;
let isApplyingFormat = false;
// ===== 편집 바 HTML 생성 =====
// ===== 편집 바 HTML 생성 =====
function createFormatBar() {
const formatBarHTML = `
@@ -21,52 +22,120 @@ function createFormatBar() {
<option value="나눔고딕">나눔고딕</option>
<option value="돋움">돋움</option>
</select>
<button class="format-btn" onclick="loadLocalFonts()">📁<span class="tooltip">폰트 불러오기</span></button>
<input type="number" class="format-select" id="fontSizeInput" value="12" min="8" max="72"
style="width:55px;" onchange="applyFontSizeInput(this.value)">
<div class="format-divider"></div>
<button class="format-btn" id="btnBold" onclick="formatText('bold')"><b>B</b><span class="tooltip">굵게 (Ctrl+B)</span></button>
<button class="format-btn" id="btnItalic" onclick="formatText('italic')"><i>I</i><span class="tooltip">기울임 (Ctrl+I)</span></button>
<button class="format-btn" id="btnUnderline" onclick="formatText('underline')"><u>U</u><span class="tooltip">밑줄 (Ctrl+U)</span></button>
<button class="format-btn" id="btnStrike" onclick="formatText('strikeThrough')"><s>S</s><span class="tooltip">취소선</span></button>
<button class="format-btn" onclick="formatText('bold')"><b>B</b><span class="tooltip">굵게</span></button>
<button class="format-btn" onclick="formatText('italic')"><i>I</i><span class="tooltip">기울임</span></button>
<button class="format-btn" onclick="formatText('underline')"><u>U</u><span class="tooltip">밑줄</span></button>
<button class="format-btn" onclick="formatText('strikeThrough')"><s>S</s><span class="tooltip">취소선</span></button>
<div class="format-divider"></div>
<button class="format-btn" onclick="adjustLetterSpacing(-0.5)">A⇠<span class="tooltip">자간 줄이기</span></button>
<button class="format-btn" onclick="adjustLetterSpacing(0.5)">A⇢<span class="tooltip">자간 늘리기</span></button>
<select class="format-select" onchange="if(this.value) formatText(this.value); this.selectedIndex=0;">
<option value="">정렬 ▾</option>
<option value="justifyLeft">⫷ 왼쪽</option>
<option value="justifyCenter">☰ 가운데</option>
<option value="justifyRight">⫸ 오른쪽</option>
</select>
<select class="format-select" onchange="if(this.value) adjustLetterSpacing(parseFloat(this.value)); this.selectedIndex=0;">
<option value="">자간 ▾</option>
<option value="-0.5">좁게</option>
<option value="-1">더 좁게</option>
<option value="0.5">넓게</option>
<option value="1">더 넓게</option>
</select>
<div class="format-divider"></div>
<div class="color-picker-btn format-btn">
<span style="border-bottom:3px solid #000;">A</span>
<input type="color" id="textColor" value="#000000" onchange="applyTextColor(this.value)">
<span class="tooltip">글자 색상</span>
</div>
<div class="color-picker-btn format-btn">
<span style="background:#ff0;padding:0 4px;">A</span>
<input type="color" id="bgColor" value="#ffff00" onchange="applyBgColor(this.value)">
<span class="tooltip">배경 색상</span>
</div>
<div class="format-divider"></div>
<button class="format-btn" onclick="formatText('justifyLeft')">⫷<span class="tooltip">왼쪽 정렬</span></button>
<button class="format-btn" onclick="formatText('justifyCenter')">☰<span class="tooltip">가운데 정렬</span></button>
<button class="format-btn" onclick="formatText('justifyRight')">⫸<span class="tooltip">오른쪽 정렬</span></button>
<div class="format-divider"></div>
<button class="format-btn" onclick="toggleBulletList()">•≡<span class="tooltip">글머리 기호</span></button>
<button class="format-btn" onclick="toggleNumberList()">1.<span class="tooltip">번호 목록</span></button>
<button class="format-btn" onclick="adjustIndent(-1)">⇤<span class="tooltip">내어쓰기</span></button>
<button class="format-btn" onclick="adjustIndent(1)">⇥<span class="tooltip">들여쓰기</span></button>
<div class="format-divider"></div>
<button class="format-btn" onclick="openTableModal()">▦<span class="tooltip">표 삽입</span></button>
<button class="format-btn" onclick="insertImage()">🖼️<span class="tooltip">그림 삽입</span></button>
<button class="format-btn" onclick="insertHR()">―<span class="tooltip">구분선</span></button>
<div class="format-divider"></div>
<select class="format-select" onchange="applyHeading(this.value)" style="min-width:100px;">
<option value="">본문</option>
<option value="h1">제목 1</option>
<option value="h2">제목 2</option>
<option value="h3">제목 3</option>
<select class="format-select" onchange="handleInsert(this.value); this.selectedIndex=0;">
<option value="">삽입 ▾</option>
<option value="table">▦ 표</option>
<option value="image">🖼️ 그림</option>
<option value="hr">― 구분선</option>
</select>
<select class="format-select" onchange="applyHeading(this.value)">
<option value="">본문</option>
<option value="h1">제목1</option>
<option value="h2">제목2</option>
<option value="h3">제목3</option>
</select>
<div class="format-divider"></div>
<button class="format-btn page-btn" onclick="smartAlign()">🔄 지능형 정렬</button>
<button class="format-btn page-btn" onclick="forcePageBreak()">📄 새페이지</button>
<button class="format-btn page-btn" onclick="moveToPrevPage()">📤 전페이지</button>
</div>
`;
return formatBarHTML;
}
// ===== 로컬 폰트 불러오기 =====
async function loadLocalFonts() {
// API 지원 여부 확인
if (!('queryLocalFonts' in window)) {
toast('⚠️ 이 브라우저는 폰트 불러오기를 지원하지 않습니다 (Chrome/Edge 필요)');
return;
}
try {
toast('🔄 폰트 불러오는 중...');
// 사용자 권한 요청 & 폰트 목록 가져오기
const fonts = await window.queryLocalFonts();
const fontSelect = document.getElementById('fontFamily');
// 기존 옵션들의 값 수집 (중복 방지)
const existingFonts = new Set();
fontSelect.querySelectorAll('option').forEach(opt => {
existingFonts.add(opt.value);
});
// 중복 제거 (family 기준)
const families = [...new Set(fonts.map(f => f.family))];
// 구분선 추가
const separator = document.createElement('option');
separator.disabled = true;
separator.textContent = '──── 내 컴퓨터 ────';
fontSelect.appendChild(separator);
// 새 폰트 추가
let addedCount = 0;
families.sort().forEach(family => {
if (!existingFonts.has(family)) {
const option = document.createElement('option');
option.value = family;
option.textContent = family;
fontSelect.appendChild(option);
addedCount++;
}
});
toast(`${addedCount}개 폰트 추가됨 (총 ${families.length}개)`);
} catch (e) {
if (e.name === 'NotAllowedError') {
toast('⚠️ 폰트 접근 권한이 거부되었습니다');
} else {
console.error('폰트 로드 오류:', e);
toast('❌ 폰트 불러오기 실패: ' + e.message);
}
}
}
// ===== 삽입 핸들러 =====
function handleInsert(type) {
if (type === 'table') openTableModal();
else if (type === 'image') insertImage();
else if (type === 'hr') insertHR();
}
// ===== 표 삽입 모달 HTML 생성 =====
function createTableModal() {
const modalHTML = `
@@ -457,11 +526,196 @@ function handleEditorKeydown(e) {
}
}
// ===== 리사이즈 핸들 추가 함수 =====
function addResizeHandle(doc, element, type) {
// wrapper 생성
const wrapper = doc.createElement('div');
wrapper.className = 'resizable-container ' + (type === 'table' ? 'table-resize block-type' : 'figure-resize');
// 초기 크기 설정
const rect = element.getBoundingClientRect();
wrapper.style.width = element.style.width || (rect.width + 'px');
// 크기 표시 툴팁
const tooltip = doc.createElement('div');
tooltip.className = 'size-tooltip';
tooltip.textContent = Math.round(rect.width) + ' × ' + Math.round(rect.height);
// 리사이즈 핸들
const handle = doc.createElement('div');
handle.className = 'resize-handle';
handle.title = '드래그하여 크기 조절';
// DOM 구조 변경
element.parentNode.insertBefore(wrapper, element);
wrapper.appendChild(element);
wrapper.appendChild(tooltip);
wrapper.appendChild(handle);
// 표는 width 100%로 시작
if (type === 'table') {
element.style.width = '100%';
}
// 리사이즈 이벤트
let isResizing = false;
let startX, startY, startWidth, startHeight;
handle.addEventListener('mousedown', function(e) {
e.preventDefault();
e.stopPropagation();
isResizing = true;
wrapper.classList.add('resizing');
startX = e.clientX;
startY = e.clientY;
startWidth = wrapper.offsetWidth;
startHeight = wrapper.offsetHeight;
doc.addEventListener('mousemove', onMouseMove);
doc.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
if (!isResizing) return;
e.preventDefault();
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
const aspectRatio = startWidth / startHeight;
let newWidth = Math.max(100, startWidth + deltaX);
let newHeight;
if (e.shiftKey) {
newHeight = newWidth / aspectRatio; // 비율 유지
} else {
newHeight = Math.max(50, startHeight + deltaY);
}
wrapper.style.width = newWidth + 'px';
// 이미지인 경우 width, height 둘 다 조절
if (type !== 'table') {
const img = wrapper.querySelector('img');
if (img) {
img.style.width = newWidth + 'px';
img.style.height = newHeight + 'px';
img.style.maxWidth = 'none';
img.style.maxHeight = 'none';
}
}
tooltip.textContent = Math.round(newWidth) + ' × ' + Math.round(newHeight);
}
function onMouseUp(e) {
if (!isResizing) return;
isResizing = false;
wrapper.classList.remove('resizing');
doc.removeEventListener('mousemove', onMouseMove);
doc.removeEventListener('mouseup', onMouseUp);
saveState();
toast('📐 크기 조절: ' + Math.round(wrapper.offsetWidth) + 'px');
}
}
// ===== iframe 내부에 편집용 스타일 주입 =====
function injectEditStyles(doc) {
if (doc.getElementById('editor-inject-style')) return;
const style = doc.createElement('style');
style.id = 'editor-inject-style';
style.textContent = `
/* 리사이즈 컨테이너 */
.resizable-container { position: relative; display: inline-block; max-width: 100%; }
.resizable-container.block-type { display: block; }
/* 리사이즈 핸들 */
.resize-handle {
position: absolute;
right: -2px;
bottom: -2px;
width: 18px;
height: 18px;
background: #00C853;
cursor: se-resize;
opacity: 0;
transition: opacity 0.2s;
z-index: 100;
border-radius: 3px 0 3px 0;
display: flex;
align-items: center;
justify-content: center;
}
.resize-handle::after {
content: '⤡';
color: white;
font-size: 12px;
font-weight: bold;
}
.resizable-container:hover .resize-handle { opacity: 0.8; }
.resize-handle:hover { opacity: 1 !important; transform: scale(1.1); }
.resizable-container.resizing { outline: 2px dashed #00C853 !important; }
.resizable-container.resizing .resize-handle { opacity: 1; background: #FF9800; }
/* 표 전용 - 파란색 핸들 */
.resizable-container.table-resize .resize-handle { background: #2196F3; }
.resizable-container.table-resize.resizing .resize-handle { background: #FF5722; }
/* 이미지 전용 */
.resizable-container.figure-resize img { display: block; }
/* 크기 표시 툴팁 */
.size-tooltip {
position: absolute;
top: -25px;
right: 0;
background: rgba(0,0,0,0.8);
color: white;
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
transition: opacity 0.2s;
pointer-events: none;
}
.resizable-container:hover .size-tooltip,
.resizable-container.resizing .size-tooltip { opacity: 1; }
/* 열 리사이즈 핸들 */
.col-resize-handle {
position: absolute;
top: 0;
width: 6px;
height: 100%;
background: transparent;
cursor: col-resize;
z-index: 50;
}
.col-resize-handle:hover { background: rgba(33, 150, 243, 0.3); }
.col-resize-handle.dragging { background: rgba(33, 150, 243, 0.5); }
/* 편집 중 하이라이트 */
[contenteditable]:focus { outline: 2px solid #00C853 !important; }
[contenteditable]:hover { outline: 1px dashed rgba(0,200,83,0.5); }
`;
doc.head.appendChild(style);
}
// ===== iframe 편집 이벤트 바인딩 =====
// ===== iframe 편집 이벤트 바인딩 =====
function bindIframeEditEvents() {
const doc = getIframeDoc();
if (!doc) return;
// 편집용 스타일 주입
injectEditStyles(doc);
// 키보드 이벤트
doc.removeEventListener('keydown', handleEditorKeydown);
doc.addEventListener('keydown', handleEditorKeydown);
@@ -479,6 +733,81 @@ function bindIframeEditEvents() {
}
clearActiveBlock();
});
// ===== 표에 리사이즈 핸들 추가 =====
doc.querySelectorAll('.body-content table, .sheet table').forEach(table => {
if (table.closest('.resizable-container')) return;
addResizeHandle(doc, table, 'table');
addColumnResizeHandles(doc, table); // 열 리사이즈 추가
});
// ===== 이미지에 리사이즈 핸들 추가 =====
doc.querySelectorAll('figure img, .body-content img, .sheet img').forEach(img => {
if (img.closest('.resizable-container')) return;
addResizeHandle(doc, img, 'image');
});
}
// ===== 표 열 리사이즈 핸들 추가 =====
function addColumnResizeHandles(doc, table) {
// 테이블에 position relative 설정
table.style.position = 'relative';
// 첫 번째 행의 셀들을 기준으로 열 핸들 생성
const firstRow = table.querySelector('tr');
if (!firstRow) return;
const cells = firstRow.querySelectorAll('th, td');
cells.forEach((cell, index) => {
if (index === cells.length - 1) return; // 마지막 열은 제외
// 이미 핸들이 있으면 스킵
if (cell.querySelector('.col-resize-handle')) return;
cell.style.position = 'relative';
const handle = doc.createElement('div');
handle.className = 'col-resize-handle';
handle.style.right = '-3px';
cell.appendChild(handle);
let startX, startWidth, nextStartWidth;
let nextCell = cells[index + 1];
handle.addEventListener('mousedown', function(e) {
e.preventDefault();
e.stopPropagation();
handle.classList.add('dragging');
startX = e.clientX;
startWidth = cell.offsetWidth;
nextStartWidth = nextCell ? nextCell.offsetWidth : 0;
doc.addEventListener('mousemove', onMouseMove);
doc.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
const delta = e.clientX - startX;
const newWidth = Math.max(30, startWidth + delta);
cell.style.width = newWidth + 'px';
// 다음 열도 조정 (테이블 전체 너비 유지)
if (nextCell && nextStartWidth > 30) {
const newNextWidth = Math.max(30, nextStartWidth - delta);
nextCell.style.width = newNextWidth + 'px';
}
}
function onMouseUp() {
handle.classList.remove('dragging');
doc.removeEventListener('mousemove', onMouseMove);
doc.removeEventListener('mouseup', onMouseUp);
saveState();
toast('📊 열 너비 조절됨');
}
});
}
// ===== 편집 모드 토글 =====
@@ -533,7 +862,7 @@ function toggleEditMode() {
function initEditor() {
// 편집 바가 없으면 생성
if (!document.getElementById('formatBar')) {
const previewContainer = document.querySelector('.preview-container');
const previewContainer = document.querySelector('.main');
if (previewContainer) {
previewContainer.insertAdjacentHTML('afterbegin', createFormatBar());
}
@@ -550,5 +879,330 @@ function initEditor() {
console.log('Editor initialized');
}
// ===== 지능형 정렬 =====
function smartAlign() {
const doc = getIframeDoc();
if (!doc) {
toast('⚠️ 문서가 로드되지 않았습니다');
return;
}
// ===== 현재 스크롤 위치 저장 =====
const iframe = getPreviewIframe();
const scrollY = iframe?.contentWindow?.scrollY || 0;
const sheets = Array.from(doc.querySelectorAll('.sheet'));
if (sheets.length < 2) {
toast('⚠️ 정렬할 본문 페이지가 없습니다');
return;
}
toast('지능형 정렬 실행 중...');
setTimeout(() => {
try {
// 1. 표지 유지
const coverSheet = sheets[0];
// 2. 보고서 제목 추출
let reportTitle = "보고서";
const existingTitle = sheets[1]?.querySelector('.rpt-title, .header-title');
if (existingTitle) reportTitle = existingTitle.innerText;
// 3. 콘텐츠 수집 (표지 제외)
const contentSheets = sheets.slice(1);
let allNodes = [];
contentSheets.forEach(sheet => {
const body = sheet.querySelector('.body-content');
if (body) {
Array.from(body.children).forEach(child => {
if (child.classList.contains('add-after-btn') ||
child.classList.contains('delete-block-btn') ||
child.classList.contains('empty-placeholder')) return;
if (['P', 'DIV', 'SPAN'].includes(child.tagName) &&
child.innerText.trim() === '' &&
!child.querySelector('img, table, figure')) return;
allNodes.push(child);
});
}
sheet.remove();
});
// 4. 설정값
const MAX_HEIGHT = 970;
const HEADING_RESERVE = 90;
let currentHeaderTitle = "목차";
let pageNum = 1;
// 5. 새 페이지 생성 함수
function createNewPage(headerText) {
const newSheet = doc.createElement('div');
newSheet.className = 'sheet';
newSheet.innerHTML = `
<div class="page-header">${headerText}</div>
<div class="body-content"></div>
<div class="page-footer">
<span class="rpt-title">${reportTitle}</span>
<span class="pg-num">- ${pageNum++} -</span>
</div>`;
doc.body.appendChild(newSheet);
return newSheet;
}
// 6. 페이지 재구성
let currentPage = createNewPage(currentHeaderTitle);
let currentBody = currentPage.querySelector('.body-content');
allNodes.forEach(node => {
// 강제 페이지 브레이크
if (node.classList && node.classList.contains('page-break-forced')) {
currentPage = createNewPage(currentHeaderTitle);
currentBody = currentPage.querySelector('.body-content');
currentBody.appendChild(node);
return;
}
// H1: 새 섹션 시작
if (node.tagName === 'H1') {
currentHeaderTitle = node.innerText.split('-')[0].trim();
if (currentBody.children.length > 0) {
currentPage = createNewPage(currentHeaderTitle);
currentBody = currentPage.querySelector('.body-content');
} else {
currentPage.querySelector('.page-header').innerText = currentHeaderTitle;
}
}
// H2, H3: 남은 공간 부족하면 새 페이지
if (['H2', 'H3'].includes(node.tagName)) {
const spaceLeft = MAX_HEIGHT - currentBody.scrollHeight;
if (spaceLeft < HEADING_RESERVE) {
currentPage = createNewPage(currentHeaderTitle);
currentBody = currentPage.querySelector('.body-content');
}
}
// 노드 추가
currentBody.appendChild(node);
// 전 페이지로 강제 이동 설정된 경우 스킵
if (node.classList && node.classList.contains('move-to-prev-page')) {
return;
}
// 높이 초과 시 새 페이지로 이동
if (currentBody.scrollHeight > MAX_HEIGHT) {
currentBody.removeChild(node);
currentPage = createNewPage(currentHeaderTitle);
currentBody = currentPage.querySelector('.body-content');
currentBody.appendChild(node);
}
});
// 7. 편집 모드였으면 복원
if (isEditing) {
bindIframeEditEvents();
}
// 8. generatedHTML 업데이트 (전역 변수)
if (typeof generatedHTML !== 'undefined') {
generatedHTML = '<!DOCTYPE html>' + doc.documentElement.outerHTML;
}
// ===== 스크롤 위치 복원 =====
setTimeout(() => {
if (iframe?.contentWindow) {
iframe.contentWindow.scrollTo(0, scrollY);
}
}, 50);
toast('✅ 지능형 정렬 완료 (' + pageNum + '페이지)');
} catch (e) {
console.error('smartAlign 오류:', e);
toast('❌ 정렬 중 오류: ' + e.message);
}
}, 100);
}
// ===== 새페이지 시작 =====
function forcePageBreak() {
const doc = getIframeDoc();
if (!doc) {
toast('⚠️ 문서가 로드되지 않았습니다');
return;
}
const selection = doc.getSelection();
if (!selection || !selection.anchorNode) {
toast('⚠️ 분리할 위치를 클릭하세요');
return;
}
let targetEl = selection.anchorNode.nodeType === 1
? selection.anchorNode
: selection.anchorNode.parentElement;
while (targetEl && targetEl.parentElement) {
if (targetEl.parentElement.classList && targetEl.parentElement.classList.contains('body-content')) {
break;
}
targetEl = targetEl.parentElement;
}
if (!targetEl || !targetEl.parentElement || !targetEl.parentElement.classList.contains('body-content')) {
toast('⚠️ 본문 블록을 먼저 클릭하세요');
return;
}
saveState();
const currentBody = targetEl.parentElement;
const currentSheet = currentBody.closest('.sheet');
const sheets = Array.from(doc.querySelectorAll('.sheet'));
const currentIndex = sheets.indexOf(currentSheet);
// 클릭한 요소부터 끝까지 수집
const elementsToMove = [];
let sibling = targetEl;
while (sibling) {
elementsToMove.push(sibling);
sibling = sibling.nextElementSibling;
}
if (elementsToMove.length === 0) {
toast('⚠️ 이동할 내용이 없습니다');
return;
}
// 다음 페이지 찾기
let nextSheet = sheets[currentIndex + 1];
let nextBody;
if (!nextSheet || !nextSheet.querySelector('.body-content')) {
const oldHeader = currentSheet.querySelector('.page-header');
const oldFooter = currentSheet.querySelector('.page-footer');
nextSheet = doc.createElement('div');
nextSheet.className = 'sheet';
nextSheet.innerHTML = `
<div class="page-header">${oldHeader ? oldHeader.innerText : ''}</div>
<div class="body-content"></div>
<div class="page-footer">
<span class="rpt-title">${oldFooter?.querySelector('.rpt-title')?.innerText || ''}</span>
<span class="pg-num">- - -</span>
</div>`;
currentSheet.after(nextSheet);
}
nextBody = nextSheet.querySelector('.body-content');
// 역순으로 맨 앞에 삽입 (순서 유지)
for (let i = elementsToMove.length - 1; i >= 0; i--) {
nextBody.insertBefore(elementsToMove[i], nextBody.firstChild);
}
// 첫 번째 요소에 페이지 브레이크 마커 추가 (나중에 지능형 정렬이 존중함)
targetEl.classList.add('page-break-forced');
// 페이지 번호만 재정렬 (smartAlign 호출 안 함!)
renumberPages(doc);
toast('✅ 다음 페이지로 이동됨');
}
// ===== 전페이지로 이동 (즉시 적용) =====
function moveToPrevPage() {
const doc = getIframeDoc();
if (!doc) {
toast('⚠️ 문서가 로드되지 않았습니다');
return;
}
const selection = doc.getSelection();
if (!selection || !selection.anchorNode) {
toast('⚠️ 이동할 블록을 클릭하세요');
return;
}
// 현재 선택된 요소에서 body-content 직계 자식 찾기
let targetEl = selection.anchorNode.nodeType === 1
? selection.anchorNode
: selection.anchorNode.parentElement;
while (targetEl && targetEl.parentElement) {
if (targetEl.parentElement.classList && targetEl.parentElement.classList.contains('body-content')) {
break;
}
targetEl = targetEl.parentElement;
}
if (!targetEl || !targetEl.parentElement || !targetEl.parentElement.classList.contains('body-content')) {
toast('⚠️ 본문 블록을 먼저 클릭하세요');
return;
}
saveState();
// 현재 sheet 찾기
const currentSheet = targetEl.closest('.sheet');
const sheets = Array.from(doc.querySelectorAll('.sheet'));
const currentIndex = sheets.indexOf(currentSheet);
// 이전 페이지 찾기 (표지 제외)
if (currentIndex <= 1) {
toast('⚠️ 이전 페이지가 없습니다');
return;
}
const prevSheet = sheets[currentIndex - 1];
const prevBody = prevSheet.querySelector('.body-content');
if (!prevBody) {
toast('⚠️ 이전 페이지에 본문 영역이 없습니다');
return;
}
// 요소를 이전 페이지 맨 아래로 이동
prevBody.appendChild(targetEl);
// 현재 페이지가 비었으면 삭제
const currentBody = currentSheet.querySelector('.body-content');
if (currentBody && currentBody.children.length === 0) {
currentSheet.remove();
}
// 페이지 번호 재정렬
renumberPages(doc);
toast('✅ 전 페이지로 이동됨');
}
// ===== 페이지 번호 재정렬 =====
function renumberPages(doc) {
const sheets = doc.querySelectorAll('.sheet');
let pageNum = 1;
sheets.forEach((sheet, idx) => {
if (idx === 0) return; // 표지는 번호 없음
const pgNum = sheet.querySelector('.pg-num');
if (pgNum) {
pgNum.innerText = `- ${pageNum++} -`;
}
});
}
// DOM 로드 시 초기화
document.addEventListener('DOMContentLoaded', initEditor);
document.addEventListener('DOMContentLoaded', initEditor);