Fix: 업로드 및 API 연결 문제 해결

- FastAPI 라우터에서 슬래시 문제로 인한 307 리다이렉트 수정
- Nginx 프록시 설정에서 경로 중복 문제 해결
- 계정 관리 시스템 구현 (로그인, 사용자 관리, 권한 설정)
- 노트북 연결 기능 수정 (notebook_id 필드 추가)
- 메모 트리 UI 개선 (수평 레이아웃, 드래그 기능 제거)
- 헤더 UI 개선 및 고정 위치 설정
- 백업/복원 스크립트 추가
- PDF 미리보기 토큰 인증 지원
This commit is contained in:
Hyungi Ahn
2025-09-03 15:58:10 +09:00
parent d4b10b16b1
commit 6e01dbdeb3
47 changed files with 3672 additions and 398 deletions

View File

@@ -63,9 +63,6 @@ window.memoTreeApp = function() {
treePanX: 0,
treePanY: 0,
nodePositions: new Map(), // 노드 ID -> {x, y} 위치 매핑
isDragging: false,
dragNode: null,
dragOffset: { x: 0, y: 0 },
// 로그인 관련 함수들
openLoginModal() {
@@ -290,7 +287,15 @@ window.memoTreeApp = function() {
const node = await window.api.createMemoNode(nodeData);
this.treeNodes.push(node);
this.selectNode(node);
// 노드 위치 재계산 (새 노드 추가 후)
this.$nextTick(() => {
this.calculateNodePositions();
// 위치 계산 완료 후 새 노드 선택
setTimeout(() => {
this.selectNode(node);
}, 50);
});
console.log('✅ 루트 노드 생성 완료');
} catch (error) {
@@ -301,6 +306,11 @@ window.memoTreeApp = function() {
// 노드 선택
selectNode(node) {
// 현재 팬 값 저장 (위치 변경 방지)
const currentPanX = this.treePanX;
const currentPanY = this.treePanY;
const currentZoom = this.treeZoom;
// 이전 노드 저장
if (this.selectedNode && this.isEditorDirty) {
this.saveNode();
@@ -323,6 +333,11 @@ window.memoTreeApp = function() {
}, 100);
}
// 팬 값 복원 (위치 변경 방지)
this.treePanX = currentPanX;
this.treePanY = currentPanY;
this.treeZoom = currentZoom;
console.log('📝 노드 선택:', node.title);
},
@@ -552,8 +567,14 @@ window.memoTreeApp = function() {
// 부모 노드 펼치기
this.expandedNodes.add(parentNode.id);
// 새 노드 선택
this.selectNode(node);
// 노드 위치 재계산 (새 노드 추가 후)
this.$nextTick(() => {
this.calculateNodePositions();
// 위치 계산 완료 후 새 노드 선택
setTimeout(() => {
this.selectNode(node);
}, 50);
});
console.log('✅ 자식 노드 생성 완료');
} catch (error) {
@@ -623,123 +644,9 @@ window.memoTreeApp = function() {
}
},
// 노드 드래그 시작
startDragNode(event, node) {
event.stopPropagation();
event.preventDefault();
console.log('🎯 노드 드래그 시작:', node.title);
this.isDragging = true;
this.dragNode = node;
// 드래그 시작 위치 계산
const rect = event.target.getBoundingClientRect();
this.dragOffset = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
// 드래그 중인 노드 스타일 적용
event.target.style.opacity = '0.7';
event.target.style.transform = 'scale(1.05)';
event.target.style.zIndex = '1000';
// 이벤트 리스너 등록
document.addEventListener('mousemove', this.handleNodeDrag.bind(this));
document.addEventListener('mouseup', this.endNodeDrag.bind(this));
},
// 노드 드래그 처리
handleNodeDrag(event) {
if (!this.isDragging || !this.dragNode) return;
event.preventDefault();
// 드래그 중인 노드 위치 업데이트
const dragElement = document.querySelector(`[data-node-id="${this.dragNode.id}"]`);
if (dragElement) {
const newX = event.clientX - this.dragOffset.x;
const newY = event.clientY - this.dragOffset.y;
dragElement.style.position = 'fixed';
dragElement.style.left = `${newX}px`;
dragElement.style.top = `${newY}px`;
dragElement.style.pointerEvents = 'none';
}
// 드롭 대상 하이라이트
this.highlightDropTarget(event);
},
// 드롭 대상 하이라이트
highlightDropTarget(event) {
// 모든 노드에서 하이라이트 제거
document.querySelectorAll('.tree-diagram-node').forEach(el => {
el.classList.remove('drop-target-highlight');
});
// 현재 마우스 위치의 노드 찾기
const elements = document.elementsFromPoint(event.clientX, event.clientY);
const targetNode = elements.find(el =>
el.classList.contains('tree-diagram-node') &&
el.getAttribute('data-node-id') !== this.dragNode.id
);
if (targetNode) {
targetNode.classList.add('drop-target-highlight');
}
},
// 노드 드래그 종료
endNodeDrag(event) {
if (!this.isDragging || !this.dragNode) return;
console.log('🎯 노드 드래그 종료');
// 드롭 대상 찾기
const elements = document.elementsFromPoint(event.clientX, event.clientY);
const targetElement = elements.find(el =>
el.classList.contains('tree-diagram-node') &&
el.getAttribute('data-node-id') !== this.dragNode.id
);
let targetNodeId = null;
if (targetElement) {
targetNodeId = targetElement.getAttribute('data-node-id');
}
// 드래그 중인 노드 스타일 복원
const dragElement = document.querySelector(`[data-node-id="${this.dragNode.id}"]`);
if (dragElement) {
dragElement.style.opacity = '';
dragElement.style.transform = '';
dragElement.style.zIndex = '';
dragElement.style.position = '';
dragElement.style.left = '';
dragElement.style.top = '';
dragElement.style.pointerEvents = '';
}
// 모든 하이라이트 제거
document.querySelectorAll('.tree-diagram-node').forEach(el => {
el.classList.remove('drop-target-highlight');
});
// 실제 노드 이동 처리
if (targetNodeId && targetNodeId !== this.dragNode.id) {
this.moveNodeToParent(this.dragNode.id, targetNodeId);
}
// 상태 초기화
this.isDragging = false;
this.dragNode = null;
this.dragOffset = { x: 0, y: 0 };
// 이벤트 리스너 제거
document.removeEventListener('mousemove', this.handleNodeDrag);
document.removeEventListener('mouseup', this.endNodeDrag);
},
// 노드를 다른 부모로 이동
async moveNodeToParent(nodeId, newParentId) {
@@ -783,15 +690,12 @@ window.memoTreeApp = function() {
// 노드 위치 계산 및 반환
getNodePosition(node) {
if (!this.nodePositions.has(node.id)) {
this.calculateNodePositions();
}
// 위치가 없으면 기본 위치 반환 (전체 재계산 방지)
const pos = this.nodePositions.get(node.id) || { x: 0, y: 0 };
return `left: ${pos.x}px; top: ${pos.y}px;`;
},
// 트리 노드 위치 자동 계산
// 트리 노드 위치 자동 계산 (가로 방향: 왼쪽에서 오른쪽)
calculateNodePositions() {
const canvas = document.getElementById('tree-canvas');
if (!canvas) return;
@@ -802,11 +706,11 @@ window.memoTreeApp = function() {
// 노드 크기 설정
const nodeWidth = 200;
const nodeHeight = 80;
const levelHeight = 150; // 레벨 간 간격
const nodeSpacing = 50; // 노드 간 간격
const levelWidth = 250; // 레벨 간 가로 간격 (왼쪽에서 오른쪽)
const nodeSpacing = 100; // 노드 간 세로 간격
const margin = 100; // 여백
// 레벨별 노드 그룹화
// 레벨별 노드 그룹화 (가로 방향)
const levels = new Map();
// 루트 노드들 찾기
@@ -814,7 +718,7 @@ window.memoTreeApp = function() {
if (rootNodes.length === 0) return;
// BFS로 레벨별 노드 배치
// BFS로 레벨별 노드 배치 (가로 방향)
const queue = [];
rootNodes.forEach(node => {
queue.push({ node, level: 0 });
@@ -835,25 +739,25 @@ window.memoTreeApp = function() {
});
}
// 트리 전체 크기 계산
// 트리 전체 크기 계산 (가로 방향)
const maxLevel = Math.max(...levels.keys());
const maxNodesInLevel = Math.max(...Array.from(levels.values()).map(nodes => nodes.length));
const treeWidth = maxNodesInLevel * nodeWidth + (maxNodesInLevel - 1) * nodeSpacing;
const treeHeight = (maxLevel + 1) * levelHeight;
const treeWidth = (maxLevel + 1) * levelWidth; // 가로 방향 전체 너비
const treeHeight = maxNodesInLevel * nodeHeight + (maxNodesInLevel - 1) * nodeSpacing; // 세로 방향 전체 높이
// 캔버스 중앙에 트리 배치하기 위한 오프셋 계산
const offsetX = Math.max(margin, (canvasWidth - treeWidth) / 2);
const offsetY = Math.max(margin, (canvasHeight - treeHeight) / 2);
// 각 레벨의 노드들 위치 계산
// 각 레벨의 노드들 위치 계산 (가로 방향)
levels.forEach((nodes, level) => {
const y = offsetY + level * levelHeight;
const levelWidth = nodes.length * nodeWidth + (nodes.length - 1) * nodeSpacing;
const startX = offsetX + (treeWidth - levelWidth) / 2;
const x = offsetX + level * levelWidth; // 가로 위치 (왼쪽에서 오른쪽)
const levelHeight = nodes.length * nodeHeight + (nodes.length - 1) * nodeSpacing;
const startY = offsetY + (treeHeight - levelHeight) / 2; // 세로 중앙 정렬
nodes.forEach((node, index) => {
const x = startX + index * (nodeWidth + nodeSpacing);
const y = startY + index * (nodeHeight + nodeSpacing);
this.nodePositions.set(node.id, { x, y });
});
});
@@ -893,17 +797,17 @@ window.memoTreeApp = function() {
// 연결선 생성
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// 부모 노드 하단 중앙에서 시작
const startX = parentPos.x + 100; // 노드 중앙
const startY = parentPos.y + 80; // 노드 하단
// 부모 노드 오른쪽 중앙에서 시작 (가로 방향)
const startX = parentPos.x + 200; // 노드 오른쪽 끝
const startY = parentPos.y + 40; // 노드 세로 중앙
// 자식 노드 상단 중앙으로 연결
const endX = childPos.x + 100; // 노드 중앙
const endY = childPos.y; // 노드 상단
// 자식 노드 왼쪽 중앙으로 연결 (가로 방향)
const endX = childPos.x; // 노드 왼쪽 끝
const endY = childPos.y + 40; // 노드 세로 중앙
// 곡선 경로 생성 (베지어 곡선)
const midY = startY + (endY - startY) / 2;
const path = `M ${startX} ${startY} C ${startX} ${midY} ${endX} ${midY} ${endX} ${endY}`;
// 곡선 경로 생성 (베지어 곡선, 가로 방향)
const midX = startX + (endX - startX) / 2;
const path = `M ${startX} ${startY} C ${midX} ${startY} ${midX} ${endY} ${endX} ${endY}`;
line.setAttribute('d', path);
line.setAttribute('stroke', '#9CA3AF');