153 lines
7.5 KiB
Plaintext
153 lines
7.5 KiB
Plaintext
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title><%= story.title %> - AI 성공 사례 - XAVIS</title>
|
|
<link rel="stylesheet" href="/public/styles.css" />
|
|
</head>
|
|
<body>
|
|
<% var pdfUrlRaw = (typeof story.pdfUrl === 'string' ? story.pdfUrl : '').trim(); var showPdfViewer = pdfUrlRaw.length > 0; %>
|
|
<% if (typeof slideImageUrls === 'undefined') { slideImageUrls = []; } %>
|
|
<% if (typeof slides === 'undefined') { slides = []; } %>
|
|
<div class="app-shell">
|
|
<%- include('partials/nav', { activeMenu: 'ai-cases' }) %>
|
|
<div class="content-area">
|
|
<% if (showPdfViewer) { %>
|
|
<main class="viewer-wrap ai-case-viewer">
|
|
<a href="/ai-cases" class="back-link">← AI 성공 사례로 돌아가기</a>
|
|
<h1><%= story.title %></h1>
|
|
<p class="description"><%= story.excerpt || (story.department + ' · ' + story.author) %></p>
|
|
<div class="ppt-tools ai-case-ppt-tools">
|
|
<span>총 <b><%= slides.length %></b>페이지</span>
|
|
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
|
|
<span><%= story.department %> · <%= story.author %><% if (story.publishedAt) { %> · <%= story.publishedAt %><% } %></span>
|
|
<% if (typeof adminMode !== 'undefined' && adminMode) { %>
|
|
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
|
|
<a href="/ai-cases/write?edit=<%= story.slug %>" class="ai-case-inline-link">편집</a>
|
|
<% } %>
|
|
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
|
|
<a href="<%= pdfUrlRaw %>" download class="ai-case-inline-link">다운로드</a>
|
|
<% if (typeof adminMode !== 'undefined' && adminMode) { %>
|
|
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
|
|
<button type="button" class="ai-case-inline-link js-ai-case-delete" data-id="<%= story.id %>" data-title="<%= story.title %>">삭제</button>
|
|
<% } %>
|
|
</div>
|
|
<% if ((story.tags || []).length) { %>
|
|
<div class="tag-row ai-case-tag-row">
|
|
<% (story.tags || []).forEach((oneTag) => { %>
|
|
<span class="tag-chip">#<%= oneTag %></span>
|
|
<% }) %>
|
|
</div>
|
|
<% } %>
|
|
<% if (!slideImageUrls || slideImageUrls.length === 0) { %>
|
|
<p class="admin-warn">PDF를 페이지 이미지로 보여 주지 못했습니다. <a href="<%= pdfUrlRaw %>" target="_blank" rel="noopener noreferrer">원본 PDF로 보기</a>. 서버에는 <code>pdftoppm</code>(Poppler)이 필요하고, 저장된 PDF 주소는 브라우저에서 열리는 것과 같이 <code>/public/...</code> 또는 그와 같은 경로의 전체 URL이어야 합니다.</p>
|
|
<% } %>
|
|
<section class="slide-list">
|
|
<% slides.forEach((slide, index) => { %>
|
|
<article class="slide-card">
|
|
<header>
|
|
<h2>페이지 <%= index + 1 %></h2>
|
|
<% if (slide.title) { %><p><%= slide.title %></p><% } %>
|
|
</header>
|
|
<% if (slideImageUrls[index]) { %>
|
|
<div class="slide-image-wrap">
|
|
<img src="<%= slideImageUrls[index] %>" alt="페이지 <%= index + 1 %>" class="slide-image" />
|
|
</div>
|
|
<% } %>
|
|
<% if (slide.lines && slide.lines.length > 0) { %>
|
|
<ul>
|
|
<% slide.lines.forEach((line) => { %>
|
|
<li><%= line %></li>
|
|
<% }) %>
|
|
</ul>
|
|
<% } %>
|
|
</article>
|
|
<% }) %>
|
|
</section>
|
|
</main>
|
|
<% } else { %>
|
|
<main class="viewer-wrap ai-case-viewer">
|
|
<a href="/ai-cases" class="back-link">← AI 성공 사례로 돌아가기</a>
|
|
<h1><%= story.title %></h1>
|
|
<p class="description"><%= story.excerpt || (story.department + ' · ' + story.author) %></p>
|
|
<div class="ppt-tools ai-case-ppt-tools">
|
|
<span><%= story.department %> · <%= story.author %><% if (story.publishedAt) { %> · <%= story.publishedAt %><% } %></span>
|
|
<% if (typeof adminMode !== 'undefined' && adminMode) { %>
|
|
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
|
|
<a href="/ai-cases/write?edit=<%= story.slug %>" class="ai-case-inline-link">편집</a>
|
|
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
|
|
<button type="button" class="ai-case-inline-link js-ai-case-delete" data-id="<%= story.id %>" data-title="<%= story.title %>">삭제</button>
|
|
<% } %>
|
|
</div>
|
|
<% if ((story.tags || []).length) { %>
|
|
<div class="tag-row ai-case-tag-row">
|
|
<% (story.tags || []).forEach((oneTag) => { %>
|
|
<span class="tag-chip">#<%= oneTag %></span>
|
|
<% }) %>
|
|
</div>
|
|
<% } %>
|
|
<section class="slide-list">
|
|
<article class="slide-card">
|
|
<header>
|
|
<h2>본문</h2>
|
|
</header>
|
|
<div id="success-md-render" class="chat-md-body success-detail-body-in-card"></div>
|
|
</article>
|
|
</section>
|
|
<script type="application/json" id="success-md-json"><%- JSON.stringify(story.bodyMarkdown || '') %></script>
|
|
</main>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
<% if (typeof adminMode !== 'undefined' && adminMode) { %>
|
|
<script>
|
|
(function() {
|
|
var btn = document.querySelector('.js-ai-case-delete');
|
|
if (!btn) return;
|
|
btn.addEventListener('click', function() {
|
|
var id = btn.getAttribute('data-id');
|
|
var title = btn.getAttribute('data-title') || '';
|
|
if (!id || !confirm('삭제할까요? ' + title)) return;
|
|
fetch('/api/ai-success-stories/' + encodeURIComponent(id), { method: 'DELETE' })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function() { window.location.href = '/ai-cases'; })
|
|
.catch(function() { alert('삭제 실패'); });
|
|
});
|
|
})();
|
|
</script>
|
|
<% } %>
|
|
<% if (!showPdfViewer) { %>
|
|
<script src="/vendor/marked/marked.umd.js"></script>
|
|
<script src="/vendor/dompurify/purify.min.js"></script>
|
|
<script>
|
|
(function() {
|
|
var el = document.getElementById('success-md-json');
|
|
var out = document.getElementById('success-md-render');
|
|
if (!el || !out) return;
|
|
var raw = '';
|
|
try { raw = JSON.parse(el.textContent || '""'); } catch (e) { raw = ''; }
|
|
if (typeof marked === 'undefined' || typeof DOMPurify === 'undefined') {
|
|
out.textContent = raw;
|
|
return;
|
|
}
|
|
marked.setOptions({ breaks: true, gfm: true });
|
|
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
|
|
if (node.tagName === 'A' && node.hasAttribute('href')) {
|
|
var href = node.getAttribute('href');
|
|
try {
|
|
var u = new URL(href, window.location.href);
|
|
if (u.protocol !== 'http:' && u.protocol !== 'https:') { node.removeAttribute('href'); return; }
|
|
} catch (e) { node.removeAttribute('href'); return; }
|
|
node.setAttribute('target', '_blank');
|
|
node.setAttribute('rel', 'noopener noreferrer');
|
|
}
|
|
});
|
|
var html = marked.parse(String(raw || ''), { async: false });
|
|
out.innerHTML = DOMPurify.sanitize(html, { USE_PROFILES: { html: true } });
|
|
})();
|
|
</script>
|
|
<% } %>
|
|
</body>
|
|
</html>
|