Widget:代号梗投稿
这个Widget用于《代号梗》页面的投稿和瀑布流展示功能。
使用方式:在页面底部添加
{{#Widget:代号梗投稿}}
<script> RLQ.push(['jquery', () => { $(document).ready(function () {
// ========== 全局变量 ==========
var api = new mw.Api();
var user = mw.config.values.wgUserName;
var allGengData = {};
var cachedImageUrls = {};
// ========== 获取用户头像(多来源兜底)==========
function getUserAvatar() {
if (window.thisUser && window.thisUser.icon) {
return window.thisUser.icon;
}
if (window.Bikit && window.Bikit.userId) {
return 'https://i0.hdslb.com/bfs/face/' + window.Bikit.userId + '.jpg';
}
var match = document.cookie.match(/DedeUserID=([^;]+)/);
if (match) {
return 'https://i0.hdslb.com/bfs/face/' + match[1] + '.jpg';
}
return ;
}
// ========== 初始化 ==========
$(".rocom_screen_box").appendTo("body");
var screenBox = document.querySelector('.rocom_screen_box');
if (screenBox) {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if ($(mutation.target).is(':visible')) {
$('body').css('overflow', 'hidden');
} else {
$('body').css('overflow', );
}
});
});
observer.observe(screenBox, { attributes: true, attributeFilter: ['style'] });
}
function extractFileName(imageCode) {
if (!imageCode) return null;
var match = imageCode.match(/\[\[file:(.*?)(?:\||\])/i);
if (match && match[1]) {
return match[1].trim();
}
return null;
}
// 通过API获取文件真实URL
async function getRealImageUrl(fileName) {
if (!fileName) return ;
if (cachedImageUrls[fileName]) return cachedImageUrls[fileName];
try {
let data = await api.get({
action: 'query',
titles: 'File:' + fileName,
prop: 'imageinfo',
iiprop: 'url',
format: 'json'
});
let pages = data.query.pages;
for (let id in pages) {
if (pages[id].imageinfo && pages[id].imageinfo[0]) {
cachedImageUrls[fileName] = pages[id].imageinfo[0].url;
return cachedImageUrls[fileName];
}
}
} catch(e) {
console.error('获取图片URL失败:', e);
}
return ;
}
// 构建图片HTML - 卡片用
async function buildImageHtml(imageCode, size) {
var fileName = extractFileName(imageCode);
if (!fileName) return ;
var url = await getRealImageUrl(fileName);
if (!url) return ;
var imgSize = size || '80px';
return '<img src="' + url + '" onerror="this.src=\'https://patchwiki.biligame.com/images/rocom/6/6c/luimkdv9n7jernb6gukhpyyc6hlnnev.png\'" style="width:' + imgSize + '; height:' + imgSize + '; object-fit:cover; border-radius:8px;">';
}
// 构建弹窗图片HTML - 完整显示
async function buildModalImageHtml(imageCode) {
var fileName = extractFileName(imageCode);
if (!fileName) return ;
var url = await getRealImageUrl(fileName);
if (!url) return ;
// 提取图片名称(不含扩展名)
var imageName = fileName.replace(/\.[^/.]+$/, "");
return '
'<img src="' + url + '" onerror="this.src=\'https://patchwiki.biligame.com/images/rocom/6/6c/luimkdv9n7jernb6gukhpyyc6hlnnev.png\'" style="width:100%; height:auto; border-radius:10px; display:block;">' +'
';
}
// ========== 加载所有梗数据 ==========
function loadAllGengData() {
return new Promise((resolve, reject) => {
api.get({
action: 'query',
list: 'categorymembers',
cmtitle: '分类:梗投稿',
cmtype: 'page',
cmlimit: '500',
format: 'json'
}).done(function(data) {
var pages = data.query.categorymembers;
if (!pages || pages.length === 0) {
resolve({});
return;
}
var pageTitles = pages.map(p => p.title);
api.get({
action: 'query',
titles: pageTitles,
prop: 'revisions',
rvprop: 'content|timestamp',
format: 'json'
}).done(function(pageData) {
var result = {};
for (var id in pageData.query.pages) {
var page = pageData.query.pages[id];
var title = page.title;
var content = page.revisions ? page.revisions[0]['*'] : ;
var match = content.match(/\{\{梗数据\n([\s\S]*?)\}\}/);
if (match) {
var params = match[1];
var geng = {};
// 改进的解析方法,支持多行内容
var paramsStr = match[1];
var paramsObj = {};
var lines = paramsStr.split('\n');
var currentKey = null;
var currentValue = [];
lines.forEach(function(line) {
line = line.trim();
if (line) {
if (line.startsWith('|')) {
// 保存上一个键值对
if (currentKey) {
paramsObj[currentKey] = currentValue.join('\n').trim();
}
// 开始新的键值对
var parts = line.split('=');
if (parts.length >= 2) {
currentKey = parts[0].replace(/^\|/, ).trim();
currentValue = [parts.slice(1).join('=').trim()];
} else {
currentKey = null;
currentValue = [];
}
} else if (currentKey) {
// 继续上一个值的内容(多行)
currentValue.push(line);
}
}
});
// 保存最后一个键值对
if (currentKey) {
paramsObj[currentKey] = currentValue.join('\n').trim();
}
geng.title = paramsObj['标题'] || ;
geng.chuchu = paramsObj['出处'] || ;
geng.image = paramsObj['图片'] || ;
geng.source = paramsObj['梗来源'] || ;
geng.detail = paramsObj['详情'] || ;
geng.contributor = paramsObj['贡献者'] || ;
geng.avatar = paramsObj['头像'] || ;
geng.activityType = paramsObj['活动类型'] || ;
geng.contentType = paramsObj['投稿类型'] || ;
geng.propName = paramsObj['道具名'] || ;
geng.speaker = paramsObj['说话人'] || ;
geng.dialogContent = paramsObj['对话内容'] || ;
geng.pageTitle = title;
// 优先使用页面修改时间,其次用模板里的提交时间
var revTimestamp = page.revisions && page.revisions[0].timestamp ? page.revisions[0].timestamp : ;
if (revTimestamp) {
var d = new Date(revTimestamp);
geng.time = d.getFullYear() + '-' +
String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0');
} else {
geng.time = paramsObj['提交时间'] || '未知时间';
}
if (geng.title) {
result[geng.title] = geng;
}
}
}
allGengData = result;
resolve(allGengData);
}).fail(reject);
}).fail(reject);
});
}
// ========== 删除梗页面(只有上传者可删除)==========
function deleteGengPage(pageTitle, gengTitle, contributor) {
if (user !== contributor) {
showCustomAlert('您只能删除自己上传的梗!');
return;
}
if (!confirm('确定要删除【' + gengTitle + '】吗?删除后无法恢复!')) {
return;
}
api.post({
action: 'delete',
title: pageTitle,
token: mw.user.tokens.get('csrfToken'),
reason: '通过梗投稿工具删除'
}).done(function() {
showCustomAlert('删除成功!正在刷新列表...');
allGengData = {};
cachedImageUrls = {};
loadAllGengData().then(function() {
renderWaterfall();
});
}).fail(function(error) {
showCustomAlert('删除失败:' + (error.error && error.error.info ? error.error.info : '您没有权限删除'));
});
}
// ========== 修改梗页面(只有上传者可修改)==========
function editGengPage(title) {
var geng = allGengData[title];
if (!geng) return;
if (user !== geng.contributor) {
showCustomAlert('您只能修改自己上传的梗!');
return;
}
$('.rocom_modal_container').remove();
$('body').css('overflow', );
// 调用Widget_代号梗投稿弹窗.html中的方法显示修改表单
if (window.showGengUploadFormForEdit) {
window.showGengUploadFormForEdit(geng);
} else {
showCustomAlert('修改功能未加载');
}
}
function updateGeng(pageTitle, newTitle, chuchu, image, source, detail, contributor) {
var pageContent = '{{梗数据\n';
pageContent += '|标题=' + newTitle + '\n';
pageContent += '|出处=' + chuchu + '\n';
pageContent += '|图片=' + image + '\n';
pageContent += '|梗来源=' + source + '\n';
pageContent += '|详情=' + detail + '\n';
pageContent += '|贡献者=' + contributor + '\n';
pageContent += '|头像=' + getUserAvatar() + '\n';
pageContent += '}}\n';
pageContent += ;
var newPageTitle = '梗/' + newTitle;
if (newPageTitle !== pageTitle) {
api.post({
action: 'move',
from: pageTitle,
to: newPageTitle,
token: mw.user.tokens.get('csrfToken'),
reason: '修改梗标题'
}).done(function() {
api.edit(newPageTitle, function() {
return { text: pageContent, summary: '通过梗投稿工具修改' };
}).done(function() {
refreshAfterEdit();
});
}).fail(function() {
api.edit(pageTitle, function() {
return { text: pageContent, summary: '通过梗投稿工具修改' };
}).done(function() {
refreshAfterEdit();
});
});
} else {
api.edit(pageTitle, function() {
return { text: pageContent, summary: '通过梗投稿工具修改' };
}).done(function() {
refreshAfterEdit();
}).fail(function(error) {
showCustomAlert('修改失败,请稍后重试');
});
}
}
function refreshAfterEdit() {
showCustomAlert('修改成功!正在刷新列表...');
allGengData = {};
cachedImageUrls = {};
loadAllGengData().then(function() {
renderWaterfall();
});
}
function bindCardEvents() {
$('.rocom_geng_card').off('click').on('click', function(e) {
if ($(e.target).hasClass('rocom_geng_card_delete')) return;
var title = decodeURIComponent($(this).data('title'));
showDetailModal(title);
});
$('.rocom_geng_card_delete').off('click').on('click', function(e) {
e.stopPropagation();
var pageTitle = decodeURIComponent($(this).data('page'));
var gengTitle = decodeURIComponent($(this).data('title'));
var contributor = decodeURIComponent($(this).data('contributor'));
deleteGengPage(pageTitle, gengTitle, contributor);
});
}
// ========== 渲染瀑布流卡片(带筛选和排序)==========
async function renderWaterfall() {
var $container = $('.rocom_geng_waterfall');
if ($container.length === 0) {
$('.rocom_lineup_line_pet_deteils').after('
');
$container = $('.rocom_geng_waterfall');
}
$container.empty();
// 获取筛选和排序参数
var searchKeyword = $('#gengSearch').val().trim().toLowerCase();
var sortType = $('#gengSortType').val();
var sortOrder = $('#gengSortOrder').val();
var myOnly = $('#gengMyOnly').prop('checked');
// 筛选数据
var filteredData = {};
for (var title in allGengData) {
var geng = allGengData[title];
// 仅查看自己投稿
if (myOnly && user && geng.contributor !== user) {
continue;
}
// 搜索关键词
if (searchKeyword) {
var match = false;
if (geng.title.toLowerCase().includes(searchKeyword) ||
geng.chuchu.toLowerCase().includes(searchKeyword) ||
geng.source.toLowerCase().includes(searchKeyword) ||
geng.detail.toLowerCase().includes(searchKeyword) ||
geng.contributor.toLowerCase().includes(searchKeyword)) {
match = true;
}
if (!match) continue;
}
filteredData[title] = geng;
}
// 排序
var titles = Object.keys(filteredData);
titles.sort(function(a, b) {
var gengA = filteredData[a];
var gengB = filteredData[b];
if (sortType === 'time') {
var timeA = gengA.time || '1970-01-01';
var timeB = gengB.time || '1970-01-01';
if (sortOrder === 'desc') {
return timeB.localeCompare(timeA);
} else if (sortOrder === 'asc') {
return timeA.localeCompare(timeB);
}
} else if (sortType === 'activity') {
var activityA = gengA['活动类型'] || ;
var activityB = gengB['活动类型'] || ;
return activityA.localeCompare(activityB);
}
return 0;
});
if (titles.length === 0) {
$container.html('
');
return;
}
for (var i = 0; i < titles.length; i++) {
var geng = filteredData[titles[i]];
var hasImage = extractFileName(geng.image) !== null;
var imageHtml = hasImage ? await buildImageHtml(geng.image, '80px') : ;
var canDelete = (user && user === geng.contributor);
var cardHtml = '
';
$container.append(cardHtml);
}
bindCardEvents();
}
// ========== 弹窗详情 ==========
async function showDetailModal(title) {
var geng = allGengData[title];
if (!geng) return;
var hasImage = extractFileName(geng.image) !== null;
var imageHtml = hasImage ? await buildModalImageHtml(geng.image) : ;
var canEdit = (user && user === geng.contributor);
var displayTime = geng.time !== '未知时间' ? geng.time : ;
var contributorName = geng.contributor || '匿名';
var avatarUrl = geng.avatar || 'https://i0.hdslb.com/bfs/face/member/noface.jpg';
var modalHtml = '
') + '
'' + '<img src="' + avatarUrl + '" ' + 'class="rocom_modal_user_avatar" ' + 'onerror="this.src=\'https://i0.hdslb.com/bfs/face/member/noface.jpg\'" ' + 'style="width:36px;height:36px;border-radius:50%;object-fit:cover;vertical-align:middle;margin-right:6px;">' + escapeHtml(contributorName) + '' + (displayTime ? '上传时间:' + displayTime + '' : ) +'
(canEdit ? '<button class="rocom_modal_edit_btn" data-title="' + encodeURIComponent(geng.title) + '">修改</button>' : ) +'
';
$('.rocom_modal_container').remove();
$('body').append(modalHtml);
$('body').css('overflow', 'hidden');
$('.rocom_modal_overlay').on('click', function() {
$('.rocom_modal_container').remove();
$('body').css('overflow', );
});
$('.rocom_modal_edit_btn').off('click').on('click', function(e) {
e.stopPropagation();
var editTitle = decodeURIComponent($(this).data('title'));
editGengPage(editTitle);
});
}
// ========== 提交梗 ==========
function submitGeng(title, chuchu, image, source, detail, contributor, activityType, activity, contentType, speaker, dialogContent) {
var pageContent = '{{梗数据\n';
pageContent += '|标题=' + title + '\n';
pageContent += '|出处=' + chuchu + '\n';
pageContent += '|梗来源=' + source + '\n';
pageContent += '|详情=' + detail + '\n';
pageContent += '|贡献者=' + contributor + '\n';
pageContent += '|头像=' + getUserAvatar() + '\n';
if (activityType) pageContent += '|活动类型=' + activityType + '\n';
if (activity) pageContent += '|具体活动=' + activity + '\n';
if (contentType) pageContent += '|投稿类型=' + contentType + '\n';
if (contentType === '道具描述' && speaker) {
pageContent += '|道具名=' + speaker + '\n';
} else if (contentType === '对话台词') {
if (speaker) pageContent += '|说话人=' + speaker + '\n';
if (dialogContent) pageContent += '|对话内容=' + dialogContent + '\n';
}
pageContent += '}}\n';
// 为对话台词添加特殊模板
if (contentType === '对话台词' && speaker && dialogContent) {
pageContent += '\n
\n';
}
pageContent += ;
var pageTitle = '梗/' + title;
api.get({
action: 'query',
titles: pageTitle,
format: 'json'
}).done(function(data) {
var pages = data.query.pages;
var exists = false;
for (var id in pages) {
if (id !== '-1') {
exists = true;
break;
}
}
if (exists) {
showCustomAlert('这个梗标题已经存在啦!请换个标题~');
return;
}
api.create(pageTitle, { summary: '通过梗投稿工具创建' }, pageContent)
.done(function() {
showCustomAlert('投稿成功!正在刷新列表...');
allGengData = {};
cachedImageUrls = {};
loadAllGengData().then(function() {
renderWaterfall();
});
})
.fail(function(error) {
showCustomAlert('投稿失败,请稍后重试');
});
});
}
function showCustomAlert(message) {
$('.rocom_alert').remove();
var $modal = $('
').html( '
'
);
$('body').append($modal);
setTimeout(function() {
$modal.fadeOut(300, function() { $(this).remove(); });
}, 2000);
}
function escapeHtml(str) {
if (!str) return ;
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
// ========== 初始化页面结构 ==========
function initPage() {
$('.rocom_lineup_upload_box').remove();
$('.rocom_geng_waterfall').remove();
$('.rocom_screen_box').remove();
// 主容器
var containerHtml = '
// 主内容区域'
// 投稿按钮和筛选区域'
'<input type="text" id="gengSearch" placeholder="搜索关键词..." style="padding:8px 12px; border:1px solid #e2d5c0; border-radius:20px; font-size:14px; width:200px;">' +
'<select id="gengSortType" style="padding:8px 12px; border:1px solid #e2d5c0; border-radius:20px; font-size:14px;">' +
'<option value="activity">活动类型</option>' +
'<option value="time" selected>上传时间</option>' +
'</select>' +
'<select id="gengSortOrder" style="padding:8px 12px; border:1px solid #e2d5c0; border-radius:20px; font-size:14px;">' +
'<option value="desc" selected>降序</option>' +
'<option value="asc">升序</option>' +
'</select>' +
'<label style="display:flex; align-items:center; gap:4px; font-size:14px; color:#5a3e2b;">' +
'<input type="checkbox" id="gengMyOnly"> 仅查看自己投稿' +
'</label>' +
''<button class="rocom_lineup_upload_btn" style="background:#c49a6c; color:white; border:none; padding:10px 28px; border-radius:40px; font-size:16px; cursor:pointer;">投稿新梗</button>' +'
// 瀑布流容器'' + '
';
// 添加到容器
$('.rocom_lineup_line_pet_deteils').append(containerHtml);
// 投稿按钮点击事件
$('.rocom_lineup_upload_btn').off('click').on('click', function() {
if (window.showGengUploadForm) {
window.showGengUploadForm();
} else {
showCustomAlert('投稿功能未加载');
}
});
// 筛选和排序事件监听器
$('#gengSearch').off('input').on('input', function() {
renderWaterfall();
});
$('#gengSortType').off('change').on('change', function() {
var sortType = $(this).val();
var $orderSelect = $('#gengSortOrder');
if (sortType === 'time') {
$orderSelect.html('<option value="desc">降序</option><option value="asc">升序</option>');
} else if (sortType === 'activity') {
$orderSelect.html('<option value="">请选择排序方式</option>');
}
renderWaterfall();
});
$('#gengSortOrder').off('change').on('change', function() {
renderWaterfall();
});
$('#gengMyOnly').off('change').on('change', function() {
renderWaterfall();
});
// 加载数据
loadAllGengData().then(function() {
renderWaterfall();
});
}
function waitForContainer() {
var checkInterval = setInterval(function() {
if ($('.rocom_lineup_line_pet_deteils').length > 0) {
clearInterval(checkInterval);
$('.rocom_lineup_line_pet_deteils').empty();
initPage();
}
}, 100);
}
// 暴露刷新函数供弹窗调用
window.refreshGengList = function() {
loadAllGengData().then(function() {
renderWaterfall();
});
};
waitForContainer();
}); }]); </script>
<style> /* ========== 瀑布流样式 ========== */ .rocom_geng_waterfall {
column-count: 4; column-gap: 16px; padding: 16px;
} @media (max-width: 1200px) { .rocom_geng_waterfall { column-count: 3; } } @media (max-width: 800px) { .rocom_geng_waterfall { column-count: 2; } } @media (max-width: 500px) { .rocom_geng_waterfall { column-count: 1; } }
.rocom_geng_card {
break-inside: avoid; background: #fffef7; border-radius: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 16px; overflow: hidden; cursor: pointer; transition: 0.2s; position: relative; padding: 16px;
} .rocom_geng_card:hover {
transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,0.12);
}
.rocom_geng_card_title {
font-size: 16px; font-weight: bold; color: #5a3e2b; margin-bottom: 4px; text-align: center;
} .rocom_geng_card_chuchu {
font-size: 12px; color: #888; margin-bottom: 8px; text-align: center;
}
.rocom_geng_card_no_img .rocom_geng_card_title {
text-align: center;
}
.rocom_geng_card_img_wrap {
text-align: center; margin-bottom: 10px;
} .rocom_geng_card_img_wrap img {
display: inline-block; width: 80px; height: 80px; object-fit: cover; border-radius: 8px;
}
.rocom_geng_card_footer {
display: flex; justify-content: space-between; align-items: center; font-size: 11px; margin-top: 8px; padding-top: 8px; border-top: 1px solid #f0ebe0;
} .rocom_geng_card_contributor {
color: #999; flex-shrink: 0;
} .rocom_geng_card_time {
color: #bbb; flex-shrink: 0; text-align: right;
}
.rocom_geng_card_delete {
position: absolute; top: 6px; right: 10px; background: rgba(0,0,0,0.5); color: white; padding: 2px 6px; border-radius: 20px; font-size: 12px; cursor: pointer; opacity: 0; transition: 0.2s;
} .rocom_geng_card:hover .rocom_geng_card_delete {
opacity: 1;
}
/* ========== 弹窗样式 ========== */ .rocom_modal_container {
position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 20000; display: flex; align-items: center; justify-content: center;
} .rocom_modal_overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.6);
} .rocom_modal_content {
position: relative; background: white; border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.3); max-height: 85vh; overflow-y: auto; width: 90%; max-width: 680px;
} .rocom_modal_body {
padding: 24px;
}
.rocom_modal_layout {
display: flex; gap: 24px; align-items: flex-start;
}
.rocom_modal_layout.rocom_modal_no_image .rocom_modal_right {
max-width: 100%;
}
.rocom_modal_left {
flex-shrink: 0; width: 180px; display: flex; align-items: center; justify-content: center; margin-top: 45px;
} .rocom_modal_image_wrap {
width: 70%; display: flex; align-items: center; justify-content: center;
} .rocom_modal_image_wrap img {
width: 100%; height: auto; display: block; border-radius: 10px; object-fit: contain;
}
.rocom_modal_right {
flex: 1; min-width: 0;
} .rocom_modal_title {
font-size: 22px; font-weight: bold; color: #5a3e2b; margin-bottom: 6px; text-align: center;
} .rocom_modal_source {
font-size: 14px; color: #c49a6c; margin-bottom: 4px; text-align: center;
}
.rocom_modal_divider {
height: 1px; background: #eee; margin: 16px 0;
} .rocom_modal_label {
font-size: 14px; font-weight: bold; color: #5a3e2b; margin: 12px 0 6px;
} .rocom_modal_text {
font-size: 14px; line-height: 1.6; color: #444; word-break: break-all;
}
.rocom_modal_detail_wrap {
max-height: calc(1.6em * 3); overflow-y: auto; overflow-x: hidden; margin-bottom: 16px;
} .rocom_modal_detail_text {
margin: 0; word-wrap: break-word; white-space: normal;
}
/* 底部区域 - 水平布局 */ .rocom_modal_bottom {
display: flex; justify-content: space-between; align-items: center; margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee; gap: 16px; margin-bottom: -10px;
} .rocom_modal_user_info {
display: flex; justify-content: space-between; align-items: center; flex: 1;
} .rocom_modal_user_name {
font-size: 13px; color: #666; flex-shrink: 0;
} .rocom_modal_user_time {
font-size: 13px; color: #666; flex-shrink: 0; text-align: right;
}
.rocom_modal_edit_btn {
padding: 6px 18px; background: #c49a6c; color: white; border: none; border-radius: 20px; cursor: pointer; font-size: 13px; transition: 0.2s; white-space: nowrap; flex-shrink: 0;
} .rocom_modal_edit_btn:hover {
background: #a87d4f;
}
/* 手机端响应式 */ @media (max-width: 500px) {
.rocom_modal_layout {
flex-direction: column;
}
.rocom_modal_left {
width: 120px;
margin: 0 auto 16px auto;
}
.rocom_modal_bottom {
margin-top: 16px;
padding-top: 10px;
}
.rocom_modal_detail_wrap {
max-height: calc(1.6em * 4);
}
.rocom_modal_user_info {
gap: 12px;
flex-wrap: wrap;
}
}
/* 上传人头像手机端适配 */ @media (max-width: 500px) {
.rocom_modal_user_avatar {
width: 32px !important;
height: 32px !important;
}
} </style>

沪公网安备 31011002002714 号