// ==UserScript==
// @name 班固米-条目职位自定义排序与折叠
// @namespace https://github.com/weiduhuo/scripts
// @version 1.2.2-1.1
// @description 对[动画]条目的制作人员信息进行职位的自定义排序与折叠,可在[设置-隐私]页面进行相关设置
// @author weiduhuo
// @match *://bgm.tv/subject/*
// @match *://bgm.tv/settings/privacy*
// @match *://bangumi.tv/subject/*
// @match *://bangumi.tv/settings/privacy*
// @match *://chii.in/subject/*
// @match *://chii.in/settings/privacy*
// @grant none
// @license MIT
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
const SCRIPT_NAME = '班固米-职位排序组件';
// 图标
const ICON = {
// 三角形顶点向右,可表展开按键
TRIANGLE_RIGHT: `
`,
// 三角形顶点向下,可表折叠按键
TRIANGLE_DOWN: `
`,
// 三角形顶点向上,可表折叠按键
TRIANGLE_UP: `
`,
};
// 条目类型
const SubjectType = {
// 所支持的类型
ANIME: 'anime',
// 待支持的类型
// BOOK: 'book', MUSIC: 'music', GAME: 'game', REAL: 'real', CHARACTER: 'character', PERSON: 'person',
getAll() {
return Object.values(this);
},
prase(value) {
if (this.getAll().includes(value)) return value;
return null;
},
// needPrase(value) {
// return value !== this.CHARACTER && value !== this.PERSON;
// },
};
/**
* 职位的排序列表`jobOrder`与默认折叠的职位`foldableJobs`的合并信息
* 基本类型:`
type = [Job | [boolean | Job, ...Job[]]]
Job = string | RegExp
* `其中`boolean`表示子序列内的职位是否默认折叠,缺损值为`False`,需位于子序列的首位才有意义
* (下文`,,`表示在`JSON`数组中插入`null`元素,用于输出格式化文本时标记换行)
*/
const staffMapList = [,
"中文名", "类型", "适合年龄", /地区/, "语言", "对白", "话数", "总话数", [true, "季数"], ,
"放送开始", "开始", "放送星期", "放送时间", "上映年度", /上映/, "发售日", "片长", /片长/, ,
,
"原作", "原案", "人物原案", "原作插图", [true, "原作协力"], ,
"团长", "总导演", "导演", "副导演", "执行导演", "主任导演", "联合导演", "系列监督", ,
"系列构成", "脚本", "编剧", [true, /脚本|内容|故事|文艺|主笔/], ,
"分镜", "OP・ED 分镜", "主演出", "演出", [true, "演出助理"], ,
"人物设定", ,
,
"总作画监督", [false, "作画监督"], [true, "作画监督助理"], "动作作画监督", "机械作画监督", "特效作画监督", /.*作画.*(监|导)/, ,
"主动画师", "主要动画师", [true, "构图"], [false, "原画"], [true, "第二原画", "补间动画"], "数码绘图", /(原画|动画|動画)(?!制|检查)/, ,
"动画检查", [true, /动画检查/], ,
,
"设定", "背景设定", "道具设计", /(? {
const matchingRoles = [];
// 1.正则匹配
if (item instanceof RegExp) {
matchingRoles.push(...Object.keys(staffDict).filter(key => item.test(key)));
if (matchingRoles.length) {
console.log(`${SCRIPT_NAME}:使用正则表达式 "${item}" 成功匹配 \{${matchingRoles}\}`);
} else return;
} else if (typeof item === 'string') {
// 2.键值匹配
if (item && item in staffDict) {
matchingRoles.push(item);
// 3.特殊关键字处理
} else if (item.startsWith('==')) {
// 激活待插入位置
insterTag = true;
insertFold = foldableJobs.includes(item);
} else return
// 4.其余情形均忽略(且对于意外类型不报错)
} else return;
// 添加职位,并判断是否默认折叠
matchingRoles.forEach(role => {
const li = staffDict[role];
if (typeof item === 'string' && foldableJobs.includes(role)
|| item instanceof RegExp && foldableJobs.includes(item)) {
li.classList.add('folded', 'foldable');
if (!hasFolded) hasFolded = true;
}
ul.appendChild(li);
delete staffDict[role]; // 从字典中删除已处理的职位
// 保存待插入位置
if (insterTag) {
liAfterIntsert = li;
insterTag = false;
}
});
});
// 将剩余未被匹配的职位按原顺序添加到待插入位置
const unmatchedJobs = Object.keys(staffDict);
if (unmatchedJobs.length === 0) {
return;
}
unmatchedJobs.forEach(role => {
const li = document.createElement('li');
li.innerHTML = staffDict[role];
if (insertFold) li.classList.add('folded', 'foldable');
if (liAfterIntsert) ul.insertBefore(li, liAfterIntsert);
// 未设置待插入位置,则默认插入到末尾,且默认不折叠
else ul.appendChild(li);
});
console.log(`${SCRIPT_NAME}:未能匹配到的职位 ${JSON.stringify(staffDict, null, 2)}`);
if (liAfterIntsert) console.log(`${SCRIPT_NAME}:激活将未能匹配职位插入指定位置`);
}
/**
* 获取一个字典来存储网页中的职位信息,
* 并对职位信息进行二次折叠
*/
function getStaffDict(ul) {
const staffDict = {};
const lis = ul.querySelectorAll(':scope > li');
lis.forEach(li => {
const tip = li.querySelector('span.tip');
if (tip) {
const role = tip.innerText.trim().slice(0, -1); // 去掉最后的冒号
// 为了正确计算元素高度,需使其 display
li.classList.remove('folded');
refoldStaff(li, tip);
staffDict[role] = li;
// li.folded 属性已经失效无需还原
}
});
return staffDict;
}
/**
* 为网页原有的`folded`类别添加`foldable`便签,用于实现切换,
* 并对职位信息进行二次折叠
*/
function addFoldableTag(ul) {
const lis = ul.querySelectorAll(':scope > li');
lis.forEach(li => {
let flag = li.classList.contains('folded');
if (flag) {
if (!hasFolded) hasFolded = true;
// 为了正确计算元素高度,需先使其 display
li.classList.remove('folded');
}
const tip = li.querySelector('span.tip');
if (tip) refoldStaff(li, tip);
if (flag) li.classList.add('folded', 'foldable');
});
}
/**
* 对超出限制行数的职位信息进行二次折叠,并添加开关。
* 实现类似于`summary`但是动态摘要的功能。
* 过滤`别名`等不定行高的`infobox`信息
*/
function refoldStaff(li, tip) {
if (li.classList.contains('sub_container')) return; // 不定行高的 infobox 信息
const lineCnt = getLineCnt(li);
if (lineCnt <= maxRefoldLines) return;
// 添加二次折叠效果 (样式将在随后通过 loadStaffStyle 动态载入)
nestElementWithChildren(li, 'div', {class: 'refoldable refolded'});
// 尝试不修改 DOM 结构仅通过样式添加折叠效果,但未果,故改为内嵌一层新元素
// 添加开关状态图标
const icon = createElement('i');
icon.innerHTML = ICON.TRIANGLE_RIGHT;
/* 尝试使用