fix(single): align article like/favorite interaction UX with reference

The like and favorite buttons gave no in-flight feedback: the label just sat
there until the request returned, the lovestar animation never replayed on a
repeat like, duplicate clicks reported "success", and guest favorites silently
wrote a cookie instead of asking the user to sign in.

- pending state: replace the count with the reference inline spinner
  (`<i class="loading zts">`) and disable the button until the response lands
- animation: strip `action-animation` before re-adding it (forced reflow), so
  every successful like replays the reference lovestar keyframes
- duplicate: when the server reports the same state and count, skip the
  animation and surface the reference warning copy (已赞过此文章了!)
- failures: restore the original count and toast `danger`; guest-only actions
  now open the site login modal instead of dead-ending on an error
- favorite requires login server-side, matching the reference; the guest
  favourite cookie branch and its reader are removed as dead code
- unify like/favorite through one bindToggle path and keep every mirror
  button (article row + footer tabbar) in sync

Tests: new article-actions-ux suite (8 cases) drives assets/js/core/article.js
against a DOM/fetch shim — pending spinner, replay, duplicate no-op,
concurrency, rollback, guest login, mirror sync. Widget suite 828/829; the one
failure (frontend-lifecycle theme-init stub) predates this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-03 16:36:45 +08:00
1 parent 3c07ef7a8d
commit 93e83e1835
7 files changed
+470 -92

No files matched your search

+9 -14
View File
@@ -487,16 +487,13 @@ if (!function_exists('jio_is_post_liked')) :
}
endif;
if (!function_exists('jio_is_post_favorited')) :
/** 收藏必须登录(与参考主题一致),游客恒为未收藏。 */
function jio_is_post_favorited($post_id = null) {
$post_id = $post_id ? (int) $post_id : get_the_ID();
if ($post_id <= 0) {
if ($post_id <= 0 || ! is_user_logged_in()) {
return false;
}
if (is_user_logged_in()) {
return in_array($post_id, jio_get_user_interaction_posts('_jio_favorite_posts'), true);
}
$cookie = jio_get_interaction_cookie_name('favorite', $post_id);
return ! empty($_COOKIE[$cookie]) && '1' === $_COOKIE[$cookie];
return in_array($post_id, jio_get_user_interaction_posts('_jio_favorite_posts'), true);
}
endif;
if (!function_exists('jio_update_post_counter')) :
@@ -568,6 +565,10 @@ endif;
if (!function_exists('jio_toggle_favorite_ajax')) :
function jio_toggle_favorite_ajax() {
check_ajax_referer('jio_public_nonce', 'nonce');
// 参考主题:收藏必须先登录,游客请求直接返回登录提示。
if (! is_user_logged_in()) {
wp_send_json_error(['message' => __('请先登录', 'jio'), 'login' => true], 401);
}
$post_id = isset($_POST['postId']) ? absint(wp_unslash($_POST['postId'])) : 0;
$post = $post_id ? get_post($post_id) : null;
if (! $post || 'post' !== $post->post_type || 'publish' !== $post->post_status) {
@@ -576,15 +577,9 @@ if (!function_exists('jio_toggle_favorite_ajax')) :
$current = jio_is_post_favorited($post_id);
$target = isset($_POST['active']) ? '1' === sanitize_text_field(wp_unslash($_POST['active'])) : ! $current;
if ($target !== $current) {
if (is_user_logged_in()) {
jio_set_user_interaction_state('_jio_favorite_posts', $post_id, $target);
} else {
jio_set_interaction_cookie(jio_get_interaction_cookie_name('favorite', $post_id), $target);
}
jio_set_user_interaction_state('_jio_favorite_posts', $post_id, $target);
$count = jio_update_post_counter($post_id, '_jio_favorite_count', $target ? 1 : -1);
if (is_user_logged_in()) {
do_action('jio_post_favorited', $post_id, get_current_user_id(), (bool) $target);
}
do_action('jio_post_favorited', $post_id, get_current_user_id(), (bool) $target);
} else {
$count = jio_get_post_favorites($post_id);
}