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:
19
index.html
19
index.html
@@ -516,16 +516,21 @@
|
||||
const favTag = link.favorite ? `<span class="tag fav">★ 즐겨찾기</span>` : "";
|
||||
const lockTag = accessible ? "" : `<span class="tag lock">접근 제한</span>`;
|
||||
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 @@
|
||||
? `<a class="btn mini" href="${openHref}" target="_blank" rel="noopener noreferrer">열기</a>`
|
||||
: `<button class="btn mini" type="button" disabled aria-disabled="true" title="이 링크는 현재 권한으로 접근할 수 없습니다.">열기</button>`;
|
||||
const copyAttrs = accessible ? "" : ` disabled aria-disabled="true" title="이 링크는 현재 권한으로 접근할 수 없습니다."`;
|
||||
const fav = faviconUrl(link.url);
|
||||
const faviconHtml = fav
|
||||
? `<img src="${esc(fav)}" alt="" loading="lazy" decoding="async" referrerpolicy="no-referrer" onerror="const p=this.parentNode; this.remove(); if(p) p.insertAdjacentHTML('beforeend','<div class="letter">${letter}</div>');" />`
|
||||
const fav = faviconCandidates(link.url);
|
||||
const faviconHtml = fav && fav.primary
|
||||
? `<img src="${esc(fav.primary)}" data-fb="${esc(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="letter">${letter}</div>');" />`
|
||||
: `<div class="letter">${letter}</div>`;
|
||||
return `
|
||||
<article class="card${accessible ? "" : " disabled"}" data-id="${esc(link.id)}" data-access="${accessible ? "1" : "0"}">
|
||||
|
||||
26
script.js
26
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 @@
|
||||
<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="letter">${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="letter">${letter}</div>');" />`
|
||||
: `<div class="letter">${letter}</div>`
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user