WatchWatch

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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Userscripts installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey installieren.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

(Ich habe bereits einen Benutzerstil Verwaltung, ich möchte ihn installieren!)

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

})();