텍스트, 오피스, PDF, HWP/HWPX, OLE 문서를 한 화면에서 열고 편집 가능한 표현으로 바꿔 다시 저장하는 작업실입니다.
브라우저 내부 처리 · 보기 + 편집 + 변환 · 가능한 형식으로 다시 저장
문서를 브라우저에서 바로 열고 가능한 표현으로 편집합니다.
.txt .md .csv .xls .xlsx .docx .pptx .pdf .hwp .hwpx .doc .ppt .msg .json .xml .html원본, 바로 수정, 구조 JSON 중 필요한 보기로 전환합니다.
열린 문서가 없습니다.
현재 파일의 감지 결과와 가능한 작업을 요약합니다.
txt/md/html/docx/pdf 등 가능한 형식으로 저장합니다.xlsx/csv/tsv/html/json으로 변환합니다.`, 'text/html'); return (doc.body.textContent || '').replace(/\u00A0/g, ' ').replace(/\n{3,}/g, '\n\n').trim(); } // ----------[🧩 sanitizeHtml 처리]---------- function sanitizeHtml(html) { return window.DOMPurify.sanitize(html, { ADD_ATTR: ['style', 'class', 'colspan', 'rowspan'], ADD_TAGS: ['style'] }); } // ----------[🧩 formatBytes 처리]---------- function formatBytes(bytes) { const value = Number(bytes) || 0; if (value < 1024) { return `${value} B`; } const units = ['KB', 'MB', 'GB', 'TB']; let size = value / 1024; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex += 1; } return `${size.toFixed(size >= 100 ? 0 : size >= 10 ? 1 : 2)} ${units[unitIndex]}`; } // ----------[🧩 normalizeBaseName 처리]---------- function normalizeBaseName(fileName) { const trimmed = String(fileName || '').trim(); const withoutExt = trimmed.replace(/\.[^.]+$/, ''); const cleaned = withoutExt.replace(/[\\/:*?"<>|]+/g, ' ').trim().replace(/\s+/g, ' '); return cleaned !== '' ? cleaned : 'document'; } // ----------[🧩 getExtension 처리]---------- function getExtension(fileName) { const trimmed = String(fileName || '').trim(); const lastDot = trimmed.lastIndexOf('.'); if (lastDot < 0) { return ''; } return trimmed.slice(lastDot + 1).toLowerCase(); } // ----------[🧩 detectCategory 처리]---------- function detectCategory(ext, file) { if (spreadsheetExtensions.has(ext)) { return 'spreadsheet'; } if (ext === 'docx') { return 'docx'; } if (archiveExtensions.has(ext)) { return 'archive'; } if (oleExtensions.has(ext)) { return 'ole'; } if (ext === 'pdf') { return 'pdf'; } if (ext === 'hwp') { return 'hwp'; } if (ext === 'hwpx') { return 'hwpx'; } if (textExtensions.has(ext)) { if (ext === 'md' || ext === 'markdown') { return 'markdown'; } if (ext === 'html' || ext === 'htm' || ext === 'mht' || ext === 'mhtml') { return 'html'; } return 'text'; } if (file && typeof file.type === 'string' && file.type === 'application/epub+zip') { return 'archive'; } if (file && typeof file.type === 'string' && file.type.startsWith('text/')) { return 'text'; } return 'binary'; } // ----------[🧩 getMonacoLanguage 처리]---------- function getMonacoLanguage(kind, ext) { if (kind === 'html') { return 'html'; } if (kind === 'markdown') { return 'markdown'; } if (kind === 'json') { return 'json'; } return monacoLanguageByExtension[ext] || 'plaintext'; } // ----------[🧩 ensureMonaco 처리]---------- async function ensureMonaco() { if (state.editor) { return state.editor; } if (!monacoReadyPromise) { monacoReadyPromise = new Promise((resolve, reject) => { if (!window.require) { reject(new Error('Monaco 로더를 찾을 수 없습니다.')); return; } window.require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs' } }); window.require(['vs/editor/editor.main'], () => { resolve(window.monaco); }, reject); }); } state.monaco = await monacoReadyPromise; state.editor = state.monaco.editor.create(refs.monacoMount, { value: '', language: 'plaintext', theme: 'vs-dark', automaticLayout: true, wordWrap: 'on', minimap: { enabled: false }, fontSize: 14, lineHeight: 22, scrollBeyondLastLine: false, tabSize: 4, insertSpaces: true }); state.editor.onDidChangeModelContent(() => { if (state.category === 'spreadsheet' || state.isProgrammaticEditorUpdate) { return; } syncEditorToState(); }); return state.editor; } // ----------[🧩 loadPdfjs 처리]---------- async function loadPdfjs() { if (!pdfjsLibPromise) { pdfjsLibPromise = import('https://cdn.jsdelivr.net/npm/pdfjs-dist@5.6.205/build/pdf.min.mjs'); } const pdfjsLib = await pdfjsLibPromise; pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@5.6.205/build/pdf.worker.min.mjs'; return pdfjsLib; } // ----------[🧩 loadDocxPreview 처리]---------- async function loadDocxPreview() { if (!docxPreviewPromise) { docxPreviewPromise = import('https://cdn.jsdelivr.net/npm/docx-preview@0.3.7/dist/docx-preview.min.mjs'); } return docxPreviewPromise; } // ----------[🧩 loadHwpjs 처리]---------- async function loadHwpjs() { if (!hwpjsPromise) { hwpjsPromise = import('https://cdn.jsdelivr.net/npm/@ohah/hwpjs@0.1.0-rc.10/+esm'); } return hwpjsPromise; } // ----------[🧩 buildTextHtml 처리]---------- function buildTextHtml(text) { return `
${escapeHtml(text)}`; } // ----------[🧩 wrapHtmlDocument 처리]---------- function wrapHtmlDocument(title, bodyHtml) { return `