import { vars } from '../archive/variable.js'; import { docVars } from './docVariable.js'; import { checkProjectInactive } from '../main.js'; import { openDocAiChoiceModal, openDocUpsertModal } from './docModalManager.js'; import { drawDocList, itemBoxSelected, renderDocViewer, toggleDocContextmenu } from './docPageRenderer.js'; import { splitBaseAndExt, hasSpecialChar } from '../archive/common.js'; import { resetViewer } from '../archive/pageRenderer.js'; let docMain = document.querySelector('.official-doc-main'); // 화면 let notDndArea = document.querySelector('.not-dnd-area'); let docMainList = document.querySelector('.official-doc-main .official-doc-list-container'); let docDndArea = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .doc-dnd-area'); let docDndImage = docDndArea?.querySelector('.doc-img'); let docDndText = docDndArea?.querySelector('.doc-text'); let isDragging = false; let isAiUsed = true; // 기본값은 true로 설정 // AI 사용 여부를 설정하는 함수 export function setAiUsed(value) { isAiUsed = value; } // AI 사용 여부를 가져오는 함수 export function getAiUsed() { return isAiUsed; } // dragover: 드래그 된 요소가 droppable 요소 위에 있는 동안 지속적으로 발생 docMain?.addEventListener('dragover', async (e) => { const types = e.dataTransfer.types; toggleDocContextmenu('.official-doc-contextmenu', false); // 파일이 아닌 항목에 대해서는 dnd 안되게 // 글자가 dnd 되는 문제 때문에 if (types && types.includes('Files')) { e.preventDefault(); // 페이지영역에 드래그 들어오면 notDndArea, docDndArea 표시 // notDndArea.style.display = 'flex'; docDndArea.style.display = 'flex'; // 드래그 상태 플래그 true isDragging = true; // dnd 영역 크기 let docMainListWidth = docMainList.offsetWidth; let docMainListHeight = docMainList.offsetHeight; let docMainListTop = docMainList.offsetTop; docDndArea.style.width = `${docMainListWidth}px`; docDndArea.style.minHeight = `${docMainListHeight}px`; docDndArea.style.top = `${docMainListTop}px`; if (e.target.matches('.doc-dnd-area')) { // dndArea 안으로 이동 docDndArea.style.background = '#b5c6c3dd'; docDndArea.style.border = '2px solid #1e5149'; // docDndImage.style.backgroundImage = "url('/main/img/archive/upload_in_dnd.svg')"; docDndText.textContent = '파일을 여기에 드롭하세요.'; docDndText.style.color = '#1e5149'; } else { // dndArea 밖으로 이동 docDndArea.style.background = '#e9eeeddd'; docDndArea.style.border = '2px solid #1e5149'; // docDndImage.style.backgroundImage = "url('/main/img/archive/upload_out_dnd.svg')"; docDndText.textContent = '파일을 여기에 드래그하세요.'; docDndText.style.color = '#a5b9b6'; } } else { return; } }); // dragleave: 드래그된 요소가 droppable 요소에서 벗어날 때 발생 docMain?.addEventListener('dragleave', async (e) => { e.preventDefault(); if (e.target.matches('.doc-dnd-area')) { // notDndArea에서 페이지영역 밖으로 나갈 때 notDndArea, dndArea 숨김 // notDndArea.style.display = 'none'; docDndArea.style.display = 'none'; // 드래그 상태 플래그 false isDragging = false; } }); // 드래그 상태에서 웹페이지 밖으로 나갈 때 notDndArea, dndArea 남아있는 경우 초기화 docMain?.addEventListener('mousemove', () => { if (isDragging) { // notDndArea.style.display = 'none'; docDndArea.style.display = 'none'; isDragging = false; } }); // drop: 드래그된 요소가 droppable 요소에 놓일 때 발생 docDndArea?.addEventListener('drop', async (e) => { e.preventDefault(); const droppedFiles = Array.from(e.dataTransfer.files); if (!droppedFiles.length) return; if (droppedFiles.length > 1) { alert('파일은 하나씩 추가해주세요.'); return; } let resourcePathArr = []; // 경로 존재 확인을 위해 resourcePathArr 저장 for (let i = 0; i < droppedFiles.length; i++) { const file = droppedFiles[i]; let resourcePath = `/${file.name}`; resourcePathArr.push(resourcePath); } // resourcePathArr을 사용해서 경로 존재 확인 let checkTargetExistsResult = await checkDocTargetExists('official', resourcePathArr); // 이미 존재하는 경로만 existingPathArr에 저장 let existingPathArr = checkTargetExistsResult.existingPathArr; if (existingPathArr.length != 0) { // 중복파일 있음 for (let i = 0; i < droppedFiles.length; i++) { const file = droppedFiles[i]; let resourcePath = `/${file.name}`; let docTitle = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.doc_title; let docDirection = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.doc_direction; let recipientOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.recipient_org; let senderOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.sender_org; let objectKey; let isExists = false; if (existingPathArr.includes(resourcePath)) { let isUpload = confirm(`'${file.name}' 파일이 이미 존재합니다.\n기존 파일을 새 파일로 교체하시겠습니까?\n[파일 제목] ${docTitle}\n[수신처] ${recipientOrg}\n[발신처] ${senderOrg}\n[위치] '${docDirection}' 공문 리스트`); if (isUpload) objectKey = checkTargetExistsResult.existingObjectKey.keyMap[resourcePath]; if (!isUpload) continue; } if (objectKey) { isExists = true; const docId = checkTargetExistsResult.existingDocId.keyMap[resourcePath]; await axios.post(`${docVars.path_name}/deleteDocData`, { docId, objectKey, isExists }); } } const uploadResult = await uploadDocFile(droppedFiles, 'exist'); const newObjectKey = uploadResult.uploadParams.objectKeyArr[0]; const params = {}; for (let i = 0; i < droppedFiles.length; i++) { const file = droppedFiles[i]; let resourcePath = `/${file.name}`; const docId = checkTargetExistsResult.existingDocId.keyMap[resourcePath]; params[`doc_id`] = docId; params[`object_key`] = newObjectKey; params[`preview_key`] = newObjectKey; params[`popup_key`] = newObjectKey; } await upsertDocData(params, 'exist'); } else { // 중복파일 없음 const droppedFileName = droppedFiles[0].name; const droppedFileExt = droppedFileName.split('.').pop().toLowerCase(); if (droppedFileExt != 'pdf') { alert('PDF 파일만 지원합니다.'); return; } // pdf 10장 넘으면 리턴 let pageCount = await checkPdfPageCount(droppedFiles[0]); if (pageCount >= 10) { if (confirm('11장 이상의 PDF는 AI 요약을 지원하지 않습니다.\n사용자 입력모달로 이동하시겠습니까?')) { setAiUsed(false); openDocUpsertModal({}, droppedFiles, droppedFileName, 'input'); return; } return; } const formData = new FormData(); // 공문 파일 for (const file of droppedFiles) { formData.append('input_file', file); } // 프롬프트 파일 try { const res = await fetch('/main/jsm/officialDoc/prompt/해외공문 프롬프트250901(한글)_json.txt'); const blob = await res.blob(); const promptFile = new File([blob], '해외공문 프롬프트250901(한글)_json.txt', { type: 'text/plain' }); formData.append('prompt_file', promptFile); } catch (error) { console.error('프롬프트 파일 로드 실패:', error); return; } // 스키마 파일 try { const res = await fetch('/main/jsm/officialDoc/prompt/default_schema.json'); const blob = await res.blob(); const schemaFile = new File([blob], 'default_schema.json', { type: 'application/json' }); formData.append('schema_file', schemaFile); } catch (error) { console.error('스키마 파일 로드 실패:', error); return; } // AI 선택 모달 열기 > 선택 완료되면 requestToAiWithChoice 호출 openDocAiChoiceModal((selectedAiType) => { requestToAiWithChoice(formData, droppedFiles, selectedAiType); }); } // notDndArea에서 페이지영역 밖으로 나갈 때 notDndArea, dndArea 숨김 notDndArea.style.display = 'none'; docDndArea.style.display = 'none'; // 드래그 상태 플래그 false isDragging = false; }); async function checkPdfPageCount(file) { const arrayBuffer = await file.arrayBuffer(); const loadingTask = window.pdfjsLib.getDocument({ data: new Uint8Array(arrayBuffer) }); const pdf = await loadingTask.promise; return pdf.numPages; } async function requestToAiWithChoice(formData, droppedFiles, aiType) { const files = formData.get('input_file'); const fileName = files ? files.name : '파일 없음'; try { showFullScreenLoading(); // 로딩 화면 보이기 // AI 사용 안함 if (aiType == 'aiNotUse') { hideFullScreenLoading(); setAiUsed(false); openDocUpsertModal({}, droppedFiles, fileName, 'input'); return; } const apiKey = await getExtractKey(); // AICell에서 발급해준 apiKey const url = { outerAi: 'https://gateway.hmac.kr/general/outer/e2e', innerAi: 'https://gateway.hmac.kr/extract/inner/structured', }; const res = await fetch(url[aiType], { method: 'POST', headers: { 'x-API-key': apiKey, }, body: formData, }); const resData = await res.json(); // AICell API(outerAi, innerAi)의 경우, 응답받은 request_id를 통해 데이터 풀링 const requestId = resData.request_id; if (!requestId) throw new Error('request_id 없음'); // api에 polling 요청 timeoutPolling( () => checkAiProgress(requestId, droppedFiles, fileName, aiType), 5000, // 5초 간격 60, // 최대 60번 > 5분 droppedFiles, fileName ); } catch (err) { handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달 } } async function checkAiProgress(requestId, droppedFiles, fileName, aiType) { try { const apiKey = await getExtractKey(); // aiType으로 데이터 풀링할 url (outerAi = general, innerAi = extract) const url = aiType === 'outerAi' ? `https://gateway.hmac.kr/general/progress/${requestId}` : `https://gateway.hmac.kr/extract/progress/${requestId}`; const res = await fetch(url, { method: 'GET', headers: { 'x-API-key': apiKey, }, }); // console.log(`요청: /extract/progress/${requestId} → status: ${res.status}`); if (!res.ok) { // 404 등 실패 시, polling 종료 handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달 throw new Error('__STOP_POLLING__'); // polling 종료를 위한 에러 던지기 } const data = await res.json(); // console.log('요약 진행 상태 응답: ', data); // final_result가 최종 ai공문요약데이터 // 결과가 완료되지 않아도 (data.status 200 || data.final_result) 조건을 충족하기 때문에 // 결과 생성 전에는 다음 polling으로 넘김 if (data.final_result == '작업이 진행 중입니다. 결과는 아직 생성되지 않았습니다.') return; // 결과에 에러 떴을 때 if (data.final_result?.error) { handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달 throw new Error('__STOP_POLLING__'); // polling 종료를 위한 에러 던지기 } // 성공 시 polling 중단하고 콜백 실행 if (data.status === 200 || data.final_result) { // console.log('최최종: ', data.final_result[0]); setAiUsed(true); let generatedData = data.final_result.generated; if (aiType === 'outerAi') generatedData = JSON.parse(data.final_result.generated); if (aiType === 'innerAi') generatedData = data.final_result[0].generated; openDocUpsertModal(generatedData, droppedFiles, fileName); hideFullScreenLoading(); // 로딩 화면 숨기기 throw new Error('__STOP_POLLING__'); // 재귀 중단용 에러 } } catch (error) { if (error.message === '__STOP_POLLING__') { throw error; // 재귀 멈추기 위한 예외는 다시 던짐 } else { console.error('polling 중 에러: ', error); handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달 throw new Error('__STOP_POLLING__'); // 재귀 중단용 에러 } } } export const timeoutPolling = (func, timeout, maxAttempts = -1, droppedFiles = [], fileName = '') => { if (maxAttempts === 0) { handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달 return; } setTimeout(async () => { try { await func(); } catch (error) { if (error.message === '__STOP_POLLING__') return; // 성공적 중단 console.error(error); } timeoutPolling(func, timeout, maxAttempts - 1, droppedFiles, fileName); }, timeout); }; // 서버 에러 대비 사용자입력모달 const handleServerError = (droppedFiles, fileName) => { hideFullScreenLoading(); if (confirm('서버와의 연결이 불안정합니다.\n사용자 입력모달로 이동하시겠습니까?')) { setAiUsed(false); openDocUpsertModal({}, droppedFiles, fileName, 'input'); } }; async function getExtractKey() { try { const res = await axios.post(`${docVars.path_name}/getExtractKey`); if (res.data.message === 'getExtractKey_success') { const apiKey = res.data.apiKey; return apiKey; } else { throw new Error('getExtractKey 실패'); } } catch (error) { console.error('getExtractKey error: ', error); return null; } } // ai 응답 기다리면서 뜨는 로딩 화면 function showFullScreenLoading() { document.querySelector('.doc-ai-loading').style.display = 'flex'; } function hideFullScreenLoading() { document.querySelector('.doc-ai-loading').style.display = 'none'; } export async function uploadDocFile(files, mode) { let dateArr = [], resourcePathArr = [], sizeArr = [], objectKeyArr = [], uploadSuccessFileArr = [], uploadFailedFileArr = []; let resourcePath; for (let i = 0; i < files.length; i++) { const file = files[i]; if (mode === 'modal_add') { resourcePath = `/${file.name}`; } else if (mode === 'add_attach') { const parentPathLi = docVars.lastClickedListTarget.closest('li'); const parentPath = parentPathLi.dataset.resourcePath; resourcePath = `${parentPath}__attachment/${file.name}`; } else if (mode === 'exist') { resourcePath = `/${file.name}`; } try { // 1) 서버에 presigned URL 요청 let axiosParams = { resourcePath: resourcePath, date: Date.now(), }; let generateUploadUrlRes = await axios.post(`${docVars.path_name}/generateUploadDocUrl`, axiosParams); if (generateUploadUrlRes.data.message == 'generateUploadDocUrl_success') { let { url, objectKey, date } = generateUploadUrlRes.data.result; // 2) presigned URL로 직접 파일 PUT 전송 await axios.put(url, file, { headers: { 'Content-Type': file.type || 'application/octet-stream', }, }); dateArr.push(date); resourcePathArr.push(resourcePath); sizeArr.push(file.size); objectKeyArr.push(objectKey); uploadSuccessFileArr.push(file.name); } } catch (error) { console.error(`❌ 업로드 실패: ${resourcePath}`, error); uploadFailedFileArr.push(file.name); } } // 업로드 정보 리턴 return { success: resourcePathArr.length > 0, uploadParams: { storageType: docVars.storageType, dateArr: dateArr, resourcePathArr: resourcePathArr, sizeArr: sizeArr, objectKeyArr: objectKeyArr, }, fileArrReset: true, uploadFailedFileArr, uploadSuccessFileArr, }; } // DB 저장 export async function upsertDocData(formData, mode) { try { if (mode === 'modal_edit') { formData.userInfoString = vars.userInfoString; if (!formData.doc_id && docVars.currentDocId) { formData.doc_id = docVars.currentDocId; } } else if (mode === 'exist') { formData.userInfoString = vars.userInfoString; } const res = await axios.post(`${docVars.path_name}/uploadDocData`, { params: formData, mode }); if (res.status != 200) { console.warn(`⚠️ ${mode} 실패`, res.data); } await syncDocInfo(['official', 'attach', null]); await getDocDataBySelected(); } catch (error) { console.error(`❌ ${mode} 요청 중 오류:`, error); throw error; } } export async function syncDocInfo(labels = null) { try { const labelArray = Array.isArray(labels) ? labels : [labels]; const resPromises = labelArray.map((label) => { return axios.get(`${docVars.path_name}/getDocData`, { params: label ? { label } : {}, }); }); const resData = await Promise.all(resPromises); const result = {}; resData.forEach((res, index) => { if (res.data.message === 'getDocData_success') { const label = labelArray[index]; if (label === 'attach') { result.allDocAttachData = res.data.data; } else { result.allDocData = res.data.data; } } }); // 전역 변수에 저장 if (result.allDocOfficialData) docVars.allDocOfficialData = result.allDocOfficialData; if (result.allDocAttachData) docVars.allDocAttachData = result.allDocAttachData; if (result.allDocData) docVars.allDocData = result.allDocData; return result; } catch (error) { console.error('syncDocInfo err ', error); } } export async function getDocDataBySelected(direction) { // console.log('선택된 파라미터:', JSON.stringify(docVars.selectParams, null, 2)); const docCategory = document.querySelector('.official-doc-category.official-doc-tab-on h6')?.innerText.trim(); const categoryValue = docCategory === '전체보기' ? '' : docCategory; try { const params = { projectId: docVars.project_id, companyType: docVars.selectParams.typeOptions, base: docVars.selectParams.baseOptions, target: docVars.selectParams.targetOptions, category: categoryValue, storageType: docVars.storageType, direction: direction, }; const res = await axios.get(`${docVars.path_name}/getDocDataBySelected`, { params: params, }); if (res.data.success) { if (direction == '' || direction == undefined || direction == null) { docVars.allDocOfficialData = res.data.listData; await drawDocList(docVars.allDocOfficialData); getDocCount(res.data.countData); } if (direction == '수신') { docVars.allRecipientListByDirection = res.data.listData await drawDocList(docVars.allRecipientListByDirection, direction); } if (direction == '발신') { docVars.allSendingListByDirection = res.data.listData await drawDocList(docVars.allSendingListByDirection, direction); } } else { console.error('getDocDataBySelected error'); } } catch (error) { console.error('getDocDataBySelected error', error); } } export function getDocCount(data) { const categoryButtons = document.querySelectorAll('.official-doc-category'); // 화면 카테고리에 있는 분류들 이름 모아놓기 const categoryNamesOnPage = []; for (let i = 0; i < categoryButtons.length; i++) { const categoryText = categoryButtons[i].querySelector('.category-text'); categoryNamesOnPage.push(categoryText.innerText); } // 전체 카운트 계산용 let totalCnt = 0; for (let i = 0; i < data.length; i++) { totalCnt += parseInt(data[i].count); } for (let i = 0; i < categoryButtons.length; i++) { const categoryText = categoryButtons[i].querySelector('.category-text'); const categoryName = categoryText.innerText; const categoryCount = categoryButtons[i].querySelector('.category-count'); if (categoryName === '전체보기') { categoryCount.innerText = `(${totalCnt})`; } else { let count = 0; for (let j = 0; j < data.length; j++) { if (data[j].doc_category === categoryName) { count = data[j].count; break; } } categoryCount.innerText = `(${count})`; } } } export async function convertDocPdf(resourcePath, docId) { const selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docId); let objectKey = selectedDoc.object_key; let convertPdfParams = { userInfoString: vars.userInfoString, storageType: vars.storageType, objectKey: objectKey, resourcePath: resourcePath, docId: docId, }; let convertPdfRes = await axios.post(`${docVars.path_name}/convertDocPdf`, { params: convertPdfParams }); // console.log(convertPdfRes); } // 셀렉트박스 회사리스트 DB에서 가져오기 export async function getGroupCompanyData() { try { const res = await axios.get(`${docVars.path_name}/getGroupCompanyData`, docVars.project_id); if (res.data.success) { docVars.groupCompanyData = res.data.data; } else { console.error('getGroupCompanyData err'); } } catch (error) { console.error('getGroupCompanyData err: ', error); } } // console.log(docVars.groupCompanyData); // docVars.groupCompanyData res 예시 // 발주처 : // 기준 : ['SY_JV'] // 상대기관 : ['DPWH'] // 발주처외 : // 기준 : ['삼안'] // 상대기관 : (4) ['서영', '진우', '경동', '한국수출입은행'] /* 컨텍스트메뉴 */ // 수정 // 수정모달 오픈 document.querySelector('#renameTargetDoc')?.addEventListener('click', () => { const selectedDoc = docVars.allDocOfficialData.find((doc) => doc.doc_id === docVars.currentDocId); if (!selectedDoc) { console.error('DB에 없는 데이터'); return; } const transformed = { 공문아이디: selectedDoc.doc_id, 수신처: selectedDoc.recipient_org, 수신처약자: selectedDoc.recipient_org_abbr, 수신자: selectedDoc.recipient_name, 수신자약자: selectedDoc.recipient_name_abbr, 발신처: selectedDoc.sender_org, 발신처약자: selectedDoc.sender_org_abbr, 발신자: selectedDoc.sender_name, 발신자약자: selectedDoc.sender_name_abbr, doc_direction: selectedDoc.doc_direction, 공문번호: selectedDoc.doc_number, 공문유형: selectedDoc.doc_type, 공문종류: selectedDoc.doc_category, 공문제목: selectedDoc.doc_title, 공문제목요약: selectedDoc.doc_title_summary, 공문간연계: selectedDoc.doc_related_docs, 공문일자: selectedDoc.doc_date, 첨부문서수: selectedDoc.attachment_count, 첨부문서제목: selectedDoc.attachment_title, 공문내용요약: selectedDoc.doc_content_summary, doc_manager: selectedDoc.doc_manager, doc_memo: selectedDoc.doc_memo, file_path: selectedDoc.file_path, project_id: selectedDoc.project_id, }; openDocUpsertModal(transformed, null, null, 'edit'); }); // 첨부파일 추가 document.querySelector('.official-doc-contextmenu-item.add-on-file.attach')?.addEventListener('change', async (event) => { let files = Array.from(event.target.files); // FileList를 배열로 변환 const selectedDocElement = document.querySelector('li.main-file-container > div.official-doc-file-info.item-selected')?.parentElement; const currentGroupId = selectedDocElement.dataset.groupId; const formData = { doc_label: 'attach', group_id: currentGroupId, }; let resourcePathArr = []; // 경로 존재 확인을 위해 resourcePathArr 저장 for (let i = 0; i < files.length; i++) { const officialFilePath = docVars.lastClickedListTarget.closest('li').dataset.resourcePath; const attachPrefix = `${officialFilePath}__attachment/`; const file = files[i]; let resourcePath = `${attachPrefix}${file.name}`; resourcePathArr.push(resourcePath); } // resourcePathArr을 사용해서 경로 존재 확인 let checkTargetExistsResult = await checkDocTargetExists('attach', resourcePathArr); // 이미 존재하는 경로만 existingPathArr에 저장 let existingPathArr = checkTargetExistsResult.existingPathArr; // resourcePathArr 초기화 resourcePathArr = []; if (files.length > 0) { const updatedFiles = []; for (let i = 0; i < files.length; i++) { let file = files[i]; const officialFilePath = docVars.lastClickedListTarget.closest('li').dataset.resourcePath; const attachPrefix = `${officialFilePath}__attachment/`; let resourcePath = `${attachPrefix}${file.name}`; let docDirection = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.doc_direction; let recipientOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.recipient_org; let senderOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.sender_org; let objectKey; if (existingPathArr.includes(resourcePath)) { let isUpload = confirm(`'${file.name}' 파일이 이미 존재합니다.\n기존 파일을 새 파일로 교체하시겠습니까?`); if (isUpload) objectKey = checkTargetExistsResult.existingObjectKey.keyMap[resourcePath]; resetViewer(); if (!isUpload) continue; } if (objectKey) { const docId = checkTargetExistsResult.existingDocId.keyMap[resourcePath]; await axios.post(`${docVars.path_name}/deleteDocData`, { docId, objectKey }); } updatedFiles.push(file); } if (updatedFiles.length === 0) { alert('업로드할 파일이 없습니다.'); return; } const uploadResult = await uploadDocFile(updatedFiles, 'add_attach'); if (!uploadResult.success) { alert('파일 업로드에 실패했습니다.'); return; } formData.userInfoString = vars.userInfoString; formData.uploadParams = uploadResult.uploadParams; formData.uploadSuccessFileArr = uploadResult.uploadSuccessFileArr; formData.uploadFailedFileArr = uploadResult.uploadFailedFileArr; await upsertDocData(formData, 'add_attach'); } else { alert('파일없음'); } }); // 첨부파일 이름 변경 let isModalOpen = false; document.querySelector('#renameAttachTargetDoc')?.addEventListener('click', () => { if (checkProjectInactive()) return; openDocRenameModal(); }); function openDocRenameModal() { if (isModalOpen) return; resetRenameModalContent(); isModalOpen = true; const modal = document.querySelector('.official-doc-modal.official-doc-context-modal'); const modalBackground = document.querySelector('.official-doc-modal-background'); modal.style.display = 'flex'; modalBackground.style.display = 'flex'; let selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docVars.currentDocId); const modalHeader = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-header'); const title = document.createElement('h3'); title.textContent = '이름 변경'; modalHeader.appendChild(title); const modalBody = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-body'); const text = document.createElement('div'); text.classList.add('text-wrap'); text.innerHTML = `변경할 파일명을 입력한 후 확인을 눌러주세요.
이름 변경 대상 : ${selectedDoc.file_path}`; modalBody.appendChild(text); const oldName = selectedDoc.file_path.split('/').pop().split('.')[0]; const inputWrap = document.createElement('div'); inputWrap.classList.add('input-wrap'); const input = document.createElement('input'); input.type = 'text'; input.value = oldName; input.spellcheck = false; inputWrap.appendChild(input); modalBody.appendChild(inputWrap); input.focus(); let confirmBtn = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-positive-btn'); let cancelBtn = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-negative-btn'); const removeEventListeners = (oldChild) => { const newChild = oldChild.cloneNode(true); oldChild.parentNode.replaceChild(newChild, oldChild); return newChild; }; confirmBtn = removeEventListeners(confirmBtn); cancelBtn = removeEventListeners(cancelBtn); confirmBtn.addEventListener('click', () => { handleRenameDoc(selectedDoc, inputWrap); }); cancelBtn.addEventListener('click', () => { closeDocRenameModal(); }); } function closeDocRenameModal() { const modal = document.querySelector('.official-doc-modal.official-doc-context-modal'); const modalBackground = document.querySelector('.official-doc-modal-background'); modal.style.display = 'none'; modalBackground.style.display = 'none'; resetRenameModalContent(); isModalOpen = false; } function resetRenameModalContent() { const modalHeader = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-header'); const modalBody = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-body'); const textWrap = modalBody.querySelector('.text-wrap'); const inputWrap = modalBody.querySelector('.input-wrap'); if (modalHeader) modalHeader.innerHTML = ''; if (modalBody) modalBody.innerHTML = ''; if (textWrap) textWrap.innerHTML = ''; if (inputWrap) inputWrap.innerHTML = ''; } async function handleRenameDoc(data, inputWrap) { let oldName = data.file_path.split('/').pop(); let newName = inputWrap.getElementsByTagName('input')[0].value.trim(); let docId = data.doc_id; let ext; let split = splitBaseAndExt(oldName); ext = split.ext; oldName = `${split.base}.${ext}`; newName = `${newName}.${ext}`; let oldPath = data.file_path; let parts = oldPath.split('/'); let topFolderPath = '/' + parts[1]; let newPath = topFolderPath == '/' ? `/${newName}` : `${topFolderPath}/${newName}`; // 기존 경고문구 있으면 삭제 if (document.querySelector('.official-doc-modal.official-doc-context-modal .input-wrap .warn')) { inputWrap.removeChild(document.querySelector('.official-doc-modal.official-doc-context-modal .input-wrap .warn')); } // 경고문구 dom 생성 let warn = document.createElement('div'); warn.classList.add('warn'); warn.style.top = `${inputWrap.offsetHeight}px`; inputWrap.appendChild(warn); let checkTargetExistsResult = await checkDocTargetExists('attach', newPath); // 상황에 따라 경고문구 텍스트 추가 또는 renameTarget 진행 if (!newName || newName == '' || newName == null) { // 빈문자 체크 warn.innerText = `파일명을 입력해주세요.`; } else if (hasSpecialChar(newName)) { // 특수문자 체크 warn.innerHTML = `다음 특수문자는 사용할 수 없습니다.
\\ / : * ? ' " \` < > | #`; } else if (checkTargetExistsResult.isExists) { // 동일이름 여부 체크 let text = `동일한 파일명이 존재합니다.`; warn.innerText = text; } else { if (inputWrap.contains(warn)) { inputWrap.removeChild(warn); } closeDocRenameModal(); let renameTargetParams = { userInfoString: vars.userInfoString, storageType: vars.storageType, resourcePath: data.file_path, newName: newName, oldName: oldName, newPath: newPath, oldPath: oldPath, docId: docId, }; let li = docVars.lastClickedListTarget.closest('li'); let res = await axios.post(`${docVars.path_name}/renameDocTarget`, { params: renameTargetParams }); if (res.data.message == 'renameDocTarget_success') { await syncDocInfo(['official', 'attach', null]); await getDocDataBySelected(); let newLi = document.querySelector(`li[data-doc-id="${li.dataset.docId}"]`); if (newLi) { itemBoxSelected(newLi); await renderDocViewer(newLi.dataset.resourcePath, newLi.dataset.docId); } } } } // 삭제 async function handleDeleteDoc(selectedDoc) { const docId = selectedDoc.doc_id; const groupId = selectedDoc.group_id; const objectKey = selectedDoc.object_key; let fileName = selectedDoc?.file_path.split('/').pop(); const docTitle = selectedDoc?.doc_title; const docLabel = selectedDoc?.doc_label; if (confirm(`정말 삭제하시겠습니까?\n\n[파일 이름] ${fileName}\n[파일 제목] ${docTitle}`)) { try { const res = await axios.post(`${docVars.path_name}/deleteDocData`, { docId, groupId, objectKey }); if (res.data.message === 'delete_success') { alert('삭제가 완료되었습니다.'); await syncDocInfo(['official', 'attach', null]); await getDocDataBySelected(); docVars.selectedDoc = null; // 미리보기 영역 초기화 document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--container').style.display = 'none'; document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--notice').style.display = 'flex'; } else { alert('삭제에 실패했습니다.'); } } catch (error) { console.error('삭제 요청 중 오류 발생:', error); alert('삭제 중 오류가 발생했습니다.'); } } } // 삭제 버튼 클릭 이벤트 async function setupDeleteDocButtons() { if (checkProjectInactive()) return; const buttons = document.querySelectorAll('.official-doc-contextmenu-item.deleteTargetDoc'); buttons.forEach((btn) => { btn.addEventListener('click', async () => { await syncDocInfo(['official', 'attach', null]); const selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docVars.currentDocId); if (selectedDoc) { await handleDeleteDoc(selectedDoc); } }); }); } setupDeleteDocButtons(); // 다운로드 async function handleDownloadDoc() { const selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docVars.currentDocId); if (selectedDoc) { let resourcePath = selectedDoc.file_path; let objectKey = selectedDoc.object_key; let generateDownloadUrlParams = { objectKey: objectKey, resourcePath: resourcePath, }; try { let generateDownloadUrlRes = await axios.post(`${docVars.path_name}/generateDownloadDocUrl`, generateDownloadUrlParams); if (generateDownloadUrlRes.data.message === 'generateDownloadDocUrl_success') { let url = generateDownloadUrlRes.data.url; const a = document.createElement('a'); a.href = url; a.download = resourcePath.split('/').pop(); a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); await new Promise((resolve) => setTimeout(resolve, 300)); // 딜레이 } } catch (error) { console.error('다운로드 URL 생성 중 오류 발생:', error); } } } // 다운로드 버튼 클릭 이벤트 함수 function setupDownloadButton() { if (checkProjectInactive()) return; document.querySelectorAll('.official-doc-contextmenu-item.downloadTargetDoc').forEach((btn) => { btn.addEventListener('click', handleDownloadDoc); }); } setupDownloadButton(); // 이미 존재하는 파일인지 확인 async function checkDocTargetExists(docLabel, resourcePathArr) { let result = {}; if (!Array.isArray(resourcePathArr)) resourcePathArr = [resourcePathArr]; let checkTargetExistsParams = { storageType: docVars.storageType, docLabel: docLabel, resourcePathArr: JSON.stringify(resourcePathArr), }; let checkTargetExistsRes = await axios.post(`${docVars.path_name}/checkDocTargetExists`, { params: checkTargetExistsParams }); if (checkTargetExistsRes.data.message == 'checkDocTargetExists_success') { let rows = checkTargetExistsRes.data.rows; result.existingPathArr = []; if (rows.length == 0) { result.isExists = false; } else { result.rows = rows; result.isExists = true; result.existingPathArr = filterFilePath(rows); result.existingObjectKey = filterObjectKey(rows); result.existingDocId = filterDocId(rows); } return result; } } function filterFilePath(rows) { if (!Array.isArray(rows)) rows = [rows]; let result = []; for (let i = 0; i < rows.length; i++) { for (let j = 0; j < rows[i].rows.length; j++) { result.push(rows[i].rows[j].file_path); } } return result; } function filterObjectKey(rows) { if (!Array.isArray(rows)) rows = [rows]; let result = []; result.keyMap = {}; for (let i = 0; i < rows.length; i++) { for (let j = 0; j < rows[i].rows.length; j++) { const file_path = rows[i].rows[j].file_path; const object_key = rows[i].rows[j].object_key; result.keyMap[file_path] = object_key; } } return result; } function filterDocId(rows) { if (!Array.isArray(rows)) rows = [rows]; let result = []; result.keyMap = {}; for (let i = 0; i < rows.length; i++) { for (let j = 0; j < rows[i].rows.length; j++) { const file_path = rows[i].rows[j].file_path; const doc_id = rows[i].rows[j].doc_id; result.keyMap[file_path] = doc_id; } } return result; }