한글뷰어 기능수정
This commit is contained in:
@@ -760,6 +760,7 @@
|
||||
<th>카테고리</th>
|
||||
<th>용량 제한</th>
|
||||
<th>상태</th>
|
||||
<th style="width: 70px;">과업개요</th>
|
||||
<th>관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1004,10 +1005,10 @@
|
||||
<h3 class="card-title">🔎 시스템 활동 로그 조회 (tb_log)</h3>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="text" class="text-input" id="search-log-user" placeholder="사용자 ID 검색...">
|
||||
<input type="text" class="text-input" id="search-log-project" placeholder="프로젝트명 검색...">
|
||||
<input type="text" class="text-input" id="filter-log-action" placeholder="조작 액션 검색...">
|
||||
<button class="btn btn-secondary" onclick="renderAuditLogs()">활동 로그 필터링</button>
|
||||
<input type="text" class="text-input" id="search-log-user" placeholder="사용자 ID 검색..." onkeyup="if(event.key === 'Enter') renderAuditLogs()">
|
||||
<input type="text" class="text-input" id="search-log-project" placeholder="프로젝트명 검색..." onkeyup="if(event.key === 'Enter') renderAuditLogs()">
|
||||
<input type="text" class="text-input" id="filter-log-action" placeholder="조작 액션 검색..." onkeyup="if(event.key === 'Enter') renderAuditLogs()">
|
||||
<button class="btn btn-secondary" onclick="renderAuditLogs()">검색</button>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table class="admin-table">
|
||||
@@ -1194,11 +1195,11 @@
|
||||
<option value="overseas">해외 프로젝트 (overseas)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-project-storage">스토리지 제한 (GB)</label>
|
||||
<input type="number" class="text-input" id="form-project-storage" value="10" min="1" max="1000">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="form-project-storage">스토리지 제한 (GB)</label>
|
||||
<input type="number" class="text-input" id="form-project-storage" value="10" min="1" max="1000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-project-active">운영 상태</label>
|
||||
<select class="select-input" id="form-project-active">
|
||||
@@ -1206,6 +1207,13 @@
|
||||
<option value="false">일시잠금 (Inactive)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-project-overview">과업개요 여부</label>
|
||||
<select class="select-input" id="form-project-overview">
|
||||
<option value="true" selected>사용 (True)</option>
|
||||
<option value="false">미사용 (False)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 10px;">
|
||||
<button class="btn btn-secondary" type="button" onclick="closeProjectModal()">취소</button>
|
||||
@@ -1616,6 +1624,7 @@
|
||||
<td>${p.category_nm || p.category || '-'}</td>
|
||||
<td>${p.storage_byte ? (Number(p.storage_byte) / (1024*1024*1024)).toFixed(0) + ' GB' : '0 GB'}</td>
|
||||
<td><span class="badge ${p.is_active ? 'active' : 'inactive'}">${p.is_active ? '활성' : '비활성'}</span></td>
|
||||
<td><span class="badge ${p.overview !== false ? 'active' : 'inactive'}">${p.overview !== false ? '사용' : '미사용'}</span></td>
|
||||
<td>
|
||||
<div class="action-btns" onclick="event.stopPropagation();">
|
||||
<button class="btn btn-secondary btn-sm" onclick="openProjectModal('edit', '${p.project_id}')">수정</button>
|
||||
@@ -1831,6 +1840,7 @@
|
||||
document.getElementById('form-project-id').removeAttribute('readonly');
|
||||
document.getElementById('form-project-id').disabled = false;
|
||||
document.getElementById('project-submit-btn').innerText = '등록 하기';
|
||||
document.getElementById('form-project-overview').value = 'true';
|
||||
form.onsubmit = submitCreateProject;
|
||||
} else {
|
||||
document.getElementById('project-modal-title').innerText = '📝 프로젝트 상세 정보 수정';
|
||||
@@ -1848,6 +1858,7 @@
|
||||
document.getElementById('form-project-category').value = p.category || '';
|
||||
document.getElementById('form-project-storage').value = p.storage_byte ? (Number(p.storage_byte) / (1024*1024*1024)).toFixed(0) : 10;
|
||||
document.getElementById('form-project-active').value = p.is_active ? 'true' : 'false';
|
||||
document.getElementById('form-project-overview').value = p.overview !== false ? 'true' : 'false';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -1869,7 +1880,8 @@
|
||||
short_nm: document.getElementById('form-project-short').value.trim(),
|
||||
category: document.getElementById('form-project-category').value,
|
||||
limit_storage: Number(document.getElementById('form-project-storage').value),
|
||||
is_active: document.getElementById('form-project-active').value === 'true'
|
||||
is_active: document.getElementById('form-project-active').value === 'true',
|
||||
overview: document.getElementById('form-project-overview').value === 'true'
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1893,7 +1905,8 @@
|
||||
short_nm: document.getElementById('form-project-short').value.trim(),
|
||||
category: document.getElementById('form-project-category').value,
|
||||
limit_storage: Number(document.getElementById('form-project-storage').value),
|
||||
is_active: document.getElementById('form-project-active').value === 'true'
|
||||
is_active: document.getElementById('form-project-active').value === 'true',
|
||||
overview: document.getElementById('form-project-overview').value === 'true'
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -2147,6 +2160,7 @@
|
||||
document.getElementById('form-user-id').removeAttribute('readonly');
|
||||
document.getElementById('form-user-id').disabled = false;
|
||||
document.getElementById('form-user-pw').required = true;
|
||||
document.getElementById('form-user-pw').placeholder = '••••••••';
|
||||
pwRow.style.display = 'flex'; // PW 보이기
|
||||
document.getElementById('user-submit-btn').innerText = '등록 하기';
|
||||
form.onsubmit = submitCreateUser;
|
||||
@@ -2155,9 +2169,10 @@
|
||||
document.getElementById('form-user-id').setAttribute('readonly', 'true');
|
||||
document.getElementById('form-user-id').disabled = true;
|
||||
|
||||
// 패스워드 입력칸 숨기기 및 필수조건 해제
|
||||
// 패스워드 필수조건 해제 및 노출 유지 (공백 시 미수정)
|
||||
document.getElementById('form-user-pw').required = false;
|
||||
pwRow.style.display = 'none';
|
||||
document.getElementById('form-user-pw').placeholder = '변경할 비밀번호 입력 (공백 시 유지)';
|
||||
pwRow.style.display = 'flex';
|
||||
|
||||
document.getElementById('user-submit-btn').innerText = '수정 하기';
|
||||
|
||||
@@ -2216,6 +2231,7 @@
|
||||
async function submitEditUser(event, userId) {
|
||||
event.preventDefault();
|
||||
const payload = {
|
||||
user_pw: document.getElementById('form-user-pw').value,
|
||||
user_nm: document.getElementById('form-user-nm').value.trim(),
|
||||
company: document.getElementById('form-user-company').value.trim(),
|
||||
dept: document.getElementById('form-user-dept').value.trim(),
|
||||
|
||||
@@ -370,7 +370,7 @@ export async function openNewWindowViewer() {
|
||||
case 'pdf' :
|
||||
case 'doc' :
|
||||
case 'ppt' :
|
||||
case 'pptx' :
|
||||
case 'pptx':
|
||||
case 'dwg' :
|
||||
case 'dxf' :
|
||||
case 'grm' :
|
||||
|
||||
@@ -94,6 +94,22 @@ async function loadSystemPolicy() {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSystemPolicyCache(policy) {
|
||||
if (policy) {
|
||||
FOLDER_KEEP_POLICY_ACTIVE = policy.is_active ?? false;
|
||||
if (FOLDER_KEEP_POLICY_ACTIVE) {
|
||||
FOLDER_KEEP_FILE_THRESHOLD = Number(policy.limit_file_count) || 3;
|
||||
FOLDER_KEEP_DAYS_THRESHOLD = Number(policy.limit_days) || 15;
|
||||
} else {
|
||||
FOLDER_KEEP_FILE_THRESHOLD = 3;
|
||||
FOLDER_KEEP_DAYS_THRESHOLD = 15;
|
||||
}
|
||||
} else {
|
||||
isPolicyLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 브라우저 뒤로가기, 앞으로가기 이벤트
|
||||
window.addEventListener('popstate', async (e)=>{
|
||||
// console.log(e);
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
changeHeaderBtnStyle,
|
||||
changeTreeItemStyle,
|
||||
changeListItemStyle,
|
||||
updateSystemPolicyCache,
|
||||
} from './pageRenderer.js';
|
||||
import { toggleModal } from './modalManager.js'
|
||||
import { mgmtFunc_addClickLog } from './managementFunctions.js';
|
||||
@@ -326,6 +327,27 @@ socket.on('popupNotice', (data)=>{
|
||||
alert(text);
|
||||
})
|
||||
|
||||
//// 보관 및 삭제 정책 변경 실시간 반영
|
||||
socket.on('updateSystemPolicy_success', async (policy) => {
|
||||
// 정책 캐시 갱신
|
||||
updateSystemPolicyCache(policy);
|
||||
|
||||
// 트리 화면 갱신 (D-Day 타이머 갱신을 위해)
|
||||
let userCurPath = getMyCurPath();
|
||||
if (userCurPath) {
|
||||
let pathSplit = userCurPath.split('/');
|
||||
let extractedPath = extractPathByLength(pathSplit, 1);
|
||||
let pageRanderingOption = {
|
||||
scope: 'tree',
|
||||
resourcePath: extractedPath,
|
||||
userCurPath: userCurPath,
|
||||
pushState: false,
|
||||
debug: '정책 변경 실시간 갱신 - tree'
|
||||
};
|
||||
await preparePageRendering(pageRanderingOption);
|
||||
}
|
||||
})
|
||||
|
||||
//// 강제 로그아웃
|
||||
socket.on('forcedLogout', () => {
|
||||
alert('프로젝트 재시작으로 인해 자동으로 로그아웃됩니다.\n다시 로그인 후 사용해주세요.');
|
||||
@@ -441,15 +463,16 @@ socket.on('addConvetPdfLog_success', async (resultData) => {
|
||||
socket.on('convertPdf_failed', async (resultData) => {
|
||||
console.log('-------- convertPdf_failed');
|
||||
console.log(resultData);
|
||||
let resourcePath = (resultData.jobData.resourcePath) ? resultData.jobData.resourcePath : resultData.jobProgress.resourcePath;
|
||||
let dataId = (resultData.jobData.dataId) ? resultData.jobData.dataId : resultData.jobProgress.dataId;
|
||||
let userInfoString = (resultData.jobData.userInfoString) ? resultData.jobData.userInfoString : resultData.jobProgress.userInfoString;
|
||||
// 서버의 convertingDataArr에서 변환 실패한 파일 정보 삭제
|
||||
let resourcePath = (resultData.jobData && resultData.jobData.resourcePath) ? resultData.jobData.resourcePath : (resultData.jobProgress ? resultData.jobProgress.resourcePath : '');
|
||||
let dataId = (resultData.jobData && resultData.jobData.dataId) ? resultData.jobData.dataId : (resultData.jobProgress ? resultData.jobProgress.dataId : '');
|
||||
let userInfoString = (resultData.jobData && resultData.jobData.userInfoString) ? resultData.jobData.userInfoString : (resultData.jobProgress ? resultData.jobProgress.userInfoString : '');
|
||||
let failedReason = resultData.failedReason || '';
|
||||
|
||||
let removeConvertingDataParams = {
|
||||
resourcePath: resourcePath,
|
||||
dataId: dataId,
|
||||
userInfoString: userInfoString
|
||||
userInfoString: userInfoString,
|
||||
stdout: failedReason
|
||||
}
|
||||
let removeConvertingDataResult = await axios.post(`${vars.path_name}/removeConvertingData`, { params: removeConvertingDataParams });
|
||||
console.log(removeConvertingDataResult);
|
||||
|
||||
@@ -573,7 +573,8 @@ export async function renderDocViewer(resourcePath, docId) {
|
||||
let excelDirectArr = ['xls', 'xlsx', 'xlsm'];
|
||||
let hwpDirectArr = ['hwp', 'hwpx'];
|
||||
let wordDirectArr = ['docx'];
|
||||
let isDirectView = excelDirectArr.includes(ext) || hwpDirectArr.includes(ext) || wordDirectArr.includes(ext);
|
||||
let pptxDirectArr = [];
|
||||
let isDirectView = excelDirectArr.includes(ext) || hwpDirectArr.includes(ext) || wordDirectArr.includes(ext) || pptxDirectArr.includes(ext);
|
||||
|
||||
let selectedDoc = docVars.allDocData?.find((doc) => doc.doc_id === docId);
|
||||
let previewKey = selectedDoc?.preview_key;
|
||||
@@ -617,12 +618,13 @@ export async function renderDocViewer(resourcePath, docId) {
|
||||
let threeArr = ['glb', 'gltf', 'obj', 'stl', 'fbx', '3dm'];
|
||||
let allArr = [...pdfArr, ...gsimArr, ...ifcArr, ...imageArr, ...videoArr, ...textArr, ...urlArr, ...zipArr, ...threeArr];
|
||||
if (allArr.includes(ext)) {
|
||||
let pdfArrFiltered = pdfArr.filter(e => !excelDirectArr.includes(e) && !hwpDirectArr.includes(e) && !wordDirectArr.includes(e));
|
||||
let pdfArrFiltered = pdfArr.filter(e => !excelDirectArr.includes(e) && !hwpDirectArr.includes(e) && !wordDirectArr.includes(e) && !pptxDirectArr.includes(e));
|
||||
|
||||
if (pdfArrFiltered.includes(ext)) viewerPdf(PresignedUrl);
|
||||
if (excelDirectArr.includes(ext)) viewerExcel(PresignedUrl);
|
||||
if (hwpDirectArr.includes(ext)) viewerHwp(PresignedUrl);
|
||||
if (wordDirectArr.includes(ext)) viewerWord(PresignedUrl);
|
||||
if (pptxDirectArr.includes(ext)) viewerPptx(PresignedUrl);
|
||||
if (gsimArr.includes(ext)) viewerGsim(PresignedUrl);
|
||||
if (ifcArr.includes(ext)) viewerIfc(PresignedUrl);
|
||||
if (threeArr.includes(ext)) viewer3d(PresignedUrl);
|
||||
@@ -860,18 +862,54 @@ export async function renderDocViewer(resourcePath, docId) {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '100%';
|
||||
container.style.height = '100%';
|
||||
container.style.overflow = 'auto';
|
||||
container.style.overflowX = 'hidden';
|
||||
container.style.overflowY = 'auto';
|
||||
container.style.padding = '20px';
|
||||
container.style.boxSizing = 'border-box';
|
||||
container.style.background = '#f5f5f5';
|
||||
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.textContent = `
|
||||
.hwp-inner-container {
|
||||
background: #ffffff;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
padding: 30px !important;
|
||||
box-sizing: border-box !important;
|
||||
min-height: 100%;
|
||||
}
|
||||
.hwp-inner-container > div > div {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
box-sizing: border-box !important;
|
||||
padding-left: 20px !important;
|
||||
padding-right: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
.hwp-inner-container table {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
table-layout: fixed !important;
|
||||
}
|
||||
.hwp-inner-container img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.hwp-inner-container {
|
||||
padding: 10px !important;
|
||||
}
|
||||
.hwp-inner-container > div > div {
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
container.appendChild(styleEl);
|
||||
|
||||
const hwpInner = document.createElement('div');
|
||||
hwpInner.style.background = '#ffffff';
|
||||
hwpInner.style.margin = '0 auto';
|
||||
hwpInner.style.maxWidth = '800px';
|
||||
hwpInner.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
||||
hwpInner.style.padding = '40px';
|
||||
hwpInner.style.minHeight = '100%';
|
||||
hwpInner.classList.add('hwp-inner-container');
|
||||
|
||||
container.appendChild(hwpInner);
|
||||
docVars.viewer.appendChild(container);
|
||||
@@ -896,6 +934,281 @@ export async function renderDocViewer(resourcePath, docId) {
|
||||
docVars.viewer.dataset.viewerType = 'hwp';
|
||||
}
|
||||
|
||||
function viewerPptx(presignedUrl) {
|
||||
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">PPTX 문서를 불러오는 중...</div>';
|
||||
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
||||
|
||||
fetch(presignedUrl)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('PPTX fetch failed');
|
||||
return res.arrayBuffer();
|
||||
})
|
||||
.then(async (arrayBuffer) => {
|
||||
try {
|
||||
const zip = await JSZip.loadAsync(arrayBuffer);
|
||||
|
||||
// Read presentation.xml to get slide size
|
||||
const presentationXmlText = await zip.file("ppt/presentation.xml").async("text");
|
||||
const parser = new DOMParser();
|
||||
const presDoc = parser.parseFromString(presentationXmlText, "text/xml");
|
||||
const sldSz = presDoc.getElementsByTagName("p:sldSz")[0];
|
||||
const cx = sldSz ? (parseInt(sldSz.getAttribute("cx"), 10) || 12192000) : 12192000;
|
||||
const cy = sldSz ? (parseInt(sldSz.getAttribute("cy"), 10) || 6858000) : 6858000;
|
||||
const ratio = (cy / cx) * 100;
|
||||
|
||||
// Get slide files
|
||||
const slideFiles = Object.keys(zip.files).filter(name => name.startsWith("ppt/slides/slide") && name.endsWith(".xml"));
|
||||
slideFiles.sort((a, b) => {
|
||||
const numA = parseInt(a.replace("ppt/slides/slide", "").replace(".xml", ""), 10);
|
||||
const numB = parseInt(b.replace("ppt/slides/slide", "").replace(".xml", ""), 10);
|
||||
return numA - numB;
|
||||
});
|
||||
|
||||
docVars.viewer.innerHTML = '';
|
||||
|
||||
const slidesContainer = document.createElement('div');
|
||||
slidesContainer.style.display = 'flex';
|
||||
slidesContainer.style.flexDirection = 'column';
|
||||
slidesContainer.style.gap = '20px';
|
||||
slidesContainer.style.alignItems = 'center';
|
||||
slidesContainer.style.background = '#f0f0f0';
|
||||
slidesContainer.style.padding = '20px';
|
||||
slidesContainer.style.width = '100%';
|
||||
slidesContainer.style.height = '100%';
|
||||
slidesContainer.style.overflow = 'auto';
|
||||
slidesContainer.style.boxSizing = 'border-box';
|
||||
docVars.viewer.appendChild(slidesContainer);
|
||||
|
||||
for (let i = 0; i < slideFiles.length; i++) {
|
||||
const slideXmlText = await zip.file(slideFiles[i]).async("text");
|
||||
const slideDoc = parser.parseFromString(slideXmlText, "text/xml");
|
||||
|
||||
const slideCard = document.createElement('div');
|
||||
slideCard.className = 'pptx-slide-card';
|
||||
slideCard.style.position = 'relative';
|
||||
slideCard.style.width = '100%';
|
||||
slideCard.style.maxWidth = '800px';
|
||||
slideCard.style.backgroundColor = '#ffffff';
|
||||
slideCard.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
||||
slideCard.style.height = '0';
|
||||
slideCard.style.paddingTop = ratio + '%';
|
||||
slideCard.style.overflow = 'hidden';
|
||||
slideCard.style.flexShrink = '0';
|
||||
|
||||
const slideContent = document.createElement('div');
|
||||
slideContent.style.position = 'absolute';
|
||||
slideContent.style.top = '0';
|
||||
slideContent.style.left = '0';
|
||||
slideContent.style.width = '100%';
|
||||
slideContent.style.height = '100%';
|
||||
slideCard.appendChild(slideContent);
|
||||
slidesContainer.appendChild(slideCard);
|
||||
|
||||
// Parse relationships for this slide
|
||||
const relMap = {};
|
||||
try {
|
||||
const slideName = slideFiles[i].split('/').pop();
|
||||
const relsFileName = `ppt/slides/_rels/${slideName}.rels`;
|
||||
const relsFile = zip.file(relsFileName);
|
||||
if (relsFile) {
|
||||
const relsXmlText = await relsFile.async("text");
|
||||
const relsDoc = parser.parseFromString(relsXmlText, "text/xml");
|
||||
const relationships = relsDoc.getElementsByTagName("Relationship");
|
||||
for (let r = 0; r < relationships.length; r++) {
|
||||
const id = relationships[r].getAttribute("Id");
|
||||
const target = relationships[r].getAttribute("Target");
|
||||
relMap[id] = target;
|
||||
}
|
||||
}
|
||||
} catch (relErr) {
|
||||
console.warn("Failed to parse relationships for slide:", slideFiles[i], relErr);
|
||||
}
|
||||
|
||||
const elements = slideDoc.querySelectorAll('p\\:sp, sp, p\\:pic, pic, p\\:graphicFrame, graphicFrame');
|
||||
|
||||
for (const elem of elements) {
|
||||
const xfrm = elem.querySelector('a\\:xfrm, xfrm');
|
||||
if (!xfrm) continue;
|
||||
|
||||
const off = xfrm.querySelector('a\\:off, off');
|
||||
const ext = xfrm.querySelector('a\\:ext, ext');
|
||||
if (!off || !ext) continue;
|
||||
|
||||
const x = parseInt(off.getAttribute('x'), 10);
|
||||
const y = parseInt(off.getAttribute('y'), 10);
|
||||
const w = parseInt(ext.getAttribute('cx'), 10);
|
||||
const h = parseInt(ext.getAttribute('cy'), 10);
|
||||
|
||||
const leftPct = (x / cx) * 100;
|
||||
const topPct = (y / cy) * 100;
|
||||
const widthPct = (w / cx) * 100;
|
||||
const heightPct = (h / cy) * 100;
|
||||
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.style.position = 'absolute';
|
||||
itemDiv.style.left = leftPct + '%';
|
||||
itemDiv.style.top = topPct + '%';
|
||||
itemDiv.style.width = widthPct + '%';
|
||||
itemDiv.style.height = heightPct + '%';
|
||||
itemDiv.style.boxSizing = 'border-box';
|
||||
|
||||
const nodeName = elem.nodeName.toLowerCase();
|
||||
if (nodeName.includes('pic')) {
|
||||
let imgUrl = null;
|
||||
try {
|
||||
const blip = elem.querySelector('a\\:blip, blip');
|
||||
const rId = blip ? (blip.getAttribute('r:embed') || blip.getAttribute('embed')) : null;
|
||||
if (rId && relMap[rId]) {
|
||||
const targetPath = relMap[rId].replace('../', 'ppt/');
|
||||
const imgFile = zip.file(targetPath);
|
||||
if (imgFile) {
|
||||
const imgBlob = await imgFile.async("blob");
|
||||
imgUrl = URL.createObjectURL(imgBlob);
|
||||
}
|
||||
}
|
||||
} catch (imgErr) {
|
||||
console.warn("Failed to extract slide image:", imgErr);
|
||||
}
|
||||
|
||||
if (imgUrl) {
|
||||
itemDiv.style.backgroundImage = `url("${imgUrl}")`;
|
||||
itemDiv.style.backgroundRepeat = 'no-repeat';
|
||||
itemDiv.style.backgroundPosition = 'center';
|
||||
itemDiv.style.backgroundSize = 'contain';
|
||||
} else {
|
||||
itemDiv.style.border = '1px dashed #cccccc';
|
||||
itemDiv.style.backgroundColor = '#f9f9f9';
|
||||
itemDiv.style.display = 'flex';
|
||||
itemDiv.style.alignItems = 'center';
|
||||
itemDiv.style.justifyContent = 'center';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.style.color = '#999999';
|
||||
label.style.fontSize = '10px';
|
||||
label.style.fontWeight = 'bold';
|
||||
label.textContent = '[그림 영역]';
|
||||
itemDiv.appendChild(label);
|
||||
}
|
||||
} else if (nodeName.includes('graphicframe')) {
|
||||
const tbl = elem.querySelector('a\\:tbl, tbl');
|
||||
if (tbl) {
|
||||
const htmlTable = document.createElement('table');
|
||||
htmlTable.style.width = '100%';
|
||||
htmlTable.style.height = '100%';
|
||||
htmlTable.style.borderCollapse = 'collapse';
|
||||
htmlTable.style.fontSize = 'calc(0.4vw + 5px)';
|
||||
htmlTable.style.fontFamily = 'sans-serif';
|
||||
htmlTable.style.backgroundColor = '#ffffff';
|
||||
htmlTable.style.boxShadow = '0 1px 3px rgba(0,0,0,0.05)';
|
||||
|
||||
const rows = tbl.querySelectorAll('a\\:tr, tr');
|
||||
rows.forEach((row, rIdx) => {
|
||||
const trEl = document.createElement('tr');
|
||||
if (rIdx === 0) {
|
||||
trEl.style.backgroundColor = '#f8f9fa';
|
||||
trEl.style.fontWeight = '600';
|
||||
} else if (rIdx % 2 === 0) {
|
||||
trEl.style.backgroundColor = '#fafafa';
|
||||
}
|
||||
|
||||
const cells = row.querySelectorAll('a\\:tc, tc');
|
||||
cells.forEach(cell => {
|
||||
const tdEl = document.createElement('td');
|
||||
tdEl.style.border = '1px solid #e0e0e0';
|
||||
tdEl.style.padding = '4px 6px';
|
||||
tdEl.style.wordBreak = 'break-all';
|
||||
tdEl.style.verticalAlign = 'middle';
|
||||
|
||||
const gridSpan = cell.getAttribute('gridSpan');
|
||||
if (gridSpan) tdEl.setAttribute('colspan', gridSpan);
|
||||
const rowSpan = cell.getAttribute('rowSpan');
|
||||
if (rowSpan) tdEl.setAttribute('rowspan', rowSpan);
|
||||
|
||||
const txBody = cell.querySelector('a\\:txBody, txBody');
|
||||
if (txBody) {
|
||||
const paragraphs = txBody.querySelectorAll('a\\:p, p');
|
||||
paragraphs.forEach(p => {
|
||||
const runs = p.querySelectorAll('a\\:r, r');
|
||||
let cellText = '';
|
||||
runs.forEach(r => {
|
||||
const t = r.querySelector('a\\:t, t');
|
||||
if (t) cellText += t.textContent;
|
||||
});
|
||||
if (cellText.trim()) {
|
||||
const pEl = document.createElement('p');
|
||||
pEl.style.margin = '0';
|
||||
pEl.style.lineHeight = '1.2';
|
||||
pEl.textContent = cellText;
|
||||
tdEl.appendChild(pEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
trEl.appendChild(tdEl);
|
||||
});
|
||||
htmlTable.appendChild(trEl);
|
||||
});
|
||||
itemDiv.appendChild(htmlTable);
|
||||
} else {
|
||||
itemDiv.style.border = '1px dashed #dddddd';
|
||||
itemDiv.style.backgroundColor = '#fdfdfd';
|
||||
itemDiv.style.display = 'flex';
|
||||
itemDiv.style.alignItems = 'center';
|
||||
itemDiv.style.justifyContent = 'center';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.style.color = '#aaaaaa';
|
||||
label.style.fontSize = '10px';
|
||||
label.style.fontWeight = 'bold';
|
||||
label.textContent = '[차트 영역]';
|
||||
itemDiv.appendChild(label);
|
||||
}
|
||||
} else {
|
||||
const txBody = elem.querySelector('p\\:txBody, txBody');
|
||||
if (txBody) {
|
||||
itemDiv.style.overflow = 'hidden';
|
||||
itemDiv.style.wordBreak = 'break-all';
|
||||
itemDiv.style.fontSize = 'calc(0.5vw + 5px)';
|
||||
itemDiv.style.fontFamily = 'sans-serif';
|
||||
itemDiv.style.color = '#333333';
|
||||
|
||||
const paragraphs = txBody.querySelectorAll('a\\:p, p');
|
||||
paragraphs.forEach(p => {
|
||||
const runs = p.querySelectorAll('a\\:r, r');
|
||||
let paraText = '';
|
||||
runs.forEach(r => {
|
||||
const t = r.querySelector('a\\:t, t');
|
||||
if (t) paraText += t.textContent;
|
||||
});
|
||||
|
||||
if (paraText.trim()) {
|
||||
const pEl = document.createElement('p');
|
||||
pEl.style.margin = '0 0 2px 0';
|
||||
pEl.style.lineHeight = '1.2';
|
||||
pEl.textContent = paraText;
|
||||
itemDiv.appendChild(pEl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
itemDiv.style.border = '1px solid #eeeeee';
|
||||
itemDiv.style.backgroundColor = 'rgba(0,0,0,0.01)';
|
||||
}
|
||||
}
|
||||
slideContent.appendChild(itemDiv);
|
||||
}
|
||||
}
|
||||
} catch (parseErr) {
|
||||
console.error("PPTX parse error:", parseErr);
|
||||
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;padding:20px;text-align:center;">PPTX 파싱 중 에러가 발생했습니다. 상단의 "PDF로 보기" 버튼을 이용해 주세요.</div>';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">PPTX 문서를 불러오는데 실패했습니다.</div>';
|
||||
});
|
||||
|
||||
docVars.viewer.dataset.viewerType = 'pptx';
|
||||
}
|
||||
|
||||
async function viewerPdf(PresignedUrl) {
|
||||
resetViewer();
|
||||
|
||||
@@ -1222,7 +1535,11 @@ document.querySelector('.official-doc-main .official-doc-preview .official-doc-p
|
||||
//Presigned URL
|
||||
let PresignedUrl;
|
||||
await syncDocInfo(['official', 'attach', null]);
|
||||
let objectKey = docVars.allDocData?.find((doc) => doc.doc_id === docId)?.preview_key;
|
||||
|
||||
let directViewExtArr = ['xls', 'xlsx', 'xlsm', 'docx', 'hwp', 'hwpx'];
|
||||
let selectedDoc = docVars.allDocData?.find((doc) => doc.doc_id === docId);
|
||||
let objectKey = directViewExtArr.includes(ext) ? selectedDoc?.object_key : selectedDoc?.preview_key;
|
||||
|
||||
if (objectKey == undefined || objectKey == `` || objectKey == null) {
|
||||
return;
|
||||
}
|
||||
@@ -1250,19 +1567,26 @@ document.querySelector('.official-doc-main .official-doc-preview .official-doc-p
|
||||
let open_ext = `pdf`;
|
||||
switch (ext) {
|
||||
case 'pdf':
|
||||
case 'hwp':
|
||||
case 'hwpx':
|
||||
case 'xls':
|
||||
case 'xlsm':
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
case 'dwg':
|
||||
case 'dxf':
|
||||
case 'grm':
|
||||
open_ext = 'pdf';
|
||||
break;
|
||||
case 'hwp':
|
||||
case 'hwpx':
|
||||
open_ext = ext;
|
||||
break;
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
case 'xlsm':
|
||||
open_ext = ext;
|
||||
break;
|
||||
case 'docx':
|
||||
open_ext = ext;
|
||||
break;
|
||||
case 'gsim':
|
||||
open_ext = 'gsim';
|
||||
break;
|
||||
|
||||
@@ -44,6 +44,9 @@ if(data && Object.keys(data).length>0 && (data.$type == 'text'|| data.type == 't
|
||||
case 'docx':
|
||||
_openDocx(fullPath, data);
|
||||
break;
|
||||
case 'pptx':
|
||||
_openPptx(fullPath, data);
|
||||
break;
|
||||
case 'hwp':
|
||||
case 'hwpx':
|
||||
_openHwp(fullPath, data);
|
||||
@@ -812,18 +815,54 @@ function _openHwp(path, data) {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '100%';
|
||||
container.style.height = '100%';
|
||||
container.style.overflow = 'auto';
|
||||
container.style.overflowX = 'hidden';
|
||||
container.style.overflowY = 'auto';
|
||||
container.style.padding = '20px';
|
||||
container.style.boxSizing = 'border-box';
|
||||
container.style.background = '#f5f5f5';
|
||||
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.textContent = `
|
||||
.hwp-inner-container {
|
||||
background: #ffffff;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
padding: 30px !important;
|
||||
box-sizing: border-box !important;
|
||||
min-height: 100%;
|
||||
}
|
||||
.hwp-inner-container > div > div {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
box-sizing: border-box !important;
|
||||
padding-left: 20px !important;
|
||||
padding-right: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
.hwp-inner-container table {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
table-layout: fixed !important;
|
||||
}
|
||||
.hwp-inner-container img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.hwp-inner-container {
|
||||
padding: 10px !important;
|
||||
}
|
||||
.hwp-inner-container > div > div {
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
container.appendChild(styleEl);
|
||||
|
||||
const hwpInner = document.createElement('div');
|
||||
hwpInner.style.background = '#ffffff';
|
||||
hwpInner.style.margin = '0 auto';
|
||||
hwpInner.style.maxWidth = '800px';
|
||||
hwpInner.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
||||
hwpInner.style.padding = '40px';
|
||||
hwpInner.style.minHeight = '100%';
|
||||
hwpInner.classList.add('hwp-inner-container');
|
||||
|
||||
container.appendChild(hwpInner);
|
||||
viewer.appendChild(container);
|
||||
@@ -844,4 +883,281 @@ function _openHwp(path, data) {
|
||||
console.error(err);
|
||||
viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">한글 문서를 불러오는데 실패했습니다.</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function _openPptx(path, data) {
|
||||
const viewer = document.getElementById('popup_viewer');
|
||||
viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">PPTX 문서를 불러오는 중...</div>';
|
||||
|
||||
if (dataId && path_name) {
|
||||
initFallbackPdfButton(dataId, path_name, resourcePath);
|
||||
}
|
||||
|
||||
fetch(path)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('PPTX fetch failed');
|
||||
return res.arrayBuffer();
|
||||
})
|
||||
.then(async (arrayBuffer) => {
|
||||
try {
|
||||
const zip = await JSZip.loadAsync(arrayBuffer);
|
||||
|
||||
// Read presentation.xml to get slide size
|
||||
const presentationXmlText = await zip.file("ppt/presentation.xml").async("text");
|
||||
const parser = new DOMParser();
|
||||
const presDoc = parser.parseFromString(presentationXmlText, "text/xml");
|
||||
const sldSz = presDoc.getElementsByTagName("p:sldSz")[0];
|
||||
const cx = sldSz ? (parseInt(sldSz.getAttribute("cx"), 10) || 12192000) : 12192000;
|
||||
const cy = sldSz ? (parseInt(sldSz.getAttribute("cy"), 10) || 6858000) : 6858000;
|
||||
const ratio = (cy / cx) * 100;
|
||||
|
||||
// Get slide files
|
||||
const slideFiles = Object.keys(zip.files).filter(name => name.startsWith("ppt/slides/slide") && name.endsWith(".xml"));
|
||||
slideFiles.sort((a, b) => {
|
||||
const numA = parseInt(a.replace("ppt/slides/slide", "").replace(".xml", ""), 10);
|
||||
const numB = parseInt(b.replace("ppt/slides/slide", "").replace(".xml", ""), 10);
|
||||
return numA - numB;
|
||||
});
|
||||
|
||||
viewer.innerHTML = '';
|
||||
|
||||
const slidesContainer = document.createElement('div');
|
||||
slidesContainer.style.display = 'flex';
|
||||
slidesContainer.style.flexDirection = 'column';
|
||||
slidesContainer.style.gap = '20px';
|
||||
slidesContainer.style.alignItems = 'center';
|
||||
slidesContainer.style.background = '#f0f0f0';
|
||||
slidesContainer.style.padding = '20px';
|
||||
slidesContainer.style.width = '100%';
|
||||
slidesContainer.style.height = '100%';
|
||||
slidesContainer.style.overflow = 'auto';
|
||||
slidesContainer.style.boxSizing = 'border-box';
|
||||
viewer.appendChild(slidesContainer);
|
||||
|
||||
for (let i = 0; i < slideFiles.length; i++) {
|
||||
const slideXmlText = await zip.file(slideFiles[i]).async("text");
|
||||
const slideDoc = parser.parseFromString(slideXmlText, "text/xml");
|
||||
|
||||
const slideCard = document.createElement('div');
|
||||
slideCard.className = 'pptx-slide-card';
|
||||
slideCard.style.position = 'relative';
|
||||
slideCard.style.width = '100%';
|
||||
slideCard.style.maxWidth = '800px';
|
||||
slideCard.style.backgroundColor = '#ffffff';
|
||||
slideCard.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
||||
slideCard.style.height = '0';
|
||||
slideCard.style.paddingTop = ratio + '%';
|
||||
slideCard.style.overflow = 'hidden';
|
||||
slideCard.style.flexShrink = '0';
|
||||
|
||||
const slideContent = document.createElement('div');
|
||||
slideContent.style.position = 'absolute';
|
||||
slideContent.style.top = '0';
|
||||
slideContent.style.left = '0';
|
||||
slideContent.style.width = '100%';
|
||||
slideContent.style.height = '100%';
|
||||
slideCard.appendChild(slideContent);
|
||||
slidesContainer.appendChild(slideCard);
|
||||
|
||||
// Parse relationships for this slide
|
||||
const relMap = {};
|
||||
try {
|
||||
const slideName = slideFiles[i].split('/').pop();
|
||||
const relsFileName = `ppt/slides/_rels/${slideName}.rels`;
|
||||
const relsFile = zip.file(relsFileName);
|
||||
if (relsFile) {
|
||||
const relsXmlText = await relsFile.async("text");
|
||||
const relsDoc = parser.parseFromString(relsXmlText, "text/xml");
|
||||
const relationships = relsDoc.getElementsByTagName("Relationship");
|
||||
for (let r = 0; r < relationships.length; r++) {
|
||||
const id = relationships[r].getAttribute("Id");
|
||||
const target = relationships[r].getAttribute("Target");
|
||||
relMap[id] = target;
|
||||
}
|
||||
}
|
||||
} catch (relErr) {
|
||||
console.warn("Failed to parse relationships for slide:", slideFiles[i], relErr);
|
||||
}
|
||||
|
||||
const elements = slideDoc.querySelectorAll('p\\:sp, sp, p\\:pic, pic, p\\:graphicFrame, graphicFrame');
|
||||
|
||||
for (const elem of elements) {
|
||||
const xfrm = elem.querySelector('a\\:xfrm, xfrm');
|
||||
if (!xfrm) continue;
|
||||
|
||||
const off = xfrm.querySelector('a\\:off, off');
|
||||
const ext = xfrm.querySelector('a\\:ext, ext');
|
||||
if (!off || !ext) continue;
|
||||
|
||||
const x = parseInt(off.getAttribute('x'), 10);
|
||||
const y = parseInt(off.getAttribute('y'), 10);
|
||||
const w = parseInt(ext.getAttribute('cx'), 10);
|
||||
const h = parseInt(ext.getAttribute('cy'), 10);
|
||||
|
||||
const leftPct = (x / cx) * 100;
|
||||
const topPct = (y / cy) * 100;
|
||||
const widthPct = (w / cx) * 100;
|
||||
const heightPct = (h / cy) * 100;
|
||||
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.style.position = 'absolute';
|
||||
itemDiv.style.left = leftPct + '%';
|
||||
itemDiv.style.top = topPct + '%';
|
||||
itemDiv.style.width = widthPct + '%';
|
||||
itemDiv.style.height = heightPct + '%';
|
||||
itemDiv.style.boxSizing = 'border-box';
|
||||
|
||||
const nodeName = elem.nodeName.toLowerCase();
|
||||
if (nodeName.includes('pic')) {
|
||||
let imgUrl = null;
|
||||
try {
|
||||
const blip = elem.querySelector('a\\:blip, blip');
|
||||
const rId = blip ? (blip.getAttribute('r:embed') || blip.getAttribute('embed')) : null;
|
||||
if (rId && relMap[rId]) {
|
||||
const targetPath = relMap[rId].replace('../', 'ppt/');
|
||||
const imgFile = zip.file(targetPath);
|
||||
if (imgFile) {
|
||||
const imgBlob = await imgFile.async("blob");
|
||||
imgUrl = URL.createObjectURL(imgBlob);
|
||||
}
|
||||
}
|
||||
} catch (imgErr) {
|
||||
console.warn("Failed to extract slide image:", imgErr);
|
||||
}
|
||||
|
||||
if (imgUrl) {
|
||||
itemDiv.style.backgroundImage = `url("${imgUrl}")`;
|
||||
itemDiv.style.backgroundRepeat = 'no-repeat';
|
||||
itemDiv.style.backgroundPosition = 'center';
|
||||
itemDiv.style.backgroundSize = 'contain';
|
||||
} else {
|
||||
itemDiv.style.border = '1px dashed #cccccc';
|
||||
itemDiv.style.backgroundColor = '#f9f9f9';
|
||||
itemDiv.style.display = 'flex';
|
||||
itemDiv.style.alignItems = 'center';
|
||||
itemDiv.style.justifyContent = 'center';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.style.color = '#999999';
|
||||
label.style.fontSize = '10px';
|
||||
label.style.fontWeight = 'bold';
|
||||
label.textContent = '[그림 영역]';
|
||||
itemDiv.appendChild(label);
|
||||
}
|
||||
} else if (nodeName.includes('graphicframe')) {
|
||||
const tbl = elem.querySelector('a\\:tbl, tbl');
|
||||
if (tbl) {
|
||||
const htmlTable = document.createElement('table');
|
||||
htmlTable.style.width = '100%';
|
||||
htmlTable.style.height = '100%';
|
||||
htmlTable.style.borderCollapse = 'collapse';
|
||||
htmlTable.style.fontSize = 'calc(0.4vw + 5px)';
|
||||
htmlTable.style.fontFamily = 'sans-serif';
|
||||
htmlTable.style.backgroundColor = '#ffffff';
|
||||
htmlTable.style.boxShadow = '0 1px 3px rgba(0,0,0,0.05)';
|
||||
|
||||
const rows = tbl.querySelectorAll('a\\:tr, tr');
|
||||
rows.forEach((row, rIdx) => {
|
||||
const trEl = document.createElement('tr');
|
||||
if (rIdx === 0) {
|
||||
trEl.style.backgroundColor = '#f8f9fa';
|
||||
trEl.style.fontWeight = '600';
|
||||
} else if (rIdx % 2 === 0) {
|
||||
trEl.style.backgroundColor = '#fafafa';
|
||||
}
|
||||
|
||||
const cells = row.querySelectorAll('a\\:tc, tc');
|
||||
cells.forEach(cell => {
|
||||
const tdEl = document.createElement('td');
|
||||
tdEl.style.border = '1px solid #e0e0e0';
|
||||
tdEl.style.padding = '4px 6px';
|
||||
tdEl.style.wordBreak = 'break-all';
|
||||
tdEl.style.verticalAlign = 'middle';
|
||||
|
||||
const gridSpan = cell.getAttribute('gridSpan');
|
||||
if (gridSpan) tdEl.setAttribute('colspan', gridSpan);
|
||||
const rowSpan = cell.getAttribute('rowSpan');
|
||||
if (rowSpan) tdEl.setAttribute('rowspan', rowSpan);
|
||||
|
||||
const txBody = cell.querySelector('a\\:txBody, txBody');
|
||||
if (txBody) {
|
||||
const paragraphs = txBody.querySelectorAll('a\\:p, p');
|
||||
paragraphs.forEach(p => {
|
||||
const runs = p.querySelectorAll('a\\:r, r');
|
||||
let cellText = '';
|
||||
runs.forEach(r => {
|
||||
const t = r.querySelector('a\\:t, t');
|
||||
if (t) cellText += t.textContent;
|
||||
});
|
||||
if (cellText.trim()) {
|
||||
const pEl = document.createElement('p');
|
||||
pEl.style.margin = '0';
|
||||
pEl.style.lineHeight = '1.2';
|
||||
pEl.textContent = cellText;
|
||||
tdEl.appendChild(pEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
trEl.appendChild(tdEl);
|
||||
});
|
||||
htmlTable.appendChild(trEl);
|
||||
});
|
||||
itemDiv.appendChild(htmlTable);
|
||||
} else {
|
||||
itemDiv.style.border = '1px dashed #dddddd';
|
||||
itemDiv.style.backgroundColor = '#fdfdfd';
|
||||
itemDiv.style.display = 'flex';
|
||||
itemDiv.style.alignItems = 'center';
|
||||
itemDiv.style.justifyContent = 'center';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.style.color = '#aaaaaa';
|
||||
label.style.fontSize = '10px';
|
||||
label.style.fontWeight = 'bold';
|
||||
label.textContent = '[차트 영역]';
|
||||
itemDiv.appendChild(label);
|
||||
}
|
||||
} else {
|
||||
const txBody = elem.querySelector('p\\:txBody, txBody');
|
||||
if (txBody) {
|
||||
itemDiv.style.overflow = 'hidden';
|
||||
itemDiv.style.wordBreak = 'break-all';
|
||||
itemDiv.style.fontSize = 'calc(0.5vw + 5px)';
|
||||
itemDiv.style.fontFamily = 'sans-serif';
|
||||
itemDiv.style.color = '#333333';
|
||||
|
||||
const paragraphs = txBody.querySelectorAll('a\\:p, p');
|
||||
paragraphs.forEach(p => {
|
||||
const runs = p.querySelectorAll('a\\:r, r');
|
||||
let paraText = '';
|
||||
runs.forEach(r => {
|
||||
const t = r.querySelector('a\\:t, t');
|
||||
if (t) paraText += t.textContent;
|
||||
});
|
||||
|
||||
if (paraText.trim()) {
|
||||
const pEl = document.createElement('p');
|
||||
pEl.style.margin = '0 0 2px 0';
|
||||
pEl.style.lineHeight = '1.2';
|
||||
pEl.textContent = paraText;
|
||||
itemDiv.appendChild(pEl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
itemDiv.style.border = '1px solid #eeeeee';
|
||||
itemDiv.style.backgroundColor = 'rgba(0,0,0,0.01)';
|
||||
}
|
||||
}
|
||||
slideContent.appendChild(itemDiv);
|
||||
}
|
||||
}
|
||||
} catch (parseErr) {
|
||||
console.error("PPTX parse error:", parseErr);
|
||||
viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;padding:20px;text-align:center;">PPTX 파싱 중 에러가 발생했습니다. 상단의 "PDF로 보기" 버튼을 이용해 주세요.</div>';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">PPTX 문서를 불러오는데 실패했습니다.</div>';
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user