Hansoo-Office-Editor / index.html
Darwin
Initial commit: HanSoo Office Editor with FastAPI backend
b5a9d38
Raw
History Blame Contribute Delete
8.45 kB
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HanSoo Office Editor</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; min-height: 100vh; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
header { background: #fff; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
h1 { color: #333; font-size: 24px; margin-bottom: 8px; }
.subtitle { color: #666; font-size: 14px; }
.upload-area { background: #fff; padding: 40px; border-radius: 12px; border: 2px dashed #ddd; text-align: center; cursor: pointer; transition: all 0.3s; }
.upload-area:hover { border-color: #4CAF50; background: #f9fff9; }
.upload-area.dragover { border-color: #4CAF50; background: #e8f5e9; }
.upload-icon { font-size: 48px; margin-bottom: 16px; }
.upload-text { color: #666; font-size: 16px; margin-bottom: 8px; }
.upload-hint { color: #999; font-size: 12px; }
.file-list { background: #fff; padding: 20px; border-radius: 12px; margin-top: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.file-item { display: flex; align-items: center; padding: 12px; border-bottom: 1px solid #eee; }
.file-item:last-child { border-bottom: none; }
.file-name { flex: 1; color: #333; }
.file-size { color: #999; font-size: 12px; margin-right: 16px; }
.file-actions { display: flex; gap: 8px; }
.btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
.btn-primary { background: #4CAF50; color: #fff; }
.btn-primary:hover { background: #45a049; }
.btn-secondary { background: #e0e0e0; color: #333; }
.btn-secondary:hover { background: #d0d0d0; }
.btn-danger { background: #f44336; color: #fff; }
.btn-danger:hover { background: #da190b; }
.status { padding: 12px; border-radius: 6px; margin-top: 16px; display: none; }
.status.success { background: #e8f5e9; color: #2e7d32; display: block; }
.status.error { background: #ffebee; color: #c62828; display: block; }
.supported-formats { margin-top: 20px; padding: 16px; background: #fff3e0; border-radius: 8px; }
.supported-formats h3 { font-size: 14px; color: #e65100; margin-bottom: 8px; }
.supported-formats p { font-size: 12px; color: #666; }
#fileInput { display: none; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>HanSoo Office Editor</h1>
<p class="subtitle">HWP Β· DOCX Β· XLSX Β· PPTX Β· PDF 파일 μ—…λ‘œλ“œ 및 처리</p>
</header>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">πŸ“</div>
<div class="upload-text">νŒŒμΌμ„ λ“œλž˜κ·Έν•˜κ±°λ‚˜ ν΄λ¦­ν•˜μ—¬ μ—…λ‘œλ“œ</div>
<div class="upload-hint">μ΅œλŒ€ 50MB 지원</div>
<input type="file" id="fileInput" multiple>
</div>
<div class="supported-formats">
<h3>μ§€μ›ν•˜λŠ” 파일 ν˜•μ‹</h3>
<p>HWP, HWPX, DOCX, XLSX, PPTX, PDF, TXT, CSV, JSON</p>
</div>
<div class="file-list" id="fileList" style="display: none;">
<h2 style="margin-bottom: 16px; font-size: 18px;">μ—…λ‘œλ“œλœ 파일</h2>
<div id="fileItems"></div>
</div>
<div class="status" id="status"></div>
</div>
<script>
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');
const fileItems = document.getElementById('fileItems');
const status = document.getElementById('status');
let uploadedFiles = [];
// λ“œλž˜κ·Έ μ•€ λ“œλ‘­
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
// 클릭 μ—…λ‘œλ“œ
uploadArea.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
async function handleFiles(files) {
for (const file of files) {
await uploadFile(file);
}
}
async function uploadFile(file) {
showStatus('μ—…λ‘œλ“œ 쀑...', 'info');
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
uploadedFiles.push({
file_id: data.file_id,
filename: data.filename,
size: data.size
});
renderFileList();
showStatus(`"${data.filename}" μ—…λ‘œλ“œ μ™„λ£Œ`, 'success');
} else {
showStatus('μ—…λ‘œλ“œ μ‹€νŒ¨', 'error');
}
} catch (error) {
showStatus(`였λ₯˜: ${error.message}`, 'error');
}
}
function renderFileList() {
if (uploadedFiles.length > 0) {
fileList.style.display = 'block';
fileItems.innerHTML = uploadedFiles.map(f => `
<div class="file-item">
<span class="file-name">${escapeHtml(f.filename)}</span>
<span class="file-size">${formatSize(f.size)}</span>
<div class="file-actions">
<button class="btn btn-primary" onclick="processFile('${f.file_id}', 'extract')">μΆ”μΆœ</button>
<button class="btn btn-danger" onclick="deleteFile('${f.file_id}')">μ‚­μ œ</button>
</div>
</div>
`).join('');
}
}
async function processFile(fileId, operation) {
showStatus('처리 쀑...', 'info');
const formData = new FormData();
formData.append('file_id', fileId);
formData.append('operation', operation);
try {
const response = await fetch('/api/process', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showStatus('처리 μ™„λ£Œ', 'success');
console.log(data);
} else {
showStatus('처리 μ‹€νŒ¨', 'error');
}
} catch (error) {
showStatus(`였λ₯˜: ${error.message}`, 'error');
}
}
async function deleteFile(fileId) {
uploadedFiles = uploadedFiles.filter(f => f.file_id !== fileId);
renderFileList();
if (uploadedFiles.length > 0) {
showStatus('파일 μ‚­μ œ μ™„λ£Œ', 'success');
}
}
function showStatus(message, type) {
status.textContent = message;
status.className = `status ${type}`;
setTimeout(() => { status.className = 'status'; }, 3000);
}
function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>