WatchWatch

GINZAwatchに時計復活。 ついでに過去ログ時間選択も使いやすくする

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey to install this script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name        WatchWatch
// @namespace   https://github.com/segabito/
// @description GINZAwatchに時計復活。 ついでに過去ログ時間選択も使いやすくする
// @include     http://www.nicovideo.jp/watch/*
// @version     0.1.6
// @grant       none
// ==/UserScript==

(function() {
  var monkey = (function() {
    'use strict';

    if (!window.WatchJsApi) {
      return;
    }

    window.WatchWatch = {};

    var console = {
      log: function() {},
      trace: function() {}
    };

    if (localStorage.watchItLater_debugMode === 'true') {
      console = window.console;
    }

    var _ = require('lodash');
    var inherit = require('cjs!inherit');
    var advice = require('advice');
    var EventDispatcher = require('watchapp/event/EventDispatcher');

    var TimeMachine = (function() {
      function TimeMachine(playerInitializer) {
        EventDispatcher.call(this);
        this._initialize(playerInitializer);
      }
      inherit(TimeMachine, EventDispatcher);

      _.assign(TimeMachine.prototype, {
        _initialize: function(playerInitializer) {
          var commentPanelView = this._commentPanelView =
              playerInitializer.rightSidePanelViewController._playerPanelTabsView._commentPanelView;
          advice.after(commentPanelView, '_showLogForm', _.bind(function(date) {
            this.dispatchEvent('timeMachine-changePastMode', true, date);
          }, this));
          advice.after(commentPanelView, '_hideLogForm', _.bind(function() {
            this.dispatchEvent('timeMachine-changePastMode', false);
          }, this));
        },
        goToPast: function(tm) {
          this._commentPanelView.loadPastComments(tm);
        },
        goToPresent: function() {
          this._commentPanelView.loadPastComments();
        }
      });

      return TimeMachine;
    })();


    _.assign(window.WatchWatch, {
      _isPastMode: false,
      _pastTime: (new Date()).getTime(),
      getPastTime: function() {
        if (this._isPastMode) {
          return this._pastTime;
        } else {
          return (new Date()).getTime();
        }
      },
      initialize: function() {
        var PlayerInitializer = require('watchapp/init/PlayerInitializer');
        this._watchInfoModel      = require('watchapp/model/WatchInfoModel').getInstance();
        this._playerAreaConnector = PlayerInitializer.playerAreaConnector;
        this._nicoPlayerConnector = PlayerInitializer.nicoPlayerConnector;
        this._timeMachine = new TimeMachine(PlayerInitializer);

        var EventDispatcher = require('watchapp/event/EventDispatcher');
        this.event                = new EventDispatcher();
        this.initializeCss();

        this.initializeUserConfig();

        this.initializeDom();
        this.initializeTimeMachine();
        this.initializeTimer();

      },
      addStyle: function(styles, id) {
        var elm = document.createElement('style');
        elm.type = 'text/css';
        if (id) { elm.id = id; }

        var text = styles.toString();
        text = document.createTextNode(text);
        elm.appendChild(text);
        var head = document.getElementsByTagName('head');
        head = head[0];
        head.appendChild(elm);
        return elm;
      },
      initializeCss: function() {
        var __css__ = (function() {/*
          .watchWatch #playerCommentPanel .playerCommentPanelHeader .currentThreadName,
          .watchWatch .select-box.comment-type
          {
            width: 120px;
          }

          .watchWatch #playerCommentPanel .playerCommentPanelHeader .comment,
          .watchWatch .header-icon.comment-log
          {
            display: none;
          }

          .watchWatch #playerCommentPanel #commentLogHeader form,
          .watchWatch .comment-panel .log-form
          {
            display: none;
          }

          .watchWatch #playerCommentPanel .section #commentLog.commentTable,
          .watchWatch .comment-panel.log-form-opened .panel-body
          {
            top: 28px;
          }

          .watchWatch .commentPanelAlert,
          .watchWatch .comment-panel .panel-alert
          {
            top: 28px;
          }


          .watchWatchContainer {
            position: absolute;
            left: 139px;
            top: 5px;
            -moz-box-sizing: border-box;
            -webkit-box-sizing: border-box;
            width: 140px;
            font-size: 10px;
            padding: 5px 4px 2px;
            text-align: center;
            white-space: nowrap;
            overflow: hidden;
            background: #f4f4f4;
            border: 1px solid #999;
            cursor: pointer;
            z-index: 10060;
          }
          .watchWatchContainer:hover {
            background: #fff;
            color: #008;
          }
          .watchWatchContainer.past {
            color: red;
          }




          .timeMachineControl.show {
            display: block;
          }
          .timeMachineControl {
            position: absolute;
            display: none;
            top: 34px;
            left: 6px;
            z-index: 10060;
            -moz-box-sizing: border-box;
            -webkit-box-sizing: border-box;
            width: 313px;
            overflow: hidden;
            background: #fff;
            box-shadow: 0 0 4px black;
          }

          .timeMachineControl .reset,
          .timeMachineControl .title {
            margin: 8px;
          }
          .timeMachineControl .datepickerContainer {
          }
          .timeMachineControl .datepickerContainer .ui-datepicker {
            display: block;
            margin: auto;
            -moz-box-sizing: border-box;
            -webkit-box-sizing: border-box;
            width: 100%;
          }
          .timeMachineControl .datepickerContainer .ui-datepicker .ui-datepicker-title select.ui-datepicker-year {
            width: 40%;
          }
          .timeMachineControl .datepickerContainer .ui-datepicker .ui-datepicker-title select.ui-datepicker-month {
            width: 40%;
          }
          .timeMachineControl .ui-widget-content {
            border: none;
          }

          .timeMachineControl .inputFormContainer {
            text-align: center;
          }
          .timeMachineControl .dateInput {
            text-align: center;
            width: 150px;
          }
          .timeMachineControl .hourInput:after {
            content: ' : ';
            font-weight: boler;
          }
          .timeMachineControl .submitButtonContainer {
            text-align: center;
            padding: 8px;
          }
          .timeMachineControl      .reset {
            display: none;
          }
          .timeMachineControl.past .reset {
            display: inline-block;
          }
          .timeMachineControl .reset, .timeMachineControl .submit {
            padding: 8px;
            cursor: pointer;
          }


          {*  *}
          .watchWatchContainer .year:after  { content: '/'; }
          .watchWatchContainer .month:after { content: '/'; }
          .watchWatchContainer .date:after  { content: ''; }

          .watchWatchContainer .day:before {
            content: '(';
          }
          .watchWatchContainer .day:after {
            content: ') ';
          }

          .watchWatchContainer.sun .day .inner:after { content: '日'; }
          .watchWatchContainer.mon .day .inner:after { content: '月'; }
          .watchWatchContainer.tue .day .inner:after { content: '火'; }
          .watchWatchContainer.wed .day .inner:after { content: '水'; }
          .watchWatchContainer.thu .day .inner:after { content: '木'; }
          .watchWatchContainer.fri .day .inner:after { content: '金'; }
          .watchWatchContainer.sat .day .inner:after { content: '土'; }

          .watchWatchContainer .hour:after { content: ':'; }
          .watchWatchContainer .min:after  { content: ':'; }
          .watchWatchContainer .sec:after  { content: '';  }

        */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');

        this.addStyle(__css__, 'WatchWatchCss');
      },
      initializeUserConfig: function() {
        var prefix = 'WatchWatch_';
        var conf = {};
        this.config = {
          get: function(key) {
            try {
              if (window.localStorage.hasOwnProperty(prefix + key)) {
                return JSON.parse(window.localStorage.getItem(prefix + key));
              }
              return conf[key];
            } catch (e) {
              return conf[key];
            }
          },
          set: function(key, value) {
            window.localStorage.setItem(prefix + key, JSON.stringify(value));
          }
        };
      },
      initializeTimeMachine: function() {
        var timeMachine         = this._timeMachine;
        var watchInfoModel      = this._watchInfoModel;
        var playerAreaConnector = this._playerAreaConnector;

        timeMachine.addEventListener('reset', $.proxy(function() {
          this._isPastMode = false;
          this.event.dispatchEvent('timeMachine-changePastMode', false, new Date());
        }, this));
        timeMachine.addEventListener('error', $.proxy(function() {
          this.event.dispatchEvent('timeMachine.error');
        }, this));

        playerAreaConnector.addEventListener('onTimeMachineDateUpdated', $.proxy(function(isPast, time) {
          console.log('dispatch.timeMachine-changePastMode', isPast, time);
          if (isPast) {
            this._isPastMode = true;
            this._pastTime = time;
            this.event.dispatchEvent('timeMachine-changePastMode', true,  new Date(time));
          } else {
            this._isPastMode = false;
            this.event.dispatchEvent('timeMachine-changePastMode', false, new Date());
          }
        }, this));

        watchInfoModel.addEventListener('reset', $.proxy(function() {
          this._isPastMode = false;
          if (initialized) {
            beforeShow();
          }
        }, this));

        var beforeShow = $.proxy(function() {
          var md = watchInfoModel.postedAt.split(' ')[0].split('/');
          var $date = this._$datepickerContainer;
          $date.datepicker('option', 'minDate', new Date(md[0], md[1] - 1, md[2]));
          $date.datepicker('option', 'maxDate', new Date());
          var v = this._$dateInput.val();
          if (v.match(/^(\d{4})[¥/-](\d{2})[¥/-](\d{2})$/)) {
            var dt = new Date(RegExp.$1, RegExp.$2 - 1, RegExp.$3);
            if (dt) $date.datepicker('setDate', dt);
          }
        }, this);

        var initialize = $.proxy(function() {
          if (initialized) { return; }
          this._$datepickerContainer.datepicker({
            dateFormat: 'yy/mm/dd',
//            beforeShow: beforeShow,
            altField: this._$dateInput,
            changeMonth: true,
            changeYear: true
          });

          initialized = true;
        }, this), initialized = false;

        this.resetDatepicker = $.proxy(function() {
          initialize();
          beforeShow();
        }, this);

        this.event.addEventListener('popup.toggle', $.proxy(function(v) {
          if (!v) { return; }
          this.resetDatepicker();
        }, this));

        this.timeMachineController = {
          goToPast: function(tm) {
            if (!tm) tm = (new Date()).getTime();
            if (typeof tm === 'object' && tm.getTime) tm = tm.getTime();

            tm = Math.min(window.WatchApp.ns.util.TimeUtil.now(), tm);
            var postedAt = new Date(watchInfoModel.postedAt.substring(0, 16).replace(/-/g, '/').split(' '));
            console.log('postedAt', postedAt.getTime(), tm);
            // 投稿直後より前は指定できない。マイメモリーやチャンネルだと怪しいかも
            // 投稿日時ぴったりもだめっぽい。 おそらく投稿確定後にスレッドが作られるため。
            tm = Math.max(postedAt.getTime() + 300 * 1000, tm);
            timeMachine.goToPast(tm);
          },
          now: function() {
            timeMachine.goToPresent();
          }
        };
      },
      initializeDom: function() {
        $('#playerTabWrapper').addClass('watchWatch');
        var $watch = this._$watch = $([
          '<div id="watchWatch" class="watchWatchContainer sun">',
            '<span class="year">2014</span>',
            '<span class="month">01</span>',
            '<span class="date">01</span>',
            '<span class="day">',
              '<span class="inner"></span>',
            '</span>',
            '<span class="hour">00</span>',
            '<span class="min">00</span>',
            '<span class="sec">00</span>',
          '</div>',
          ''].join(''));
        var $popup = this._$popup = $([
          '<div id="watchWatchTimeMachine" class="timeMachineControl">',
          '<h1 class="title">過去のコメントを開く</h1>',
          '<div class="datepickerContainer"></div>',
          '<div class="inputFormContainer">',
            '<input type="text" name="date" value="2014/01/01" class="dateInput">',
            '<select name="hour"   class="hourInput"></select>',
            '<select name="minute" class="minuteInput"></select>',
          '</div>',
          '',
          '<div class="submitButtonContainer">',
            '<button class="submit">過去ログを見る</button>',
            '<button class="reset">最新ログに戻る</button>',
          '</div>',
          '</div>',
          ''].join(''));
        $('#playerCommentPanel, #playerTabContainer .comment-panel').after($watch).after($popup);

        this._$datepickerContainer = $popup.find('.datepickerContainer');
        this._$dateInput    = $popup.find('.dateInput');
        this._$hourInput    = $popup.find('.hourInput');
        this._$minuteInput  = $popup.find('.minuteInput');
        this._$submitButton = $popup.find('.submit');
        this._$resetButton  = $popup.find('.reset');

        var options = [];
        for (var i = 0; i < 60; i++) {
          var m = ('0' + i).slice(-2);
          options.push(['<option value="', m, '">', m, '</option>'].join(''));
        }
        this._$hourInput.html(options.slice(0, 24).join(''));
        this._$minuteInput.html(options.join(''));

        var resetInput = $.proxy(function(time) {
          var date;
          if (typeof time === 'number') { date = new Date(time); }
          else
          if (typeof time === 'object' && time.getTime) date = time;
          else
          if (!time) date = new Date();
          console.log('resetInput', time, date);
          var y = date.getFullYear();
          var m = (date.getMonth() + 101).toString().slice(-2);
          var d = ('0' + date.getDate()).slice(-2);

          var hh = ('0' + date.getHours()).slice(-2);
          var mm = ('0' + date.getMinutes()).slice(-2);
          this._$dateInput  .val(y + '/' + m + '/' + d);
          this._$hourInput  .val(hh);
          this._$minuteInput.val(mm);
          //console.log('dt', y, m, d, hh, mm);
        }, this);

        $watch.on('click', $.proxy(function() {
          this.togglePopup();
        }, this));

        this.togglePopup = $.proxy(function(v) {
          if (typeof v === 'boolean') {
            this._$popup.toggleClass('show', v);
          } else {
            this._$popup.toggleClass('show');
          }
          resetInput(this.getPastTime());
          this.event.dispatchEvent('popup.toggle', this._$popup.hasClass('show'));
        }, this);
        this.showPopup = $.proxy(function() { this.togglePopup(true);  }, this);
        this.hidePopup = $.proxy(function() { this.togglePopup(false); }, this);

        this.event.addEventListener('timeMachine-changePastMode', $.proxy(function(isPast, time) {
          this.hidePopup();
          console.log('initializeDom.timeMachine-changePastMode', isPast, time);
          this._$watch.toggleClass('past', isPast);
          this._$popup.toggleClass('past', isPast);
          resetInput(time);
        }, this));
        this.event.addEventListener('timeMachine.error', $.proxy(function() {
          this.hidePopup(); // エラーメッセージが見えないので閉じる
        }, this));

        this._$submitButton.on('click', $.proxy(function() {
          var dd = this._$dateInput.val().replace(/-/g, '/');
          var tt = this._$hourInput.val() + ':' + this._$minuteInput.val();
          var dt = new Date(dd + ' ' + tt + ':00');

          this.timeMachineController.goToPast(dt);
        }, this));
        this._$resetButton.on('click', $.proxy(function() {
          this.timeMachineController.now();
        }, this));

        $watch = $popup = null;
      },
      initializeTimer: function() {
        var date = new window.Date();
        var $watch  = this._$watch;
        var $year  = $watch.find('.year');
        var $month = $watch.find('.month');
        var $date  = $watch.find('.date');
        var $hour  = $watch.find('.hour');
        var $min   = $watch.find('.min');
        var $sec   = $watch.find('.sec');
        var days   = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
        var timer;

        var update = function(force, time) {
          if (time) {
            date.setTime(time);
            console.log('setTime', time, date);
          } else {
            date.setTime((new Date()).getTime());
          }
          var sec = date.getSeconds();
          $sec.text(('0' + sec).slice(-2));

          if (force || sec === 0) {
            var min = date.getMinutes();
            $min.text(('0' + min).slice(-2));

            if (force || min === 0) {
              $hour .text(('0' + date.getHours()).slice(-2));
              $date .text(('0' + date.getDate()) .slice(-2));
              var month = date.getMonth();
              $month.text((date.getMonth() + 101).toString().slice(-2));
              $year .text(date.getFullYear());
              var day = date.getDay();
              $watch
                .removeClass('month' + (((month + 11) % 12) + 1))
                .addClass(   'month' +   (month +  1))
                .removeClass(days[(day + 6) % 7] )
                .addClass(   days[day]);
            }
          }
        };
        var onTimer = this._onTimer = $.proxy(function() {
          if (this._isPastMode) { return; }
          update(false);
        }, this);

        this.event.addEventListener('timeMachine-changePastMode', function(isPast, time) {
          console.log('timer.timeMachine-changePastMode', isPast, time, new Date(time));
          if (isPast) {
            window.setTimeout(function() { update(true, time); }, 1000);
          } else {
            update(true);
          }
        });

        update(true);
        window.WatchApp.mixin(this, {
          watch: {
            start: function() {
              if (timer) return;
              timer = window.setInterval($.proxy(onTimer, this), 600);
            },
            stop: function() {
              window.clearInterval(timer);
              timer = null;
            },
            refresh: function() {
              update(true);
            }
          }
        });
        this.watch.start();
      }
    });

    if (window.PlayerApp) {
      require(['WatchApp'], function() {
        var watchInfoModel = require('watchapp/model/WatchInfoModel').getInstance();
        if (watchInfoModel.initialized) {
          window.WatchWatch.initialize();
        } else {
          var onReset = function() {
            watchInfoModel.removeEventListener('reset', onReset);
            window.setTimeout(function() {
              watchInfoModel.removeEventListener('reset', onReset);
              window.WatchWatch.initialize();
            }, 0);
          };
          watchInfoModel.addEventListener('reset', onReset);
        }
      });
    }


  });

  var script = document.createElement('script');
  script.id = 'WatchWatchLoader';
  script.setAttribute('type', 'text/javascript');
  script.setAttribute('charset', 'UTF-8');
  script.appendChild(document.createTextNode('(' + monkey + ')()'));
  document.body.appendChild(script);

})();