refactor(js): app.js 를 브라우저 네이티브 ESM 모듈로 분할

public/js/{util,ui,reactions,modals,comments,feed,main}.js 로 분리하고
dashboard/project 에서 <script type="module" src="/public/js/main.js"> 로 로드.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 17:53:46 +09:00
parent bc1327a6dc
commit 2b74b59f81
10 changed files with 384 additions and 387 deletions

63
public/js/feed.js Normal file
View File

@@ -0,0 +1,63 @@
// 피드 검색·정렬·즐겨찾기 필터 + 내 프로젝트 필터 (모두 클라이언트 처리).
import { $, $$ } from "./util.js";
function applyFeed() {
const grid = $("#feedGrid"); if (!grid) return;
const q = (($("#feedSearch") && $("#feedSearch").value) || "").trim().toLowerCase();
const starOnly = $("#feedStarFilter") && $("#feedStarFilter").classList.contains("on");
let visible = 0;
$$(".fcard", grid).forEach((c) => {
const hit = !q || (c.getAttribute("data-search") || "").indexOf(q) >= 0;
const st = !starOnly || c.getAttribute("data-starred") === "true";
const show = hit && st;
c.style.display = show ? "" : "none";
if (show) visible++;
});
const empty = $("#feedEmpty");
if (empty) {
empty.hidden = visible !== 0;
empty.textContent = starOnly && visible === 0
? "아직 즐겨찾기한 프로젝트가 없어요 · 카드의 ⭐ 버튼으로 추가하세요"
: "“" + (($("#feedSearch") && $("#feedSearch").value) || "") + "” 와 일치하는 프로젝트가 없습니다.";
}
}
function sortFeed(mode) {
const grid = $("#feedGrid"); if (!grid) return;
const cards = $$(".fcard", grid);
cards.sort((a, b) => {
if (mode === "popular") {
return (+b.getAttribute("data-pop") || 0) - (+a.getAttribute("data-pop") || 0)
|| (b.getAttribute("data-created") || "").localeCompare(a.getAttribute("data-created") || "");
}
return (b.getAttribute("data-created") || "").localeCompare(a.getAttribute("data-created") || "");
});
cards.forEach((c) => grid.appendChild(c));
}
export function initFeed() {
const search = $("#feedSearch");
if (search) search.addEventListener("input", applyFeed);
const sl = $("#sortLatest"), sp = $("#sortPopular");
if (sl) sl.addEventListener("click", () => { sl.classList.add("on"); if (sp) sp.classList.remove("on"); sortFeed("latest"); });
if (sp) sp.addEventListener("click", () => { sp.classList.add("on"); if (sl) sl.classList.remove("on"); sortFeed("popular"); });
const sf = $("#feedStarFilter");
if (sf) sf.addEventListener("click", () => { sf.classList.toggle("on"); applyFeed(); });
}
export function initMyFilter() {
const tabs = $$("[data-myfilter]");
if (!tabs.length) return;
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
const f = tab.getAttribute("data-myfilter"); // all | public | private
tabs.forEach((t) => t.classList.toggle("on", t === tab));
$$("#myProjGrid .pcard").forEach((c) => {
const vis = c.getAttribute("data-vis");
c.style.display = (f === "all" || vis === f) ? "" : "none";
});
const add = $("#addCard");
if (add) add.style.display = f === "all" ? "" : "none";
});
});
}