WatchWatch

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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);

})();