MediaWiki:Common.js:修订间差异
MediaWiki界面页面
更多操作
无编辑摘要 |
无编辑摘要 |
||
| (未显示同一用户的51个中间版本) | |||
| 第20行: | 第20行: | ||
}); | }); | ||
// | // 用户颜色、图标、状态、师徒、昵称、里程碑、公告、植物/僵尸筛选、好友、私信 主逻辑 | ||
$(function () { | $(function () { | ||
// ==================== 动态注入彩虹样式 ==================== | // ==================== 动态注入彩虹样式 ==================== | ||
| 第35行: | 第35行: | ||
// ==================== 配置区 ==================== | // ==================== 配置区 ==================== | ||
var mentorList = ['愤怒的郎朗', 'Yuyaabc', '虚位以待', '知更鸟头号粉丝', '凌玥']; | var mentorList = ['愤怒的郎朗', 'Yuyaabc', '虚位以待', '知更鸟头号粉丝', '凌玥']; | ||
var newbieEditThreshold = 25; | var newbieEditThreshold = 25; | ||
var milestoneThresholds = [10, 50, 100, 500, 1000, 2500, 5000, 10000, 20000, 50000]; | |||
// ==================== 辅助函数 ==================== | // ==================== 辅助函数 ==================== | ||
| 第49行: | 第50行: | ||
} | } | ||
function getLevelIcons(editcount) { | function getLevelIcons(editcount) { | ||
var totalStars = Math.floor(editcount / 100); | var totalStars = Math.floor(editcount / 100); | ||
if (totalStars === 0) return ''; | if (totalStars === 0) return ''; | ||
var crowns = Math.floor(totalStars / 125); | var crowns = Math.floor(totalStars / 125); | ||
var remaining = totalStars % 125; | var remaining = totalStars % 125; | ||
var suns = Math.floor(remaining / 25); | var suns = Math.floor(remaining / 25); | ||
remaining %= 25; | remaining %= 25; | ||
var moons = Math.floor(remaining / 5); | var moons = Math.floor(remaining / 5); | ||
var stars = remaining % 5; | var stars = remaining % 5; | ||
| 第83行: | 第83行: | ||
} | } | ||
// ==================== 核心:颜色 + 图标 + 状态 + 师徒 ==================== | function getTitleByEditcount(ec) { | ||
if (ec >= 10000) return '🐉 神话'; | |||
if (ec >= 5000) return '👑 传奇'; | |||
if (ec >= 2500) return '🏆 大师'; | |||
if (ec >= 1000) return '💎 专家'; | |||
if (ec >= 500) return '🔥 资深'; | |||
if (ec >= 100) return '⭐ 活跃'; | |||
if (ec >= 50) return '🌳 入门'; | |||
if (ec >= 10) return '🌿 新手'; | |||
return '🌱 萌新'; | |||
} | |||
function addTitleTag($link, editcount) { | |||
if ($link.data('title-tag-added')) return; | |||
$link.data('title-tag-added', true); | |||
var title = getTitleByEditcount(editcount); | |||
var $tag = $('<span class="user-title-tag">' + title + '</span>'); | |||
$link.after($tag); | |||
} | |||
// ==================== 核心:颜色 + 图标 + 状态 + 师徒 + 昵称 ==================== | |||
function colorizeAndStatus($links) { | function colorizeAndStatus($links) { | ||
if ($links.length === 0) return; | if ($links.length === 0) return; | ||
| 第103行: | 第123行: | ||
}); | }); | ||
var batchSize = 50; | var batchSize = 50; | ||
var batches = []; | var batches = []; | ||
| 第142行: | 第161行: | ||
if (typeof ec !== 'undefined') { | if (typeof ec !== 'undefined') { | ||
updateLevelIcons($this, ec); | updateLevelIcons($this, ec); | ||
var isInsideCard = $this.closest('.citizen-menu_card-content, .citizen-userMenu').length > 0; | |||
if (!isInsideCard) { | |||
addTitleTag($this, ec); | |||
} | |||
} | } | ||
}); | }); | ||
| 第154行: | 第177行: | ||
}); | }); | ||
$fresh.each(function () { | $fresh.each(function () { | ||
if (isUserLink(this)) { | if (isUserLink(this)) { | ||
| 第228行: | 第250行: | ||
} | } | ||
// ==================== | // ==================== 师徒标签 ==================== | ||
function addMentorTag($link, username) { | function addMentorTag($link, username) { | ||
if ($link.data('mentor-tag-added')) return; | if ($link.data('mentor-tag-added')) return; | ||
if ($link.next('.user-tag').length) return; | if ($link.next('.user-tag').length) return; | ||
var $tag; | var $tag; | ||
if (mentorList.indexOf(username) !== -1) { | if (mentorList.indexOf(username) !== -1) { | ||
| 第283行: | 第305行: | ||
} | } | ||
}).fail(function () {}); | }).fail(function () {}); | ||
} | |||
// ==================== 编辑里程碑弹窗 ==================== | |||
var currentUser = mw.config.get('wgUserName'); | |||
if (currentUser) { | |||
var api = new mw.Api(); | |||
api.get({ | |||
action: 'query', | |||
list: 'users', | |||
ususers: currentUser, | |||
usprop: 'editcount' | |||
}).then(function(data) { | |||
var editcount = 0; | |||
if (data.query && data.query.users && data.query.users.length > 0) { | |||
editcount = data.query.users[0].editcount || 0; | |||
} | |||
var achievedMilestone = null; | |||
milestoneThresholds.forEach(function(m) { | |||
if (editcount >= m) achievedMilestone = m; | |||
}); | |||
if (achievedMilestone) { | |||
var storageKey = 'mw_milestone_shown_' + currentUser + '_' + achievedMilestone; | |||
if (!localStorage.getItem(storageKey)) { | |||
localStorage.setItem(storageKey, '1'); | |||
var $overlay = $('<div>', { | |||
css: { | |||
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', | |||
background: 'rgba(0,0,0,0.5)', 'z-index': 99998, | |||
display: 'flex', 'align-items': 'center', 'justify-content': 'center' | |||
} | |||
}); | |||
var $box = $('<div>', { | |||
css: { | |||
background: '#fff', 'border-radius': '12px', padding: '30px 40px', | |||
'text-align': 'center', 'box-shadow': '0 4px 20px rgba(0,0,0,0.3)', | |||
'max-width': '400px', width: '80%', position: 'relative' | |||
} | |||
}); | |||
var $title = $('<h2>', { | |||
text: '🎉 恭喜!', | |||
css: { 'margin-bottom': '15px', 'font-size': '24px', color: '#333' } | |||
}); | |||
var $msg = $('<p>', { | |||
text: '你已达到 ' + achievedMilestone + ' 次编辑!', | |||
css: { 'font-size': '18px', 'margin-bottom': '25px', color: '#555' } | |||
}); | |||
var $btn = $('<button>', { | |||
text: '太棒了!', | |||
css: { | |||
background: '#4CAF50', color: '#fff', border: 'none', | |||
padding: '10px 30px', 'border-radius': '8px', 'font-size': '16px', cursor: 'pointer' | |||
}, | |||
click: function() { | |||
$overlay.fadeOut(300, function() { $(this).remove(); }); | |||
} | |||
}); | |||
$box.append($title, $msg, $btn); | |||
$overlay.append($box); | |||
$('body').append($overlay); | |||
} | |||
} | |||
}).fail(function() {}); | |||
} | } | ||
| 第308行: | 第396行: | ||
var $cdMax = $('#pf-cd-max'); | var $cdMax = $('#pf-cd-max'); | ||
var $type = $('#pf-type'); | var $type = $('#pf-type'); | ||
var $version = $('#pf-version'); | |||
var $reset = $('#pf-reset'); | var $reset = $('#pf-reset'); | ||
| 第317行: | 第406行: | ||
var cdTarget = cdRaw !== '' ? parseFloat(cdRaw) : null; | var cdTarget = cdRaw !== '' ? parseFloat(cdRaw) : null; | ||
var type = $type.val(); | var type = $type.val(); | ||
var version = $version.val(); | |||
$cards.each(function () { | $cards.each(function () { | ||
| 第324行: | 第414行: | ||
var cd = parseFloat($card.data('cooldown')) || 0; | var cd = parseFloat($card.data('cooldown')) || 0; | ||
var t = ($card.data('type') || '').toString(); | var t = ($card.data('type') || '').toString(); | ||
var v = ($card.data('version') || '').toString(); | |||
var show = true; | var show = true; | ||
| 第333行: | 第424行: | ||
if (cardTypes.indexOf(type) === -1) show = false; | if (cardTypes.indexOf(type) === -1) show = false; | ||
} | } | ||
if (version && v !== version) show = false; | |||
$card.toggleClass('hidden-card', !show); | $card.toggleClass('hidden-card', !show); | ||
}); | }); | ||
| 第341行: | 第433行: | ||
$cdMax.on('input keyup', filterPlants); | $cdMax.on('input keyup', filterPlants); | ||
$type.on('change', filterPlants); | $type.on('change', filterPlants); | ||
$version.on('change', filterPlants); | |||
$reset.on('click', function () { | $reset.on('click', function () { | ||
$name.val(''); | $name.val(''); | ||
| 第346行: | 第439行: | ||
$cdMax.val(''); | $cdMax.val(''); | ||
$type.val(''); | $type.val(''); | ||
$version.val(''); | |||
filterPlants(); | filterPlants(); | ||
}); | }); | ||
} | } | ||
// ==================== | // ==================== 僵尸卡片筛选 ==================== | ||
function getArmorArray(healthStr) { | function getArmorArray(healthStr) { | ||
if (!healthStr || healthStr.indexOf('+') === -1) return []; | if (!healthStr || healthStr.indexOf('+') === -1) return []; | ||
| 第358行: | 第451行: | ||
} | } | ||
function extractHealth(healthStr) { | function extractHealth(healthStr) { | ||
var matches = healthStr.match(/\d+/g); | var matches = healthStr.match(/\d+/g); | ||
| 第374行: | 第466行: | ||
var $speed = $('#zf-speed'); | var $speed = $('#zf-speed'); | ||
var $ztype = $('#zf-type'); | var $ztype = $('#zf-type'); | ||
var $zversion = $('#zf-version'); | |||
var $zreset = $('#zf-reset'); | var $zreset = $('#zf-reset'); | ||
| 第383行: | 第476行: | ||
var speed = $speed.val(); | var speed = $speed.val(); | ||
var type = $ztype.val(); | var type = $ztype.val(); | ||
var version = $zversion.val(); | |||
$zcards.each(function () { | $zcards.each(function () { | ||
| 第392行: | 第486行: | ||
var health = extractHealth(healthStr); | var health = extractHealth(healthStr); | ||
var armors = getArmorArray(healthStr); | var armors = getArmorArray(healthStr); | ||
var v = ($card.data('version') || '').toString(); | |||
var show = true; | var show = true; | ||
| 第397行: | 第492行: | ||
if (healthTarget !== null && health !== healthTarget) show = false; | if (healthTarget !== null && health !== healthTarget) show = false; | ||
if (armor === 'none') { | if (armor === 'none') { | ||
if (armors.length > 0) show = false; | if (armors.length > 0) show = false; | ||
} else if (armor) { | } else if (armor) { | ||
if (armors.indexOf(armor) === -1) show = false; | if (armors.indexOf(armor) === -1) show = false; | ||
} | } | ||
| 第414行: | 第506行: | ||
if (cardTypes.indexOf(type) === -1) show = false; | if (cardTypes.indexOf(type) === -1) show = false; | ||
} | } | ||
if (version && v !== version) show = false; | |||
$card.toggleClass('hidden-card', !show); | $card.toggleClass('hidden-card', !show); | ||
}); | }); | ||
| 第423行: | 第516行: | ||
$speed.on('change', filterZombies); | $speed.on('change', filterZombies); | ||
$ztype.on('change', filterZombies); | $ztype.on('change', filterZombies); | ||
$zversion.on('change', filterZombies); | |||
$zreset.on('click', function () { | $zreset.on('click', function () { | ||
$zname.val(''); | $zname.val(''); | ||
| 第429行: | 第523行: | ||
$speed.val(''); | $speed.val(''); | ||
$ztype.val(''); | $ztype.val(''); | ||
$zversion.val(''); | |||
filterZombies(); | filterZombies(); | ||
}); | }); | ||
} | } | ||
// ==================== 返回顶部按钮 ==================== | |||
$('body').append('<button id="back-to-top" title="返回顶部">⬆</button>'); | |||
var $backBtn = $('#back-to-top'); | |||
$(window).scroll(function() { | |||
$backBtn.toggle($(this).scrollTop() > 300); | |||
}); | |||
$backBtn.click(function() { | |||
$('html, body').animate({ scrollTop: 0 }, 400); | |||
}); | |||
// ==================== 好友系统 ==================== | |||
var friendApi = new mw.Api(); | |||
function getFriendPageName(username) { | |||
return 'User:' + username + '/friends'; | |||
} | |||
function getFriendRequestsPageName(username) { | |||
return 'User:' + username + '/friendrequests'; | |||
} | |||
function loadFriendList(username, callback) { | |||
friendApi.get({ | |||
action: 'query', | |||
titles: getFriendPageName(username), | |||
prop: 'revisions', | |||
rvprop: 'content', | |||
rvlimit: 1 | |||
}).then(function(data) { | |||
var pages = data.query.pages; | |||
for (var id in pages) { | |||
if (pages[id].revisions && pages[id].revisions[0]) { | |||
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {} | |||
} | |||
} | |||
callback([]); | |||
}).fail(function() { callback([]); }); | |||
} | |||
function loadFriendRequests(username, callback) { | |||
friendApi.get({ | |||
action: 'query', | |||
titles: getFriendRequestsPageName(username), | |||
prop: 'revisions', | |||
rvprop: 'content', | |||
rvlimit: 1 | |||
}).then(function(data) { | |||
var pages = data.query.pages; | |||
for (var id in pages) { | |||
if (pages[id].revisions && pages[id].revisions[0]) { | |||
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {} | |||
} | |||
} | |||
callback([]); | |||
}).fail(function() { callback([]); }); | |||
} | |||
function saveToPage(pageName, data, summary, callback) { | |||
friendApi.postWithEditToken({ | |||
action: 'edit', | |||
title: pageName, | |||
text: JSON.stringify(data, null, 2), | |||
summary: summary, | |||
minor: true, | |||
bot: true | |||
}).then(function() { | |||
if (callback) callback(true); | |||
}).fail(function() { | |||
if (callback) callback(false); | |||
}); | |||
} | |||
function sendFriendRequest(toUser) { | |||
if (!toUser || toUser === currentUser) return; | |||
loadFriendList(currentUser, function(myFriends) { | |||
if (myFriends.indexOf(toUser) !== -1) { | |||
alert('你们已经是好友了!'); | |||
return; | |||
} | |||
loadFriendRequests(toUser, function(requests) { | |||
var alreadySent = requests.some(function(r) { return r.from === currentUser; }); | |||
if (alreadySent) { | |||
alert('你已经发送过好友请求了,请等待对方回应。'); | |||
return; | |||
} | |||
requests.push({ from: currentUser, time: Date.now() }); | |||
saveToPage(getFriendRequestsPageName(toUser), requests, currentUser + ' 发送了好友请求', function(success) { | |||
if (success) { | |||
alert('好友请求已发送给 ' + toUser + '!'); | |||
updateFriendButton(toUser, 'pending'); | |||
} else { | |||
alert('发送失败,请稍后重试。'); | |||
} | |||
}); | |||
}); | |||
}); | |||
} | |||
function acceptFriendRequest(fromUser) { | |||
loadFriendList(currentUser, function(myFriends) { | |||
myFriends.push(fromUser); | |||
saveToPage(getFriendPageName(currentUser), myFriends, '添加好友: ' + fromUser, function() { | |||
loadFriendList(fromUser, function(theirFriends) { | |||
theirFriends.push(currentUser); | |||
saveToPage(getFriendPageName(fromUser), theirFriends, '添加好友: ' + currentUser, function() { | |||
loadFriendRequests(currentUser, function(requests) { | |||
requests = requests.filter(function(r) { return r.from !== fromUser; }); | |||
saveToPage(getFriendRequestsPageName(currentUser), requests, '接受好友请求', function() { | |||
if ($('#friend-requests-list').length) showFriendRequests(); | |||
updateFriendButton(fromUser, 'added'); | |||
}); | |||
}); | |||
}); | |||
}); | |||
}); | |||
}); | |||
} | |||
function rejectFriendRequest(fromUser) { | |||
loadFriendRequests(currentUser, function(requests) { | |||
requests = requests.filter(function(r) { return r.from !== fromUser; }); | |||
saveToPage(getFriendRequestsPageName(currentUser), requests, '拒绝好友请求', function() { | |||
if ($('#friend-requests-list').length) showFriendRequests(); | |||
}); | |||
}); | |||
} | |||
function removeFriend(friendName) { | |||
if (!confirm('确定要删除好友 ' + friendName + ' 吗?')) return; | |||
loadFriendList(currentUser, function(myFriends) { | |||
myFriends = myFriends.filter(function(f) { return f !== friendName; }); | |||
saveToPage(getFriendPageName(currentUser), myFriends, '删除好友: ' + friendName, function() { | |||
loadFriendList(friendName, function(theirFriends) { | |||
theirFriends = theirFriends.filter(function(f) { return f !== currentUser; }); | |||
saveToPage(getFriendPageName(friendName), theirFriends, '删除好友: ' + currentUser, function() { | |||
if ($('#friend-list-container').length) showFriendList(); | |||
updateFriendButton(friendName, 'add'); | |||
}); | |||
}); | |||
}); | |||
}); | |||
} | |||
function updateFriendButton(username, status) { | |||
var $btn = $('#friend-action-btn'); | |||
if (!$btn.length) return; | |||
$btn.removeClass('friend-add-btn friend-added-btn friend-pending-btn'); | |||
if (status === 'added') { | |||
$btn.addClass('friend-added-btn').text('✓ 已添加'); | |||
$btn.off('click').click(function() { removeFriend(username); }); | |||
} else if (status === 'pending') { | |||
$btn.addClass('friend-pending-btn').text('⏳ 等待确认').off('click'); | |||
} else { | |||
$btn.addClass('friend-add-btn').text('+ 加好友'); | |||
$btn.off('click').click(function() { sendFriendRequest(username); }); | |||
} | |||
} | |||
function showFriendRequests() { | |||
loadFriendRequests(currentUser, function(requests) { | |||
var $list = $('#friend-requests-list'); | |||
if (!$list.length) return; | |||
$list.empty(); | |||
if (requests.length === 0) { | |||
$list.append('<p style="color:#999;">暂无好友请求</p>'); | |||
return; | |||
} | |||
requests.forEach(function(r) { | |||
var $item = $('<div>', { class: 'friend-request-item' }); | |||
$item.append('<span><a href="/w/User:' + encodeURIComponent(r.from) + '">' + r.from + '</a></span>'); | |||
var $actions = $('<div>'); | |||
$actions.append('<button class="friend-accept-btn" data-from="' + r.from + '">接受</button>'); | |||
$actions.append('<button class="friend-reject-btn" data-from="' + r.from + '">拒绝</button>'); | |||
$item.append($actions); | |||
$list.append($item); | |||
}); | |||
$list.off('click').on('click', '.friend-accept-btn', function() { | |||
acceptFriendRequest($(this).data('from')); | |||
}).on('click', '.friend-reject-btn', function() { | |||
rejectFriendRequest($(this).data('from')); | |||
}); | |||
}); | |||
} | |||
function showFriendList() { | |||
loadFriendList(currentUser, function(friends) { | |||
loadFriendRequests(currentUser, function(requests) { | |||
var $overlay = $('<div>', { class: 'friend-overlay' }); | |||
var $dialog = $('<div>', { class: 'friend-dialog' }); | |||
var realCount = friends.length; | |||
$dialog.append('<h3>👥 好友列表 <span class="friend-count">' + realCount + '人</span></h3>'); | |||
if (requests.length > 0) { | |||
$dialog.append('<p style="color:#f44336;cursor:pointer;" id="friend-req-notice">📩 有 ' + requests.length + ' 条好友请求,点击查看</p>'); | |||
} | |||
$dialog.append('<div id="friend-requests-list" style="display:none;margin:10px 0;"></div>'); | |||
if (realCount === 0) { | |||
$dialog.append('<p style="color:#999;">还没有好友,去其他用户页面加好友吧!</p>'); | |||
} else { | |||
var $container = $('<div>', { id: 'friend-list-container', style: 'margin:10px 0;' }); | |||
friends.forEach(function(f) { | |||
var $item = $('<span>', { class: 'friend-list-item' }); | |||
$item.append('<a href="/w/User:' + encodeURIComponent(f) + '">' + f + '</a>'); | |||
$item.append(' <a href="javascript:void(0)" class="friend-msg-link" data-friend="' + f + '" title="发私信">✉️</a>'); | |||
$item.append('<span class="friend-remove" data-friend="' + f + '"> ×</span>'); | |||
$container.append($item); | |||
}); | |||
$dialog.append($container); | |||
} | |||
$dialog.append('<button style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>'); | |||
$overlay.append($dialog); | |||
$('body').append($overlay); | |||
$overlay.on('click', function(e) { | |||
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') { | |||
$overlay.remove(); | |||
} | |||
}); | |||
$dialog.on('click', '#friend-req-notice', function() { | |||
var $reqList = $('#friend-requests-list'); | |||
$reqList.toggle(); | |||
if ($reqList.is(':visible')) showFriendRequests(); | |||
}); | |||
$dialog.on('click', '.friend-remove', function() { | |||
removeFriend($(this).data('friend')); | |||
}); | |||
$dialog.on('click', '.friend-msg-link', function() { | |||
var friendName = $(this).data('friend'); | |||
$overlay.remove(); | |||
sendMessage(friendName); | |||
}); | |||
}); | |||
}); | |||
} | |||
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) { | |||
var pageUser = mw.config.get('wgTitle'); | |||
if (pageUser !== currentUser) { | |||
var $btn = $('<button>', { | |||
id: 'friend-action-btn', | |||
class: 'friend-btn friend-add-btn', | |||
text: '+ 加好友' | |||
}); | |||
$('#firstHeading').append($btn); | |||
loadFriendList(currentUser, function(myFriends) { | |||
if (myFriends.indexOf(pageUser) !== -1) { | |||
updateFriendButton(pageUser, 'added'); | |||
} else { | |||
loadFriendRequests(pageUser, function(requests) { | |||
var alreadySent = requests.some(function(r) { return r.from === currentUser; }); | |||
if (alreadySent) { | |||
updateFriendButton(pageUser, 'pending'); | |||
} else { | |||
$btn.click(function() { sendFriendRequest(pageUser); }); | |||
} | |||
}); | |||
} | |||
}); | |||
} | |||
} | |||
if (currentUser) { | |||
var $userMenu = $('#pt-userpage, .citizen-userMenu').first(); | |||
if ($userMenu.length) { | |||
$userMenu.after(' <a href="javascript:void(0)" id="friend-list-trigger" style="font-size:13px;" title="好友列表">👥</a>'); | |||
} | |||
$(document).on('click', '#friend-list-trigger', function() { | |||
showFriendList(); | |||
}); | |||
setInterval(function() { | |||
loadFriendRequests(currentUser, function(requests) { | |||
var $notice = $('#friend-request-count'); | |||
if (requests.length > 0) { | |||
if (!$notice.length && $('#friend-list-trigger').length) { | |||
$('#friend-list-trigger').after('<span id="friend-request-count" style="color:#f44336;font-size:11px;vertical-align:super;">' + requests.length + '</span>'); | |||
} else if ($notice.length) { | |||
$notice.text(requests.length); | |||
} | |||
} else { | |||
$notice.remove(); | |||
} | |||
}); | |||
}, 60000); | |||
} | |||
// ==================== 私信系统 ==================== | |||
var msgApi = new mw.Api(); | |||
function getMsgPageName(username) { | |||
return 'User:' + username + '/messages'; | |||
} | |||
function loadMessages(callback) { | |||
if (!currentUser) { callback([]); return; } | |||
msgApi.get({ | |||
action: 'query', | |||
titles: getMsgPageName(currentUser), | |||
prop: 'revisions', | |||
rvprop: 'content', | |||
rvlimit: 1 | |||
}).then(function(data) { | |||
var pages = data.query.pages; | |||
for (var id in pages) { | |||
if (pages[id].revisions && pages[id].revisions[0]) { | |||
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {} | |||
} | |||
} | |||
callback([]); | |||
}).fail(function() { callback([]); }); | |||
} | |||
function saveMessages(msgs, callback) { | |||
if (!currentUser) return; | |||
msgApi.postWithEditToken({ | |||
action: 'edit', | |||
title: getMsgPageName(currentUser), | |||
text: JSON.stringify(msgs, null, 2), | |||
summary: '更新私信', | |||
minor: true, | |||
bot: true | |||
}).then(function() { | |||
if (callback) callback(true); | |||
}).fail(function() { | |||
if (callback) callback(false); | |||
}); | |||
} | |||
function getUnreadCount(msgs) { | |||
var count = 0; | |||
msgs.forEach(function(m) { if (!m.read) count++; }); | |||
return count; | |||
} | |||
function updateMsgBadge() { | |||
loadMessages(function(msgs) { | |||
var count = getUnreadCount(msgs); | |||
var $badge = $('#msg-badge'); | |||
if ($badge.length === 0) { | |||
var $drawer = $('.citizen-drawer').first(); | |||
if ($drawer.length) { | |||
$drawer.css('position', 'relative'); | |||
$drawer.append('<span id="msg-badge" class="msg-badge" title="新私信">0</span>'); | |||
$badge = $('#msg-badge'); | |||
} | |||
} | |||
if ($badge.length) { | |||
$badge.text(count).toggle(count > 0); | |||
} | |||
}); | |||
} | |||
function showInbox() { | |||
loadMessages(function(msgs) { | |||
var $overlay = $('<div>', { class: 'msg-overlay' }); | |||
var $box = $('<div>', { class: 'msg-box' }); | |||
$box.append('<h3 style="margin-bottom:15px;">📬 私信箱</h3>'); | |||
msgs.sort(function(a, b) { return b.time - a.time; }); | |||
if (msgs.length === 0) { | |||
$box.append('<p style="color:#999;">暂无消息</p>'); | |||
} else { | |||
msgs.forEach(function(m, index) { | |||
var $item = $('<div>', { class: 'msg-item' + (m.read ? '' : ' unread') }); | |||
var timeStr = new Date(m.time).toLocaleString('zh-CN'); | |||
$item.append( | |||
'<div><span class="msg-sender">' + m.from + '</span><span class="msg-time">' + timeStr + '</span></div>', | |||
'<div class="msg-text">' + m.text.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>') + '</div>', | |||
'<div class="msg-actions">' + | |||
(m.read ? '' : '<button class="mark-read" data-index="' + index + '">已读</button>') + | |||
'<button class="del-msg" data-index="' + index + '">删除</button>' + | |||
'<button class="reply-msg" data-index="' + index + '" data-from="' + m.from + '">回复</button>' + | |||
'</div>' | |||
); | |||
$box.append($item); | |||
}); | |||
} | |||
$box.append('<button style="margin-top:15px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>'); | |||
$overlay.append($box); | |||
$('body').append($overlay); | |||
$overlay.on('click', function(e) { | |||
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') { | |||
$overlay.remove(); | |||
updateMsgBadge(); | |||
} | |||
}); | |||
$box.on('click', '.mark-read', function() { | |||
var idx = parseInt($(this).data('index')); | |||
msgs[idx].read = true; | |||
saveMessages(msgs, function() { | |||
updateMsgBadge(); | |||
$overlay.remove(); | |||
showInbox(); | |||
}); | |||
}); | |||
$box.on('click', '.del-msg', function() { | |||
var idx = parseInt($(this).data('index')); | |||
msgs.splice(idx, 1); | |||
saveMessages(msgs, function() { | |||
$overlay.remove(); | |||
showInbox(); | |||
}); | |||
}); | |||
$box.on('click', '.reply-msg', function() { | |||
var toUser = $(this).data('from'); | |||
$overlay.remove(); | |||
sendMessage(toUser); | |||
}); | |||
}); | |||
} | |||
function sendMessage(toUser) { | |||
if (!toUser) return; | |||
var $overlay = $('<div>', { class: 'msg-overlay' }); | |||
var $box = $('<div>', { class: 'msg-box' }); | |||
$box.append('<h3 style="margin-bottom:10px;">✉️ 发送私信给 ' + toUser + '</h3>'); | |||
$box.append('<textarea class="msg-textarea" placeholder="输入消息内容..."></textarea>'); | |||
$box.append( | |||
'<button class="msg-send-submit" style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#4CAF50;color:#fff;border:none;border-radius:6px;">发送</button>', | |||
'<button style="margin-left:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">取消</button>' | |||
); | |||
$overlay.append($box); | |||
$('body').append($overlay); | |||
$overlay.on('click', function(e) { | |||
if ($(e.target).is($overlay) || $(e.target).text() === '取消') { | |||
$overlay.remove(); | |||
} | |||
}); | |||
$box.on('click', '.msg-send-submit', function() { | |||
var text = $box.find('.msg-textarea').val().trim(); | |||
if (!text) return; | |||
$box.find('.msg-send-submit').prop('disabled', true).text('发送中...'); | |||
var tempApi = new mw.Api(); | |||
tempApi.get({ | |||
action: 'query', | |||
titles: getMsgPageName(toUser), | |||
prop: 'revisions', | |||
rvprop: 'content', | |||
rvlimit: 1 | |||
}).then(function(data) { | |||
var pages = data.query.pages; | |||
var msgs = []; | |||
for (var id in pages) { | |||
if (pages[id].revisions && pages[id].revisions[0]) { | |||
try { msgs = JSON.parse(pages[id].revisions[0]['*']); } catch(e) {} | |||
} | |||
} | |||
msgs.push({ | |||
from: currentUser, | |||
to: toUser, | |||
text: text, | |||
time: Date.now(), | |||
read: false | |||
}); | |||
tempApi.postWithEditToken({ | |||
action: 'edit', | |||
title: getMsgPageName(toUser), | |||
text: JSON.stringify(msgs, null, 2), | |||
summary: currentUser + ' 发来一条私信', | |||
minor: false, | |||
bot: false | |||
}).then(function() { | |||
$overlay.remove(); | |||
alert('私信已发送给 ' + toUser + '!'); | |||
}).fail(function(err) { | |||
$overlay.remove(); | |||
alert('发送失败:' + (err.error && err.error.info ? err.error.info : '未知错误')); | |||
}); | |||
}).fail(function() { | |||
$overlay.remove(); | |||
alert('发送失败,请稍后重试。'); | |||
}); | |||
}); | |||
} | |||
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) { | |||
var msgPageUser = mw.config.get('wgTitle'); | |||
if (msgPageUser !== currentUser) { | |||
var $msgBtn = $('<button>', { | |||
text: '✉️ 发送私信', | |||
class: 'msg-send-btn', | |||
click: function() { sendMessage(msgPageUser); } | |||
}); | |||
$('#firstHeading').append($msgBtn); | |||
} | |||
} | |||
if (currentUser) { | |||
updateMsgBadge(); | |||
$(document).on('click', '#msg-badge', function() { | |||
showInbox(); | |||
}); | |||
var $msgEntry = $('<a>', { | |||
href: 'javascript:void(0)', | |||
id: 'msg-inbox-trigger', | |||
text: '✉️', | |||
title: '私信箱', | |||
css: { 'font-size': '13px', 'margin-left': '8px', 'cursor': 'pointer', 'text-decoration': 'none' } | |||
}); | |||
var $friendTrigger = $('#friend-list-trigger'); | |||
if ($friendTrigger.length) { | |||
$friendTrigger.after(' '); | |||
$friendTrigger.after($msgEntry); | |||
} | |||
$(document).on('click', '#msg-inbox-trigger', function() { | |||
showInbox(); | |||
}); | |||
setInterval(function() { | |||
updateMsgBadge(); | |||
}, 30000); | |||
} | |||
// ==================== 版本更新公告弹窗 ==================== | |||
function checkUpdateNotice() { | |||
var $versionEl = $('.update-version'); | |||
if ($versionEl.length === 0) return; | |||
var currentVersion = $versionEl.text().trim(); | |||
if (!currentVersion) return; | |||
var dismissedVersion = localStorage.getItem('mw_update_dismissed'); | |||
if (dismissedVersion !== currentVersion) { | |||
var $overlay = $('<div>', { | |||
css: { | |||
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', | |||
background: 'rgba(0,0,0,0.6)', 'z-index': 99999, | |||
display: 'flex', 'align-items': 'center', 'justify-content': 'center' | |||
} | |||
}); | |||
var $box = $('<div>', { | |||
css: { | |||
background: '#fff', 'border-radius': '12px', padding: '30px', | |||
'max-width': '550px', width: '90%', 'max-height': '80vh', | |||
'overflow-y': 'auto', 'box-shadow': '0 8px 30px rgba(0,0,0,0.4)', | |||
position: 'relative' | |||
} | |||
}); | |||
var $content = $('<div>').css({'text-align':'left','font-size':'14px','line-height':'1.8'}).html( | |||
'<div style="text-align:center;font-size:18px;font-weight:bold;margin-bottom:5px;">【植物大战僵尸杂交版0.22版本更新公告】</div>' + | |||
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">更新时间:2026年6月7号</div>'+ | |||
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">电脑(Windows)版链接:https://pan.quark.cn/s/b145573873c3</div>'+ | |||
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">其他版本:https://pan.quark.cn/s/3ffc82554918</div>'+ | |||
'<b>🌱 植物更新</b><br>全新的植物加入了我们,我们的实力将更加强悍!<br>'+ | |||
'<b>白卡:</b>幽灵吞噬者、僵尸地刺、魅惑咖啡豆、魅惑三叶草、灵感菇、蒜鸟、咖啡三叶草、叶子高坚果、巨型南瓜壳、绷带高坚果、磁力樱桃炸弹、樱桃土豆雷<br>' + | |||
'<b>至尊金卡:</b>黄金西瓜投手、大王钢齿花<br>' + | |||
'<b>臻享钻卡:</b>生命重塑者<br>' + | |||
'<b>星光闪卡:</b>黄金锤子<br><br>' + | |||
'<b>🧟 僵尸/障碍物更新</b><br>飞行器僵尸、胆小鬼僵尸、传送门僵尸<br><br>' + | |||
'<b>🗺️ 关卡更新</b><br>' + | |||
'冒险:第八章 1~4关<br>' + | |||
'金卡挑战:大王钢齿花 1~3,黄金西瓜投手 1~3<br>' + | |||
'钻卡挑战:生命重塑者 1~6<br><br>' + | |||
'<b>🎨 杂项更新</b><br>' + | |||
'植物皮肤更新:黄金西瓜投手-幸运之王、大王钢齿花-银锋锐影、生命重塑者-浮生净莲(商店15000购买)、魔鬼辣椒-拘灵炼狱(在线关卡5水晶兑换)<br>' + | |||
'道具更新:骷髅铲<br><br>' + | |||
'<b>⚙️ 全新功能</b><br>' + | |||
'新增游戏指令:/phonk [on/off] [弹性强度数]、/dismember [on/off]<br>' + | |||
'局内设置新增果冻弹性效果开关与强度调整<br><br>' + | |||
'<b>⚖️ 平衡性调整</b><br>' + | |||
'植物价格调整:巨型南瓜雪橇 200→300、墓碑爆破者 150→100<br>' + | |||
'植物强度调整:宝藏树桩亡语 1~3张黄金碎片→1张<br><br>' + | |||
'<b>🛠️ 游戏优化</b><br>' + | |||
'优化了关卡进度存档、返回选关体验、在线关卡分类筛选与通关率显示、更多模式板块内容返回体验<br>' + | |||
'修复商店与植物试玩切换假死BUG、追加宝藏树桩亡语掉落黄金碎片、荧光木槌可对僵尸使用、本次更新修复了一堆BUG<br><br>' + | |||
'<b>💎 水晶兑换商店</b><br>' + | |||
'巨型南瓜壳 5 | 磁力樱桃炸弹 10 | 绷带高坚果 10<br>' + | |||
'叶子高坚果 15 | 樱桃土豆雷 15 | 魔鬼辣椒皮肤-拘灵炼狱 5<br><br>' + | |||
'<span style="color:#888;">结语:我们会持续建立与玩家社群的紧密联系,您的支持就是对我们工作最大的认可</span>' | |||
); | |||
var $closeBtn = $('<button>', { | |||
text: '我知道了', | |||
css: { | |||
display: 'block', margin: '20px auto 0', | |||
background: '#4CAF50', color: '#fff', border: 'none', | |||
padding: '10px 30px', 'border-radius': '8px', | |||
'font-size': '16px', cursor: 'pointer' | |||
}, | |||
click: function() { | |||
localStorage.setItem('mw_update_dismissed', currentVersion); | |||
$overlay.fadeOut(300, function() { $(this).remove(); }); | |||
} | |||
}); | |||
$box.append($content, $closeBtn); | |||
$overlay.append($box); | |||
$('body').append($overlay); | |||
$overlay.on('click', function(e) { | |||
if ($(e.target).is($overlay)) { | |||
$overlay.fadeOut(300, function() { $(this).remove(); }); | |||
} | |||
}); | |||
} | |||
} | |||
setTimeout(function() { | |||
checkUpdateNotice(); | |||
}, 500); | |||
// ==================== Wiki 宠物 ==================== | |||
(function() { | |||
var pets = { | |||
'冰瓜香蒲': { | |||
idle: 'https://new.pvzhe.wiki/images/1/10/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E5%BE%85%E6%9C%BA.gif', | |||
petting: 'https://new.pvzhe.wiki/images/4/47/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E6%8A%9A%E6%91%B8.gif', | |||
size: 100 | |||
}, | |||
'小猫向日葵': { | |||
idle: 'https://new.pvzhe.wiki/images/9/9b/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E5%BE%85%E6%9C%BA.gif', | |||
petting: 'https://new.pvzhe.wiki/images/8/85/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E6%8A%9A%E6%91%B8.gif', | |||
size: 100 | |||
} | |||
}; | |||
var petNames = Object.keys(pets); | |||
var currentPet = localStorage.getItem('wiki_pet_current') || '冰瓜香蒲'; | |||
if (!pets[currentPet]) currentPet = '冰瓜香蒲'; | |||
var petState = 'idle'; | |||
var petTimer = null; | |||
var affectionKey = 'wiki_pet_affection_'; | |||
var positionKey = 'wiki_pet_position'; | |||
var affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0; | |||
var affectionPerPet = 5; | |||
var affectionCooldown = 100; | |||
var lastPetTime = 0; | |||
var isDragging = false; | |||
var dragStartX, dragStartY, petStartX, petStartY; | |||
var hasMoved = false; | |||
var levels = [ | |||
{ min: 0, title: '陌生', emoji: '🤔' }, | |||
{ min: 50, title: '认识', emoji: '👋' }, | |||
{ min: 150, title: '友好', emoji: '😊' }, | |||
{ min: 400, title: '亲密', emoji: '💚' }, | |||
{ min: 1000, title: '挚友', emoji: '💖' }, | |||
{ min: 2500, title: '灵魂伴侣', emoji: '✨' } | |||
]; | |||
function getLevel(aff) { | |||
var lvl = levels[0]; | |||
for (var i = levels.length - 1; i >= 0; i--) { | |||
if (aff >= levels[i].min) { lvl = levels[i]; break; } | |||
} | |||
return lvl; | |||
} | |||
function saveAffection() { localStorage.setItem(affectionKey + currentPet, affection); } | |||
function savePosition() { | |||
var pos = $pet.position(); | |||
var bottom = $(window).height() - pos.top - $pet.height(); | |||
localStorage.setItem(positionKey, JSON.stringify({ left: pos.left, bottom: Math.max(0, bottom) })); | |||
} | |||
function showAffectionPanel() { | |||
var lvl = getLevel(affection); | |||
var nextLvl = null; | |||
for (var i = 0; i < levels.length; i++) { | |||
if (affection < levels[i].min) { nextLvl = levels[i]; break; } | |||
} | |||
var text = lvl.emoji + ' ' + lvl.title + ' (' + affection + ')'; | |||
if (nextLvl) { | |||
var progress = Math.round((affection - lvl.min) / (nextLvl.min - lvl.min) * 100); | |||
text += ' → ' + nextLvl.emoji + ' ' + progress + '%'; | |||
} else { text += ' MAX'; } | |||
$affectionPanel.text(text).css('color', '#fff'); | |||
} | |||
function spawnHeart(x, y) { | |||
var hearts = ['💚', '💙', '💛', '💜', '❤️', '💖', '✨']; | |||
var heart = hearts[Math.floor(Math.random() * hearts.length)]; | |||
var $heart = $('<div>', { class: 'pet-heart', text: heart, css: { left: x, top: y } }); | |||
$('body').append($heart); | |||
setTimeout(function() { $heart.remove(); }, 1500); | |||
} | |||
function triggerLevelUp() { | |||
$pet.addClass('level-up'); | |||
setTimeout(function() { $pet.removeClass('level-up'); }, 800); | |||
} | |||
function switchPet(newPet) { | |||
if (newPet === currentPet) return; | |||
currentPet = newPet; | |||
localStorage.setItem('wiki_pet_current', currentPet); | |||
affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0; | |||
$img.attr('src', pets[currentPet].idle); | |||
showAffectionPanel(); | |||
setPetState('idle'); | |||
} | |||
var savedPos = JSON.parse(localStorage.getItem(positionKey)) || { left: 20, bottom: 20 }; | |||
var $pet = $('<div>', { | |||
class: 'wiki-pet', | |||
css: { opacity: 0, left: savedPos.left + 'px', bottom: savedPos.bottom + 'px', top: 'auto', right: 'auto' } | |||
}); | |||
var $img = $('<img>', { | |||
src: pets[currentPet].idle, | |||
alt: currentPet, | |||
draggable: 'false', | |||
css: { width: pets[currentPet].size + 'px', height: 'auto' } | |||
}); | |||
var $affectionPanel = $('<div>', { class: 'pet-affection' }); | |||
var $tooltip = $('<div>', { class: 'pet-tooltip', text: '点击抚摸我~ 🐱' }); | |||
var $switchBtn = $('<button>', { class: 'pet-switch-btn', text: '🔄', title: '切换宠物' }); | |||
$pet.append($img, $affectionPanel, $tooltip, $switchBtn); | |||
$('body').append($pet); | |||
showAffectionPanel(); | |||
setTimeout(function() { $pet.animate({ opacity: 1 }, 500); }, 1000); | |||
$switchBtn.on('click', function(e) { | |||
e.stopPropagation(); | |||
e.preventDefault(); | |||
var idx = petNames.indexOf(currentPet); | |||
var nextIdx = (idx + 1) % petNames.length; | |||
switchPet(petNames[nextIdx]); | |||
}); | |||
function setPetState(state) { | |||
if (petState === state) return; | |||
petState = state; | |||
clearTimeout(petTimer); | |||
$pet.removeClass('petting'); | |||
if (state === 'idle') { | |||
$img.attr('src', pets[currentPet].idle); | |||
$img.css({ width: pets[currentPet].size + 'px', height: 'auto' }); | |||
$tooltip.text('点击抚摸我~ ' + getLevel(affection).emoji); | |||
} else if (state === 'petting') { | |||
$img.attr('src', pets[currentPet].petting); | |||
var petSize = currentPet === '小猫向日葵' ? pets[currentPet].size * 1.2 : Math.round(pets[currentPet].size * 1.2); | |||
$img.css({ width: petSize + 'px', height: 'auto' }); | |||
$pet.addClass('petting'); | |||
$tooltip.text('好舒服~ 💚'); | |||
petTimer = setTimeout(function() { setPetState('idle'); }, 1200); | |||
} | |||
} | |||
$pet.on('mousedown', function(e) { | |||
if (e.button !== 0) return; | |||
if ($(e.target).is($switchBtn)) return; | |||
isDragging = true; hasMoved = false; | |||
dragStartX = e.clientX; dragStartY = e.clientY; | |||
petStartX = $pet.offset().left; petStartY = $pet.offset().top; | |||
$pet.css('transition', 'none'); | |||
e.preventDefault(); | |||
}); | |||
$(document).on('mousemove', function(e) { | |||
if (!isDragging) return; | |||
var dx = e.clientX - dragStartX, dy = e.clientY - dragStartY; | |||
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true; | |||
var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size)); | |||
var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size)); | |||
$pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' }); | |||
}); | |||
$(document).on('mouseup', function() { | |||
if (!isDragging) return; | |||
isDragging = false; | |||
$pet.css('transition', 'transform 0.2s'); | |||
savePosition(); | |||
}); | |||
$pet.on('click', function(e) { | |||
if ($(e.target).is($switchBtn)) return; | |||
e.stopPropagation(); | |||
if (hasMoved) return; | |||
var now = Date.now(); | |||
if (now - lastPetTime < affectionCooldown) return; | |||
lastPetTime = now; | |||
setPetState('petting'); | |||
var oldLevel = getLevel(affection).title; | |||
affection += affectionPerPet; | |||
saveAffection(); | |||
showAffectionPanel(); | |||
var newLevel = getLevel(affection).title; | |||
if (newLevel !== oldLevel) { | |||
triggerLevelUp(); | |||
$affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700'); | |||
setTimeout(function() { showAffectionPanel(); }, 2000); | |||
} | |||
var offset = $pet.offset(); | |||
spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10); | |||
}); | |||
setInterval(function() { | |||
if (petState === 'idle' && !isDragging && Math.random() < 0.3) { | |||
$pet.css('transform', 'scale(1.05) translateY(-5px)'); | |||
setTimeout(function() { $pet.css('transform', 'scale(1) translateY(0)'); }, 500); | |||
} | |||
}, 10000); | |||
$pet.on('touchstart', function(e) { | |||
$pet.addClass('touching'); | |||
var touch = e.originalEvent.touches[0]; | |||
isDragging = true; hasMoved = false; | |||
dragStartX = touch.clientX; dragStartY = touch.clientY; | |||
petStartX = $pet.offset().left; petStartY = $pet.offset().top; | |||
$pet.css('transition', 'none'); | |||
e.stopPropagation(); | |||
}); | |||
$pet.on('touchend', function() { | |||
$pet.removeClass('touching'); | |||
}); | |||
$(document).on('touchmove', function(e) { | |||
if (!isDragging) return; | |||
var touch = e.originalEvent.touches[0]; | |||
var dx = touch.clientX - dragStartX, dy = touch.clientY - dragStartY; | |||
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true; | |||
var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size)); | |||
var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size)); | |||
$pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' }); | |||
}); | |||
$(document).on('touchend', function() { | |||
if (!isDragging) return; | |||
isDragging = false; | |||
$pet.css('transition', 'transform 0.2s'); | |||
$pet.removeClass('touching'); | |||
savePosition(); | |||
if (!hasMoved) { | |||
var now = Date.now(); | |||
if (now - lastPetTime < affectionCooldown) return; | |||
lastPetTime = now; | |||
setPetState('petting'); | |||
var oldLevel = getLevel(affection).title; | |||
affection += affectionPerPet; | |||
saveAffection(); | |||
showAffectionPanel(); | |||
var newLevel = getLevel(affection).title; | |||
if (newLevel !== oldLevel) { | |||
triggerLevelUp(); | |||
$affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700'); | |||
setTimeout(function() { showAffectionPanel(); }, 2000); | |||
} | |||
var offset = $pet.offset(); | |||
spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10); | |||
} | |||
}); | |||
})(); | |||
// ==================== 阅读位置记忆 ==================== | |||
(function() { | |||
var storageKey = 'mw_readposition_enabled'; | |||
var positionKey = 'mw_readposition_data'; | |||
var enabled = localStorage.getItem(storageKey) === 'true'; | |||
function restorePosition() { | |||
if (!enabled) return; | |||
var data = JSON.parse(localStorage.getItem(positionKey) || '{}'); | |||
var currentPage = mw.config.get('wgPageName'); | |||
if (data[currentPage]) { | |||
var scrollTarget = data[currentPage]; | |||
$(function() { | |||
setTimeout(function() { | |||
$('html, body').scrollTop(scrollTarget); | |||
}, 500); | |||
}); | |||
} | |||
} | |||
function savePosition() { | |||
if (!enabled) return; | |||
var currentPage = mw.config.get('wgPageName'); | |||
var scrollTop = $(window).scrollTop(); | |||
if (scrollTop > 50) { | |||
var data = JSON.parse(localStorage.getItem(positionKey) || '{}'); | |||
data[currentPage] = scrollTop; | |||
localStorage.setItem(positionKey, JSON.stringify(data)); | |||
} | |||
} | |||
function insertSwitch() { | |||
var $target = $('.citizen-preferences-section__content').last(); | |||
if (!$target.length) return; | |||
if ($('.readposition-switch').length) return; | |||
var $switch = $( | |||
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group readposition-switch">' + | |||
'<input id="readposition-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (enabled ? ' checked' : '') + '>' + | |||
'<span class="cdx-toggle-switch__switch">' + | |||
'<span class="cdx-toggle-switch__switch__grip"></span>' + | |||
'</span>' + | |||
'<div class="cdx-label cdx-toggle-switch__label">' + | |||
'<label class="cdx-label__label" for="readposition-toggle">' + | |||
'<span class="cdx-label__label__text">记住阅读位置</span>' + | |||
'</label>' + | |||
'<span class="cdx-label__description">返回页面时恢复上次滚动位置</span>' + | |||
'</div>' + | |||
'</span>' | |||
); | |||
$target.append($switch); | |||
$('#readposition-toggle').on('change', function() { | |||
enabled = $(this).is(':checked'); | |||
localStorage.setItem(storageKey, enabled ? 'true' : 'false'); | |||
if (!enabled) { | |||
localStorage.removeItem(positionKey); | |||
} | |||
}); | |||
} | |||
$(window).on('beforeunload', function() { | |||
savePosition(); | |||
}); | |||
setInterval(function() { | |||
savePosition(); | |||
}, 5000); | |||
restorePosition(); | |||
var observer = new MutationObserver(function(mutations) { | |||
mutations.forEach(function(mutation) { | |||
if ($('.citizen-preferences-section__content').is(':visible') && !$('.readposition-switch').length) { | |||
insertSwitch(); | |||
} | |||
}); | |||
}); | |||
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] }); | |||
if ($('.citizen-preferences-section__content').is(':visible')) { | |||
insertSwitch(); | |||
} | |||
})(); | |||
// ==================== 宠物显示开关 ==================== | |||
(function() { | |||
var petVisibleKey = 'wiki_pet_visible'; | |||
var petVisible = localStorage.getItem(petVisibleKey) !== 'false'; | |||
function togglePet(show) { | |||
if (show) { | |||
$('.wiki-pet').fadeIn(300); | |||
} else { | |||
$('.wiki-pet').fadeOut(300); | |||
} | |||
} | |||
function insertPetSwitch() { | |||
var $target = $('.citizen-preferences-section__content').last(); | |||
if (!$target.length) return; | |||
if ($('.pet-visible-switch').length) return; | |||
var $switch = $( | |||
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group pet-visible-switch">' + | |||
'<input id="pet-visible-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (petVisible ? ' checked' : '') + '>' + | |||
'<span class="cdx-toggle-switch__switch">' + | |||
'<span class="cdx-toggle-switch__switch__grip"></span>' + | |||
'</span>' + | |||
'<div class="cdx-label cdx-toggle-switch__label">' + | |||
'<label class="cdx-label__label" for="pet-visible-toggle">' + | |||
'<span class="cdx-label__label__text">显示宠物</span>' + | |||
'</label>' + | |||
'<span class="cdx-label__description">在页面显示Wiki宠物</span>' + | |||
'</div>' + | |||
'</span>' | |||
); | |||
$target.append($switch); | |||
$('#pet-visible-toggle').on('change', function() { | |||
petVisible = $(this).is(':checked'); | |||
localStorage.setItem(petVisibleKey, petVisible ? 'true' : 'false'); | |||
togglePet(petVisible); | |||
}); | |||
} | |||
// 初始状态 - 立即检查 | |||
if (!petVisible) { | |||
var checkPet = setInterval(function() { | |||
if ($('.wiki-pet').length) { | |||
$('.wiki-pet').css({ opacity: 0, display: 'none' }); | |||
clearInterval(checkPet); | |||
} | |||
}, 100); | |||
setTimeout(function() { clearInterval(checkPet); }, 5000); | |||
} | |||
var observer = new MutationObserver(function(mutations) { | |||
mutations.forEach(function(mutation) { | |||
if ($('.citizen-preferences-section__content').is(':visible') && !$('.pet-visible-switch').length) { | |||
insertPetSwitch(); | |||
} | |||
}); | |||
}); | |||
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] }); | |||
if ($('.citizen-preferences-section__content').is(':visible')) { | |||
insertPetSwitch(); | |||
} | |||
})(); | |||
}); // 结束主 $(function () { ... }) | }); // 结束主 $(function () { ... }) | ||
2026年6月13日 (六) 10:15的最新版本
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */
// 自动加载 MediaWiki:Footer 页面内容,并插入到每个页面底部
$(document).ready(function() {
const namespace = mw.config.get('wgNamespaceNumber');
const action = mw.config.get('wgAction');
if (namespace !== 0 || action !== 'view') {
return;
}
fetch("/api.php?action=parse&page=MediaWiki:Footer&format=json")
.then(res => res.json())
.then(data => {
if (data.parse && data.parse.text) {
const html = data.parse.text['*'];
$('#mw-content-text').append('<div class="global-footer">' + html + '</div>');
}
});
});
// 用户颜色、图标、状态、师徒、昵称、里程碑、公告、植物/僵尸筛选、好友、私信 主逻辑
$(function () {
// ==================== 动态注入彩虹样式 ====================
var rainbowStyle = document.createElement('style');
rainbowStyle.textContent =
'.rainbow-user {' +
'font-weight: bold;' +
'background: repeating-linear-gradient(90deg, red 0px, orange 10px, yellow 20px, green 30px, blue 40px, indigo 50px, violet 60px);' +
'-webkit-background-clip: text;' +
'-webkit-text-fill-color: transparent;' +
'background-clip: text;' +
'}';
document.head.appendChild(rainbowStyle);
// ==================== 配置区 ====================
var mentorList = ['愤怒的郎朗', 'Yuyaabc', '虚位以待', '知更鸟头号粉丝', '凌玥'];
var newbieEditThreshold = 25;
var milestoneThresholds = [10, 50, 100, 500, 1000, 2500, 5000, 10000, 20000, 50000];
// ==================== 辅助函数 ====================
function getUserName(link) {
var $span = $(link).find('span').first();
if ($span.length) return $span.text().trim();
return $(link).text().trim();
}
function isUserLink(link) {
return $(link).is('a.mw-userlink') || $(link).find('span').length > 0;
}
function getLevelIcons(editcount) {
var totalStars = Math.floor(editcount / 100);
if (totalStars === 0) return '';
var crowns = Math.floor(totalStars / 125);
var remaining = totalStars % 125;
var suns = Math.floor(remaining / 25);
remaining %= 25;
var moons = Math.floor(remaining / 5);
var stars = remaining % 5;
var icons = '';
if (crowns > 0) icons += '👑'.repeat(crowns);
if (suns > 0) icons += '☀️'.repeat(suns);
if (moons > 0) icons += '🌙'.repeat(moons);
if (stars > 0) icons += '⭐'.repeat(stars);
return icons;
}
function updateLevelIcons($link, editcount) {
var iconStr = getLevelIcons(editcount);
var $iconSpan = $link.next('.user-level-icons');
if (iconStr === '') {
$iconSpan.remove();
return;
}
if ($iconSpan.length === 0) {
$iconSpan = $('<span class="user-level-icons"></span>');
$link.after($iconSpan);
}
$iconSpan.text(iconStr);
}
function getTitleByEditcount(ec) {
if (ec >= 10000) return '🐉 神话';
if (ec >= 5000) return '👑 传奇';
if (ec >= 2500) return '🏆 大师';
if (ec >= 1000) return '💎 专家';
if (ec >= 500) return '🔥 资深';
if (ec >= 100) return '⭐ 活跃';
if (ec >= 50) return '🌳 入门';
if (ec >= 10) return '🌿 新手';
return '🌱 萌新';
}
function addTitleTag($link, editcount) {
if ($link.data('title-tag-added')) return;
$link.data('title-tag-added', true);
var title = getTitleByEditcount(editcount);
var $tag = $('<span class="user-title-tag">' + title + '</span>');
$link.after($tag);
}
// ==================== 核心:颜色 + 图标 + 状态 + 师徒 + 昵称 ====================
function colorizeAndStatus($links) {
if ($links.length === 0) return;
var $fresh = $links.filter(function () {
return !$(this).data('user-status-processed');
});
if ($fresh.length === 0) return;
var users = [];
$fresh.each(function () {
var name = getUserName(this);
if (name && users.indexOf(name) === -1) users.push(name);
});
if (users.length === 0) return;
$fresh.each(function () {
$(this).data('user-status-processed', true);
});
var batchSize = 50;
var batches = [];
for (var i = 0; i < users.length; i += batchSize) {
batches.push(users.slice(i, i + batchSize));
}
var processBatch = function (batch) {
var api = new mw.Api();
return api.get({
action: 'query',
list: 'users',
ususers: batch.join('|'),
usprop: 'editcount'
}).then(function (data) {
var classMap = {};
var editCountMap = {};
if (data.query && data.query.users) {
data.query.users.forEach(function (u) {
var ec = u.editcount || 0;
editCountMap[u.name] = ec;
if (ec >= 5000) classMap[u.name] = 'rainbow-user';
else if (ec >= 2500) classMap[u.name] = 'gold-user';
else if (ec >= 1000) classMap[u.name] = 'platinum-user';
else if (ec >= 500) classMap[u.name] = 'silver-user';
else if (ec >= 1) classMap[u.name] = 'bronze-user';
});
}
$fresh.each(function () {
var $this = $(this);
var name = getUserName(this);
var cls = classMap[name];
if (cls) {
$this.removeClass('bronze-user silver-user platinum-user gold-user rainbow-user');
$this.addClass(cls);
}
var ec = editCountMap[name];
if (typeof ec !== 'undefined') {
updateLevelIcons($this, ec);
var isInsideCard = $this.closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
if (!isInsideCard) {
addTitleTag($this, ec);
}
}
});
});
};
var colorPromise = $.Deferred().resolve();
batches.forEach(function (batch) {
colorPromise = colorPromise.then(function () {
return processBatch(batch);
});
});
$fresh.each(function () {
if (isUserLink(this)) {
var isInsideCard = $(this).closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
if (!isInsideCard) {
var username = getUserName(this);
addStatusDot($(this), username);
addMentorTag($(this), username);
}
}
});
}
// ==================== 状态圆点 ====================
function addStatusDot($link, username) {
if ($link.data('status-dot-added')) return;
$link.data('status-dot-added', true);
var $dot = $('<span class="user-status-dot status-offline" title="离线"></span>');
$link.after($dot);
var cacheKey = 'mw_user_status_' + mw.config.get('wgDBname') + '_' + username;
var cached = localStorage.getItem(cacheKey);
var now = Date.now();
if (cached) {
try {
var data = JSON.parse(cached);
if (now - data.timestamp < 5 * 60 * 1000) {
updateDotStyle($dot, data.lastEditTime);
return;
}
} catch (e) {}
}
var api = new mw.Api();
api.get({
action: 'query',
list: 'usercontribs',
ucuser: username,
uclimit: 1,
ucprop: 'timestamp'
}).then(function (data) {
var lastEditTime = null;
if (data.query && data.query.usercontribs && data.query.usercontribs.length > 0) {
lastEditTime = data.query.usercontribs[0].timestamp;
}
localStorage.setItem(cacheKey, JSON.stringify({
lastEditTime: lastEditTime,
timestamp: now
}));
updateDotStyle($dot, lastEditTime);
}).fail(function () {});
}
function updateDotStyle($dot, lastEditTime) {
if (!lastEditTime) {
$dot.attr('title', '离线');
$dot.removeClass('status-online status-away').addClass('status-offline');
return;
}
var last = new Date(lastEditTime).getTime();
var diffMinutes = (Date.now() - last) / 60000;
if (diffMinutes < 15) {
$dot.attr('title', '在线(15分钟内活跃)');
$dot.removeClass('status-offline status-away').addClass('status-online');
} else if (diffMinutes < 60) {
$dot.attr('title', '近期活跃(1小时内)');
$dot.removeClass('status-offline status-online').addClass('status-away');
} else {
$dot.attr('title', '离线');
$dot.removeClass('status-online status-away').addClass('status-offline');
}
}
// ==================== 师徒标签 ====================
function addMentorTag($link, username) {
if ($link.data('mentor-tag-added')) return;
if ($link.next('.user-tag').length) return;
var $tag;
if (mentorList.indexOf(username) !== -1) {
$link.data('mentor-tag-added', true);
$tag = $('<span class="user-tag user-tag-mentor">导师</span>');
$link.after($tag);
return;
}
if ($link.data('newbie-tag-checked')) return;
$link.data('newbie-tag-checked', true);
var cacheKey = 'mw_newbie_check_' + mw.config.get('wgDBname') + '_' + username;
var cached = localStorage.getItem(cacheKey);
var now = Date.now();
if (cached) {
try {
var data = JSON.parse(cached);
if (now - data.timestamp < 60 * 60 * 1000) {
if (data.editcount <= newbieEditThreshold) {
$tag = $('<span class="user-tag user-tag-newbie">新手</span>');
$link.after($tag);
}
return;
}
} catch (e) {}
}
var api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: username,
usprop: 'editcount'
}).then(function (data) {
var editcount = 0;
if (data.query && data.query.users && data.query.users.length > 0) {
editcount = data.query.users[0].editcount || 0;
}
localStorage.setItem(cacheKey, JSON.stringify({
editcount: editcount,
timestamp: now
}));
if (editcount <= newbieEditThreshold) {
if ($link.next('.user-tag-newbie').length === 0) {
$tag = $('<span class="user-tag user-tag-newbie">新手</span>');
$link.after($tag);
}
}
}).fail(function () {});
}
// ==================== 编辑里程碑弹窗 ====================
var currentUser = mw.config.get('wgUserName');
if (currentUser) {
var api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: currentUser,
usprop: 'editcount'
}).then(function(data) {
var editcount = 0;
if (data.query && data.query.users && data.query.users.length > 0) {
editcount = data.query.users[0].editcount || 0;
}
var achievedMilestone = null;
milestoneThresholds.forEach(function(m) {
if (editcount >= m) achievedMilestone = m;
});
if (achievedMilestone) {
var storageKey = 'mw_milestone_shown_' + currentUser + '_' + achievedMilestone;
if (!localStorage.getItem(storageKey)) {
localStorage.setItem(storageKey, '1');
var $overlay = $('<div>', {
css: {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'rgba(0,0,0,0.5)', 'z-index': 99998,
display: 'flex', 'align-items': 'center', 'justify-content': 'center'
}
});
var $box = $('<div>', {
css: {
background: '#fff', 'border-radius': '12px', padding: '30px 40px',
'text-align': 'center', 'box-shadow': '0 4px 20px rgba(0,0,0,0.3)',
'max-width': '400px', width: '80%', position: 'relative'
}
});
var $title = $('<h2>', {
text: '🎉 恭喜!',
css: { 'margin-bottom': '15px', 'font-size': '24px', color: '#333' }
});
var $msg = $('<p>', {
text: '你已达到 ' + achievedMilestone + ' 次编辑!',
css: { 'font-size': '18px', 'margin-bottom': '25px', color: '#555' }
});
var $btn = $('<button>', {
text: '太棒了!',
css: {
background: '#4CAF50', color: '#fff', border: 'none',
padding: '10px 30px', 'border-radius': '8px', 'font-size': '16px', cursor: 'pointer'
},
click: function() {
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
$box.append($title, $msg, $btn);
$overlay.append($box);
$('body').append($overlay);
}
}
}).fail(function() {});
}
// ==================== 用户链接处理器启动 ====================
var linkSelector = 'a.mw-userlink, .citizen-menu_card-content a, .citizen-userMenu a';
colorizeAndStatus($(linkSelector));
var observer = new MutationObserver(function () {
colorizeAndStatus($(linkSelector));
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setInterval(function () {
colorizeAndStatus($(linkSelector));
}, 3000);
// ==================== 植物卡片筛选 ====================
if ($('#pf-name').length) {
var $cards = $('.pvzhe-card');
var $name = $('#pf-name');
var $sunMax = $('#pf-sun-max');
var $cdMax = $('#pf-cd-max');
var $type = $('#pf-type');
var $version = $('#pf-version');
var $reset = $('#pf-reset');
function filterPlants() {
var name = $name.val().toLowerCase();
var sunRaw = $sunMax.val().trim();
var cdRaw = $cdMax.val().trim();
var sunTarget = sunRaw !== '' ? parseFloat(sunRaw) : null;
var cdTarget = cdRaw !== '' ? parseFloat(cdRaw) : null;
var type = $type.val();
var version = $version.val();
$cards.each(function () {
var $card = $(this);
var n = $card.find('.pvzhe-card-name').text().toLowerCase();
var sun = parseFloat($card.data('sun')) || 0;
var cd = parseFloat($card.data('cooldown')) || 0;
var t = ($card.data('type') || '').toString();
var v = ($card.data('version') || '').toString();
var show = true;
if (name && n.indexOf(name) === -1) show = false;
if (sunTarget !== null && sun !== sunTarget) show = false;
if (cdTarget !== null && cd !== cdTarget) show = false;
if (type) {
var cardTypes = t.split(',').map(function (s) { return s.trim(); });
if (cardTypes.indexOf(type) === -1) show = false;
}
if (version && v !== version) show = false;
$card.toggleClass('hidden-card', !show);
});
}
$name.on('input', filterPlants);
$sunMax.on('input keyup', filterPlants);
$cdMax.on('input keyup', filterPlants);
$type.on('change', filterPlants);
$version.on('change', filterPlants);
$reset.on('click', function () {
$name.val('');
$sunMax.val('');
$cdMax.val('');
$type.val('');
$version.val('');
filterPlants();
});
}
// ==================== 僵尸卡片筛选 ====================
function getArmorArray(healthStr) {
if (!healthStr || healthStr.indexOf('+') === -1) return [];
var armorPart = healthStr.split('+')[0].trim();
return armorPart.split(',').map(function(s) { return s.trim(); });
}
function extractHealth(healthStr) {
var matches = healthStr.match(/\d+/g);
if (matches && matches.length > 0) {
return parseFloat(matches[matches.length - 1]) || 0;
}
return 0;
}
if ($('#zf-name').length) {
var $zcards = $('.pvzhe-card');
var $zname = $('#zf-name');
var $healthMin = $('#zf-health-min');
var $armor = $('#zf-armor');
var $speed = $('#zf-speed');
var $ztype = $('#zf-type');
var $zversion = $('#zf-version');
var $zreset = $('#zf-reset');
function filterZombies() {
var name = $zname.val().toLowerCase();
var healthRaw = $healthMin.val().trim();
var healthTarget = healthRaw !== '' ? parseFloat(healthRaw) : null;
var armor = $armor.val();
var speed = $speed.val();
var type = $ztype.val();
var version = $zversion.val();
$zcards.each(function () {
var $card = $(this);
var n = $card.find('.pvzhe-card-name').text().toLowerCase();
var t = ($card.data('type') || '').toString();
var s = ($card.data('speed') || '').toString();
var healthStr = ($card.data('health') || '').toString();
var health = extractHealth(healthStr);
var armors = getArmorArray(healthStr);
var v = ($card.data('version') || '').toString();
var show = true;
if (name && n.indexOf(name) === -1) show = false;
if (healthTarget !== null && health !== healthTarget) show = false;
if (armor === 'none') {
if (armors.length > 0) show = false;
} else if (armor) {
if (armors.indexOf(armor) === -1) show = false;
}
if (speed) {
var cardSpeeds = s.split(',').map(function (v) { return v.trim(); });
if (cardSpeeds.indexOf(speed) === -1) show = false;
}
if (type) {
var cardTypes = t.split(',').map(function (v) { return v.trim(); });
if (cardTypes.indexOf(type) === -1) show = false;
}
if (version && v !== version) show = false;
$card.toggleClass('hidden-card', !show);
});
}
$zname.on('input', filterZombies);
$healthMin.on('input keyup', filterZombies);
$armor.on('change', filterZombies);
$speed.on('change', filterZombies);
$ztype.on('change', filterZombies);
$zversion.on('change', filterZombies);
$zreset.on('click', function () {
$zname.val('');
$healthMin.val('');
$armor.val('');
$speed.val('');
$ztype.val('');
$zversion.val('');
filterZombies();
});
}
// ==================== 返回顶部按钮 ====================
$('body').append('<button id="back-to-top" title="返回顶部">⬆</button>');
var $backBtn = $('#back-to-top');
$(window).scroll(function() {
$backBtn.toggle($(this).scrollTop() > 300);
});
$backBtn.click(function() {
$('html, body').animate({ scrollTop: 0 }, 400);
});
// ==================== 好友系统 ====================
var friendApi = new mw.Api();
function getFriendPageName(username) {
return 'User:' + username + '/friends';
}
function getFriendRequestsPageName(username) {
return 'User:' + username + '/friendrequests';
}
function loadFriendList(username, callback) {
friendApi.get({
action: 'query',
titles: getFriendPageName(username),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function loadFriendRequests(username, callback) {
friendApi.get({
action: 'query',
titles: getFriendRequestsPageName(username),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function saveToPage(pageName, data, summary, callback) {
friendApi.postWithEditToken({
action: 'edit',
title: pageName,
text: JSON.stringify(data, null, 2),
summary: summary,
minor: true,
bot: true
}).then(function() {
if (callback) callback(true);
}).fail(function() {
if (callback) callback(false);
});
}
function sendFriendRequest(toUser) {
if (!toUser || toUser === currentUser) return;
loadFriendList(currentUser, function(myFriends) {
if (myFriends.indexOf(toUser) !== -1) {
alert('你们已经是好友了!');
return;
}
loadFriendRequests(toUser, function(requests) {
var alreadySent = requests.some(function(r) { return r.from === currentUser; });
if (alreadySent) {
alert('你已经发送过好友请求了,请等待对方回应。');
return;
}
requests.push({ from: currentUser, time: Date.now() });
saveToPage(getFriendRequestsPageName(toUser), requests, currentUser + ' 发送了好友请求', function(success) {
if (success) {
alert('好友请求已发送给 ' + toUser + '!');
updateFriendButton(toUser, 'pending');
} else {
alert('发送失败,请稍后重试。');
}
});
});
});
}
function acceptFriendRequest(fromUser) {
loadFriendList(currentUser, function(myFriends) {
myFriends.push(fromUser);
saveToPage(getFriendPageName(currentUser), myFriends, '添加好友: ' + fromUser, function() {
loadFriendList(fromUser, function(theirFriends) {
theirFriends.push(currentUser);
saveToPage(getFriendPageName(fromUser), theirFriends, '添加好友: ' + currentUser, function() {
loadFriendRequests(currentUser, function(requests) {
requests = requests.filter(function(r) { return r.from !== fromUser; });
saveToPage(getFriendRequestsPageName(currentUser), requests, '接受好友请求', function() {
if ($('#friend-requests-list').length) showFriendRequests();
updateFriendButton(fromUser, 'added');
});
});
});
});
});
});
}
function rejectFriendRequest(fromUser) {
loadFriendRequests(currentUser, function(requests) {
requests = requests.filter(function(r) { return r.from !== fromUser; });
saveToPage(getFriendRequestsPageName(currentUser), requests, '拒绝好友请求', function() {
if ($('#friend-requests-list').length) showFriendRequests();
});
});
}
function removeFriend(friendName) {
if (!confirm('确定要删除好友 ' + friendName + ' 吗?')) return;
loadFriendList(currentUser, function(myFriends) {
myFriends = myFriends.filter(function(f) { return f !== friendName; });
saveToPage(getFriendPageName(currentUser), myFriends, '删除好友: ' + friendName, function() {
loadFriendList(friendName, function(theirFriends) {
theirFriends = theirFriends.filter(function(f) { return f !== currentUser; });
saveToPage(getFriendPageName(friendName), theirFriends, '删除好友: ' + currentUser, function() {
if ($('#friend-list-container').length) showFriendList();
updateFriendButton(friendName, 'add');
});
});
});
});
}
function updateFriendButton(username, status) {
var $btn = $('#friend-action-btn');
if (!$btn.length) return;
$btn.removeClass('friend-add-btn friend-added-btn friend-pending-btn');
if (status === 'added') {
$btn.addClass('friend-added-btn').text('✓ 已添加');
$btn.off('click').click(function() { removeFriend(username); });
} else if (status === 'pending') {
$btn.addClass('friend-pending-btn').text('⏳ 等待确认').off('click');
} else {
$btn.addClass('friend-add-btn').text('+ 加好友');
$btn.off('click').click(function() { sendFriendRequest(username); });
}
}
function showFriendRequests() {
loadFriendRequests(currentUser, function(requests) {
var $list = $('#friend-requests-list');
if (!$list.length) return;
$list.empty();
if (requests.length === 0) {
$list.append('<p style="color:#999;">暂无好友请求</p>');
return;
}
requests.forEach(function(r) {
var $item = $('<div>', { class: 'friend-request-item' });
$item.append('<span><a href="/w/User:' + encodeURIComponent(r.from) + '">' + r.from + '</a></span>');
var $actions = $('<div>');
$actions.append('<button class="friend-accept-btn" data-from="' + r.from + '">接受</button>');
$actions.append('<button class="friend-reject-btn" data-from="' + r.from + '">拒绝</button>');
$item.append($actions);
$list.append($item);
});
$list.off('click').on('click', '.friend-accept-btn', function() {
acceptFriendRequest($(this).data('from'));
}).on('click', '.friend-reject-btn', function() {
rejectFriendRequest($(this).data('from'));
});
});
}
function showFriendList() {
loadFriendList(currentUser, function(friends) {
loadFriendRequests(currentUser, function(requests) {
var $overlay = $('<div>', { class: 'friend-overlay' });
var $dialog = $('<div>', { class: 'friend-dialog' });
var realCount = friends.length;
$dialog.append('<h3>👥 好友列表 <span class="friend-count">' + realCount + '人</span></h3>');
if (requests.length > 0) {
$dialog.append('<p style="color:#f44336;cursor:pointer;" id="friend-req-notice">📩 有 ' + requests.length + ' 条好友请求,点击查看</p>');
}
$dialog.append('<div id="friend-requests-list" style="display:none;margin:10px 0;"></div>');
if (realCount === 0) {
$dialog.append('<p style="color:#999;">还没有好友,去其他用户页面加好友吧!</p>');
} else {
var $container = $('<div>', { id: 'friend-list-container', style: 'margin:10px 0;' });
friends.forEach(function(f) {
var $item = $('<span>', { class: 'friend-list-item' });
$item.append('<a href="/w/User:' + encodeURIComponent(f) + '">' + f + '</a>');
$item.append(' <a href="javascript:void(0)" class="friend-msg-link" data-friend="' + f + '" title="发私信">✉️</a>');
$item.append('<span class="friend-remove" data-friend="' + f + '"> ×</span>');
$container.append($item);
});
$dialog.append($container);
}
$dialog.append('<button style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
$overlay.append($dialog);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
$overlay.remove();
}
});
$dialog.on('click', '#friend-req-notice', function() {
var $reqList = $('#friend-requests-list');
$reqList.toggle();
if ($reqList.is(':visible')) showFriendRequests();
});
$dialog.on('click', '.friend-remove', function() {
removeFriend($(this).data('friend'));
});
$dialog.on('click', '.friend-msg-link', function() {
var friendName = $(this).data('friend');
$overlay.remove();
sendMessage(friendName);
});
});
});
}
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
var pageUser = mw.config.get('wgTitle');
if (pageUser !== currentUser) {
var $btn = $('<button>', {
id: 'friend-action-btn',
class: 'friend-btn friend-add-btn',
text: '+ 加好友'
});
$('#firstHeading').append($btn);
loadFriendList(currentUser, function(myFriends) {
if (myFriends.indexOf(pageUser) !== -1) {
updateFriendButton(pageUser, 'added');
} else {
loadFriendRequests(pageUser, function(requests) {
var alreadySent = requests.some(function(r) { return r.from === currentUser; });
if (alreadySent) {
updateFriendButton(pageUser, 'pending');
} else {
$btn.click(function() { sendFriendRequest(pageUser); });
}
});
}
});
}
}
if (currentUser) {
var $userMenu = $('#pt-userpage, .citizen-userMenu').first();
if ($userMenu.length) {
$userMenu.after(' <a href="javascript:void(0)" id="friend-list-trigger" style="font-size:13px;" title="好友列表">👥</a>');
}
$(document).on('click', '#friend-list-trigger', function() {
showFriendList();
});
setInterval(function() {
loadFriendRequests(currentUser, function(requests) {
var $notice = $('#friend-request-count');
if (requests.length > 0) {
if (!$notice.length && $('#friend-list-trigger').length) {
$('#friend-list-trigger').after('<span id="friend-request-count" style="color:#f44336;font-size:11px;vertical-align:super;">' + requests.length + '</span>');
} else if ($notice.length) {
$notice.text(requests.length);
}
} else {
$notice.remove();
}
});
}, 60000);
}
// ==================== 私信系统 ====================
var msgApi = new mw.Api();
function getMsgPageName(username) {
return 'User:' + username + '/messages';
}
function loadMessages(callback) {
if (!currentUser) { callback([]); return; }
msgApi.get({
action: 'query',
titles: getMsgPageName(currentUser),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function saveMessages(msgs, callback) {
if (!currentUser) return;
msgApi.postWithEditToken({
action: 'edit',
title: getMsgPageName(currentUser),
text: JSON.stringify(msgs, null, 2),
summary: '更新私信',
minor: true,
bot: true
}).then(function() {
if (callback) callback(true);
}).fail(function() {
if (callback) callback(false);
});
}
function getUnreadCount(msgs) {
var count = 0;
msgs.forEach(function(m) { if (!m.read) count++; });
return count;
}
function updateMsgBadge() {
loadMessages(function(msgs) {
var count = getUnreadCount(msgs);
var $badge = $('#msg-badge');
if ($badge.length === 0) {
var $drawer = $('.citizen-drawer').first();
if ($drawer.length) {
$drawer.css('position', 'relative');
$drawer.append('<span id="msg-badge" class="msg-badge" title="新私信">0</span>');
$badge = $('#msg-badge');
}
}
if ($badge.length) {
$badge.text(count).toggle(count > 0);
}
});
}
function showInbox() {
loadMessages(function(msgs) {
var $overlay = $('<div>', { class: 'msg-overlay' });
var $box = $('<div>', { class: 'msg-box' });
$box.append('<h3 style="margin-bottom:15px;">📬 私信箱</h3>');
msgs.sort(function(a, b) { return b.time - a.time; });
if (msgs.length === 0) {
$box.append('<p style="color:#999;">暂无消息</p>');
} else {
msgs.forEach(function(m, index) {
var $item = $('<div>', { class: 'msg-item' + (m.read ? '' : ' unread') });
var timeStr = new Date(m.time).toLocaleString('zh-CN');
$item.append(
'<div><span class="msg-sender">' + m.from + '</span><span class="msg-time">' + timeStr + '</span></div>',
'<div class="msg-text">' + m.text.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>') + '</div>',
'<div class="msg-actions">' +
(m.read ? '' : '<button class="mark-read" data-index="' + index + '">已读</button>') +
'<button class="del-msg" data-index="' + index + '">删除</button>' +
'<button class="reply-msg" data-index="' + index + '" data-from="' + m.from + '">回复</button>' +
'</div>'
);
$box.append($item);
});
}
$box.append('<button style="margin-top:15px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
$overlay.remove();
updateMsgBadge();
}
});
$box.on('click', '.mark-read', function() {
var idx = parseInt($(this).data('index'));
msgs[idx].read = true;
saveMessages(msgs, function() {
updateMsgBadge();
$overlay.remove();
showInbox();
});
});
$box.on('click', '.del-msg', function() {
var idx = parseInt($(this).data('index'));
msgs.splice(idx, 1);
saveMessages(msgs, function() {
$overlay.remove();
showInbox();
});
});
$box.on('click', '.reply-msg', function() {
var toUser = $(this).data('from');
$overlay.remove();
sendMessage(toUser);
});
});
}
function sendMessage(toUser) {
if (!toUser) return;
var $overlay = $('<div>', { class: 'msg-overlay' });
var $box = $('<div>', { class: 'msg-box' });
$box.append('<h3 style="margin-bottom:10px;">✉️ 发送私信给 ' + toUser + '</h3>');
$box.append('<textarea class="msg-textarea" placeholder="输入消息内容..."></textarea>');
$box.append(
'<button class="msg-send-submit" style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#4CAF50;color:#fff;border:none;border-radius:6px;">发送</button>',
'<button style="margin-left:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">取消</button>'
);
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '取消') {
$overlay.remove();
}
});
$box.on('click', '.msg-send-submit', function() {
var text = $box.find('.msg-textarea').val().trim();
if (!text) return;
$box.find('.msg-send-submit').prop('disabled', true).text('发送中...');
var tempApi = new mw.Api();
tempApi.get({
action: 'query',
titles: getMsgPageName(toUser),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
var msgs = [];
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { msgs = JSON.parse(pages[id].revisions[0]['*']); } catch(e) {}
}
}
msgs.push({
from: currentUser,
to: toUser,
text: text,
time: Date.now(),
read: false
});
tempApi.postWithEditToken({
action: 'edit',
title: getMsgPageName(toUser),
text: JSON.stringify(msgs, null, 2),
summary: currentUser + ' 发来一条私信',
minor: false,
bot: false
}).then(function() {
$overlay.remove();
alert('私信已发送给 ' + toUser + '!');
}).fail(function(err) {
$overlay.remove();
alert('发送失败:' + (err.error && err.error.info ? err.error.info : '未知错误'));
});
}).fail(function() {
$overlay.remove();
alert('发送失败,请稍后重试。');
});
});
}
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
var msgPageUser = mw.config.get('wgTitle');
if (msgPageUser !== currentUser) {
var $msgBtn = $('<button>', {
text: '✉️ 发送私信',
class: 'msg-send-btn',
click: function() { sendMessage(msgPageUser); }
});
$('#firstHeading').append($msgBtn);
}
}
if (currentUser) {
updateMsgBadge();
$(document).on('click', '#msg-badge', function() {
showInbox();
});
var $msgEntry = $('<a>', {
href: 'javascript:void(0)',
id: 'msg-inbox-trigger',
text: '✉️',
title: '私信箱',
css: { 'font-size': '13px', 'margin-left': '8px', 'cursor': 'pointer', 'text-decoration': 'none' }
});
var $friendTrigger = $('#friend-list-trigger');
if ($friendTrigger.length) {
$friendTrigger.after(' ');
$friendTrigger.after($msgEntry);
}
$(document).on('click', '#msg-inbox-trigger', function() {
showInbox();
});
setInterval(function() {
updateMsgBadge();
}, 30000);
}
// ==================== 版本更新公告弹窗 ====================
function checkUpdateNotice() {
var $versionEl = $('.update-version');
if ($versionEl.length === 0) return;
var currentVersion = $versionEl.text().trim();
if (!currentVersion) return;
var dismissedVersion = localStorage.getItem('mw_update_dismissed');
if (dismissedVersion !== currentVersion) {
var $overlay = $('<div>', {
css: {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'rgba(0,0,0,0.6)', 'z-index': 99999,
display: 'flex', 'align-items': 'center', 'justify-content': 'center'
}
});
var $box = $('<div>', {
css: {
background: '#fff', 'border-radius': '12px', padding: '30px',
'max-width': '550px', width: '90%', 'max-height': '80vh',
'overflow-y': 'auto', 'box-shadow': '0 8px 30px rgba(0,0,0,0.4)',
position: 'relative'
}
});
var $content = $('<div>').css({'text-align':'left','font-size':'14px','line-height':'1.8'}).html(
'<div style="text-align:center;font-size:18px;font-weight:bold;margin-bottom:5px;">【植物大战僵尸杂交版0.22版本更新公告】</div>' +
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">更新时间:2026年6月7号</div>'+
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">电脑(Windows)版链接:https://pan.quark.cn/s/b145573873c3</div>'+
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">其他版本:https://pan.quark.cn/s/3ffc82554918</div>'+
'<b>🌱 植物更新</b><br>全新的植物加入了我们,我们的实力将更加强悍!<br>'+
'<b>白卡:</b>幽灵吞噬者、僵尸地刺、魅惑咖啡豆、魅惑三叶草、灵感菇、蒜鸟、咖啡三叶草、叶子高坚果、巨型南瓜壳、绷带高坚果、磁力樱桃炸弹、樱桃土豆雷<br>' +
'<b>至尊金卡:</b>黄金西瓜投手、大王钢齿花<br>' +
'<b>臻享钻卡:</b>生命重塑者<br>' +
'<b>星光闪卡:</b>黄金锤子<br><br>' +
'<b>🧟 僵尸/障碍物更新</b><br>飞行器僵尸、胆小鬼僵尸、传送门僵尸<br><br>' +
'<b>🗺️ 关卡更新</b><br>' +
'冒险:第八章 1~4关<br>' +
'金卡挑战:大王钢齿花 1~3,黄金西瓜投手 1~3<br>' +
'钻卡挑战:生命重塑者 1~6<br><br>' +
'<b>🎨 杂项更新</b><br>' +
'植物皮肤更新:黄金西瓜投手-幸运之王、大王钢齿花-银锋锐影、生命重塑者-浮生净莲(商店15000购买)、魔鬼辣椒-拘灵炼狱(在线关卡5水晶兑换)<br>' +
'道具更新:骷髅铲<br><br>' +
'<b>⚙️ 全新功能</b><br>' +
'新增游戏指令:/phonk [on/off] [弹性强度数]、/dismember [on/off]<br>' +
'局内设置新增果冻弹性效果开关与强度调整<br><br>' +
'<b>⚖️ 平衡性调整</b><br>' +
'植物价格调整:巨型南瓜雪橇 200→300、墓碑爆破者 150→100<br>' +
'植物强度调整:宝藏树桩亡语 1~3张黄金碎片→1张<br><br>' +
'<b>🛠️ 游戏优化</b><br>' +
'优化了关卡进度存档、返回选关体验、在线关卡分类筛选与通关率显示、更多模式板块内容返回体验<br>' +
'修复商店与植物试玩切换假死BUG、追加宝藏树桩亡语掉落黄金碎片、荧光木槌可对僵尸使用、本次更新修复了一堆BUG<br><br>' +
'<b>💎 水晶兑换商店</b><br>' +
'巨型南瓜壳 5 | 磁力樱桃炸弹 10 | 绷带高坚果 10<br>' +
'叶子高坚果 15 | 樱桃土豆雷 15 | 魔鬼辣椒皮肤-拘灵炼狱 5<br><br>' +
'<span style="color:#888;">结语:我们会持续建立与玩家社群的紧密联系,您的支持就是对我们工作最大的认可</span>'
);
var $closeBtn = $('<button>', {
text: '我知道了',
css: {
display: 'block', margin: '20px auto 0',
background: '#4CAF50', color: '#fff', border: 'none',
padding: '10px 30px', 'border-radius': '8px',
'font-size': '16px', cursor: 'pointer'
},
click: function() {
localStorage.setItem('mw_update_dismissed', currentVersion);
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
$box.append($content, $closeBtn);
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay)) {
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
}
}
setTimeout(function() {
checkUpdateNotice();
}, 500);
// ==================== Wiki 宠物 ====================
(function() {
var pets = {
'冰瓜香蒲': {
idle: 'https://new.pvzhe.wiki/images/1/10/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E5%BE%85%E6%9C%BA.gif',
petting: 'https://new.pvzhe.wiki/images/4/47/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E6%8A%9A%E6%91%B8.gif',
size: 100
},
'小猫向日葵': {
idle: 'https://new.pvzhe.wiki/images/9/9b/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E5%BE%85%E6%9C%BA.gif',
petting: 'https://new.pvzhe.wiki/images/8/85/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E6%8A%9A%E6%91%B8.gif',
size: 100
}
};
var petNames = Object.keys(pets);
var currentPet = localStorage.getItem('wiki_pet_current') || '冰瓜香蒲';
if (!pets[currentPet]) currentPet = '冰瓜香蒲';
var petState = 'idle';
var petTimer = null;
var affectionKey = 'wiki_pet_affection_';
var positionKey = 'wiki_pet_position';
var affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0;
var affectionPerPet = 5;
var affectionCooldown = 100;
var lastPetTime = 0;
var isDragging = false;
var dragStartX, dragStartY, petStartX, petStartY;
var hasMoved = false;
var levels = [
{ min: 0, title: '陌生', emoji: '🤔' },
{ min: 50, title: '认识', emoji: '👋' },
{ min: 150, title: '友好', emoji: '😊' },
{ min: 400, title: '亲密', emoji: '💚' },
{ min: 1000, title: '挚友', emoji: '💖' },
{ min: 2500, title: '灵魂伴侣', emoji: '✨' }
];
function getLevel(aff) {
var lvl = levels[0];
for (var i = levels.length - 1; i >= 0; i--) {
if (aff >= levels[i].min) { lvl = levels[i]; break; }
}
return lvl;
}
function saveAffection() { localStorage.setItem(affectionKey + currentPet, affection); }
function savePosition() {
var pos = $pet.position();
var bottom = $(window).height() - pos.top - $pet.height();
localStorage.setItem(positionKey, JSON.stringify({ left: pos.left, bottom: Math.max(0, bottom) }));
}
function showAffectionPanel() {
var lvl = getLevel(affection);
var nextLvl = null;
for (var i = 0; i < levels.length; i++) {
if (affection < levels[i].min) { nextLvl = levels[i]; break; }
}
var text = lvl.emoji + ' ' + lvl.title + ' (' + affection + ')';
if (nextLvl) {
var progress = Math.round((affection - lvl.min) / (nextLvl.min - lvl.min) * 100);
text += ' → ' + nextLvl.emoji + ' ' + progress + '%';
} else { text += ' MAX'; }
$affectionPanel.text(text).css('color', '#fff');
}
function spawnHeart(x, y) {
var hearts = ['💚', '💙', '💛', '💜', '❤️', '💖', '✨'];
var heart = hearts[Math.floor(Math.random() * hearts.length)];
var $heart = $('<div>', { class: 'pet-heart', text: heart, css: { left: x, top: y } });
$('body').append($heart);
setTimeout(function() { $heart.remove(); }, 1500);
}
function triggerLevelUp() {
$pet.addClass('level-up');
setTimeout(function() { $pet.removeClass('level-up'); }, 800);
}
function switchPet(newPet) {
if (newPet === currentPet) return;
currentPet = newPet;
localStorage.setItem('wiki_pet_current', currentPet);
affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0;
$img.attr('src', pets[currentPet].idle);
showAffectionPanel();
setPetState('idle');
}
var savedPos = JSON.parse(localStorage.getItem(positionKey)) || { left: 20, bottom: 20 };
var $pet = $('<div>', {
class: 'wiki-pet',
css: { opacity: 0, left: savedPos.left + 'px', bottom: savedPos.bottom + 'px', top: 'auto', right: 'auto' }
});
var $img = $('<img>', {
src: pets[currentPet].idle,
alt: currentPet,
draggable: 'false',
css: { width: pets[currentPet].size + 'px', height: 'auto' }
});
var $affectionPanel = $('<div>', { class: 'pet-affection' });
var $tooltip = $('<div>', { class: 'pet-tooltip', text: '点击抚摸我~ 🐱' });
var $switchBtn = $('<button>', { class: 'pet-switch-btn', text: '🔄', title: '切换宠物' });
$pet.append($img, $affectionPanel, $tooltip, $switchBtn);
$('body').append($pet);
showAffectionPanel();
setTimeout(function() { $pet.animate({ opacity: 1 }, 500); }, 1000);
$switchBtn.on('click', function(e) {
e.stopPropagation();
e.preventDefault();
var idx = petNames.indexOf(currentPet);
var nextIdx = (idx + 1) % petNames.length;
switchPet(petNames[nextIdx]);
});
function setPetState(state) {
if (petState === state) return;
petState = state;
clearTimeout(petTimer);
$pet.removeClass('petting');
if (state === 'idle') {
$img.attr('src', pets[currentPet].idle);
$img.css({ width: pets[currentPet].size + 'px', height: 'auto' });
$tooltip.text('点击抚摸我~ ' + getLevel(affection).emoji);
} else if (state === 'petting') {
$img.attr('src', pets[currentPet].petting);
var petSize = currentPet === '小猫向日葵' ? pets[currentPet].size * 1.2 : Math.round(pets[currentPet].size * 1.2);
$img.css({ width: petSize + 'px', height: 'auto' });
$pet.addClass('petting');
$tooltip.text('好舒服~ 💚');
petTimer = setTimeout(function() { setPetState('idle'); }, 1200);
}
}
$pet.on('mousedown', function(e) {
if (e.button !== 0) return;
if ($(e.target).is($switchBtn)) return;
isDragging = true; hasMoved = false;
dragStartX = e.clientX; dragStartY = e.clientY;
petStartX = $pet.offset().left; petStartY = $pet.offset().top;
$pet.css('transition', 'none');
e.preventDefault();
});
$(document).on('mousemove', function(e) {
if (!isDragging) return;
var dx = e.clientX - dragStartX, dy = e.clientY - dragStartY;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size));
var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size));
$pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' });
});
$(document).on('mouseup', function() {
if (!isDragging) return;
isDragging = false;
$pet.css('transition', 'transform 0.2s');
savePosition();
});
$pet.on('click', function(e) {
if ($(e.target).is($switchBtn)) return;
e.stopPropagation();
if (hasMoved) return;
var now = Date.now();
if (now - lastPetTime < affectionCooldown) return;
lastPetTime = now;
setPetState('petting');
var oldLevel = getLevel(affection).title;
affection += affectionPerPet;
saveAffection();
showAffectionPanel();
var newLevel = getLevel(affection).title;
if (newLevel !== oldLevel) {
triggerLevelUp();
$affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700');
setTimeout(function() { showAffectionPanel(); }, 2000);
}
var offset = $pet.offset();
spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10);
});
setInterval(function() {
if (petState === 'idle' && !isDragging && Math.random() < 0.3) {
$pet.css('transform', 'scale(1.05) translateY(-5px)');
setTimeout(function() { $pet.css('transform', 'scale(1) translateY(0)'); }, 500);
}
}, 10000);
$pet.on('touchstart', function(e) {
$pet.addClass('touching');
var touch = e.originalEvent.touches[0];
isDragging = true; hasMoved = false;
dragStartX = touch.clientX; dragStartY = touch.clientY;
petStartX = $pet.offset().left; petStartY = $pet.offset().top;
$pet.css('transition', 'none');
e.stopPropagation();
});
$pet.on('touchend', function() {
$pet.removeClass('touching');
});
$(document).on('touchmove', function(e) {
if (!isDragging) return;
var touch = e.originalEvent.touches[0];
var dx = touch.clientX - dragStartX, dy = touch.clientY - dragStartY;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size));
var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size));
$pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' });
});
$(document).on('touchend', function() {
if (!isDragging) return;
isDragging = false;
$pet.css('transition', 'transform 0.2s');
$pet.removeClass('touching');
savePosition();
if (!hasMoved) {
var now = Date.now();
if (now - lastPetTime < affectionCooldown) return;
lastPetTime = now;
setPetState('petting');
var oldLevel = getLevel(affection).title;
affection += affectionPerPet;
saveAffection();
showAffectionPanel();
var newLevel = getLevel(affection).title;
if (newLevel !== oldLevel) {
triggerLevelUp();
$affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700');
setTimeout(function() { showAffectionPanel(); }, 2000);
}
var offset = $pet.offset();
spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10);
}
});
})();
// ==================== 阅读位置记忆 ====================
(function() {
var storageKey = 'mw_readposition_enabled';
var positionKey = 'mw_readposition_data';
var enabled = localStorage.getItem(storageKey) === 'true';
function restorePosition() {
if (!enabled) return;
var data = JSON.parse(localStorage.getItem(positionKey) || '{}');
var currentPage = mw.config.get('wgPageName');
if (data[currentPage]) {
var scrollTarget = data[currentPage];
$(function() {
setTimeout(function() {
$('html, body').scrollTop(scrollTarget);
}, 500);
});
}
}
function savePosition() {
if (!enabled) return;
var currentPage = mw.config.get('wgPageName');
var scrollTop = $(window).scrollTop();
if (scrollTop > 50) {
var data = JSON.parse(localStorage.getItem(positionKey) || '{}');
data[currentPage] = scrollTop;
localStorage.setItem(positionKey, JSON.stringify(data));
}
}
function insertSwitch() {
var $target = $('.citizen-preferences-section__content').last();
if (!$target.length) return;
if ($('.readposition-switch').length) return;
var $switch = $(
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group readposition-switch">' +
'<input id="readposition-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (enabled ? ' checked' : '') + '>' +
'<span class="cdx-toggle-switch__switch">' +
'<span class="cdx-toggle-switch__switch__grip"></span>' +
'</span>' +
'<div class="cdx-label cdx-toggle-switch__label">' +
'<label class="cdx-label__label" for="readposition-toggle">' +
'<span class="cdx-label__label__text">记住阅读位置</span>' +
'</label>' +
'<span class="cdx-label__description">返回页面时恢复上次滚动位置</span>' +
'</div>' +
'</span>'
);
$target.append($switch);
$('#readposition-toggle').on('change', function() {
enabled = $(this).is(':checked');
localStorage.setItem(storageKey, enabled ? 'true' : 'false');
if (!enabled) {
localStorage.removeItem(positionKey);
}
});
}
$(window).on('beforeunload', function() {
savePosition();
});
setInterval(function() {
savePosition();
}, 5000);
restorePosition();
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if ($('.citizen-preferences-section__content').is(':visible') && !$('.readposition-switch').length) {
insertSwitch();
}
});
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('.citizen-preferences-section__content').is(':visible')) {
insertSwitch();
}
})();
// ==================== 宠物显示开关 ====================
(function() {
var petVisibleKey = 'wiki_pet_visible';
var petVisible = localStorage.getItem(petVisibleKey) !== 'false';
function togglePet(show) {
if (show) {
$('.wiki-pet').fadeIn(300);
} else {
$('.wiki-pet').fadeOut(300);
}
}
function insertPetSwitch() {
var $target = $('.citizen-preferences-section__content').last();
if (!$target.length) return;
if ($('.pet-visible-switch').length) return;
var $switch = $(
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group pet-visible-switch">' +
'<input id="pet-visible-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (petVisible ? ' checked' : '') + '>' +
'<span class="cdx-toggle-switch__switch">' +
'<span class="cdx-toggle-switch__switch__grip"></span>' +
'</span>' +
'<div class="cdx-label cdx-toggle-switch__label">' +
'<label class="cdx-label__label" for="pet-visible-toggle">' +
'<span class="cdx-label__label__text">显示宠物</span>' +
'</label>' +
'<span class="cdx-label__description">在页面显示Wiki宠物</span>' +
'</div>' +
'</span>'
);
$target.append($switch);
$('#pet-visible-toggle').on('change', function() {
petVisible = $(this).is(':checked');
localStorage.setItem(petVisibleKey, petVisible ? 'true' : 'false');
togglePet(petVisible);
});
}
// 初始状态 - 立即检查
if (!petVisible) {
var checkPet = setInterval(function() {
if ($('.wiki-pet').length) {
$('.wiki-pet').css({ opacity: 0, display: 'none' });
clearInterval(checkPet);
}
}, 100);
setTimeout(function() { clearInterval(checkPet); }, 5000);
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if ($('.citizen-preferences-section__content').is(':visible') && !$('.pet-visible-switch').length) {
insertPetSwitch();
}
});
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('.citizen-preferences-section__content').is(':visible')) {
insertPetSwitch();
}
})();
}); // 结束主 $(function () { ... })