WatchWatch

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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

})();