Add JS fallback render and remove asset query strings

Render basic cards if script.js fails to execute, show quick login icons by default, and avoid asset query params that can break on some static hosts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dsyoon
2026-02-07 18:22:39 +09:00
parent d51951a241
commit 37fe555941
2 changed files with 65 additions and 7 deletions

View File

@@ -9,7 +9,7 @@
<meta http-equiv="Expires" content="0" />
<title>NCue | 개인 링크 홈</title>
<meta name="description" content="개인 서비스 링크를 모아 관리하는 홈 화면" />
<link rel="stylesheet" href="./styles.css?v=20260207-quicklogin" />
<link rel="stylesheet" href="./styles.css" />
<!-- Auth0 SPA SDK (정적 사이트용) -->
<!-- jsDelivr 차단/실패 시 unpkg로 자동 대체 -->
<script
@@ -30,7 +30,7 @@
allowedEmails: [],
};
</script>
<script defer src="./script.js?v=20260207-quicklogin"></script>
<script defer src="./script.js"></script>
</head>
<body>
<a class="skip-link" href="#main">본문으로 건너뛰기</a>
@@ -75,16 +75,16 @@
<span class="user-dot" aria-hidden="true"></span>
<span class="user-text" id="userText"></span>
</div>
<div class="sns-login" id="snsLogin" hidden>
<div class="sns-login" id="snsLogin">
<span class="sns-label">간편로그인</span>
<div class="sns-row">
<button class="sns-btn sns-naver" id="btnNaver" type="button" hidden aria-label="네이버로 로그인">
<button class="sns-btn sns-naver is-disabled" id="btnNaver" type="button" aria-label="네이버로 로그인">
<span class="sns-letter">N</span>
</button>
<button class="sns-btn sns-kakao" id="btnKakao" type="button" hidden aria-label="카카오로 로그인">
<button class="sns-btn sns-kakao is-disabled" id="btnKakao" type="button" aria-label="카카오로 로그인">
<span class="sns-kakao-bubble" aria-hidden="true"></span>
</button>
<button class="sns-btn sns-google" id="btnGoogle" type="button" hidden aria-label="구글로 로그인">
<button class="sns-btn sns-google is-disabled" id="btnGoogle" type="button" aria-label="구글로 로그인">
<span class="sns-google-g">G</span>
</button>
</div>
@@ -131,7 +131,7 @@
<section class="grid" id="grid" aria-label="링크 목록"></section>
<section class="empty" id="empty" hidden>
<section class="empty" id="empty">
<div class="empty-title">표시할 링크가 없습니다.</div>
<div class="empty-sub">상단의 “추가” 버튼으로 새 링크를 등록하세요.</div>
</section>
@@ -341,6 +341,61 @@
]
</script>
<!-- Fallback: if script.js fails to load, render basic cards -->
<script>
setTimeout(() => {
if (window.__LINKS_APP_BOOTED__) return;
const grid = document.getElementById("grid");
const empty = document.getElementById("empty");
const dataEl = document.getElementById("linksData");
if (!grid || !dataEl) return;
try {
const links = JSON.parse(dataEl.textContent || "[]");
if (!Array.isArray(links) || links.length === 0) return;
const esc = (s) =>
String(s)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
const domain = (u) => {
try {
return new URL(u).host;
} catch {
return String(u || "").replace(/^https?:\/\//i, "").split("/")[0] || "";
}
};
grid.innerHTML = links
.map((l) => {
const t = esc(l.title || domain(l.url) || "Link");
const d = esc(domain(l.url));
const u = esc(l.url || "#");
const desc = esc(l.description || "");
return `
<article class="card">
<div class="card-head">
<div class="card-title">
<div class="favicon" aria-hidden="true"><div class="letter">${t.slice(0, 1)}</div></div>
<div class="title-wrap">
<div class="title">${t}</div>
<div class="domain">${d}</div>
</div>
</div>
</div>
<div class="card-desc">${desc || "&nbsp;"}</div>
<div class="card-actions">
<a class="btn mini" href="${u}" target="_blank" rel="noopener noreferrer">열기</a>
</div>
</article>
`;
})
.join("");
if (empty) empty.hidden = true;
} catch {}
}, 80);
</script>
<noscript>
<div class="noscript">이 페이지는 JavaScript가 필요합니다.</div>
</noscript>

View File

@@ -1,6 +1,9 @@
(() => {
"use strict";
// Mark boot so index.html fallback won't run
globalThis.__LINKS_APP_BOOTED__ = true;
const STORAGE_KEY = "links_home_v1";
const THEME_KEY = "links_home_theme_v1";
const AUTH_TOAST_ONCE_KEY = "links_home_auth_toast_once_v1";