', {
text: '没有匹配选项',
class: 'bgm-dropdown-item',
css: { color: '#999', cursor: 'default' }
}).appendTo($dropdownContent);
}
};
// 将选项值插入到输入框
function insertOptionValue(inputElem, value) {
const text = inputElem.value;
const pos = inputElem.selectionStart;
const mode = inputElem.dataset.dropdownMode || 'append'; // 获取插入模式
const conn = inputElem.dataset.dropdownConnector || '、';
// 根据不同的插入模式处理
if (mode === 'replace') {
// 直接替换
inputElem.value = value;
} else if (mode === 'append') {
// 自动补全逻辑改进
if (text) {
const currentWord = text.slice(0, pos).toLowerCase();
if (value.toLowerCase().startsWith(currentWord)) {
// 如果当前输入是目标值的开头,直接替换整个值
inputElem.value = value + text.slice(pos);
inputElem.setSelectionRange(value.length, value.length);
inputElem.focus();
return;
}
// 处理分隔符和空格
const lastChar = text.slice(-1);
if (['、',',',',','+',' '].includes(lastChar)) {
// 如果最后一个字符是分隔符,直接添加新值
inputElem.value = text.trimRight() + value;
} else {
// 否则添加分隔符和新值
inputElem.value = text + conn + value;
}
} else {
// 如果输入框为空,直接设置值
inputElem.value = value;
}
}
inputElem.focus();
}
// 处理所有字段
const processAllFields = () => {
const $ = window.jQuery || window.$;
// 处理ID字段和对应的属性字段
$('input.inputtext.id').each(function() {
const $idInput = $(this);
const idValue = $idInput.val().trim();
const $propInput = $idInput.next('.inputtext.prop');
// 为空ID字段添加下拉菜单(如果配置了)
if (idValue === '' && config[''] && !$idInput.data('dropdownAdded')) {
createDropdown(this, config[''].options, config[''].mode);
}
// 为其他配置的字段添加下拉菜单
if ($propInput.length) {
Object.entries(config).forEach(([key, options]) => {
const keys = key.split(',').map(k => k.trim());
if (keys.includes(idValue) && !$propInput.data('dropdownAdded')) {
createDropdown($propInput[0], options.options, options.mode, options.connector);
}
});
}
});
};
// 处理动态添加的字段
const processDynamicField = (element) => {
const $ = window.jQuery || window.$;
const $element = $(element);
// 如果元素本身是输入框
if ($element.is('input.inputtext.id')) {
const $idInput = $element;
const idValue = $idInput.val().trim();
const $propInput = $idInput.next('.inputtext.prop');
// 空ID字段处理
if (idValue === '' && config['']) {
createDropdown($idInput[0], config[''].options, config[''].mode);
}
// 属性字段处理
if ($propInput.length) {
Object.entries(config).forEach(([key, options]) => {
const keys = key.split(',').map(k => k.trim());
if (keys.includes(idValue)) {
createDropdown($propInput[0], options.options, options.mode, options.connector);
}
});
}
}
// 如果元素是容器
else {
// 处理常规ID字段
$element.find('input.inputtext.id').each(function() {
const $idInput = $(this);
const idValue = $idInput.val().trim();
const $propInput = $idInput.next('.inputtext.prop');
// 空ID字段处理
if (idValue === '' && config['']) {
createDropdown($idInput[0], config[''].options, config[''].mode);
}
// 属性字段处理
if ($propInput.length) {
Object.entries(config).forEach(([key, options]) => {
const keys = key.split(',').map(k => k.trim());
if (keys.includes(idValue)) {
createDropdown($propInput[0], options.options, options.mode, options.connector);
}
});
}
});
}
};
// 初始化函数
const init = () => {
const $ = window.jQuery || window.$;
// 添加设置按钮
addSettingsButton();
// 初始处理所有字段
$(document).ready(function() {
processAllFields();
// 监听动态添加的字段
$(document).on('DOMNodeInserted', function(e) {
const $target = $(e.target);
if ($target.find('input.inputtext.id').length || $target.is('input.inputtext.id')) {
setTimeout(() => processDynamicField(e.target), 100);
}
});
// 点击页面其他位置时隐藏所有下拉框
$(document).on('click', function(e) {
if (!$(e.target).closest('.bgm-input-wrapper').length && !$(e.target).closest('.bgm-config-modal').length) {
$('.bgm-dropdown-content').removeClass('bgm-dropdown-active');
}
});
});
// 监听添加新行的按钮点击事件
$(document).on('click', '.newbtn', function() {
setTimeout(processAllFields, 100);
});
// 处理现有字段ID值变化的情况
$(document).on('change', 'input.inputtext.id', function() {
const $idInput = $(this);
const idValue = $idInput.val().trim();
const $propInput = $idInput.next('.inputtext.prop');
// 若属性字段已有下拉菜单,需要移除
if ($propInput.data('dropdownAdded')) {
if ($propInput.next('.bgm-dropdown-content').length) {
$propInput.next('.bgm-dropdown-content').remove();
}
$propInput.data('dropdownAdded', false);
// 如果输入框被包装了,尝试解包
if ($propInput.parent().hasClass('bgm-input-wrapper')) {
$propInput.unwrap();
}
}
// 添加新的下拉菜单
if ($propInput.length) {
Object.entries(config).forEach(([key, options]) => {
const keys = key.split(',').map(k => k.trim());
if (keys.includes(idValue)) {
createDropdown($propInput[0], options.options, options.mode, options.connector);
}
});
}
});
};
// 执行初始化
init();
}
/* =============
单行本快捷创建
============== */
function initBgmCreateSubject() {
// 调试日志函数
function log(message) {
console.log(`[BGM助手] ${message}`);
}
// 通知系统
const notification = {
// 添加通知区域
addArea() {
if (document.querySelector('#bgm-notification-area')) return;
const notificationArea = document.createElement('div');
notificationArea.id = 'bgm-notification-area';
notificationArea.style.position = 'fixed';
notificationArea.style.top = '10px';
notificationArea.style.right = '10px';
notificationArea.style.padding = '8px 15px';
notificationArea.style.backgroundColor = '#369CF8';
notificationArea.style.color = 'white';
notificationArea.style.borderRadius = '5px';
notificationArea.style.zIndex = '9999';
notificationArea.style.display = 'none';
notificationArea.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
document.body.appendChild(notificationArea);
},
// 显示通知
show(message, isError = false) {
this.addArea();
const notificationArea = document.querySelector('#bgm-notification-area');
notificationArea.textContent = message;
notificationArea.style.backgroundColor = isError ? '#FF5151' : '#369CF8';
notificationArea.style.display = 'block';
// 3秒后隐藏通知
setTimeout(() => {
notificationArea.style.display = 'none';
}, 3000);
// 如果支持GM通知,也显示一个
if (typeof GM_notification !== 'undefined') {
GM_notification({
title: 'Bangumi 条目创建助手',
text: message,
timeout: 3000
});
}
// 同时在控制台输出
log(message);
}
};
// 数据存储系统
const storage = {
// 保存数据
save(key, value) {
if (typeof GM_setValue !== 'undefined') {
GM_setValue(key, value);
log(`使用GM_setValue保存数据: ${key}`);
return true;
} else if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem(key, value);
log(`使用localStorage保存数据: ${key}`);
return true;
} catch (e) {
log(`localStorage保存失败: ${e.message}`);
return false;
}
}
return false;
},
// 获取数据
get(key) {
if (typeof GM_getValue !== 'undefined') {
const value = GM_getValue(key);
log(`使用GM_getValue获取数据: ${key}=${value ? '有数据' : '无数据'}`);
return value;
} else if (typeof localStorage !== 'undefined') {
try {
const value = localStorage.getItem(key);
log(`使用localStorage获取数据: ${key}=${value ? '有数据' : '无数据'}`);
return value;
} catch (e) {
log(`localStorage获取失败: ${e.message}`);
return null;
}
}
return null;
},
// 删除数据
remove(key) {
if (typeof GM_deleteValue !== 'undefined') {
GM_deleteValue(key);
log(`使用GM_deleteValue删除数据: ${key}`);
} else if (typeof localStorage !== 'undefined') {
try {
localStorage.removeItem(key);
log(`使用localStorage删除数据: ${key}`);
} catch (e) {
log(`localStorage删除失败: ${e.message}`);
}
}
},
// 根据前缀查找最新的存储项
findLatestByPrefix(prefix) {
if (typeof localStorage === 'undefined') return null;
const foundKeys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(prefix)) {
foundKeys.push(key);
}
}
if (foundKeys.length === 0) return null;
foundKeys.sort();
return foundKeys[foundKeys.length - 1].split('_').slice(0, -1).join('_');
}
};
// 网络请求系统
const network = {
// 发起请求,自动选择合适的方法
request(url, onSuccess, onError) {
log(`发起请求: ${url}`);
// 尝试使用fetch
if (window.fetch) {
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP状态码: ${response.status}`);
}
return response.text();
})
.then(html => onSuccess(html))
.catch(e => {
log(`Fetch错误: ${e.message}`);
// fetch失败,尝试使用GM_xmlhttpRequest
if (typeof GM_xmlhttpRequest !== 'undefined') {
this.requestWithGM(url, onSuccess, onError);
} else if (onError) {
onError(`请求失败: ${e.message}`);
}
});
}
// 否则尝试使用GM_xmlhttpRequest
else if (typeof GM_xmlhttpRequest !== 'undefined') {
this.requestWithGM(url, onSuccess, onError);
}
// 都不支持
else if (onError) {
onError("浏览器不支持所需的请求方法");
}
},
// 使用GM_xmlhttpRequest请求
requestWithGM(url, onSuccess, onError) {
log(`使用GM_xmlhttpRequest请求: ${url}`);
GM_xmlhttpRequest({
method: "GET",
url: url.startsWith('//') ? window.location.protocol + url : url,
timeout: 10000, // 10秒超时
onload: function(response) {
if (response.status !== 200) {
if (onError) onError(`请求失败,状态码: ${response.status}`);
return;
}
onSuccess(response.responseText);
},
onerror: function(error) {
if (onError) onError("网络请求失败,请检查网络连接");
log("请求失败", error);
},
ontimeout: function() {
if (onError) onError("请求超时,请稍后重试");
}
});
}
};
// 条目处理系统
const entryHandler = {
// 识别Infobox类型
identifyType(content) {
if (!content) return "Unknown";
if (content.includes("Infobox animanga/Novel")) {
return "Novel";
} else if (content.includes("Infobox animanga/Manga")) {
return "Manga";
}
return "Unknown";
},
// 存储条目数据
storeEntryData(content, type, title, sourceId) {
const keyPrefix = `bgm_infobox_${sourceId}`;
const savedContent = storage.save(`${keyPrefix}_content`, content);
const savedType = storage.save(`${keyPrefix}_type`, type);
storage.save(`${keyPrefix}_title`, title);
return savedContent && savedType;
},
// 解析编辑页内容
parseEditPage(html, title, sourceId) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
// 检查是否需要登录
const loginForm = doc.querySelector('form[action="/FollowTheRabbit"]');
if (loginForm) {
notification.show("获取失败,请先登录Bangumi", true);
return false;
}
const textarea = doc.querySelector('#subject_summary');
if (!textarea) {
notification.show("未找到条目内容,可能没有编辑权限", true);
return false;
}
const content = textarea.value;
if (!content) {
notification.show("条目内容为空", true);
return false;
}
// 识别条目类型
const type = this.identifyType(content);
if (type === "Unknown") {
notification.show("未识别 Infobox 类型(支持 Novel 或 Manga)", true);
return false;
}
// 存储数据
if (!this.storeEntryData(content, type, title, sourceId)) {
notification.show("保存数据失败,请检查浏览器权限", true);
return false;
}
return true;
} catch (e) {
notification.show(`解析页面失败: ${e.message}`, true);
console.error(e);
return false;
}
},
// 将原标题处理为新建条目标题
processTitle(title) {
if (!title) return "";
const match = title.match(/\((\d+)\)$/);
if (match) {
const num = parseInt(match[1], 10) + 1;
return title.replace(/\(\d+\)$/, `(${num})`);
} else {
return `${title} (1)`;
}
}
};
// 页面功能系统
const pageFeatures = {
// 在条目页添加"新条目"按钮
addCreateButton() {
const currentPath = location.pathname;
const matchSubject = currentPath.match(/^\/subject\/(\d+)$/);
if (!matchSubject) {
log("当前页面不是条目页面,不添加按钮");
return;
}
// 检查类型是否为"小说"或"漫画"
const genreSmall = document.querySelector('small.grey');
if (!genreSmall || !(genreSmall.textContent.trim() === '小说' || genreSmall.textContent.trim() === '漫画')) {
log(`条目类型不是小说或漫画: ${genreSmall ? genreSmall.textContent.trim() : '未找到'}`);
return;
}
// 查找导航栏并避免重复添加
const nav = document.querySelector(".subjectNav .navTabs, .navTabs");
if (!nav || nav.querySelector(".create-button")) {
log(nav ? "按钮已存在,不重复添加" : "未找到导航栏");
return;
}
// 准备通知区域
notification.addArea();
// 创建并添加按钮
const createLi = document.createElement("li");
createLi.className = "create-button";
const subjectId = matchSubject[1];
createLi.innerHTML = `
新条目`;
// 按钮点击事件
createLi.querySelector("a").addEventListener("click", () => {
this.handleCreateButtonClick(subjectId);
});
nav.appendChild(createLi);
log("成功添加新条目按钮");
},
// 处理创建按钮点击
handleCreateButtonClick(subjectId) {
notification.show("正在获取条目内容...");
const currentHost = window.location.hostname;
const editUrl = `//${currentHost}/subject/${subjectId}/edit`;
const title = document.querySelector('h1 a[property="v:itemreviewed"]')?.textContent?.trim() ||
document.querySelector('h1.nameSingle a')?.textContent?.trim() || '';
// 唯一标识符
const uniqueId = Date.now().toString();
// 发起请求获取编辑页内容
network.request(
editUrl,
(html) => {
// 成功处理编辑页内容
if (entryHandler.parseEditPage(html, title, uniqueId)) {
notification.show("条目内容获取成功,正在打开创建页面...");
window.open(`//${currentHost}/new_subject/1?source=${uniqueId}`, "_blank");
}
},
(errorMsg) => {
notification.show(errorMsg, true);
}
);
},
// 添加"复制创建"按钮到新建条目页面
addCloneButton() {
if (!location.pathname.includes("/new_subject/")) {
log("当前不是新建条目页面,不添加克隆按钮");
return;
}
// 查找标题并检查
const titleHeader = document.querySelector('h1');
if (!titleHeader || !titleHeader.textContent.includes('添加')) {
log(`标题元素不符合条件: ${titleHeader ? titleHeader.textContent : '未找到'}`);
return;
}
// 避免重复添加
if (document.querySelector('#clone-entry-button')) {
log("克隆按钮已存在,不重复添加");
return;
}
// 准备通知区域
notification.addArea();
// 创建按钮
const cloneButton = document.createElement('button');
cloneButton.id = 'clone-entry-button';
cloneButton.textContent = '新条目';
cloneButton.style.cssText = 'background-color:#F09199; color:white; border:none; padding:5px 10px; border-radius:4px; margin-left:10px; cursor:pointer; font-weight:bold; text-align:center;';
// 添加到页面
titleHeader.appendChild(cloneButton);
log("成功添加克隆按钮");
// 添加点击事件
cloneButton.addEventListener('click', () => {
this.handleCloneButtonClick();
});
},
// 处理克隆按钮点击
handleCloneButtonClick() {
// 尝试切换到Wiki模式
const wikiModeLink = document.querySelector('a[onclick="NormaltoWCODE()"]');
if (wikiModeLink) {
wikiModeLink.click();
log("已自动切换到Wiki模式");
}
// 获取表单内容
const infobox = document.querySelector("#subject_infobox");
const titleInput = document.querySelector('input[name="subject_title"]');
if (!infobox || !titleInput) {
notification.show("无法获取表单内容,请确保已切换到Wiki模式", true);
return;
}
const content = infobox.value;
const title = titleInput.value;
if (!content) {
notification.show("Infobox内容为空", true);
return;
}
// 识别类型
const type = entryHandler.identifyType(content);
if (type === "Unknown") {
notification.show("未识别 Infobox 类型(支持 Novel 或 Manga)", true);
return;
}
// 生成唯一ID
const cloneId = 'clone_' + Date.now();
// 存储数据
if (!entryHandler.storeEntryData(content, type, title, cloneId)) {
notification.show("保存数据失败,请检查浏览器权限", true);
return;
}
notification.show("已复制当前页面条目内容,正在打开新建页面...");
// 打开新页面
const currentHost = window.location.hostname;
window.open(`//${currentHost}/new_subject/1?source=${cloneId}`, "_blank");
},
// 自动填充表单
fillNewSubjectForm() {
// 获取URL参数
const sourceId = new URL(window.location.href).searchParams.get("source");
if (!sourceId) {
log("没有找到source参数,不执行填充");
return;
}
log(`检测到source参数: ${sourceId}`);
notification.addArea();
// 构建键名前缀并获取数据
const keyPrefix = `bgm_infobox_${sourceId}`;
let content = storage.get(`${keyPrefix}_content`);
let type = storage.get(`${keyPrefix}_type`);
let title = storage.get(`${keyPrefix}_title`);
// 如果找不到数据,尝试使用旧版键名
if (!content || !type) {
log(`未找到数据: ${keyPrefix}`);
// 尝试旧版键名
const oldKeyPrefix = `infobox_${sourceId}`;
const oldContent = storage.get(`${oldKeyPrefix}_content`);
const oldType = storage.get(`${oldKeyPrefix}_type`);
const oldTitle = storage.get(`${oldKeyPrefix}_title`);
if (oldContent && oldType) {
log(`找到旧版数据: ${oldKeyPrefix}`);
content = oldContent;
type = oldType;
title = oldTitle;
} else {
// 尝试找到最新的相关键
const latestKeyBase = storage.findLatestByPrefix('bgm_infobox_') || storage.findLatestByPrefix('infobox_');
if (latestKeyBase) {
log(`尝试使用最近的键前缀: ${latestKeyBase}`);
content = storage.get(`${latestKeyBase}_content`);
type = storage.get(`${latestKeyBase}_type`);
title = storage.get(`${latestKeyBase}_title`);
}
}
if (!content || !type) {
notification.show("未找到条目内容,请重试", true);
return;
}
}
// 开始填充表单
this.performFormFill(content, type, title, keyPrefix);
},
// 执行表单填充
performFormFill(content, type, title, keyPrefix) {
// 定义类型映射
const typeMap = {
"Novel": { id: "cat_novel", tpl: "Novel" },
"Manga": { id: "cat_comic", tpl: "Manga" }
};
const category = typeMap[type];
if (!category) {
log(`未知条目类型: ${type}`);
return;
}
notification.show("正在填充条目信息...");
log(`准备填充 ${type} 类型的条目,标题: ${title || '无标题'}`);
// 等待表单加载
this.waitForForm(category, content, title, keyPrefix);
},
// 等待表单加载
waitForForm(category, content, title, keyPrefix) {
// 检查表单是否加载完成
const checkForm = () => {
const infoboxInput = document.querySelector("#subject_infobox");
const titleInput = document.querySelector('input[name="subject_title"]');
const typeRadio = document.getElementById(category.id);
return infoboxInput && titleInput && typeRadio;
};
// 如果表单已加载,立即填充
if (checkForm()) {
this.doFillForm(category, content, title, keyPrefix);
return;
}
// 否则等待表单加载
log("等待表单加载...");
let attempts = 0;
const maxAttempts = 30; // 最多等待6秒
const waitInterval = setInterval(() => {
attempts++;
if (checkForm()) {
clearInterval(waitInterval);
log("表单已加载,开始填充");
this.doFillForm(category, content, title, keyPrefix);
return;
}
if (attempts >= maxAttempts) {
clearInterval(waitInterval);
log("表单加载超时");
notification.show("表单加载超时,请手动填写", true);
}
}, 200);
},
// 实际执行表单填充
doFillForm(category, content, title, keyPrefix) {
try {
// 选择分类
const typeRadio = document.getElementById(category.id);
if (typeRadio) {
typeRadio.click();
log(`已选择类型: ${category.tpl}`);
} else {
log(`未找到类型选择框: ${category.id}`);
}
// 等待分类选择完成后填充表单
setTimeout(() => {
try {
// 填充内容
const infobox = document.querySelector("#subject_infobox");
if (infobox) {
infobox.value = content;
log("已填充Infobox内容");
infobox.dispatchEvent(new Event('change', { bubbles: true }));
}
// 填充标题
const titleInput = document.querySelector('input[name="subject_title"]');
if (titleInput && title) {
const newTitle = entryHandler.processTitle(title);
titleInput.value = newTitle;
log(`已填充标题: ${newTitle}`);
titleInput.dispatchEvent(new Event('change', { bubbles: true }));
}
// 根据需要切换模式
const currentMode = document.querySelector('#header_infobox');
const wikiModeLink = document.querySelector('a[onclick*="NormaltoWCODE"]');
const simpleModeLink = document.querySelector('a[onclick*="WCODEtoNormal"]');
const isInWikiMode = currentMode && currentMode.textContent.includes('WCODE');
if (!isInWikiMode && wikiModeLink) {
wikiModeLink.click();
log("已切换到wiki模式");
} else if (isInWikiMode && simpleModeLink) {
simpleModeLink.click();
log("已切换到入门模式");
}
// 显示成功通知
notification.show("条目信息填充完成");
// 清理临时数据
if (keyPrefix) {
storage.remove(`${keyPrefix}_content`);
storage.remove(`${keyPrefix}_type`);
storage.remove(`${keyPrefix}_title`);
log("已清理临时数据");
}
} catch (e) {
notification.show(`填充表单时出错: ${e.message}`, true);
console.error(e);
}
}, 500);
} catch (e) {
notification.show(`选择分类时出错: ${e.message}`, true);
console.error(e);
}
}
};
// 初始化
function init() {
log("Bangumi条目创建助手初始化中...");
// 若当前页面是条目页面,添加"新条目"按钮
if (location.pathname.match(/^\/subject\/\d+$/)) {
log("检测到条目页面");
pageFeatures.addCreateButton();
}
// 若当前页面是创建条目页,添加功能
if (location.pathname.includes("/new_subject/1")) {
log("检测到新建条目页面");
pageFeatures.addCloneButton();
// 如果有source参数,则填充表单
if (location.search.includes("source=")) {
log("检测到source参数,准备填充表单");
setTimeout(function() {
pageFeatures.fillNewSubjectForm();
}, 500);
}
}
log("初始化完成");
}
// 页面加载完成后执行初始化
window.addEventListener("load", init);
// 也可以在DOM加载完成后就执行,提高响应速度
if (document.readyState === "interactive" || document.readyState === "complete") {
init();
} else {
document.addEventListener("DOMContentLoaded", init);
}
}
/* =======
编辑预览
========= */
function initBgmPreview() {
// 标记预览功能是否已禁用
let previewDisabled = false;
// 保存原始按钮的引用,防止重复创建
let originalButtons = new Map();
// 拦截提交按钮,添加预览逻辑并确保漫画条目类型正确
function interceptSubmitButtons() {
const submitButtons = document.querySelectorAll('input.inputBtn[value="提交"][name="submit"][type="submit"]');
submitButtons.forEach(button => {
if (originalButtons.has(button)) return;
const originalForm = button.form;
const originalSubmitEvent = originalForm ? originalForm.onsubmit : null;
originalButtons.set(button, {
originalOnClick: button.onclick,
originalForm: originalForm,
originalSubmitEvent: originalSubmitEvent,
handled: true
});
button.onclick = function(event) {
if (previewDisabled) return true;
event.preventDefault();
showPreview(button);
return false;
};
if (originalForm) {
originalForm.onsubmit = function(event) {
if (previewDisabled) {
return originalSubmitEvent ? originalSubmitEvent.call(this, event) : true;
}
if (event && event.submittedViaPreview) {
const infobox = document.querySelector('#subject_infobox');
const isManga = infobox && infobox.value.includes('Infobox animanga/Manga');
if (isManga) {
let platformInput = document.querySelector('input[name="platform"][value="1001"]');
if (platformInput && !platformInput.checked) {
platformInput.checked = true;
platformInput.click();
console.log('强制选择了漫画类型(platform=1001)');
}
if (!document.querySelector('input[name="platform"][type="hidden"]')) {
const hiddenPlatform = document.createElement('input');
hiddenPlatform.type = 'hidden';
hiddenPlatform.name = 'platform';
hiddenPlatform.value = '1001';
originalForm.appendChild(hiddenPlatform);
console.log('添加了隐藏的 platform 字段:value=1001');
} else {
const existingHidden = document.querySelector('input[name="platform"][type="hidden"]');
if (existingHidden) {
existingHidden.value = '1001';
console.log('更新了隐藏的 platform 字段:value=1001');
}
}
const comicRadio = document.querySelector('#cat_comic');
if (comicRadio && !comicRadio.checked) {
comicRadio.click();
console.log('重新选择了漫画单选框(cat_comic)');
}
if (typeof WikiTpl === 'function') {
WikiTpl('Manga');
console.log('手动调用了 WikiTpl("Manga")');
}
}
return originalSubmitEvent ? originalSubmitEvent.call(this, event) : true;
}
event.preventDefault();
showPreview(button);
return false;
};
}
});
}
// 保存表单数据
function saveFormData() {
const formData = {};
document.querySelectorAll('input, textarea, select').forEach(el => {
if (el.name) {
formData[el.name] = el.value;
}
});
return formData;
}
// 恢复表单数据
function restoreFormData(formData) {
for (const name in formData) {
const el = document.querySelector(`[name="${name}"]`);
if (el) {
el.value = formData[name];
}
}
}
// 阻止Enter键提交表单
function preventEnterSubmit() {
document.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !previewDisabled &&
!(document.activeElement &&
(document.activeElement.tagName === 'INPUT' ||
document.activeElement.tagName === 'TEXTAREA'))) {
event.preventDefault();
return false;
}
});
}
// 切换到WCODE模式并等待内容加载
function switchToWCODEMode(callback) {
const wikiModeLink = document.querySelector('a.l[onclick="NormaltoWCODE()"]');
if (!wikiModeLink) {
console.log('已在WCODE模式或无法切换');
callback();
return;
}
console.log('切换到WCODE模式');
wikiModeLink.click();
waitForInfobox(callback);
}
// 等待Infobox内容加载
function waitForInfobox(callback, maxAttempts = 30, interval = 200) {
let attempts = 0;
const checkInfobox = () => {
const infobox = document.querySelector('#subject_infobox');
if (infobox && infobox.value) {
console.log('Infobox内容已加载');
callback();
} else if (attempts >= maxAttempts) {
console.error('Infobox加载超时');
callback();
} else {
attempts++;
setTimeout(checkInfobox, interval);
}
};
checkInfobox();
}
// 收集所有表单字段数据并获取原始数据
function collectFormData() {
const formData = {};
// 收集标题
const titleInput = document.querySelector('input[name="subject_title"]');
if (titleInput) {
formData.title = {
current: titleInput.value,
original: titleInput.defaultValue || ""
};
}
// 收集Infobox
const infobox = document.querySelector('#subject_infobox');
if (infobox) {
formData.infobox = {
current: infobox.value,
original: window.originalInfoboxContent || ""
};
}
// 收集简介
const summary = document.querySelector('textarea[name="subject_summary"]');
if (summary) {
formData.summary = {
current: summary.value,
original: summary.defaultValue || ""
};
}
// 收集标签
const tags = document.querySelector('input[name="subject_meta_tags"]');
if (tags) {
formData.tags = {
current: tags.value,
original: tags.defaultValue || ""
};
}
// 收集编辑摘要
const editSummary = document.querySelector('input[name="editSummary"]');
if (editSummary) {
formData.editSummary = {
current: editSummary.value,
original: editSummary.defaultValue || ""
};
}
return formData;
}
// 条目信息匹配
function diffLines(originalLines, newLines) {
const result = [];
let i = 0, j = 0;
while (i < originalLines.length || j < newLines.length) {
const oldLine = i < originalLines.length ? originalLines[i] : null;
const newLine = j < newLines.length ? newLines[j] : null;
const normalizedOldLine = oldLine ? oldLine.trim() : null;
const normalizedNewLine = newLine ? newLine.trim() : null;
const isEqual = normalizedOldLine === normalizedNewLine && normalizedOldLine !== null && normalizedNewLine !== null;
if (oldLine === null && newLine !== null) {
result.push({ old: '', new: newLine });
j++;
} else if (oldLine !== null && newLine === null) {
result.push({ old: oldLine, new: '' });
i++;
} else if (isEqual || calculateSimilarity(normalizedOldLine, normalizedNewLine) > 0.95) {
result.push({ old: oldLine, new: newLine });
i++; j++;
} else {
const similarity = calculateSimilarity(oldLine, newLine);
if (similarity > 0.7) {
result.push({
old: oldLine,
new: newLine,
isDiff: true
});
i++; j++;
} else {
const nextOldMatch = findBestMatchIndex(newLine, originalLines.slice(i + 1));
const nextNewMatch = findBestMatchIndex(oldLine, newLines.slice(j + 1));
const nextOldIndex = nextOldMatch !== -1 ? i + 1 + nextOldMatch : -1;
const nextNewIndex = nextNewMatch !== -1 ? j + 1 + nextNewMatch : -1;
if (nextOldIndex !== -1 && (nextNewIndex === -1 || nextOldIndex - i < nextNewIndex - j)) {
result.push({ old: oldLine, new: '' });
i++;
} else if (nextNewIndex !== -1) {
result.push({ old: '', new: newLine });
j++;
} else {
result.push({
old: oldLine,
new: newLine,
isDiff: true
});
i++; j++;
}
}
}
}
return result.filter(p => p.old !== '' || p.new !== '');
}
// 保留相似度最高且超过设定阈值的行
function findBestMatchIndex(str, array) {
if (!str || !array || array.length === 0) return -1;
let bestIndex = -1;
let bestSimilarity = 0.7;
for (let i = 0; i < array.length; i++) {
const similarity = calculateSimilarity(str, array[i]);
if (similarity > bestSimilarity) {
bestSimilarity = similarity;
bestIndex = i;
}
}
return bestIndex;
}
// 计算相似度函数
function calculateSimilarity(str1, str2) {
if (!str1 && !str2) return 1.0;
if (!str1 || !str2) return 0.0;
const s1 = str1.trim();
const s2 = str2.trim();
if (s1 === s2) return 1.0;
const len1 = s1.length;
const len2 = s2.length;
if (Math.abs(len1 - len2) / Math.max(len1, len2) > 0.3) {
return 0.0;
}
let commonLength = 0;
const minLen = Math.min(len1, len2);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonLength++;
} else {
break;
}
}
let suffixMatch = 0;
for (let i = 1; i <= minLen - commonLength; i++) {
if (s1[len1 - i] === s2[len2 - i]) {
suffixMatch++;
} else {
break;
}
}
commonLength += suffixMatch;
if (/[^\u0000-\u007F]/.test(s1) || /[^\u0000-\u007F]/.test(s2)) {
const baseScore = commonLength / Math.max(len1, len2);
if (baseScore > 0.7) {
return Math.min(1.0, baseScore * 1.2);
}
}
return commonLength / Math.max(len1, len2);
}
// 精确标记两个字符串之间的差异
function highlightCharDifferences(oldStr, newStr) {
if (oldStr === newStr) {
return { old: escapeHtml(oldStr), new: escapeHtml(newStr) };
}
if (!oldStr || !newStr) {
return {
old: oldStr ? `
${escapeHtml(oldStr)}` : ' ',
new: newStr ? `
${escapeHtml(newStr)}` : ' '
};
}
let prefixLength = 0;
const minLength = Math.min(oldStr.length, newStr.length);
while (prefixLength < minLength && oldStr[prefixLength] === newStr[prefixLength]) {
prefixLength++;
}
let suffixLength = 0;
while (
suffixLength < minLength - prefixLength &&
oldStr[oldStr.length - 1 - suffixLength] === newStr[newStr.length - 1 - suffixLength]
) {
suffixLength++;
}
const oldPrefix = escapeHtml(oldStr.substring(0, prefixLength));
const oldSuffix = escapeHtml(oldStr.substring(oldStr.length - suffixLength));
const oldDiff = escapeHtml(oldStr.substring(prefixLength, oldStr.length - suffixLength));
const newPrefix = escapeHtml(newStr.substring(0, prefixLength));
const newSuffix = escapeHtml(newStr.substring(newStr.length - suffixLength));
const newDiff = escapeHtml(newStr.substring(prefixLength, newStr.length - suffixLength));
return {
old: `${oldPrefix}${oldDiff ? `
${oldDiff}` : ''}${oldSuffix}`,
new: `${newPrefix}${newDiff ? `
${newDiff}` : ''}${newSuffix}`
};
}
// 创建差异对比视图的UI
function createDiffView(title, originalText, newText) {
const section = document.createElement('div');
section.className = 'preview-section';
const sectionTitle = document.createElement('div');
sectionTitle.className = 'preview-section-title';
sectionTitle.textContent = title;
section.appendChild(sectionTitle);
const diffContainer = document.createElement('div');
diffContainer.className = 'preview-diff-container';
const header = document.createElement('div');
header.className = 'preview-diff-header';
header.innerHTML = '
修改前(左)/修改后(右)';
diffContainer.appendChild(header);
const content = document.createElement('div');
content.className = 'preview-diff-content';
const ori = originalText.split('\n'), neu = newText.split('\n');
const lines = diffLines(ori, neu);
lines.forEach((pair, idx) => {
const line = document.createElement('div');
line.className = 'preview-diff-line';
const lineNumber = document.createElement('div');
lineNumber.className = 'preview-diff-line-number';
lineNumber.textContent = idx + 1;
line.appendChild(lineNumber);
const oldContent = document.createElement('div');
oldContent.className = 'preview-diff-old';
const newContent = document.createElement('div');
newContent.className = 'preview-diff-new';
if (pair.old === pair.new) {
oldContent.innerHTML = escapeHtml(pair.old);
newContent.innerHTML = escapeHtml(pair.new);
} else if (pair.old === '' || pair.new === '') {
oldContent.innerHTML = pair.old ? `
${escapeHtml(pair.old)}` : ' ';
newContent.innerHTML = pair.new ? `
${escapeHtml(pair.new)}` : ' ';
} else {
const diffs = highlightCharDifferences(pair.old, pair.new);
oldContent.innerHTML = diffs.old;
newContent.innerHTML = diffs.new;
}
line.appendChild(oldContent);
line.appendChild(newContent);
content.appendChild(line);
});
diffContainer.appendChild(content);
section.appendChild(diffContainer);
return section;
}
// HTML 转义函数
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 检查是否已存在预览界面
function isPreviewActive() {
return document.querySelector('.preview-overlay') !== null;
}
// 关闭预览界面
function closePreview() {
const overlay = document.querySelector('.preview-overlay');
if (overlay) {
document.body.removeChild(overlay);
}
}
// 显示预览界面
function showPreview(originalButton) {
if (isPreviewActive()) {
closePreview();
}
// 检查当前是否在Normal模式,如果是,则切换到WCODE模式
switchToWCODEMode(() => {
// 保存当前表单数据以便恢复
const savedFormData = saveFormData();
// 获取表单数据
const formData = collectFormData();
const overlay = document.createElement('div');
overlay.className = 'preview-overlay';
const container = document.createElement('div');
container.className = 'preview-container';
const closeButton = document.createElement('div');
closeButton.className = 'preview-close';
closeButton.textContent = '×';
closeButton.onclick = function() {
restoreFormData(savedFormData);
closePreview();
};
const header = document.createElement('div');
header.className = 'preview-header';
header.textContent = '预览对比';
const content = document.createElement('div');
content.className = 'preview-content';
const fieldNames = {
title: '标题',
infobox: '条目信息',
summary: '简介',
tags: '标签',
editSummary: '编辑摘要'
};
let hasContent = false;
for (const key in formData) {
if (formData[key] && formData[key].current !== formData[key].original) {
const fieldTitle = fieldNames[key] || key;
content.appendChild(createDiffView(fieldTitle, formData[key].original, formData[key].current));
hasContent = true;
}
}
if (!hasContent) {
const noChanges = document.createElement('div');
noChanges.style.textAlign = 'center';
noChanges.style.padding = '20px';
noChanges.textContent = '没有检测到内容变化';
content.appendChild(noChanges);
}
const buttonsContainer = document.createElement('div');
buttonsContainer.className = 'preview-buttons';
const cancelButton = document.createElement('button');
cancelButton.className = 'preview-button preview-button-cancel';
cancelButton.textContent = '取消';
cancelButton.onclick = function() {
restoreFormData(savedFormData);
closePreview();
};
const confirmButton = document.createElement('button');
confirmButton.className = 'preview-button preview-button-confirm';
confirmButton.textContent = '确认';
const form = originalButton.form;
confirmButton.onclick = function() {
closePreview();
previewDisabled = true;
if (form) {
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
submitEvent.submittedViaPreview = true;
form.dispatchEvent(submitEvent);
} else {
originalButton.click();
}
};
buttonsContainer.appendChild(cancelButton);
buttonsContainer.appendChild(confirmButton);
container.appendChild(closeButton);
container.appendChild(header);
container.appendChild(content);
container.appendChild(buttonsContainer);
overlay.appendChild(container);
document.body.appendChild(overlay);
});
}
// 捕获原始Infobox内容
function captureOriginalEditorContent() {
setTimeout(() => {
switchToWCODEMode(() => {
const infobox = document.querySelector('#subject_infobox');
if (infobox) {
window.originalInfoboxContent = infobox.defaultValue || infobox.value;
console.log('捕获原始Infobox内容');
}
});
}, 1000);
}
// 修复按钮消失问题
function fixButtonDisappearing() {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.removedNodes.length > 0) {
Array.from(mutation.removedNodes).forEach(node => {
if (node.nodeType === 1 &&
(node.matches && node.matches('input.inputBtn[value="提交"]') ||
node.querySelector && node.querySelector('input.inputBtn[value="提交"]'))) {
setTimeout(interceptSubmitButtons, 0);
}
});
}
});
});
const config = { childList: true, subtree: true };
observer.observe(document.body, config);
}
// 页面加载完成后初始化
window.addEventListener('load', function() {
captureOriginalEditorContent();
interceptSubmitButtons();
fixButtonDisappearing();
preventEnterSubmit();
});
}
/* ===========
启动所有功能
============== */
function startEnhancer() {
initNavButtons();
observeURLChanges();
initCoverUpload();
initBatchRelation();
initBgmDropdownMenu();
initBgmCreateSubject();
initBgmPreview();
console.log("Bangumi Ultimate Enhancer 已启动");
}
// 在DOM加载完成后启动脚本
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startEnhancer);
} else {
startEnhancer();
}
})();