Initial commit: add FastAPI MVP (모프) and existing web app
Includes FastAPI+Jinja2+HTMX+SQLite implementation with seed categories, plus deployment templates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
107
templates/detail.html
Normal file
107
templates/detail.html
Normal file
@@ -0,0 +1,107 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-2xl font-bold leading-snug">{{ prompt.title }}</h1>
|
||||
<div class="mt-2 text-xs text-gray-500 flex flex-wrap items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded bg-gray-100 text-gray-700">
|
||||
{% for c in categories %}
|
||||
{% if c.id == prompt.category_id %}{{ c.name }}{% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
<span>작성자: <span class="font-semibold text-gray-700">{{ prompt.author_nickname }}</span></span>
|
||||
<span>복사 <span id="copy-count" class="font-semibold text-gray-700">{{ prompt.copy_count }}</span></span>
|
||||
</div>
|
||||
{% if prompt.description %}
|
||||
<div class="mt-3 text-sm text-gray-700 whitespace-pre-wrap">{{ prompt.description }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div id="like-{{ prompt.id }}">
|
||||
<button
|
||||
class="px-3 py-2 rounded border text-sm {% if liked %}bg-gray-100 text-gray-500 cursor-not-allowed{% else %}bg-white hover:bg-gray-50{% endif %}"
|
||||
hx-post="/like/{{ prompt.id }}"
|
||||
hx-target="#like-{{ prompt.id }}"
|
||||
hx-swap="outerHTML"
|
||||
{% if liked %}disabled{% endif %}
|
||||
>
|
||||
{% if liked %}좋아요✓{% else %}좋아요{% endif %}
|
||||
<span class="font-semibold">({{ like_count }})</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="copy-btn"
|
||||
class="px-3 py-2 rounded bg-gray-900 text-white text-sm hover:bg-gray-800"
|
||||
type="button"
|
||||
>
|
||||
복사
|
||||
</button>
|
||||
<a href="/new" class="px-3 py-2 rounded border bg-white text-sm hover:bg-gray-50 text-center">
|
||||
+ 새 프롬프트
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="text-sm text-gray-600 mb-2">프롬프트</div>
|
||||
<pre
|
||||
id="prompt-content"
|
||||
class="p-4 bg-white border rounded text-sm overflow-auto whitespace-pre-wrap leading-relaxed"
|
||||
>{{ prompt.content }}</pre>
|
||||
<div id="copy-toast" class="mt-2 text-xs text-gray-500 hidden">클립보드에 복사했습니다.</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const btn = document.getElementById("copy-btn");
|
||||
const pre = document.getElementById("prompt-content");
|
||||
const toast = document.getElementById("copy-toast");
|
||||
const copyCountEl = document.getElementById("copy-count");
|
||||
|
||||
async function incCopyCount() {
|
||||
try {
|
||||
const res = await fetch("/copy/{{ prompt.id }}", { method: "POST" });
|
||||
const data = await res.json();
|
||||
if (copyCountEl && typeof data.copy_count === "number") {
|
||||
copyCountEl.textContent = String(data.copy_count);
|
||||
}
|
||||
} catch (e) {
|
||||
// 실패해도 UX는 유지(복사 기능이 핵심)
|
||||
}
|
||||
}
|
||||
|
||||
async function copyText(text) {
|
||||
// 최신 브라우저는 navigator.clipboard 사용
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return;
|
||||
}
|
||||
// HTTP/구형 환경 fallback
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.left = "-9999px";
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
|
||||
btn.addEventListener("click", async () => {
|
||||
const text = pre.textContent || "";
|
||||
try {
|
||||
await copyText(text);
|
||||
toast.classList.remove("hidden");
|
||||
setTimeout(() => toast.classList.add("hidden"), 1200);
|
||||
incCopyCount();
|
||||
} catch (e) {
|
||||
alert("복사에 실패했습니다. 다시 시도해주세요.");
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user