Gate link access by login level
Disable open/copy for non-allowed services based on anonymous/logged-in/admin email tiers and show an '접근 제한' tag on restricted cards. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
56
script.js
56
script.js
@@ -69,6 +69,36 @@
|
|||||||
canManage: false,
|
canManage: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Access levels (open/copy)
|
||||||
|
const ACCESS_ANON_IDS = new Set(["dsyoon-ncue-net", "family-ncue-net", "link-ncue-net"]);
|
||||||
|
const ACCESS_USER_IDS = new Set([
|
||||||
|
"dsyoon-ncue-net",
|
||||||
|
"family-ncue-net",
|
||||||
|
"tts-ncue-net",
|
||||||
|
"meeting-ncue-net",
|
||||||
|
"link-ncue-net",
|
||||||
|
"dreamgirl-ncue-net",
|
||||||
|
]);
|
||||||
|
const ACCESS_ADMIN_EMAILS = new Set(["dosangyoon@gmail.com", "dsyoon@ncue.net"]);
|
||||||
|
|
||||||
|
function getUserEmail() {
|
||||||
|
const e = auth && auth.user && auth.user.email ? String(auth.user.email) : "";
|
||||||
|
return e.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAdminEmail(email) {
|
||||||
|
const e = String(email || "").trim().toLowerCase();
|
||||||
|
return ACCESS_ADMIN_EMAILS.has(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canAccessLink(link) {
|
||||||
|
const email = getUserEmail();
|
||||||
|
if (email && isAdminEmail(email)) return true;
|
||||||
|
const id = String(link && link.id ? link.id : "");
|
||||||
|
if (email) return ACCESS_USER_IDS.has(id);
|
||||||
|
return ACCESS_ANON_IDS.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
const auth = {
|
const auth = {
|
||||||
client: null,
|
client: null,
|
||||||
user: null,
|
user: null,
|
||||||
@@ -261,9 +291,11 @@
|
|||||||
const desc = escapeHtml(link.description || "");
|
const desc = escapeHtml(link.description || "");
|
||||||
const url = escapeHtml(link.url);
|
const url = escapeHtml(link.url);
|
||||||
const starClass = link.favorite ? "star on" : "star";
|
const starClass = link.favorite ? "star on" : "star";
|
||||||
|
const accessible = canAccessLink(link);
|
||||||
const tags = (link.tags || []).slice(0, 8);
|
const tags = (link.tags || []).slice(0, 8);
|
||||||
const tagHtml = [
|
const tagHtml = [
|
||||||
link.favorite ? `<span class="tag fav">★ 즐겨찾기</span>` : "",
|
link.favorite ? `<span class="tag fav">★ 즐겨찾기</span>` : "",
|
||||||
|
accessible ? "" : `<span class="tag lock">접근 제한</span>`,
|
||||||
...tags.map((t) => `<span class="tag">#${escapeHtml(t)}</span>`),
|
...tags.map((t) => `<span class="tag">#${escapeHtml(t)}</span>`),
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -274,8 +306,20 @@
|
|||||||
const lockTitle = state.canManage ? "" : ' title="관리 기능은 로그인 후 사용 가능합니다."';
|
const lockTitle = state.canManage ? "" : ' title="관리 기능은 로그인 후 사용 가능합니다."';
|
||||||
const fav = faviconUrl(link.url);
|
const fav = faviconUrl(link.url);
|
||||||
|
|
||||||
|
const accessDisabledAttr = accessible ? "" : " disabled aria-disabled=\"true\"";
|
||||||
|
const accessDisabledTitle = accessible ? "" : " title=\"이 링크는 현재 권한으로 접근할 수 없습니다.\"";
|
||||||
|
|
||||||
|
const openHtml = accessible
|
||||||
|
? `<a class="btn mini" href="${url}" target="_blank" rel="noopener noreferrer" data-act="open">열기</a>`
|
||||||
|
: `<button class="btn mini" type="button"${accessDisabledAttr}${accessDisabledTitle}>열기</button>`;
|
||||||
|
|
||||||
|
const copyDisabledAttr = accessible ? "" : " disabled aria-disabled=\"true\"";
|
||||||
|
const copyDisabledTitle = accessible ? "" : " title=\"이 링크는 현재 권한으로 접근할 수 없습니다.\"";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<article class="card" data-id="${escapeHtml(link.id)}">
|
<article class="card${accessible ? "" : " disabled"}" data-id="${escapeHtml(link.id)}" data-access="${
|
||||||
|
accessible ? "1" : "0"
|
||||||
|
}">
|
||||||
<div class="card-head">
|
<div class="card-head">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<div class="favicon" aria-hidden="true">
|
<div class="favicon" aria-hidden="true">
|
||||||
@@ -299,8 +343,8 @@
|
|||||||
<div class="tags">${tagHtml || ""}</div>
|
<div class="tags">${tagHtml || ""}</div>
|
||||||
|
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<a class="btn mini" href="${url}" target="_blank" rel="noopener noreferrer" data-act="open">열기</a>
|
${openHtml}
|
||||||
<button class="btn mini" type="button" data-act="copy">URL 복사</button>
|
<button class="btn mini" type="button" data-act="copy"${copyDisabledAttr}${copyDisabledTitle}>URL 복사</button>
|
||||||
<button class="btn mini" type="button" data-act="edit"${lockAttr}${lockTitle}>편집</button>
|
<button class="btn mini" type="button" data-act="edit"${lockAttr}${lockTitle}>편집</button>
|
||||||
<button class="btn mini mini-danger" type="button" data-act="del"${lockAttr}${lockTitle}>삭제</button>
|
<button class="btn mini mini-danger" type="button" data-act="del"${lockAttr}${lockTitle}>삭제</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1033,6 +1077,12 @@
|
|||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
const act = btn.getAttribute("data-act");
|
const act = btn.getAttribute("data-act");
|
||||||
|
// access gate for open/copy
|
||||||
|
if ((act === "open" || act === "copy") && card.getAttribute("data-access") === "0") {
|
||||||
|
toast("이 링크는 현재 권한으로 접근할 수 없습니다.");
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (auth.mode === "enabled" && !state.canManage && (act === "fav" || act === "edit" || act === "del")) {
|
if (auth.mode === "enabled" && !state.canManage && (act === "fav" || act === "edit" || act === "del")) {
|
||||||
toast("관리 기능은 로그인(허용 이메일) 후 사용 가능합니다.");
|
toast("관리 기능은 로그인(허용 이메일) 후 사용 가능합니다.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
18
styles.css
18
styles.css
@@ -655,10 +655,20 @@ html[data-theme="light"] .favicon .letter {
|
|||||||
color: rgba(180, 255, 210, 0.9);
|
color: rgba(180, 255, 210, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag.lock {
|
||||||
|
border-color: rgba(239, 68, 68, 0.32);
|
||||||
|
background: rgba(239, 68, 68, 0.08);
|
||||||
|
color: rgba(255, 200, 200, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
html[data-theme="light"] .tag.fav {
|
html[data-theme="light"] .tag.fav {
|
||||||
color: rgba(0, 120, 70, 0.92);
|
color: rgba(0, 120, 70, 0.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .tag.lock {
|
||||||
|
color: rgba(140, 20, 20, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
.card-actions {
|
.card-actions {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -666,6 +676,14 @@ html[data-theme="light"] .tag.fav {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card.disabled {
|
||||||
|
opacity: 0.78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.disabled:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mini {
|
.mini {
|
||||||
padding: 9px 10px;
|
padding: 9px 10px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user