mu-mo SKE48 Table Arranger

mu-moのSKE48劇場盤申込フォームをメンバー・部ごとに並び替えるスクリプトです。

// ==UserScript==
// @name        mu-mo SKE48 Table Arranger
// @namespace   http://gplus.to/fronoske/
// @version     0.6
// @description mu-moのSKE48劇場盤申込フォームをメンバー・部ごとに並び替えるスクリプトです。
// @icon        https://lh5.googleusercontent.com/-uig_zdvbqg0/VPm1KuvvwLI/AAAAAAAAAYo/Fzy_7UM-oIs/s40-no/icon32.png
// @author      fronoske

// @match       http://shop.mu-mo.net/avx/sv/list1?jsiteid=SKA&categ_id=*
// @require     https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
//
// @run-at      document-idle
// @grant       none
// @noframes
// ==/UserScript==

var arrangeMumo = function(){

  // singleによって異なる。17thは1~7部、18thは3部と4部の間に「昼の部」がある。ユニットシングルは5部まで。
  var PERIODS = ['1', '2', '3', '昼の', '4', '5', '6'];
  if ( $("body").text().indexOf('昼の') == -1)
    PERIODS = PERIODS.filter(function(v){ return v != '昼の'; });
  if ( $("body").text().indexOf('第6部') == -1)
    PERIODS = PERIODS.filter(function(v){ return v != '6'; });

  var name2team = function(name){
    // 必ずしも全員の名前が必要なわけではない。各チームの先頭メンバーが分かればよい。
    var reS = /^(東李苑|犬塚あさな|大矢真那|北川綾巴|後藤理沙子|杉山愛佳|竹内舞|都築里佳|野口由芽|野島樺乃|二村春香|松井珠理奈|松本慈子|宮前杏実|矢方美紀|山内鈴蘭|山田樹奈)$/;
    var reK2 = /^(青木詩織|荒井優希|石田安奈|内山命|江籠裕奈|大場美奈|小畑優奈|北野瑠華|白井琴望|惣田紗莉渚|高木由麻奈|髙塚夏生|高柳明音|竹内彩姫|日高優月|古畑奈和|松村香織)$/;
    var reE = /^(市野成美|井田玲音名|加藤るみ|鎌田菜月|木本花音|熊崎晴香|後藤楽々|斉藤真木子|酒井萌衣|佐藤すみれ|柴田阿弥|菅原茉椰|須田亜香里|髙寺沙菜|谷真理佳|福士奈央)$/;
    var reT = /^(相川暖花|浅井裕華|太田彩夏|片岡成美|川崎成美|末永桜花|髙畑結希|町音葉|村井純奈|和田愛菜|一色嶺奈|上村亜柚香|水野愛理)$/;
    if (reS.test(name)) return 'S';
    else if (reK2.test(name)) return 'K2';
    else if (reE.test(name)) return 'E';
    else if (reT.test(name)) return 'T';
    else return null;
  };

  var btnTexts = ['並び替え!', '圧縮!', 'リセット'];
  
  var wrapTeamName = function(div){
    $(div).contents().filter(function() {return this.nodeType === 3;}).each(function(idx, e){
      if (/^ *(チーム[SKE]|(ドラフト)?研究生)/gm.test( $(e).text())){ $(e).remove(); }
    });
  };

  var index2color = function(idx){
    return "#fff1cb";
    /* var colors = ["#fff1cb", "#fef7e5", "#f8b500"];
    return colors[idx % colors.length]; */
  };
  
  var doFixPlaceBar = function(){
    var classHtml = "<style>.fixed {position: fixed; top: 0; width: 100%; z-index: 10000;}</style>";
    $(classHtml).appendTo($("body"));
    var nav = $("div.tabberlive > ul.tabbernav");
    var offset = nav.offset();
    $(window).scroll(function () {
      if($(window).scrollTop() > offset.top) {
        nav.addClass('fixed');
      } else {
        nav.removeClass('fixed');
      }
    });
  };

  var arrangeBlocks = function(div){
    /*
      Bu ... クラス
      blocks[メンバー名] = [Bu, Bu, Bu, ... , Bu]
    */
    var reNewLine = new RegExp("\n*", "g");
    var Bu = function (td) {
      var arr = td.html() .replace(reNewLine, '') .split(/<.+?>/);
      this.name = $.trim(arr[0]);
      this.period = arr[1].match(/\d|昼の/) [0];
      this.jq = td;
      this.jq.html(this.jq.html() .replace('枚', '').replace(/第\d部|昼の部/, ''));
    };
    var blocks = [];
    var tds = $(div) .find('td');
    for (var i = 0; i < tds.length; i++) {
      var td = $(tds[i]);
      var bu = new Bu(td);
      if (blocks[bu.name]) {
        blocks[bu.name].push(bu);
      } else {
        blocks[bu.name] = [ bu ];
      }
    }
    var thHtml = '<tr>';
    // 18th「昼の部」対応
    // for (i = 1; i <= 7; i++) thHtml += '<th bgcolor="#FABB00">第' + i + '部</th>';
    PERIODS.forEach(function(period){ thHtml += '<th bgcolor="#FABB00">' + period + '部</th>'; });
    thHtml += '</tr>';
    var th = $(thHtml) .clone();

    var table = $('<table>') .addClass('members').addClass("new").appendTo(div);
    var memberCount = 0;
    var prevTeam = '';
    for (var name in blocks) {
      var team = name2team(name);
      if (team && prevTeam != team){ // name2teamでヒットしなかったら何もしない
        table.append(th.clone());
        prevTeam = team;
      }
      var tr = $('<tr>');
      var memberBlocks = blocks[name];
      var memberColor = index2color(memberCount);
      // 18th「昼の部」対応
      // for (var iperiod = 1; period <= 7; period++) {
      PERIODS.forEach( function(period){
        var blockForthePeriod = $.grep(memberBlocks, function (block, idx) {
          return (block.period == period);
        });
        if (blockForthePeriod.length == 1) {
          blockForthePeriod[0].jq.css("background-color", memberColor).appendTo(tr);
        } else {
          $('<td class="none">').css("background-color", "#fff1cb").appendTo(tr);
        }
      });
      tr.appendTo(table);
      memberCount++;
    }
    table.append(th.clone());
  };

  var updateMatrix = function(table){
    var matrix= $.map(table.find('tr'), function(v, k){
      return [ $.makeArray($(v).children()) ];
    });
    return matrix;
  };

  var swap = function(a, b){
    a = $(a);
    b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
  };

  var pushUp = function(matrix, nRow){
    var trVacant = 999;
    var tr = matrix[nRow];
    for(var nCol=0; nCol < tr.length; nCol++){
      var td = $(tr[nCol]);
      if (td.hasClass("none")) continue;
      var nVacant = 0;
      for(var i=1; i < nRow; i++){
        var cell = $(matrix[nRow-i][nCol]);
        if (cell.hasClass("none")) nVacant++;
        else break;
      }
      if (nVacant < trVacant){
        trVacant = nVacant;
      }
    }
    if (trVacant > 0 ){
      for(nCol=0; nCol < tr.length; nCol++){
        td = $(tr[nCol]);
        if (!td.hasClass("none")){
          swap(td, $(matrix[nRow-trVacant][nCol]));
        }
      }
    }
  };

  var shrinkBlocks = function(){
    var tables = $("table.members.new");
    for(var nTable = 0; nTable < tables.length; nTable++){
      var table = $(tables[nTable]);
      var trs = table.find("tr");
      var maxRow = trs.length;
      for(var nRow=1; nRow < maxRow; nRow++){
        var matrix = updateMatrix(table);
        pushUp(matrix, nRow);
      }
      for(nRow=0; nRow < maxRow; nRow++){
        var tr = $(trs[nRow]);
        if (tr.text()==""){tr.remove();}
      }
    }
  };

  var removeOldTables = function(){
    $("table.members:not(.new)").remove();
  };

  var doAllDivs = function (command){
    switch(command){
    case 'arrange':
      var divs = $('div.tabbertab');
      for (var i = 0; i < divs.length; i++) {
        var div = divs[i];
        wrapTeamName(div);
        arrangeBlocks(div);
      }
      break;
    case 'shrink':
      shrinkBlocks();
      break;
    }
  };

  var toggleArrange = function (obj){
    var button = $(obj);
    var nextMode = '';
    var btnText = '';
    switch( button.attr('data-mode') ){
    case 'arrange':
      nextMode = 'shrink';
      doAllDivs('shrink');
      btnText = btnTexts[2];
      break;
    case 'shrink':
      nextMode = 'normal';
      btnText = btnTexts[0];
      location.reload(true);
      break;
    default:
      nextMode = 'arrange';
      btnText = btnTexts[1];
      doAllDivs('arrange');
    }
    button.attr('data-mode', nextMode).text(btnText);
  };

  //main
  var btnHtml = '<button>' + btnTexts[0] + '</button>';
  var button = $(btnHtml).css({
    "padding": "5px 15px",
    "width": "8.5em",
    "background": "#f8b500",
    "color": "white",
    "border": "2px solid white",
    "border-radius": "8px",
    "font-weight": "bold",
    "font": "normal middle Roboto, Arial, sans-serif"
    }).hover(
      function () { $(this).css({cursor: "pointer"})},
      function () { $(this).css({cursor: "auto"})}
      );
  var buttonDiv = $('<div id="toggle-arrange"></div>').css("text-align", "right");
  buttonDiv.append(button);
  var content=$("div.content:first");
  buttonDiv.insertBefore(content);
  $(document).on('click', 'div#toggle-arrange > button', function (event){
    event.preventDefault();
    event.stopPropagation();
    toggleArrange(event.target);
    removeOldTables();
    doFixPlaceBar();
  });

};

new arrangeMumo();