- Introduced common.js for shared functions including API calls, error reporting, and UI helpers. - Implemented feedback.js for the feedback reporting page, including form handling and displaying feedback status. - Added layout.js for managing galaxy and section layouts in the visualization. - Created login.js for user authentication with session management. - Developed search.js for term searching based on user input and synonyms. - Implemented signup.js for user registration with validation and immediate login.
87 lines
3.4 KiB
JavaScript
87 lines
3.4 KiB
JavaScript
// 모든 페이지 공통: API 호출, 오류 자동 보고, 작은 도우미.
|
|
|
|
export const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
|
|
export const STATUS_LABEL = { draft: "검수 전", review: "검토 중", confirmed: "확정", rejected: "반려" };
|
|
export const FEEDBACK_STATUS_LABEL = { new: "접수", in_progress: "처리 중", resolved: "반영 완료", rejected: "반영 안 함", duplicate: "중복" };
|
|
export const REL_TYPE_LABEL = {
|
|
part_of: "구성 요소",
|
|
precedes: "다음 단계",
|
|
informs: "자료로 활용",
|
|
contrasts_with: "구분할 개념",
|
|
related_to: "관련 개념",
|
|
};
|
|
export const METHOD_LABEL = { wireframe: "시안 예시", curated: "비교 개념 초안", toc: "목차 구조", definition: "정의 문장", name: "이름 포함", manual: "직접 입력", auto: "자동 추출", draft: "AI 초안(교재 문장 기반)" };
|
|
|
|
export async function api(path, opts = {}) {
|
|
const init = { credentials: "same-origin", ...opts, headers: { ...(opts.headers || {}) } };
|
|
if (opts.body && typeof opts.body !== "string") {
|
|
init.body = JSON.stringify(opts.body);
|
|
init.headers["Content-Type"] = "application/json";
|
|
}
|
|
const res = await fetch(path, init);
|
|
let data = null;
|
|
const ct = res.headers.get("content-type") || "";
|
|
if (ct.includes("json")) data = await res.json();
|
|
else data = await res.text();
|
|
if (!res.ok) {
|
|
const msg = (data && data.detail) ? (Array.isArray(data.detail) ? data.detail.map((d) => d.msg).join(", ") : data.detail) : `요청 실패 (${res.status})`;
|
|
const err = new Error(msg);
|
|
err.status = res.status;
|
|
throw err;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
let toastTimer;
|
|
export function toast(msg, isError = false) {
|
|
let el = document.querySelector(".toast");
|
|
if (!el) {
|
|
el = document.createElement("div");
|
|
el.className = "toast";
|
|
el.setAttribute("role", "status");
|
|
document.body.appendChild(el);
|
|
}
|
|
el.textContent = msg;
|
|
el.classList.toggle("error", isError);
|
|
el.hidden = false;
|
|
clearTimeout(toastTimer);
|
|
toastTimer = setTimeout(() => (el.hidden = true), 3500);
|
|
}
|
|
|
|
// 화면 오류를 서버로 보내 관리자 피드백 목록(자동 수집 오류)에 쌓는다.
|
|
const sent = new Set();
|
|
function report(message, stack, source) {
|
|
const key = message + "|" + (stack || "").split("\n")[0];
|
|
if (sent.has(key) || sent.size > 20) return;
|
|
sent.add(key);
|
|
try {
|
|
fetch("/api/client-errors", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ message: String(message).slice(0, 2000), stack: (stack || "").slice(0, 8000), url: location.href, source }),
|
|
keepalive: true,
|
|
}).catch(() => {});
|
|
} catch (_) { /* 보고 실패는 무시 */ }
|
|
}
|
|
window.addEventListener("error", (e) => report(e.message, e.error && e.error.stack, `${e.filename}:${e.lineno}:${e.colno}`));
|
|
window.addEventListener("unhandledrejection", (e) => {
|
|
const r = e.reason || {};
|
|
if (r.status && r.status < 500) return; // 4xx는 사용자 입력 문제
|
|
report(r.message || String(r), r.stack, "promise");
|
|
});
|
|
|
|
export function fmtDate(s) {
|
|
if (!s) return "";
|
|
return s.replace("T", " ").slice(0, 16);
|
|
}
|
|
|
|
export function debounce(fn, ms = 150) {
|
|
let t;
|
|
return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); };
|
|
}
|
|
|
|
export function qs(name) {
|
|
return new URLSearchParams(location.search).get(name);
|
|
}
|