diff --git a/frontend/static/js/memo-tree.js b/frontend/static/js/memo-tree.js
index a35e8b2..c7c6558 100644
--- a/frontend/static/js/memo-tree.js
+++ b/frontend/static/js/memo-tree.js
@@ -23,6 +23,13 @@ window.memoTreeApp = function() {
showLoginModal: false,
showMobileEditModal: false,
+ // 알림 시스템
+ notification: {
+ show: false,
+ message: '',
+ type: 'info' // 'success', 'error', 'info'
+ },
+
// 로그인 폼 상태
loginForm: {
email: '',
@@ -503,6 +510,20 @@ window.memoTreeApp = function() {
},
// 트리 타입별 아이콘 가져오기
+ // 알림 표시
+ showNotification(message, type = 'info') {
+ this.notification = {
+ show: true,
+ message: message,
+ type: type
+ };
+
+ // 3초 후 자동으로 숨김
+ setTimeout(() => {
+ this.notification.show = false;
+ }, 3000);
+ },
+
getTreeIcon(treeType) {
const icons = {
novel: '📚',
@@ -604,10 +625,144 @@ window.memoTreeApp = function() {
// 노드 드래그 시작
startDragNode(event, node) {
- // 현재는 드래그 기능 비활성화 (패닝과 충돌 방지)
event.stopPropagation();
- console.log('드래그 시작:', node.title);
- // TODO: 노드 드래그 앤 드롭 구현
+ 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) {
+ try {
+ console.log(`📦 노드 이동: ${nodeId} -> 부모: ${newParentId}`);
+
+ const moveData = {
+ parent_id: newParentId,
+ sort_order: 0 // 새 부모의 첫 번째 자식으로
+ };
+
+ await window.api.moveMemoNode(nodeId, moveData);
+
+ // 트리 다시 로드
+ await this.loadTreeNodes();
+
+ console.log('✅ 노드 이동 완료');
+ this.showNotification('노드가 이동되었습니다.', 'success');
+
+ } catch (error) {
+ console.error('❌ 노드 이동 실패:', error);
+ this.showNotification('노드 이동에 실패했습니다.', 'error');
+ }
},
// 노드 인라인 편집