feat(badges): medal info remote modal + grant notification
Add zibll-like badge list/single AJAX modals, Message notify on grant, author-home/user-center entry points, and front wear handler.
This commit is contained in:
@@ -139,6 +139,38 @@
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Global wear handler (remote modal + UC modal).
|
||||
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");
|
||||
// Soft refresh modal content if remote host present.
|
||||
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 {
|
||||
|
||||
@@ -65,6 +65,12 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
|
||||
<button type="button" class="btn-ghost btn-sm" data-aksrt-badges-open>
|
||||
<i class="fas fa-award" aria-hidden="true"></i> <?php esc_html_e('我的徽章', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php
|
||||
$remote_badge = \AKSRT\User\Badges::get_info_link((int) $user_id, 'btn-ghost btn-sm', __('徽章弹层', 'aksrt'));
|
||||
if ($remote_badge !== '') {
|
||||
echo $remote_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="<?php echo esc_url($messages_url); ?>" class="btn-ghost btn-sm">
|
||||
|
||||
@@ -968,7 +968,28 @@ trait ScriptsPart1b {
|
||||
}
|
||||
|
||||
|
||||
$this->enqueue_public_fonts_js_and_data($base_uri, $core_dep, $deps, $is_fixed_homepage, $is_home_or_archive, $is_single_post, $is_single_resource, $version);
|
||||
|
||||
// Badges UI: user center + public profile (zibll medal modal/list).
|
||||
if (class_exists('\AKSRT\User\Badges') && \AKSRT\User\Badges::enabled()) {
|
||||
$is_badge_surface = $is_profile_page
|
||||
|| is_author()
|
||||
|| (function_exists('aksrt_is_user_home_page') && aksrt_is_user_home_page())
|
||||
|| (function_exists('aksrt_is_user_center_page') && aksrt_is_user_center_page());
|
||||
if ($is_badge_surface) {
|
||||
$badges_js_path_global = get_template_directory() . '/assets/js/user-badges.js';
|
||||
if (file_exists($badges_js_path_global) && !wp_script_is('aksrt-user-badges', 'enqueued')) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-user-badges',
|
||||
$base_uri . '/assets/js/user-badges.js',
|
||||
['aksrt-script', 'aksrt-core-remote-modal'],
|
||||
(string) filemtime($badges_js_path_global),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->enqueue_public_fonts_js_and_data($base_uri, $core_dep, $deps, $is_fixed_homepage, $is_home_or_archive, $is_single_post, $is_single_resource, $version);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,209 @@ class Badges {
|
||||
|
||||
public function init(): void {
|
||||
add_action('rest_api_init', [$this, 'register_routes']);
|
||||
add_action('aksrt_badge_granted', [$this, 'on_badge_granted_notify'], 10, 3);
|
||||
// HTML modal for remote-modal (zibll user_medal_info_modal).
|
||||
add_action('wp_ajax_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']);
|
||||
add_action('wp_ajax_nopriv_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']);
|
||||
add_action('wp_ajax_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']);
|
||||
add_action('wp_ajax_nopriv_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify user when a badge is granted (zibll user_add_medal message).
|
||||
*/
|
||||
public function on_badge_granted_notify(int $user_id, string $slug, int $actor = 0): void {
|
||||
if ($user_id <= 0 || $slug === '') {
|
||||
return;
|
||||
}
|
||||
$map = self::get_dictionary_map();
|
||||
if (!isset($map[$slug])) {
|
||||
return;
|
||||
}
|
||||
$label = (string) ($map[$slug]['label'] ?? $slug);
|
||||
$title = sprintf(
|
||||
/* translators: %s badge name */
|
||||
__('恭喜您!获得新的徽章[%s]', 'aksrt'),
|
||||
$label
|
||||
);
|
||||
$content = sprintf(
|
||||
/* translators: %s badge name */
|
||||
__('恭喜您获得徽章「%s」', 'aksrt'),
|
||||
$label
|
||||
);
|
||||
if (class_exists('\AKSRT\Messages\Message')) {
|
||||
\AKSRT\Messages\Message::get_instance()->send($user_id, 'medal', $content, [
|
||||
'title' => $title,
|
||||
'target_url' => function_exists('aksrt_get_user_center_url')
|
||||
? aksrt_get_user_center_url()
|
||||
: home_url('/'),
|
||||
'slug' => $slug,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: full badge list modal HTML.
|
||||
*/
|
||||
public function ajax_info_modal(): void {
|
||||
$user_id = max(0, (int) ($_REQUEST['id'] ?? $_REQUEST['user_id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ($user_id <= 0) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||
wp_send_json_error(['message' => __('用户不存在', 'aksrt')], 404);
|
||||
}
|
||||
$html = self::render_info_modal_html($user_id);
|
||||
wp_send_json_success(['html' => $html]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: single badge detail modal.
|
||||
*/
|
||||
public function ajax_single_info_modal(): void {
|
||||
$slug = sanitize_key((string) ($_REQUEST['name'] ?? $_REQUEST['slug'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$user_id = max(0, (int) ($_REQUEST['user_id'] ?? $_REQUEST['id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ($slug === '') {
|
||||
wp_send_json_error(['message' => __('徽章不存在', 'aksrt')], 404);
|
||||
}
|
||||
$html = self::render_single_modal_html($slug, $user_id);
|
||||
wp_send_json_success(['html' => $html]);
|
||||
}
|
||||
|
||||
/**
|
||||
* zibll zib_get_user_medal_info_modal lite.
|
||||
*/
|
||||
public static function render_info_modal_html(int $user_id): string {
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return '<div class="aksrt-badge-modal"><p>' . esc_html__('用户不存在', 'aksrt') . '</p></div>';
|
||||
}
|
||||
$current = get_current_user_id();
|
||||
$is_self = $current > 0 && $current === $user_id;
|
||||
$display = $is_self ? __('我', 'aksrt') : $user->display_name;
|
||||
$avatar = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user_id, ['size' => 64])
|
||||
: get_avatar_url($user_id, ['size' => 64]);
|
||||
$data = self::list_for_user($user_id, $is_self || self::can_manually_set($current));
|
||||
$items = is_array($data['items'] ?? null) ? $data['items'] : [];
|
||||
$wear = (string) ($data['wear'] ?? '');
|
||||
|
||||
$html = '<div class="aksrt-badge-modal">';
|
||||
$html .= '<div class="aksrt-badge-modal__head">';
|
||||
$html .= '<img class="aksrt-badge-modal__avatar" src="' . esc_url($avatar) . '" alt="" width="40" height="40" />';
|
||||
$html .= '<div class="aksrt-badge-modal__title">' . esc_html(sprintf(__('%s的徽章', 'aksrt'), $display));
|
||||
if ($wear !== '') {
|
||||
$map = self::get_dictionary_map();
|
||||
if (isset($map[$wear])) {
|
||||
$html .= ' <span class="aksrt-badge-modal__wear-tag">' . esc_html(sprintf(__('佩戴:%s', 'aksrt'), (string) $map[$wear]['label'])) . '</span>';
|
||||
}
|
||||
} else {
|
||||
$html .= ' <span class="badg badg-sm c-red">' . esc_html__('暂无徽章', 'aksrt') . '</span>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
$html .= '<button type="button" class="aksrt-remote-modal__close" data-aksrt-remote-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">×</button>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="aksrt-badge-modal__list mini-scrollbar">';
|
||||
if (!$items) {
|
||||
$html .= '<div class="aksrt-badge-modal__empty">' . esc_html__('暂无徽章', 'aksrt') . '</div>';
|
||||
} else {
|
||||
foreach ($items as $item) {
|
||||
$slug = (string) ($item['slug'] ?? '');
|
||||
$label = (string) ($item['label'] ?? $slug);
|
||||
$icon = (string) ($item['icon_url'] ?? '');
|
||||
$is_wear = !empty($item['wear']);
|
||||
$html .= '<div class="aksrt-badge-modal__item' . ($is_wear ? ' is-wear' : '') . '" data-slug="' . esc_attr($slug) . '">';
|
||||
if ($icon !== '') {
|
||||
$html .= '<img class="medal-icon img-icon" src="' . esc_url($icon) . '" alt="" width="40" height="40" loading="lazy" />';
|
||||
} else {
|
||||
$html .= '<span class="aksrt-badge-modal__icon-fa"><i class="fas fa-award" aria-hidden="true"></i></span>';
|
||||
}
|
||||
$html .= '<div class="aksrt-badge-modal__meta"><div class="aksrt-badge-modal__name">' . esc_html($label) . '</div>';
|
||||
if ($is_wear) {
|
||||
$html .= '<div class="em09 c-blue">' . esc_html__('当前佩戴', 'aksrt') . '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if ($is_self && !$is_wear) {
|
||||
$html .= '<button type="button" class="but jb-blue but-sm" data-aksrt-badge-wear="' . esc_attr($slug) . '">' . esc_html__('佩戴此徽章', 'aksrt') . '</button>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
}
|
||||
}
|
||||
$html .= '</div></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single badge card modal.
|
||||
*/
|
||||
public static function render_single_modal_html(string $slug, int $user_id = 0): string {
|
||||
$map = self::get_dictionary_map();
|
||||
$slug = sanitize_key($slug);
|
||||
if ($slug === '' || !isset($map[$slug])) {
|
||||
return '<div class="aksrt-badge-modal"><p>' . esc_html__('徽章不存在', 'aksrt') . '</p></div>';
|
||||
}
|
||||
$item = $map[$slug];
|
||||
$current = get_current_user_id();
|
||||
$is_self = $user_id > 0 && $current === $user_id;
|
||||
$owned = $user_id > 0 ? self::get_owned($user_id) : [];
|
||||
$has = in_array($slug, $owned, true);
|
||||
$wear = $user_id > 0 ? self::get_wear($user_id) : '';
|
||||
$is_wear = $has && $wear === $slug;
|
||||
|
||||
$html = '<div class="aksrt-badge-modal aksrt-badge-modal--single">';
|
||||
$html .= '<button type="button" class="aksrt-remote-modal__close" data-aksrt-remote-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">×</button>';
|
||||
$html .= '<div class="aksrt-badge-modal__card">';
|
||||
if (!empty($item['icon_url'])) {
|
||||
$html .= '<img class="medal-icon img-icon" src="' . esc_url((string) $item['icon_url']) . '" alt="" width="72" height="72" />';
|
||||
} else {
|
||||
$html .= '<span class="aksrt-badge-modal__icon-fa lg"><i class="fas fa-award" aria-hidden="true"></i></span>';
|
||||
}
|
||||
$html .= '<div class="aksrt-badge-modal__name">' . esc_html((string) $item['label']) . '</div>';
|
||||
if ($is_self) {
|
||||
$html .= $has
|
||||
? '<div class="c-blue em09 mt6">' . esc_html__('已获得', 'aksrt') . '</div>'
|
||||
: '<div class="c-yellow em09 mt6">' . esc_html__('暂未获得', 'aksrt') . '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if ($is_self && $has && !$is_wear) {
|
||||
$html .= '<div class="aksrt-badge-modal__actions"><button type="button" class="but jb-blue btn-block" data-aksrt-badge-wear="' . esc_attr($slug) . '">' . esc_html__('佩戴此徽章', 'aksrt') . '</button></div>';
|
||||
} elseif ($is_self && $is_wear) {
|
||||
$html .= '<div class="aksrt-badge-modal__actions"><span class="badg btn-block c-blue">' . esc_html__('当前佩戴此徽章', 'aksrt') . '</span></div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote-modal trigger for a user's badge list (zibll zib_get_user_medal_info_link).
|
||||
*/
|
||||
public static function get_info_link(int $user_id, string $class = '', ?string $text = null): string {
|
||||
if ($user_id <= 0 || !self::enabled()) {
|
||||
return '';
|
||||
}
|
||||
if ($text === null) {
|
||||
$text = __('徽章明细', 'aksrt');
|
||||
}
|
||||
if (!function_exists('aksrt_get_remote_modal_link')) {
|
||||
return '';
|
||||
}
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'action' => 'aksrt_user_badge_info_modal',
|
||||
'id' => $user_id,
|
||||
],
|
||||
admin_url('admin-ajax.php')
|
||||
);
|
||||
return aksrt_get_remote_modal_link([
|
||||
'tag' => 'a',
|
||||
'class' => trim('user-medal-info ' . $class),
|
||||
'text' => $text,
|
||||
'url' => $url,
|
||||
'data_class' => 'full-sm',
|
||||
'height' => 240,
|
||||
'mobile_bottom' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
|
||||
@@ -102,6 +102,16 @@ $pm_url = ($pm_enabled && $messages_url !== '')
|
||||
</h1>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// Badge detail remote-modal (zibll user medal info).
|
||||
if (class_exists('\AKSRT\User\Badges') && \AKSRT\User\Badges::enabled() && \AKSRT\User\Badges::public_display()) {
|
||||
$badge_link = \AKSRT\User\Badges::get_info_link($user_id, 'but but-sm jb-blue mt6', __('徽章明细', 'aksrt'));
|
||||
if ($badge_link !== '') {
|
||||
echo '<div class="aksrt-author-hero__badges">' . $badge_link . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($show_desc && $user->description) : ?>
|
||||
<p class="aksrt-author-hero__desc"><?php echo esc_html($user->description); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user