Migrate to Pages: public/meme assets, gallery and scripts; remove root meme/; tooling defaults
Made-with: Cursor
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
color: var(--fg);
|
||||
}
|
||||
header {
|
||||
padding: 1.25rem clamp(1rem, 4vw, 2rem);
|
||||
padding: 1.25rem clamp(0.5rem, 2vw, 1.25rem);
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -43,91 +43,103 @@
|
||||
.sub a {
|
||||
color: inherit;
|
||||
}
|
||||
main {
|
||||
padding: 1.25rem clamp(1rem, 4vw, 2rem) 3rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.category {
|
||||
margin-bottom: 2.25rem;
|
||||
}
|
||||
.category h2 {
|
||||
font-size: 1.05rem;
|
||||
margin: 0 0 0.75rem;
|
||||
.cat-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.4rem;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: 0.75rem 0 0;
|
||||
margin: 0.15rem -0.35rem 0;
|
||||
scrollbar-width: thin;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
mask-image: linear-gradient(to right, transparent, #000 0.5rem, #000 calc(100% - 0.5rem), transparent);
|
||||
}
|
||||
.badge {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
background: var(--accent);
|
||||
opacity: 0.95;
|
||||
.cat-tabs::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
.pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin: 0.5rem 0 1rem;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
.pager button {
|
||||
.cat-tab {
|
||||
flex: 0 0 auto;
|
||||
font: inherit;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.86rem;
|
||||
padding: 0.4rem 0.7rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.12s ease, border-color 0.12s ease;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.pager button:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
.cat-tab:hover {
|
||||
background: color-mix(in oklab, var(--accent) 65%, transparent);
|
||||
}
|
||||
.cat-tab[aria-selected="true"] {
|
||||
font-weight: 650;
|
||||
border-color: color-mix(in oklab, CanvasText 28%, var(--border));
|
||||
background: color-mix(in oklab, var(--accent) 100%, transparent);
|
||||
}
|
||||
.cat-tab:focus-visible {
|
||||
outline: 2px solid color-mix(in oklab, CanvasText 40%, transparent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
main {
|
||||
padding: 1.25rem clamp(0.5rem, 2vw, 1rem) 3rem;
|
||||
max-width: none;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* 手机:每行固定 4 列 */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
|
||||
gap: 0.65rem;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 0.4rem 0.35rem;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
/* 电脑:每行最多 12 列,占满可用宽度 */
|
||||
@media (min-width: 769px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
gap: 0.55rem 0.5rem;
|
||||
}
|
||||
}
|
||||
.tile {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
padding: 0.45rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: transform 0.12s ease, border-color 0.12s ease;
|
||||
text-align: center;
|
||||
transition: opacity 0.15s ease;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.tile:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: color-mix(in oklab, var(--border) 55%, CanvasText);
|
||||
opacity: 0.88;
|
||||
}
|
||||
.tile:focus-visible {
|
||||
outline: 2px solid color-mix(in oklab, CanvasText 35%, transparent);
|
||||
outline-offset: 2px;
|
||||
outline: 2px solid color-mix(in oklab, CanvasText 40%, transparent);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.tile img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 112px;
|
||||
aspect-ratio: 1;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in oklab, Canvas 92%, CanvasText 8%);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.tile .label {
|
||||
margin-top: 0.35rem;
|
||||
font-size: 0.72rem;
|
||||
opacity: 0.8;
|
||||
margin-top: 0.2rem;
|
||||
font-size: 0.68rem;
|
||||
opacity: 0.55;
|
||||
word-break: break-all;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.toast {
|
||||
position: fixed;
|
||||
@@ -160,32 +172,229 @@
|
||||
opacity: 0.8;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.sheet-root {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: env(safe-area-inset-bottom, 0) 0.75rem 0.75rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.sheet-root[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
.sheet-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: color-mix(in oklab, CanvasText 38%, transparent);
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sheet-panel {
|
||||
position: relative;
|
||||
width: min(22rem, 100%);
|
||||
max-height: min(85vh, 28rem);
|
||||
overflow: auto;
|
||||
border-radius: 14px 14px 0 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 -8px 32px color-mix(in oklab, CanvasText 22%, transparent);
|
||||
padding: 0.65rem 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
.sheet-root {
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
.sheet-panel {
|
||||
border-radius: 14px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.sheet-title {
|
||||
margin: 0;
|
||||
padding: 0.35rem 1rem 0.65rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
opacity: 0.85;
|
||||
word-break: break-all;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.sheet-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
font-size: 0.95rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.sheet-btn:hover,
|
||||
.sheet-btn:focus-visible {
|
||||
background: color-mix(in oklab, var(--accent) 85%, transparent);
|
||||
}
|
||||
.sheet-btn:active {
|
||||
opacity: 0.92;
|
||||
}
|
||||
.sheet-cancel {
|
||||
margin-top: 0.25rem;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
opacity: 0.78;
|
||||
}
|
||||
.sheet-size-bar {
|
||||
padding: 0.5rem 1rem 0.65rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.sheet-size-label {
|
||||
display: block;
|
||||
font-size: 0.72rem;
|
||||
opacity: 0.68;
|
||||
margin-bottom: 0.45rem;
|
||||
}
|
||||
.sheet-size-btns {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.sheet-size-btn {
|
||||
font: inherit;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
padding: 0.35rem 0.55rem;
|
||||
min-width: 2.75rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.sheet-size-btn.is-active {
|
||||
border-color: color-mix(in oklab, CanvasText 28%, var(--border));
|
||||
background: color-mix(in oklab, var(--accent) 100%, transparent);
|
||||
}
|
||||
.sheet-size-btn:focus-visible {
|
||||
outline: 2px solid color-mix(in oklab, CanvasText 40%, transparent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>表情包图库</h1>
|
||||
<p class="sub">
|
||||
按文件夹分类 · 点击卡片复制可分享的直链 ·
|
||||
<a href="/">API 说明</a>
|
||||
· 每页数量:电脑端 100,手机端 40(随窗口宽度自动切换)
|
||||
<a href="/">API</a>
|
||||
·
|
||||
<a href="/memes.json"><code>memes.json</code></a>
|
||||
</p>
|
||||
<nav class="cat-tabs" id="cat-tabs" aria-label="表情包分类"></nav>
|
||||
</header>
|
||||
<main id="app">
|
||||
<p class="empty">加载中…</p>
|
||||
</main>
|
||||
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
||||
<div id="sheet-root" class="sheet-root" hidden>
|
||||
<button type="button" class="sheet-backdrop" id="sheet-backdrop" aria-label="关闭"></button>
|
||||
<div class="sheet-panel" role="dialog" aria-modal="true" aria-labelledby="sheet-title">
|
||||
<p id="sheet-title" class="sheet-title"></p>
|
||||
<button type="button" class="sheet-btn" id="act-url">复制 url链接</button>
|
||||
<button type="button" class="sheet-btn" id="act-md">复制 Markdown</button>
|
||||
<div class="sheet-size-bar" id="sheet-size-bar" role="group" aria-label="HTML 宽度">
|
||||
<span class="sheet-size-label">HTML 宽度</span>
|
||||
<div class="sheet-size-btns">
|
||||
<button type="button" class="sheet-size-btn" data-w="96" aria-pressed="false">96</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="128" aria-pressed="false">128</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="160" aria-pressed="false">160</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="200" aria-pressed="false">200</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="240" aria-pressed="false">240</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="280" aria-pressed="false">280</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="320" aria-pressed="false">320</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="400" aria-pressed="false">400</button>
|
||||
<button type="button" class="sheet-size-btn" data-w="original" aria-pressed="false">原图</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="sheet-btn" id="act-html">复制 HTML</button>
|
||||
<button type="button" class="sheet-btn" id="act-download">下载</button>
|
||||
<button type="button" class="sheet-btn sheet-cancel" id="act-close">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const app = document.getElementById("app");
|
||||
const tabsEl = document.getElementById("cat-tabs");
|
||||
const toastEl = document.getElementById("toast");
|
||||
/** @type {Record<string, number>} */
|
||||
const pageByCat = {};
|
||||
const sheetRoot = document.getElementById("sheet-root");
|
||||
const sheetTitle = document.getElementById("sheet-title");
|
||||
const sheetBackdrop = document.getElementById("sheet-backdrop");
|
||||
/** @type {{ url: string; file: string; category: string } | null} */
|
||||
var sheetCtx = null;
|
||||
/** @type {number | null} null = 原图尺寸 */
|
||||
var selectedWidthPx = 240;
|
||||
var WIDTH_STORAGE = "meme_copy_img_width";
|
||||
var VALID_HTML_WIDTHS = [96, 128, 160, 200, 240, 280, 320, 400];
|
||||
|
||||
function pageSize() {
|
||||
return window.matchMedia("(max-width: 768px)").matches ? 40 : 100;
|
||||
function loadSavedWidth() {
|
||||
try {
|
||||
var v = localStorage.getItem(WIDTH_STORAGE);
|
||||
if (v === "original") selectedWidthPx = null;
|
||||
else {
|
||||
var n = parseInt(v, 10);
|
||||
selectedWidthPx =
|
||||
VALID_HTML_WIDTHS.indexOf(n) >= 0 ? n : 240;
|
||||
}
|
||||
} catch (e) {
|
||||
selectedWidthPx = 240;
|
||||
}
|
||||
}
|
||||
|
||||
function persistWidth() {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
WIDTH_STORAGE,
|
||||
selectedWidthPx == null ? "original" : String(selectedWidthPx)
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function syncSizeBarUI() {
|
||||
var bar = document.getElementById("sheet-size-bar");
|
||||
if (!bar) return;
|
||||
bar.querySelectorAll(".sheet-size-btn").forEach(function (btn) {
|
||||
var w = btn.getAttribute("data-w");
|
||||
var active =
|
||||
(w === "original" && selectedWidthPx == null) ||
|
||||
(w !== "original" && parseInt(w, 10) === selectedWidthPx);
|
||||
btn.classList.toggle("is-active", active);
|
||||
btn.setAttribute("aria-pressed", active ? "true" : "false");
|
||||
});
|
||||
}
|
||||
|
||||
function wireSizeBar() {
|
||||
document.querySelectorAll("#sheet-size-bar .sheet-size-btn").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
var w = btn.getAttribute("data-w");
|
||||
selectedWidthPx = w === "original" ? null : parseInt(w, 10);
|
||||
persistWidth();
|
||||
syncSizeBarUI();
|
||||
});
|
||||
});
|
||||
}
|
||||
loadSavedWidth();
|
||||
wireSizeBar();
|
||||
syncSizeBarUI();
|
||||
|
||||
function showToast(msg) {
|
||||
toastEl.textContent = msg;
|
||||
toastEl.classList.add("show");
|
||||
@@ -195,99 +404,214 @@
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
async function copyText(text) {
|
||||
async function copyText(text, toastMsg) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
showToast("已复制链接,可粘贴到聊天或文档");
|
||||
showToast(toastMsg || "已复制到剪贴板");
|
||||
} catch {
|
||||
window.prompt("复制以下链接", text);
|
||||
window.prompt("请手动复制:", text);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeAttr(s) {
|
||||
return String(s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<");
|
||||
}
|
||||
|
||||
function markdownImage(url, alt) {
|
||||
var a = String(alt || "表情").replace(/\]/g, "");
|
||||
return "";
|
||||
}
|
||||
|
||||
function htmlEmbed(url, alt, widthPx) {
|
||||
var parts =
|
||||
'<img src="' +
|
||||
escapeAttr(url) +
|
||||
'" alt="' +
|
||||
escapeAttr(alt || "") +
|
||||
'" loading="lazy" draggable="false"';
|
||||
if (widthPx != null) {
|
||||
parts +=
|
||||
' width="' +
|
||||
widthPx +
|
||||
'" style="max-width:' +
|
||||
widthPx +
|
||||
'px;width:100%;height:auto"';
|
||||
}
|
||||
return parts + " />";
|
||||
}
|
||||
|
||||
function openSheet(url, file, category) {
|
||||
sheetCtx = { url: url, file: file, category: category };
|
||||
sheetTitle.textContent = (category ? category + " / " : "") + file;
|
||||
syncSizeBarUI();
|
||||
sheetRoot.hidden = false;
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
function closeSheet() {
|
||||
sheetRoot.hidden = true;
|
||||
document.body.style.overflow = "";
|
||||
sheetCtx = null;
|
||||
}
|
||||
|
||||
function wireSheetActions() {
|
||||
sheetBackdrop.addEventListener("click", closeSheet);
|
||||
document.getElementById("act-close").addEventListener("click", closeSheet);
|
||||
document.getElementById("act-url").addEventListener("click", function () {
|
||||
if (!sheetCtx) return;
|
||||
copyText(sheetCtx.url, "已复制链接");
|
||||
closeSheet();
|
||||
});
|
||||
document.getElementById("act-md").addEventListener("click", function () {
|
||||
if (!sheetCtx) return;
|
||||
var alt = sheetCtx.category + "/" + sheetCtx.file;
|
||||
copyText(markdownImage(sheetCtx.url, alt), "已复制 Markdown");
|
||||
closeSheet();
|
||||
});
|
||||
document.getElementById("act-html").addEventListener("click", function () {
|
||||
if (!sheetCtx) return;
|
||||
var alt = sheetCtx.category + "/" + sheetCtx.file;
|
||||
var tip =
|
||||
selectedWidthPx == null ? "已复制 HTML" : "已复制 HTML(" + selectedWidthPx + "px)";
|
||||
copyText(htmlEmbed(sheetCtx.url, alt, selectedWidthPx), tip);
|
||||
closeSheet();
|
||||
});
|
||||
document.getElementById("act-download").addEventListener("click", function () {
|
||||
if (!sheetCtx) return;
|
||||
var u = sheetCtx.url;
|
||||
var name = sheetCtx.file;
|
||||
closeSheet();
|
||||
(async function () {
|
||||
try {
|
||||
var res = await fetch(u);
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
var blob = await res.blob();
|
||||
var objectUrl = URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
a.href = objectUrl;
|
||||
a.download = name;
|
||||
a.rel = "noopener";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
showToast("已开始下载");
|
||||
} catch (e) {
|
||||
var link = document.createElement("a");
|
||||
link.href = u;
|
||||
link.download = name;
|
||||
link.rel = "noopener";
|
||||
link.target = "_blank";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
showToast("已尝试下载");
|
||||
}
|
||||
})();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape" && !sheetRoot.hidden) closeSheet();
|
||||
});
|
||||
}
|
||||
wireSheetActions();
|
||||
|
||||
function readHashCategory() {
|
||||
var raw = location.hash.replace(/^#/, "");
|
||||
if (!raw) return null;
|
||||
try {
|
||||
return decodeURIComponent(raw);
|
||||
} catch (e) {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function setHashCategory(id) {
|
||||
var enc = encodeURIComponent(id);
|
||||
if (location.hash !== "#" + enc) {
|
||||
history.replaceState(null, "", "#" + enc);
|
||||
}
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
const origin = data.baseUrl || window.location.origin;
|
||||
const categories = data.categories || [];
|
||||
var origin = data.baseUrl || window.location.origin;
|
||||
var categories = data.categories || [];
|
||||
if (!categories.length) {
|
||||
tabsEl.innerHTML = "";
|
||||
app.innerHTML =
|
||||
'<div class="empty">暂无表情包。请将 WebP 放入 <code>public/meme/分类文件夹/</code> 后执行 <code>npm run build</code> 并重新部署。</div>';
|
||||
'<div class="empty">暂无数据。构建并部署后可在此查看。</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
app.innerHTML = "";
|
||||
const mq = window.matchMedia("(max-width: 768px)");
|
||||
let lastSize = pageSize();
|
||||
|
||||
function ensurePage(catId, totalPages) {
|
||||
let p = pageByCat[catId] || 1;
|
||||
if (p > totalPages) p = totalPages;
|
||||
if (p < 1) p = 1;
|
||||
pageByCat[catId] = p;
|
||||
return p;
|
||||
var selectedId = readHashCategory();
|
||||
var validIds = {};
|
||||
for (var i = 0; i < categories.length; i++) {
|
||||
validIds[categories[i].id] = true;
|
||||
}
|
||||
if (!selectedId || !validIds[selectedId]) {
|
||||
selectedId = categories[0].id;
|
||||
setHashCategory(selectedId);
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
const ps = pageSize();
|
||||
if (ps !== lastSize) {
|
||||
lastSize = ps;
|
||||
for (const key of Object.keys(pageByCat)) pageByCat[key] = 1;
|
||||
function findCategory(id) {
|
||||
for (var j = 0; j < categories.length; j++) {
|
||||
if (categories[j].id === id) return categories[j];
|
||||
}
|
||||
return categories[0];
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
tabsEl.innerHTML = "";
|
||||
for (var k = 0; k < categories.length; k++) {
|
||||
var cat = categories[k];
|
||||
var btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "cat-tab";
|
||||
btn.setAttribute("role", "tab");
|
||||
btn.setAttribute("aria-selected", cat.id === selectedId ? "true" : "false");
|
||||
btn.textContent = (cat.name || cat.id) + " · " + (cat.items || []).length;
|
||||
btn.addEventListener(
|
||||
"click",
|
||||
(function (id) {
|
||||
return function () {
|
||||
if (selectedId === id) return;
|
||||
selectedId = id;
|
||||
setHashCategory(id);
|
||||
renderTabs();
|
||||
renderGrid();
|
||||
};
|
||||
})(cat.id)
|
||||
);
|
||||
tabsEl.appendChild(btn);
|
||||
}
|
||||
var selectedBtn = tabsEl.querySelector('[aria-selected="true"]');
|
||||
if (selectedBtn) {
|
||||
selectedBtn.scrollIntoView({ inline: "center", block: "nearest", behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
|
||||
function renderGrid() {
|
||||
var cat = findCategory(selectedId);
|
||||
var items = cat.items || [];
|
||||
app.innerHTML = "";
|
||||
for (const cat of categories) {
|
||||
app.appendChild(renderCategory(cat, origin, ps));
|
||||
}
|
||||
}
|
||||
|
||||
function renderCategory(cat, origin, ps) {
|
||||
const section = document.createElement("section");
|
||||
section.className = "category";
|
||||
const items = cat.items || [];
|
||||
const totalPages = Math.max(1, Math.ceil(items.length / ps));
|
||||
const page = ensurePage(cat.id, totalPages);
|
||||
const start = (page - 1) * ps;
|
||||
const slice = items.slice(start, start + ps);
|
||||
|
||||
const h2 = document.createElement("h2");
|
||||
h2.appendChild(document.createTextNode(cat.name || cat.id));
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "badge";
|
||||
badge.textContent = items.length + " 张";
|
||||
h2.appendChild(badge);
|
||||
section.appendChild(h2);
|
||||
|
||||
const pager = document.createElement("div");
|
||||
pager.className = "pager";
|
||||
const prev = document.createElement("button");
|
||||
prev.type = "button";
|
||||
prev.textContent = "上一页";
|
||||
prev.disabled = page <= 1;
|
||||
prev.addEventListener("click", function () {
|
||||
pageByCat[cat.id] = page - 1;
|
||||
renderAll();
|
||||
});
|
||||
const next = document.createElement("button");
|
||||
next.type = "button";
|
||||
next.textContent = "下一页";
|
||||
next.disabled = page >= totalPages;
|
||||
next.addEventListener("click", function () {
|
||||
pageByCat[cat.id] = page + 1;
|
||||
renderAll();
|
||||
});
|
||||
const info = document.createElement("span");
|
||||
info.textContent =
|
||||
"第 " + page + " / " + totalPages + " 页 · 本页 " + slice.length + " 张";
|
||||
pager.appendChild(prev);
|
||||
pager.appendChild(next);
|
||||
pager.appendChild(info);
|
||||
section.appendChild(pager);
|
||||
|
||||
const grid = document.createElement("div");
|
||||
var grid = document.createElement("div");
|
||||
grid.className = "grid";
|
||||
for (const it of slice) {
|
||||
const url = it.url || origin + "/meme/" + encodeURIComponent(cat.id) + "/" + encodeURIComponent(it.file);
|
||||
for (let n = 0; n < items.length; n++) {
|
||||
const it = items[n];
|
||||
const url =
|
||||
it.url ||
|
||||
origin +
|
||||
"/meme/" +
|
||||
encodeURIComponent(cat.id) +
|
||||
"/" +
|
||||
encodeURIComponent(it.file);
|
||||
const tile = document.createElement("button");
|
||||
tile.type = "button";
|
||||
tile.className = "tile";
|
||||
tile.title = "点击复制:" + url;
|
||||
tile.title = it.file;
|
||||
const img = document.createElement("img");
|
||||
img.loading = "lazy";
|
||||
img.alt = it.file;
|
||||
@@ -298,19 +622,27 @@
|
||||
label.textContent = it.file;
|
||||
tile.appendChild(label);
|
||||
tile.addEventListener("click", function () {
|
||||
copyText(url);
|
||||
openSheet(url, it.file, cat.name || cat.id);
|
||||
});
|
||||
grid.appendChild(tile);
|
||||
}
|
||||
section.appendChild(grid);
|
||||
return section;
|
||||
app.appendChild(grid);
|
||||
}
|
||||
|
||||
renderAll();
|
||||
mq.addEventListener("change", renderAll);
|
||||
renderTabs();
|
||||
renderGrid();
|
||||
|
||||
window.addEventListener("hashchange", function () {
|
||||
var hid = readHashCategory();
|
||||
if (hid && validIds[hid] && hid !== selectedId) {
|
||||
selectedId = hid;
|
||||
renderTabs();
|
||||
renderGrid();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fetch("/api/v1/memes")
|
||||
fetch("/memes.json")
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
||||
return r.json();
|
||||
@@ -318,9 +650,9 @@
|
||||
.then(render)
|
||||
.catch(function (e) {
|
||||
app.innerHTML =
|
||||
'<div class="error">无法加载列表:<code>' +
|
||||
'<div class="error">加载失败:<code>' +
|
||||
String(e.message || e) +
|
||||
'</code>。请确认 Worker 已部署且路由正确。</div>';
|
||||
"</code></div>";
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user