Files
ai-dev-portal/public/js/main.js
2620227 a199114453 feat(notifications): 알림 종 + 공지 기능 추가
- 헤더 프로필 왼쪽 알림 종: 드롭다운·안읽음 배지·60초 폴링(최초 폴링 도입)
- 내 프로젝트 좋아요/즐겨찾기/댓글 시 소유자에게 알림(본인 제외·재토글 도배 방지)
- 관리자 공지: /announcements 목록 + 작성/수정/삭제, 등록 시 전 직원에게 fan-out 알림
- notifications/announcements 테이블(멱등) + requireAdmin 가드 + bell/megaphone 아이콘

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:10:04 +09:00

63 lines
2.7 KiB
JavaScript

// 엔트리 — 각 모듈 초기화 + 서버 시작 신호(토스트/이스터에그/모달 열기) 처리.
// _header/dashboard/project 에서 <script type="module" src="/public/js/main.js"> 로 로드.
import { EGGS_ON, toast, burstThumbs, burstRocket } from "./util.js";
import { initTheme, initUserMenu, initLogoEgg } from "./ui.js";
import { initReactions } from "./reactions.js";
import { initModals, openDetail } from "./modals.js";
import { initComments } from "./comments.js";
import { initFeed, initMyFilter } from "./feed.js";
import { initNotifications } from "./notifications.js";
import { initAnnouncements } from "./announcements.js";
// 서버가 <body data-*> 로 전달한 1회성 신호 처리
function initSignals() {
const b = document.body;
if (b.getAttribute("data-toast")) toast(b.getAttribute("data-toast"));
const egg = b.getAttribute("data-egg");
if (egg === "lgtm") { burstThumbs(); toast("LGTM 👍 — Looks Good To Me!"); }
else if (egg === "rocket") burstRocket();
const open = b.getAttribute("data-open-modal");
if (open) openDetail(open);
stripSignalParams();
}
// 1회성 신호 쿼리(toast/egg/open)는 표시 후 주소창에서 지운다.
// history.replaceState 라 새 히스토리 항목 없이 URL 만 정리 → 새로고침/공유 시 토스트가 다시 뜨지 않는다.
function stripSignalParams() {
try {
const url = new URL(window.location.href);
const before = url.search;
["toast", "egg", "open"].forEach((k) => url.searchParams.delete(k));
if (url.search !== before) history.replaceState(null, "", url.pathname + url.search + url.hash);
} catch (e) {}
}
function consoleArt() {
if (!EGGS_ON) return;
try {
const art = [" ___ ____ ____ _______ __", " / | / _/ / __ \\/ ____/ | / /", " / /| | / / / / / / __/ | | / / ", "/ ___ |_/ / / /_/ / /___ | |/ / ", "/_/ |_/___/ /_____/_____/ |___/ "].join("\n");
console.log("%c" + art, "color:#0969da;font-family:monospace;font-size:11px;line-height:1.1");
console.log("%c👀 코드 구경 왔어요? 환영합니다.", "color:#1f883d;font-size:13px;font-weight:bold");
console.log("%c이슈는 https://gitea.bokdev.in/playground/ai-dev-portal/issues에 제보해주세요! 언제든 환영합니다.", "color:#656d76;font-size:12px");
console.log("%c댓글에 LGTM을 쳐보세요.", "color:#8b949e;font-size:11px");
} catch (e) {}
}
function boot() {
initTheme();
initUserMenu();
initLogoEgg();
initReactions();
initModals();
initComments();
initFeed();
initMyFilter();
initNotifications();
initAnnouncements();
initSignals();
consoleArt();
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
else boot();