Files
wordpress-theme-aksrt/assets/js/messages.js
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

246 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Jio message center DOM navigation.
*
* @package Jio
*/
(function () {
'use strict';
var root = document.querySelector('[data-message-center]');
if (!root) return;
var nonce = typeof jioData !== 'undefined' ? jioData.nonce : '';
var ajaxUrl = typeof jioData !== 'undefined' ? jioData.ajaxUrl : '/wp-admin/admin-ajax.php';
var content = root.querySelector('[data-message-content]');
var nav = root.querySelector('[data-message-nav]');
var state = {
view: root.dataset.view || 'unread',
page: 1,
detail: 0,
paginateType: root.dataset.paginateType || 'ajax_lists',
};
var pollInterval = 60000;
function post(action, data) {
var body = new URLSearchParams({ action: action, nonce: nonce });
Object.keys(data || {}).forEach(function (key) {
var value = data[key];
if (Array.isArray(value)) {
value.forEach(function (item) { body.append(key + '[]', item); });
} else {
body.append(key, value);
}
});
return fetch(ajaxUrl, { method: 'POST', body: body, credentials: 'same-origin' })
.then(function (response) { return response.json(); });
}
function routeFor(view) {
var link = nav.querySelector('[data-message-view="' + view + '"]');
return link ? link.href : window.location.href;
}
function updateHeaderBadge(unread) {
var count = Math.max(0, Number(unread) || 0);
var headerBadge = document.querySelector('.jio-notification-badge');
var avatarDot = document.querySelector('.nav-user__dot');
if (headerBadge) {
headerBadge.textContent = String(count);
headerBadge.style.display = count > 0 ? '' : 'none';
}
if (avatarDot) avatarDot.style.display = count > 0 ? '' : 'none';
}
function renderError(message) {
content.innerHTML = '<div class="messages-empty"><span class="messages-empty__icon"><i class="fa fas fa-exclamation-circle"></i></span><p class="messages-empty__text"></p></div>';
content.querySelector('.messages-empty__text').textContent = message || '加载失败,请稍后重试';
}
function appendMessagePage(markup) {
var incoming = document.createElement('div');
incoming.innerHTML = markup;
var currentList = content.querySelector('.messages-list');
var incomingList = incoming.querySelector('.messages-list');
if (!currentList || !incomingList) {
content.innerHTML = markup;
return null;
}
var existingIds = new Set();
currentList.querySelectorAll('[data-notification-id]').forEach(function (item) {
existingIds.add(item.dataset.notificationId);
});
// 骨架常驻列表末尾:新条目一律插到它之前,形状与参考主题一致
// (post_ajax 的 beforeSend 把 loader 挪到最后一张卡片之后)。
var skeleton = currentList.querySelector(':scope > .post_ajax_loader');
var firstAdded = null;
incomingList.querySelectorAll('.message-item').forEach(function (item) {
if (existingIds.has(item.dataset.notificationId)) return;
if (!firstAdded) firstAdded = item;
if (skeleton) {
currentList.insertBefore(item, skeleton);
} else {
currentList.appendChild(item);
}
});
var currentPagination = content.querySelector('.messages-pagination');
var incomingPagination = incoming.querySelector('.messages-pagination');
if (currentPagination) currentPagination.remove();
if (incomingPagination) content.appendChild(incomingPagination);
return firstAdded;
}
function load(next, options) {
var requested = {
view: next.view || state.view,
page: next.page || 1,
detail: next.detail || 0,
};
var settings = options || {};
root.classList.add('is-loading');
content.setAttribute('aria-busy', 'true');
// 骨架只在 AJAX 请求期间展开(.is-loading),与文章列表翻页同一契约。
var skeleton = content.querySelector('.messages-list > .post_ajax_loader');
if (skeleton) skeleton.classList.add('is-loading');
return post('jio_load_message_center', {
view: requested.view,
page: requested.page,
notification_id: requested.detail,
}).then(function (result) {
if (!result.success || !result.data) {
throw new Error(result.data && result.data.message ? result.data.message : '加载失败,请稍后重试');
}
requested.paginateType = result.data.paginate_type || state.paginateType;
state = requested;
root.dataset.view = state.view;
root.dataset.paginateType = state.paginateType;
nav.innerHTML = result.data.nav;
var firstAdded = null;
if (settings.append && state.paginateType === 'ajax_lists') {
firstAdded = appendMessagePage(result.data.content);
} else {
// 整块替换:服务端每个 .messages-list 都自带收起态骨架,无需搬运旧节点。
// 旧节点此刻带着 .is-loading,搬进空列表 / 详情页会脱离
// `.messages-list >` 选择器,finally 摘不掉类,骨架会常驻。
content.innerHTML = result.data.content;
}
updateHeaderBadge(result.data.unread);
if (settings.history === 'push') {
window.history.pushState(state, '', routeFor(state.view));
} else if (settings.history === 'replace') {
window.history.replaceState(state, '', routeFor(state.view));
}
if (firstAdded) {
firstAdded.setAttribute('tabindex', '-1');
firstAdded.focus({ preventScroll: true });
} else {
content.focus({ preventScroll: true });
}
}).catch(function (error) {
renderError(error.message);
}).finally(function () {
root.classList.remove('is-loading');
content.removeAttribute('aria-busy');
// 收起所有残留的骨架(不限于 .messages-list 直接子节点),保证任何分支都不会卡在加载态。
content.querySelectorAll('.post_ajax_loader.is-loading').forEach(function (loader) {
loader.classList.remove('is-loading');
});
});
}
function reloadList() {
return load({ view: state.view, page: state.page, detail: 0 });
}
document.addEventListener('click', function (event) {
var tab = event.target.closest('[data-message-view]');
if (!tab || !root.contains(tab) || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
event.preventDefault();
load({ view: tab.dataset.messageView, page: 1, detail: 0 }, { history: 'push' });
});
document.addEventListener('click', function (event) {
var button = event.target.closest('.js-msg-detail');
if (!button || !root.contains(button)) return;
event.preventDefault();
load({ view: button.dataset.view || state.view, page: state.page, detail: button.dataset.id });
});
document.addEventListener('click', function (event) {
var button = event.target.closest('[data-message-back]');
if (!button || !root.contains(button)) return;
load({ view: button.dataset.view || state.view, page: state.page, detail: 0 });
});
document.addEventListener('click', function (event) {
var button = event.target.closest('[data-message-page]');
if (!button || !root.contains(button)) return;
event.preventDefault();
load({ view: state.view, page: button.dataset.messagePage, detail: 0 });
});
document.addEventListener('click', function (event) {
var button = event.target.closest('[data-message-next-page]');
if (!button || !root.contains(button)) return;
event.preventDefault();
load(
{ view: state.view, page: button.dataset.messageNextPage, detail: 0 },
{ append: true }
);
});
document.addEventListener('click', function (event) {
var button = event.target.closest('.js-msg-mark-read');
if (!button || !root.contains(button)) return;
post('jio_mark_notification_read', { notification_id: button.dataset.id }).then(function (result) {
if (result.success) reloadList();
});
});
document.addEventListener('click', function (event) {
var button = event.target.closest('.js-msg-delete');
if (!button || !root.contains(button)) return;
post('jio_delete_notification', { notification_id: button.dataset.id }).then(function (result) {
if (result.success) reloadList();
});
});
document.addEventListener('click', function (event) {
var button = event.target.closest('.js-msg-mark-all-read');
if (!button || !root.contains(button)) return;
post('jio_mark_notification_read', { mark_all: '1' }).then(function (result) {
if (result.success) reloadList();
});
});
document.addEventListener('click', function (event) {
var button = event.target.closest('.js-msg-clear-read');
if (!button || !root.contains(button)) return;
var ids = [];
root.querySelectorAll('.message-item:not(.is-unread)[data-notification-id]').forEach(function (item) {
if (/^\d+$/.test(item.dataset.notificationId)) ids.push(item.dataset.notificationId);
});
if (!ids.length) return;
post('jio_delete_notifications', { notification_ids: ids }).then(function (result) {
if (result.success) reloadList();
});
});
window.addEventListener('popstate', function (event) {
var previous = event.state;
if (previous && previous.view) {
load(previous);
}
});
window.history.replaceState(state, '', routeFor(state.view));
window.setInterval(function () {
post('jio_check_new_notifications', {}).then(function (result) {
if (result.success && result.data) updateHeaderBadge(result.data.unread);
}).catch(function () {});
}, pollInterval);
})();