mail.ncue.net(Roundcube) 파비콘 보강

- mail.ncue.net은 /roundcube/favicon.ico를 우선 시도
- 실패 시 /favicon.ico로 자동 재시도 후 글자 폴백
- script.js 및 index.html 폴백에 동일 적용

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dsyoon
2026-02-08 12:55:00 +09:00
parent 39629006a7
commit 6b426eaccc
2 changed files with 29 additions and 16 deletions

View File

@@ -181,18 +181,26 @@
}
}
function faviconUrl(url) {
function faviconCandidates(url) {
try {
const u = new URL(url);
// default: site root favicon
// special: allow per-path favicon like https://ncue.net/dsyoon/favicon.ico (internal only)
const host = String(u.hostname || "").toLowerCase();
const isNcue = host === "ncue.net" || host.endsWith(".ncue.net");
const parts = u.pathname.split("/").filter(Boolean);
if (isNcue && parts.length) return `${u.origin}/${parts[0]}/favicon.ico`;
return `${u.origin}/favicon.ico`;
const rootFav = `${u.origin}/favicon.ico`;
// Host-specific hint (Roundcube etc.)
const hintPath = host === "mail.ncue.net" ? "/roundcube/favicon.ico" : "";
const hintFav = hintPath ? `${u.origin}${hintPath}` : "";
// Path-based favicon like https://ncue.net/dsyoon/favicon.ico (internal only)
const pathFav = isNcue && parts.length ? `${u.origin}/${parts[0]}/favicon.ico` : "";
const primary = pathFav || hintFav || rootFav;
const fallback = primary !== rootFav ? rootFav : "";
return { primary, fallback };
} catch {
return "";
return { primary: "", fallback: "" };
}
}
@@ -328,7 +336,7 @@
const letter = escapeHtml((link.title || domain || "L").trim().slice(0, 1).toUpperCase());
const lockAttr = state.canManage ? "" : ' disabled aria-disabled="true"';
const lockTitle = state.canManage ? "" : ' title="관리 기능은 로그인 후 사용 가능합니다."';
const fav = faviconUrl(link.url);
const fav = faviconCandidates(link.url);
const accessDisabledAttr = accessible ? "" : " disabled aria-disabled=\"true\"";
const accessDisabledTitle = accessible ? "" : " title=\"이 링크는 현재 권한으로 접근할 수 없습니다.\"";
@@ -349,8 +357,8 @@
<div class="card-title">
<div class="favicon" aria-hidden="true">
${
fav
? `<img src="${escapeHtml(fav)}" alt="" loading="lazy" decoding="async" referrerpolicy="no-referrer" onerror="const p=this.parentNode; this.remove(); if(p) p.insertAdjacentHTML('beforeend','<div class=&quot;letter&quot;>${letter}</div>');" />`
fav && fav.primary
? `<img src="${escapeHtml(fav.primary)}" data-fb="${escapeHtml(fav.fallback || "")}" alt="" loading="lazy" decoding="async" referrerpolicy="no-referrer" onerror="const p=this.parentNode; const fb=this.dataset.fb; if(fb){ this.dataset.fb=''; this.onerror=null; this.src=fb; return; } this.remove(); if(p) p.insertAdjacentHTML('beforeend','<div class=&quot;letter&quot;>${letter}</div>');" />`
: `<div class="letter">${letter}</div>`
}
</div>