/** * AKSRT user badges — zibll medal UX parity. * * User-center modal: category-grouped medal card grid + in-modal detail * view (medal-background card, obtain time, wear action). Also provides the * global wear handler used inside remote (AJAX HTML) badge modals. */ (function () { 'use strict'; var aksrt = window.aksrtData || {}; var siteUrl = aksrt.siteUrl || ''; if (window.location.protocol === 'https:' && siteUrl.substring(0, 5) === 'http:') { siteUrl = 'https:' + siteUrl.substring(5); } var REST = (siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1'; var NONCE = aksrt.restNonce || aksrt.nonce || ''; function api(path, options) { var opts = Object.assign({ credentials: 'same-origin', headers: Object.assign({ 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE }, (options && options.headers) || {}), }, options || {}); return fetch(REST + path, opts).then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { return { ok: r.ok, status: r.status, json: j }; }); }); } function toast(msg, type) { if (window.AksrtToast && typeof window.AksrtToast.show === 'function') { window.AksrtToast.show(msg, type || 'info'); return; } if (window.aksrtNotify) { window.aksrtNotify(msg, type || 'info'); return; } // fallback try { console.log('[AKSRT badges]', msg); } catch (_) {} } function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } /* ---------- shared partials ---------- */ function iconHtml(item, cls) { var icon = item && item.icon_url ? String(item.icon_url) : ''; var label = item && item.label ? String(item.label) : ''; if (icon) { return '' + escapeHtml(label) + ''; } return ''; } function emptyHtml(text, desc) { return '
' + '
' + '
' + escapeHtml(text) + '
' + (desc ? '
' + escapeHtml(desc) + '
' : '') + '
'; } function cardHtml(item) { var cls = 'medal-card' + (item.owned ? ' is-has' : ' no-has') + (item.wear ? ' is-wear' : ''); return '
' + iconHtml(item) + '
' + escapeHtml(item.label) + '
' + (item.wear ? '
' + escapeHtml('佩戴中') + '
' : '') + '
'; } /* ---------- user-center modal ---------- */ function init() { var openers = document.querySelectorAll('[data-aksrt-badges-open]'); var modal = document.querySelector('[data-aksrt-badges-modal]'); if (!modal || !openers.length) return; var userId = parseInt(modal.getAttribute('data-user-id') || '0', 10); var canWear = modal.getAttribute('data-can-wear') === '1'; var medalBg = modal.getAttribute('data-medal-bg') || ''; var body = modal.querySelector('[data-aksrt-badges-body]'); var detailBox = modal.querySelector('[data-aksrt-badges-detail]'); var countSlot = modal.querySelector('[data-aksrt-badges-count]'); var wearSlot = modal.querySelector('[data-aksrt-badges-wear-slot]'); var state = { data: null, detailSlug: '' }; var telemetry = window.AksrtModalTelemetry; var telemetryId = ''; function telemetryMark(event) { if (telemetry && telemetryId && typeof telemetry.mark === 'function') telemetry.mark(telemetryId, event); } function findItem(slug) { var cats = (state.data && state.data.categories) || []; for (var i = 0; i < cats.length; i++) { var items = cats[i].items || []; for (var j = 0; j < items.length; j++) { if (items[j].slug === slug) return items[j]; } } return null; } function wornItem() { var wear = state.data && state.data.wear; return wear ? findItem(wear) : null; } /* ----- view switching ----- */ function showView(name) { modal.querySelectorAll('[data-aksrt-badges-view]').forEach(function (view) { view.hidden = view.getAttribute('data-aksrt-badges-view') !== name; }); if (name === 'grid') state.detailSlug = ''; } function openModal() { modal.hidden = false; document.body.classList.add('uc-modal-open'); } function closeModal() { modal.hidden = true; document.body.classList.remove('uc-modal-open'); showView('grid'); } /* ----- render: head ----- */ function renderHead() { var count = state.data && typeof state.data.count === 'number' ? state.data.count : 0; if (countSlot) { countSlot.textContent = count > 0 ? '共 ' + count + ' 枚' : ''; } if (!wearSlot) return; var worn = wornItem(); wearSlot.innerHTML = worn ? '' + iconHtml(worn) + '' + escapeHtml(worn.label) + '' : ''; } /* ----- render: grid ----- */ function renderGrid() { if (!body) return; var cats = (state.data && state.data.categories) || []; if (!cats.length) { body.innerHTML = emptyHtml('暂无徽章', '积极参与社区互动即可获得徽章'); return; } body.innerHTML = cats.map(function (cat) { var cards = (cat.items || []).map(function (item) { return ''; }).join(''); return '
' + '
' + escapeHtml(cat.cat_name || '') + '
' + '
' + cards + '
' + '
'; }).join(''); } /* ----- render: detail ----- */ function renderDetail(slug) { var item = findItem(slug); if (!item || !detailBox) return; state.detailSlug = slug; var stateLine = ''; if (item.owned && item.time) { stateLine = '
' + escapeHtml(item.time + ' 获得') + '
'; } else if (!item.owned) { stateLine = '
' + escapeHtml('暂未获得') + '
'; } var action = ''; if (canWear && item.owned && !item.wear) { action = ''; } else if (item.wear) { action = '' + escapeHtml('当前佩戴此徽章') + ''; } detailBox.innerHTML = '
' + '' + '
' + '
' + (medalBg ? '
' : '') + '
' + iconHtml(item) + '
' + escapeHtml(item.label) + '
' + (item.desc ? '
' + escapeHtml(item.desc) + '
' : '') + stateLine + '
' + '
' + (action ? '
' + action + '
' : ''); showView('detail'); } /* ----- header worn icon sync (profile header, next to the name) ----- */ function syncHeaderIcon() { var worn = wornItem(); if (!worn || !worn.icon_url) return; document.querySelectorAll('.aksrt-identity-medal.is-wear').forEach(function (node) { if (node.tagName === 'IMG') { node.src = worn.icon_url; node.alt = worn.label; node.title = worn.label; } else { var img = document.createElement('img'); img.className = 'medal-icon img-icon ml3 aksrt-identity-medal is-wear'; img.src = worn.icon_url; img.alt = worn.label; img.title = worn.label; img.setAttribute('loading', 'lazy'); img.setAttribute('decoding', 'async'); node.parentNode.replaceChild(img, node); } }); } /* ----- data ----- */ function load() { if (!userId) return Promise.resolve(); if (body) { body.innerHTML = '
' + escapeHtml('加载中…') + '
'; } return api('/users/' + userId + '/badges').then(function (r) { if (!r.ok || !r.json || !r.json.data) { telemetryMark('error'); if (body) { body.innerHTML = '
' + '' + escapeHtml('加载失败') + '' + '' + '
'; } return; } state.data = r.json.data; telemetryMark('first-content'); renderHead(); renderGrid(); telemetryMark('ready'); }); } function wear(slug, btn) { btn.disabled = true; api('/users/me/badges/wear', { method: 'POST', body: JSON.stringify({ slug: slug }), }).then(function (r) { if (!r.ok) { toast((r.json && (r.json.message || (r.json.data && r.json.data.message))) || '佩戴失败', 'error'); btn.disabled = false; return; } toast((r.json && r.json.message) || '已佩戴徽章', 'success'); state.data = (r.json && r.json.data) || state.data; renderHead(); renderGrid(); renderDetail(slug); syncHeaderIcon(); }).catch(function () { toast('网络错误', 'error'); btn.disabled = false; }); } /* ----- events ----- */ openers.forEach(function (btn) { btn.addEventListener('click', function (e) { e.preventDefault(); telemetryId = telemetry && typeof telemetry.start === 'function' ? telemetry.start('badges') : ''; openModal(); telemetryMark('shell'); showView('grid'); load(); }); }); modal.querySelectorAll('[data-aksrt-badges-close]').forEach(function (btn) { btn.addEventListener('click', function (e) { e.preventDefault(); closeModal(); }); }); document.addEventListener('keydown', function (e) { if (e.key !== 'Escape' || modal.hidden) return; var detailView = modal.querySelector('[data-aksrt-badges-view="detail"]'); // Detail view open: ESC steps back to the grid first. if (detailView && !detailView.hidden) { showView('grid'); return; } closeModal(); }); modal.addEventListener('click', function (e) { var back = e.target.closest('[data-aksrt-badges-back]'); if (back) { e.preventDefault(); showView('grid'); return; } var retry = e.target.closest('[data-aksrt-badges-retry]'); if (retry) { e.preventDefault(); load(); return; } var wearBtn = e.target.closest('[data-aksrt-badge-wear]'); if (wearBtn && canWear) { e.preventDefault(); wear(wearBtn.getAttribute('data-aksrt-badge-wear') || '', wearBtn); return; } var card = e.target.closest('[data-aksrt-badge-detail]'); if (card) { e.preventDefault(); renderDetail(card.getAttribute('data-aksrt-badge-detail') || ''); } }); } /* ---------- remote modal: global wear handler ---------- */ document.addEventListener('click', function (e) { var wearBtn = e.target.closest('[data-aksrt-badge-wear]'); if (!wearBtn) return; // UC modal has its own handler when canWear on modal root. var modalRoot = wearBtn.closest('[data-aksrt-badges-modal]'); if (modalRoot) return; e.preventDefault(); var slug = wearBtn.getAttribute('data-aksrt-badge-wear') || ''; if (!slug) return; wearBtn.disabled = true; api('/users/me/badges/wear', { method: 'POST', body: JSON.stringify({ slug: slug }), }).then(function (r) { if (!r.ok) { toast((r.json && (r.json.message || (r.json.data && r.json.data.message))) || '佩戴失败', 'error'); wearBtn.disabled = false; return; } toast((r.json && r.json.message) || '已佩戴徽章', 'success'); // Re-open the refreshed single-badge modal so state stays in sync. var refreshed = null; try { var uid = parseInt((r.json && r.json.data && r.json.data.user_id) || '0', 10); var data = r.json && r.json.data; var isSelf = data && data.is_self; if (isSelf && data.wear) { refreshed = (window.aksrtData && aksrtData.ajaxUrl) ? aksrtData.ajaxUrl + '?action=aksrt_single_badge_info_modal&name=' + encodeURIComponent(data.wear) + '&user_id=' + (uid || 0) : null; } } catch (_) {} try { if (refreshed && window.AksrtRemoteModal && typeof window.AksrtRemoteModal.openRemote === 'function') { window.AksrtRemoteModal.openRemote({ url: refreshed, className: 'modal-mini', mobileBottom: true }); } else { document.dispatchEvent(new CustomEvent('aksrt:remote-modal:close')); } } catch (_) { try { document.dispatchEvent(new CustomEvent('aksrt:remote-modal:close')); } catch (__) {} } }).catch(function () { toast('网络错误', 'error'); wearBtn.disabled = false; }); }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();