Files
Todo-Project/frontend/inbox.html
Hyungi Ahn 0b967a84fa 🚀 시놀로지 배포 준비 완료
 주요 변경사항:
- 단일 docker-compose.yml로 통합 (로컬/시놀로지 환경 지원)
- 시놀로지 볼륨 매핑 설정 (volume1: 이미지, volume3: 데이터)
- 통합 배포 가이드 및 자동 배포 스크립트 추가
- 완전한 Memos 스타일 워크플로우 구현

🎯 새로운 기능:
- 📝 메모 작성 (upload.html) - 이미지 업로드 지원
- 📥 수신함 (inbox.html) - 메모 편집 및 Todo/보드 변환
-  Todo 목록 (todo-list.html) - 오늘 할 일 관리
- 📋 보드 (board.html) - 프로젝트 관리, 접기/펼치기, 이미지 지원
- 📚 아카이브 (archive.html) - 완료된 보드 보관
- 🔐 초기 설정 화면 - 관리자 계정 생성

🔧 기술적 개선:
- 이미지 업로드/편집 완전 지원
- 반응형 디자인 및 모바일 최적화
- 보드 완료 후 자동 숨김 처리
- 메모 편집 시 제목 필드 제거
- 테스트 로그인 버튼 제거 (프로덕션 준비)
- 과거 코드 정리 (TodoService, CalendarSyncService 등)

📦 배포 관련:
- env.synology.example - 시놀로지 환경 설정 템플릿
- SYNOLOGY_DEPLOYMENT_GUIDE.md - 상세한 배포 가이드
- deploy-synology.sh - 원클릭 자동 배포 스크립트
- Nginx 정적 파일 서빙 및 이미지 프록시 설정

🗑️ 정리된 파일:
- 사용하지 않는 HTML 페이지들 (dashboard, calendar, checklist 등)
- 복잡한 통합 서비스들 (integrations 폴더)
- 중복된 시놀로지 설정 파일들
2025-09-24 09:12:39 +09:00

799 lines
33 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>수신함 - Todo Project</title>
<link rel="icon" type="image/x-icon" href="static/icons/favicon.ico">
<link rel="apple-touch-icon" href="static/icons/apple-touch-icon.png">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+KR:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--parchment: #f7f3e9;
--parchment-dark: #f0ead6;
--ink: #2c1810;
--ink-light: #5d4e37;
--sepia: #8b7355;
--gold: #d4af37;
--shadow: rgba(139, 115, 85, 0.2);
}
body {
font-family: 'Noto Serif KR', serif;
background: linear-gradient(135deg, #f7f3e9 0%, #f0ead6 100%);
background-attachment: fixed;
color: var(--ink);
}
.parchment-container {
background: var(--parchment);
background-image:
radial-gradient(circle at 25% 25%, rgba(139, 115, 85, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(139, 115, 85, 0.05) 0%, transparent 50%);
border: 2px solid var(--sepia);
border-radius: 8px;
box-shadow:
0 8px 32px var(--shadow),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
position: relative;
}
.parchment-container::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, var(--gold), var(--sepia), var(--gold));
border-radius: 10px;
z-index: -1;
opacity: 0.3;
}
.header-vintage {
background: linear-gradient(135deg, var(--parchment), var(--parchment-dark));
border-bottom: 3px solid var(--gold);
box-shadow: 0 2px 10px var(--shadow);
}
.memo-item {
background: var(--parchment-dark);
border: 1px solid var(--sepia);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
transition: all 0.3s ease;
}
.memo-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px var(--shadow);
}
.todo-button {
background: linear-gradient(135deg, var(--sepia), var(--ink-light));
color: var(--parchment);
border: 2px solid var(--gold);
border-radius: 20px;
padding: 0.5rem 1rem;
font-family: 'Noto Serif KR', serif;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 2px 8px var(--shadow);
}
.todo-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--shadow);
background: linear-gradient(135deg, var(--ink-light), var(--ink));
}
.edit-button {
background: var(--gold);
color: var(--ink);
border: 1px solid var(--gold);
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 40px;
}
.edit-button:hover {
background: var(--sepia);
border-color: var(--sepia);
color: white;
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--shadow);
}
.cancel-button {
background: transparent;
color: var(--ink-light);
border: 1px solid var(--sepia);
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.cancel-button:hover {
background: var(--sepia);
color: white;
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
gap: 0.5rem;
max-width: 300px;
}
.image-item {
aspect-ratio: 1;
border-radius: 4px;
overflow: hidden;
border: 1px solid var(--sepia);
}
.image-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.date-modal {
background: rgba(44, 24, 16, 0.8);
backdrop-filter: blur(5px);
}
.date-input {
background: var(--parchment);
border: 2px solid var(--sepia);
border-radius: 8px;
padding: 0.75rem;
font-family: 'Noto Serif KR', serif;
color: var(--ink);
}
.date-input:focus {
outline: none;
border-color: var(--gold);
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.2);
}
</style>
</head>
<body>
<!-- 헤더 -->
<header class="header-vintage">
<div class="max-w-4xl mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center">
<h1 class="text-2xl font-semibold" style="color: var(--ink);">
<i class="fas fa-inbox mr-3" style="color: var(--gold);"></i>
수신함
</h1>
</div>
<div class="flex space-x-2">
<a href="upload.html" class="todo-button">
<i class="fas fa-feather-alt mr-2"></i>메모
</a>
<a href="inbox.html" class="todo-button" style="background: var(--gold); border-color: var(--gold); color: var(--ink);">
<i class="fas fa-inbox mr-2"></i>수신함
</a>
<a href="todo-list.html" class="todo-button">
<i class="fas fa-tasks mr-2"></i>Todo 목록
</a>
<a href="board.html" class="todo-button">
<i class="fas fa-clipboard mr-2"></i>보드
</a>
<a href="archive.html" class="todo-button">
<i class="fas fa-archive mr-2"></i>아카이브
</a>
</div>
</div>
</div>
</header>
<!-- 메인 컨텐츠 -->
<main class="max-w-4xl mx-auto px-4 py-8">
<div class="parchment-container p-6">
<div id="memoList">
<!-- 메모 목록이 여기에 표시됩니다 -->
</div>
<div id="emptyState" class="hidden text-center py-12">
<i class="fas fa-inbox text-6xl mb-4" style="color: var(--sepia); opacity: 0.5;"></i>
<h3 class="text-xl font-medium mb-2" style="color: var(--ink-light);">수신함이 비어있습니다</h3>
<p class="text-sm mb-6" style="color: var(--sepia);">새로운 메모를 작성해보세요</p>
<a href="upload.html" class="todo-button">
<i class="fas fa-feather-alt mr-2"></i>첫 메모 작성하기
</a>
</div>
</div>
</main>
<!-- 변환 모달 -->
<div id="conversionModal" class="hidden fixed inset-0 date-modal flex items-center justify-center z-50">
<div class="parchment-container p-6 max-w-md w-full mx-4">
<h3 id="modalTitle" class="text-lg font-semibold mb-4 text-center" style="color: var(--ink);">
<i id="modalIcon" class="mr-2" style="color: var(--gold);"></i>
<span id="modalText"></span>
</h3>
<!-- Todo 변환 시 날짜 선택 -->
<div id="todoDateSection" class="mb-4 hidden">
<label class="block text-sm font-medium mb-2" style="color: var(--ink-light);">시작 날짜</label>
<input type="date" id="todoStartDate" class="date-input w-full">
</div>
<!-- 보드 변환 시 제목 입력 -->
<div id="boardTitleSection" class="mb-4 hidden">
<label class="block text-sm font-medium mb-2" style="color: var(--ink-light);">보드 제목</label>
<input type="text" id="boardTitle" class="date-input w-full" placeholder="예: 9월 선일정밀 가공품">
</div>
<div class="flex space-x-3">
<button onclick="confirmConversion()" class="todo-button flex-1">
<i class="fas fa-check mr-2"></i>확인
</button>
<button onclick="closeConversionModal()" class="todo-button flex-1" style="background: var(--sepia);">
<i class="fas fa-times mr-2"></i>취소
</button>
</div>
</div>
</div>
<!-- 편집 모달 -->
<div id="editModal" class="hidden fixed inset-0 date-modal flex items-center justify-center z-50">
<div class="parchment-container p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
<h3 class="text-lg font-semibold mb-4 text-center" style="color: var(--ink);">
<i class="fas fa-edit mr-2" style="color: var(--gold);"></i>
메모 편집
</h3>
<form id="editForm" class="space-y-4">
<!-- 내용 입력 -->
<div>
<label class="block text-sm font-medium mb-2" style="color: var(--ink-light);">내용</label>
<textarea id="editDescription" rows="6" class="w-full px-3 py-2 border rounded-lg resize-none"
style="border-color: var(--sepia); background: white; color: var(--ink);"
placeholder="내용을 입력하세요" required></textarea>
</div>
<!-- 기존 이미지 -->
<div id="existingImages" class="hidden">
<label class="block text-sm font-medium mb-2" style="color: var(--ink-light);">기존 이미지</label>
<div id="existingImageGrid" class="grid grid-cols-2 md:grid-cols-3 gap-3 mb-3"></div>
</div>
<!-- 새 이미지 추가 -->
<div>
<label class="block text-sm font-medium mb-2" style="color: var(--ink-light);">새 이미지 추가</label>
<div class="space-y-3">
<!-- 데스크톱: 파일 선택 -->
<div class="desktop-upload">
<input type="file" id="editImageInput" multiple accept="image/*" class="hidden">
<button type="button" onclick="document.getElementById('editImageInput').click()"
class="w-full py-3 px-4 border-2 border-dashed rounded-lg transition-colors"
style="border-color: var(--sepia); color: var(--ink-light);"
onmouseover="this.style.borderColor='var(--gold)'; this.style.backgroundColor='var(--parchment-dark)'"
onmouseout="this.style.borderColor='var(--sepia)'; this.style.backgroundColor='transparent'">
<i class="fas fa-images mr-2"></i>이미지 선택 (최대 5장)
</button>
</div>
<!-- 모바일: 카메라/갤러리 -->
<div class="mobile-upload hidden">
<div class="grid grid-cols-2 gap-3">
<button type="button" onclick="captureEditImage()" class="photo-button">
<i class="fas fa-camera mr-2"></i>카메라
</button>
<button type="button" onclick="document.getElementById('editImageInput').click()" class="photo-button">
<i class="fas fa-images mr-2"></i>갤러리
</button>
</div>
</div>
</div>
<!-- 새 이미지 미리보기 -->
<div id="newImagePreview" class="hidden mt-3">
<div id="newImageGrid" class="grid grid-cols-2 md:grid-cols-3 gap-3"></div>
</div>
</div>
<!-- 버튼 -->
<div class="flex justify-end space-x-3 pt-4">
<button type="button" onclick="closeEditModal()" class="cancel-button">
취소
</button>
<button type="submit" class="todo-button">
<i class="fas fa-save mr-2"></i>저장
</button>
</div>
</form>
</div>
</div>
<!-- JavaScript -->
<script src="static/js/api.js"></script>
<script src="static/js/auth.js"></script>
<script>
let currentMemoId = null;
let currentConversionType = null;
let currentEditMemo = null;
let newEditImages = [];
let existingImages = [];
// 페이지 초기화
document.addEventListener('DOMContentLoaded', () => {
// 인증 확인
const token = localStorage.getItem('authToken') || localStorage.getItem('token');
if (!token) {
window.location.href = 'index.html';
return;
}
loadMemos();
});
// 메모 목록 로드
async function loadMemos() {
try {
const memos = await TodoAPI.getTodos(null, 'memo');
renderMemos(memos);
} catch (error) {
console.error('메모 로드 실패:', error);
showEmptyState();
}
}
// 메모 목록 렌더링
function renderMemos(memos) {
const memoList = document.getElementById('memoList');
const emptyState = document.getElementById('emptyState');
if (!memos || memos.length === 0) {
showEmptyState();
return;
}
emptyState.classList.add('hidden');
memoList.innerHTML = memos.map(memo => {
const createdAt = new Date(memo.created_at);
const timeAgo = getTimeAgo(createdAt);
const hasImages = memo.image_urls && memo.image_urls.length > 0;
return `
<div class="memo-item">
<div class="flex justify-between items-start mb-3">
<div class="flex-1">
${memo.title ? `<h3 class="font-medium text-lg mb-2" style="color: var(--ink);">${memo.title}</h3>` : ''}
<p class="text-sm mb-2" style="color: var(--ink-light);">
${memo.description || '내용 없음'}
</p>
<div class="flex items-center text-xs" style="color: var(--sepia);">
<i class="fas fa-clock mr-1"></i>
<span>${timeAgo}</span>
${hasImages ? `<i class="fas fa-images ml-3 mr-1"></i><span>${memo.image_urls.length}장</span>` : ''}
</div>
</div>
<div class="flex space-x-2 ml-4">
<button onclick="openEditModal('${memo.id}')" class="edit-button" title="편집">
<i class="fas fa-edit"></i>
</button>
<button onclick="openConversionModal('${memo.id}', 'todo')" class="todo-button">
<i class="fas fa-tasks mr-2"></i>Todo로
</button>
<button onclick="openConversionModal('${memo.id}', 'board')" class="todo-button">
<i class="fas fa-clipboard mr-2"></i>보드로
</button>
</div>
</div>
${hasImages ? `
<div class="image-grid mt-3">
${memo.image_urls.slice(0, 4).map(url => `
<div class="image-item">
<img src="${url}" alt="첨부 이미지" onclick="showImageModal('${url}')">
</div>
`).join('')}
${memo.image_urls.length > 4 ? `
<div class="image-item flex items-center justify-center" style="background: var(--parchment); border: 2px dashed var(--sepia);">
<span class="text-xs" style="color: var(--sepia);">+${memo.image_urls.length - 4}</span>
</div>
` : ''}
</div>
` : ''}
</div>
`;
}).join('');
}
// 빈 상태 표시
function showEmptyState() {
document.getElementById('memoList').innerHTML = '';
document.getElementById('emptyState').classList.remove('hidden');
}
// 변환 모달 열기
function openConversionModal(memoId, type) {
currentMemoId = memoId;
currentConversionType = type;
const modalIcon = document.getElementById('modalIcon');
const modalText = document.getElementById('modalText');
const todoDateSection = document.getElementById('todoDateSection');
const boardTitleSection = document.getElementById('boardTitleSection');
if (type === 'todo') {
modalIcon.className = 'fas fa-tasks mr-2';
modalText.textContent = 'Todo로 변환';
todoDateSection.classList.remove('hidden');
boardTitleSection.classList.add('hidden');
// 기본값을 오늘 날짜로 설정
const today = new Date().toISOString().split('T')[0];
document.getElementById('todoStartDate').value = today;
} else if (type === 'board') {
modalIcon.className = 'fas fa-clipboard mr-2';
modalText.textContent = '보드로 변환';
todoDateSection.classList.add('hidden');
boardTitleSection.classList.remove('hidden');
// 기존 메모 내용을 기본 제목으로 설정
const memo = document.querySelector(`[onclick*="${memoId}"]`);
if (memo) {
const h3Element = memo.querySelector('h3');
const pElement = memo.querySelector('p');
let description = '';
if (h3Element) {
description = h3Element.textContent.trim();
} else if (pElement) {
description = pElement.textContent.trim();
// "내용 없음"이면 빈 문자열로 설정
if (description === '내용 없음') {
description = '';
}
}
document.getElementById('boardTitle').value = description;
}
}
document.getElementById('conversionModal').classList.remove('hidden');
}
// 변환 모달 닫기
function closeConversionModal() {
currentMemoId = null;
currentConversionType = null;
document.getElementById('conversionModal').classList.add('hidden');
}
// 변환 확인
async function confirmConversion() {
if (!currentMemoId || !currentConversionType) return;
try {
if (currentConversionType === 'todo') {
const startDate = document.getElementById('todoStartDate').value;
if (!startDate) {
alert('시작 날짜를 선택해주세요.');
return;
}
// 메모를 Todo로 변환
await TodoAPI.updateTodo(currentMemoId, {
category: 'todo',
start_date: startDate,
status: 'pending'
});
alert('Todo로 변환되었습니다!');
} else if (currentConversionType === 'board') {
const boardTitle = document.getElementById('boardTitle').value.trim();
if (!boardTitle) {
alert('보드 제목을 입력해주세요.');
return;
}
// 메모를 보드로 변환 (헤더로 설정)
await TodoAPI.updateTodo(currentMemoId, {
category: 'board',
title: boardTitle,
board_id: currentMemoId, // 자기 자신을 board_id로 설정
is_board_header: true,
status: 'pending'
});
alert('보드로 변환되었습니다!');
}
closeConversionModal();
loadMemos(); // 목록 새로고침
} catch (error) {
console.error('변환 실패:', error);
alert('변환에 실패했습니다.');
}
}
// 시간 경과 표시
function getTimeAgo(date) {
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return '방금 전';
if (diffMins < 60) return `${diffMins}분 전`;
if (diffHours < 24) return `${diffHours}시간 전`;
if (diffDays < 7) return `${diffDays}일 전`;
return date.toLocaleDateString('ko-KR', {
month: 'short',
day: 'numeric'
});
}
// 이미지 모달 표시 (간단한 구현)
function showImageModal(imageUrl) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 date-modal flex items-center justify-center z-50';
modal.innerHTML = `
<div class="max-w-4xl max-h-4xl p-4">
<img src="${imageUrl}" class="max-w-full max-h-full object-contain rounded-lg">
</div>
`;
modal.onclick = () => modal.remove();
document.body.appendChild(modal);
}
// 편집 모달 열기
async function openEditModal(memoId) {
try {
// 메모 정보 가져오기
const memo = await TodoAPI.getTodoById(memoId);
currentEditMemo = memo;
// 폼 채우기
document.getElementById('editDescription').value = memo.description || '';
// 기존 이미지 처리
existingImages = memo.image_urls || [];
if (existingImages.length > 0) {
document.getElementById('existingImages').classList.remove('hidden');
renderExistingImages();
} else {
document.getElementById('existingImages').classList.add('hidden');
}
// 새 이미지 초기화
newEditImages = [];
document.getElementById('newImagePreview').classList.add('hidden');
// 모바일/데스크톱 구분
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile) {
document.querySelector('.desktop-upload').classList.add('hidden');
document.querySelector('.mobile-upload').classList.remove('hidden');
} else {
document.querySelector('.desktop-upload').classList.remove('hidden');
document.querySelector('.mobile-upload').classList.add('hidden');
}
// 파일 입력 이벤트 리스너
const fileInput = document.getElementById('editImageInput');
fileInput.onchange = handleEditImageSelect;
// 편집 폼 이벤트 리스너
const editForm = document.getElementById('editForm');
editForm.onsubmit = handleEditSubmit;
document.getElementById('editModal').classList.remove('hidden');
} catch (error) {
console.error('메모 로드 실패:', error);
alert('메모를 불러올 수 없습니다.');
}
}
// 편집 모달 닫기
function closeEditModal() {
currentEditMemo = null;
newEditImages = [];
existingImages = [];
document.getElementById('editModal').classList.add('hidden');
}
// 기존 이미지 렌더링
function renderExistingImages() {
const grid = document.getElementById('existingImageGrid');
grid.innerHTML = existingImages.map((url, index) => `
<div class="relative">
<img src="${url}" alt="기존 이미지" class="w-full h-24 object-cover rounded-lg">
<button type="button" onclick="removeExistingImage(${index})"
class="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600">
×
</button>
</div>
`).join('');
}
// 기존 이미지 제거
function removeExistingImage(index) {
existingImages.splice(index, 1);
if (existingImages.length === 0) {
document.getElementById('existingImages').classList.add('hidden');
} else {
renderExistingImages();
}
}
// 새 이미지 선택 처리
async function handleEditImageSelect(event) {
const files = Array.from(event.target.files);
const totalImages = existingImages.length + newEditImages.length + files.length;
if (totalImages > 5) {
alert('최대 5장까지만 업로드할 수 있습니다.');
return;
}
for (const file of files) {
try {
// 이미지 압축
const compressedFile = await compressImageSimple(file, 0.7, 1920);
const base64 = await convertFileToBase64(compressedFile);
newEditImages.push({
file: compressedFile,
preview: base64
});
} catch (error) {
console.error('이미지 처리 실패:', error);
}
}
renderNewImages();
event.target.value = '';
}
// 새 이미지 렌더링
function renderNewImages() {
if (newEditImages.length === 0) {
document.getElementById('newImagePreview').classList.add('hidden');
return;
}
document.getElementById('newImagePreview').classList.remove('hidden');
const grid = document.getElementById('newImageGrid');
grid.innerHTML = newEditImages.map((img, index) => `
<div class="relative">
<img src="${img.preview}" alt="새 이미지" class="w-full h-24 object-cover rounded-lg">
<button type="button" onclick="removeNewImage(${index})"
class="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600">
×
</button>
</div>
`).join('');
}
// 새 이미지 제거
function removeNewImage(index) {
newEditImages.splice(index, 1);
renderNewImages();
}
// 모바일 카메라 캡처
async function captureEditImage() {
try {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.capture = 'environment';
input.onchange = handleEditImageSelect;
input.click();
} catch (error) {
console.error('카메라 접근 실패:', error);
alert('카메라에 접근할 수 없습니다.');
}
}
// 편집 폼 제출
async function handleEditSubmit(event) {
event.preventDefault();
const description = document.getElementById('editDescription').value.trim();
if (!description) {
alert('내용을 입력해주세요.');
return;
}
try {
// 새 이미지 업로드
const newImageUrls = [];
for (const img of newEditImages) {
try {
const uploadResult = await TodoAPI.uploadImage(img.file);
newImageUrls.push(uploadResult.file_url);
} catch (error) {
console.error('이미지 업로드 실패:', error);
}
}
// 모든 이미지 URL 합치기
const allImageUrls = [...existingImages, ...newImageUrls];
// 메모 업데이트
const updateData = {
description: description,
image_urls: allImageUrls
};
await TodoAPI.updateTodo(currentEditMemo.id, updateData);
alert('메모가 성공적으로 수정되었습니다!');
closeEditModal();
loadMemos(); // 목록 새로고침
} catch (error) {
console.error('메모 수정 실패:', error);
alert('메모 수정에 실패했습니다.');
}
}
// 이미지 압축 함수 (upload.html에서 복사)
async function compressImageSimple(file, quality = 0.7, maxWidth = 1920) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
const ratio = Math.min(maxWidth / img.width, maxWidth / img.height);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.toBlob(resolve, 'image/jpeg', quality);
};
img.src = URL.createObjectURL(file);
});
}
// Base64 변환 함수
function convertFileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// 전역 함수 등록
window.openConversionModal = openConversionModal;
window.closeConversionModal = closeConversionModal;
window.confirmConversion = confirmConversion;
window.showImageModal = showImageModal;
window.openEditModal = openEditModal;
window.closeEditModal = closeEditModal;
window.removeExistingImage = removeExistingImage;
window.removeNewImage = removeNewImage;
window.captureEditImage = captureEditImage;
</script>
</body>
</html>