Bangumi-Restructure Person Works Filter Layout

Restructure person works filter into a grid layout, and sort staff roles

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Bangumi-Restructure Person Works Filter Layout
// @name:zh-CN   班固米-重置人物作品过滤器布局
// @version      0.1.1
// @description  Restructure person works filter into a grid layout, and sort staff roles
// @author       weiduhuo
// @namespace    https://github.com/weiduhuo/scripts
// @match        *://bgm.tv/person/*
// @match        *://bangumi.tv/person/*
// @match        *://chii.in/person/*
// @grant        none
// @license      MIT
// @description:zh-CN  将人物作品过滤器重新排列为网格布局,并基于接口数据对职位进行排序
// ==/UserScript==

(function () {
  'use strict';

  const Group2StaffPrefId = {
    'book': 2,
    'anime': 0,
    'music': 3,
    'game': 1,
    'real': 4,
  };

  const urlPattern = /^\/person\/\d+\/works(?!\/voice)/; // 排除配音角色页面
  if (!urlPattern.test(window.location.pathname)) return;
  const subjectFilter = document.querySelector('.subjectFilter');
  if (!subjectFilter) return;

  subjectFilter.classList.add('filterRelayout');
  const totalGroups = subjectFilter.firstElementChild;
  const totalStaffs = subjectFilter.lastElementChild;
  totalGroups.style.display = 'none';
  totalStaffs.style.display = 'none';

  const style = document.createElement('style');
  style.innerHTML = `
    .subjectFilter.filterRelayout {
      display: grid;
      grid-template-columns: auto 1fr;
    }
    .subjectFilter.filterRelayout > div.item,
    .subjectFilter.filterRelayout > ul.item > li {
      padding: 3px 0 3px 10px;
    }
    .subjectFilter.filterRelayout > .item {
      border-top: 1px solid #FFF;
      border-bottom: 1px solid #EEE;
    }
    html[data-theme='dark'] .subjectFilter.filterRelayout > .item {
      border-top: 1px solid transparent;
      border-bottom: 1px solid #444;
    }
    .subjectFilter.filterRelayout > .item:nth-child(odd) {
      border-right: 1px solid #EEE;
      /* padding-right: 10px; */
    }
    html[data-theme='dark'] .subjectFilter.filterRelayout > .item:nth-child(odd) {
      border-right: 1px solid #444;
    }
    .subjectFilter.filterRelayout > .item:nth-child(even) {
      border-left: 1px solid #FFF;
    }
    html[data-theme='dark'] .subjectFilter.filterRelayout > .item:nth-child(even) {
      border-left: 1px solid transparent;
    }
    .subjectFilter.filterRelayout > ul.item > li {
      float: left;
    }
    .subjectFilter.filterRelayout .title span {
      display: block;
      color: #aaa;
      padding: 2px 5px;
    }
    .subjectFilter.filterRelayout .item a {
      display: block;
      padding: 2px 5px;
    }
    .subjectFilter.filterRelayout .item a:hover,
    .subjectFilter.filterRelayout .item a.focus {
      background: #f09199;
      color: #fff;
      text-decoration: none;
      -webkit-border-radius: 5px;
      -moz-border-radius: 5px;
      border-radius: 5px;
      -moz-background-clip: padding;
      -webkit-background-clip: padding-box;
      background-clip: padding-box;
    }
  `;
  document.head.appendChild(style);

  const groupStaffs = [];
  const fragment = document.createDocumentFragment();
  const staffs = totalStaffs.querySelectorAll('li');
  const staffHeader = document.createElement('ul');
  staffHeader.className = 'item';
  staffHeader.append(staffs[0], staffs[1]);

  for (const [i, groupLi] of totalGroups.querySelectorAll('li').entries()) {
    if (i === 1) continue;
    const a = groupLi.firstElementChild;
    const div = document.createElement('div');
    div.className = 'item';
    div.appendChild(a);
    if (i === 0) {
      div.classList.add('title');
      fragment.append(div, staffHeader); // 横向表头
      continue;
    }
    const ul = document.createElement('ul');
    ul.className = 'item';
    fragment.append(div, ul);
    const groupName = getLastPathSegment(a);
    groupStaffs[Group2StaffPrefId[groupName]] = ul;
  }

  for (const [i, staffLi] of staffs.entries()) {
    if (i <= 1) continue;
    const preId = getStaffPrefId(staffLi);
    groupStaffs[preId].appendChild(staffLi);
  }

  subjectFilter.append(fragment);
  totalGroups.remove();
  totalStaffs.remove();

  // 职位排序
  for (const [groupName, id] of Object.entries(Group2StaffPrefId)) {
    if (!groupStaffs[id]) continue;
    let orderMap = localStorage.getItem(`BangumiStaffSorting_${groupName}JobOrderMap`);
    if (!orderMap) continue;
    orderMap = JSON.parse(orderMap).exact;
    const JobDict = {};
    for (const jobLi of groupStaffs[id].children) {
      JobDict[getJobName(jobLi)] = jobLi;
    }
    let sortedJobs = Object.keys(JobDict);
    sortedJobs.sort((a, b) => orderMap[a] - orderMap[b]);
    for (const job of sortedJobs) {
      groupStaffs[id].appendChild(JobDict[job]);
    }
  }

  function getJobName(li) {
    return li.innerText.trim().split(' (')[0];
  }

  function getStaffPrefId(li) {
    const a = li.querySelector('a');
    const id = getLastPathSegment(a);
    if (id.length < 4) return 0;
    return id[0];
  }

  function getLastPathSegment(a) {
    return a.pathname.split('/').pop();
  }

})();