diff --git a/index.html b/index.html index 33f3626..68c03e1 100644 --- a/index.html +++ b/index.html @@ -516,16 +516,21 @@ const favTag = link.favorite ? `★ 즐겨찾기` : ""; const lockTag = accessible ? "" : `접근 제한`; const letter = esc((link.title || d || "L").trim().slice(0, 1).toUpperCase()); - function faviconUrl(rawUrl) { + function faviconCandidates(rawUrl) { try { const uu = new URL(String(rawUrl || "")); const host = String(uu.hostname || "").toLowerCase(); const isNcue = host === "ncue.net" || host.endsWith(".ncue.net"); const parts = uu.pathname.split("/").filter(Boolean); - if (isNcue && parts.length) return `${uu.origin}/${parts[0]}/favicon.ico`; - return `${uu.origin}/favicon.ico`; + const rootFav = `${uu.origin}/favicon.ico`; + const hintPath = host === "mail.ncue.net" ? "/roundcube/favicon.ico" : ""; + const hintFav = hintPath ? `${uu.origin}${hintPath}` : ""; + const pathFav = isNcue && parts.length ? `${uu.origin}/${parts[0]}/favicon.ico` : ""; + const primary = pathFav || hintFav || rootFav; + const fallback = primary !== rootFav ? rootFav : ""; + return { primary, fallback }; } catch { - return ""; + return { primary: "", fallback: "" }; } } function buildOpenUrl(rawUrl) { @@ -551,9 +556,9 @@ ? `열기` : ``; const copyAttrs = accessible ? "" : ` disabled aria-disabled="true" title="이 링크는 현재 권한으로 접근할 수 없습니다."`; - const fav = faviconUrl(link.url); - const faviconHtml = fav - ? `` + const fav = faviconCandidates(link.url); + const faviconHtml = fav && fav.primary + ? `` : `
${letter}
`; return `
diff --git a/script.js b/script.js index a6c192d..a629ab8 100644 --- a/script.js +++ b/script.js @@ -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 @@