WatchItLater

動画を見る前にマイリストに登録したいGreasemonkey (Chrome/Fx用)

目前为 2014-08-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WatchItLater
  3. // @namespace https://github.com/segabito/
  4. // @description 動画を見る前にマイリストに登録したいGreasemonkey (Chrome/Fx用)
  5. // @include http://www.nicovideo.jp/*
  6. // @include http://i.nicovideo.jp/*
  7. // @include http://ch.nicovideo.jp/*
  8. // @include http://ext.nicovideo.jp/thumb/*
  9. // @exclude http://ads*.nicovideo.jp/*
  10. // @exclude http://live*.nicovideo.jp/*
  11. // @exclude http://dic.nicovideo.jp/*
  12. // @exclude http://www.upload.nicovideo.jp/*
  13. // @exclude http://upload.nicovideo.jp/*
  14. // @exclude http://ch.nicovideo.jp/tool/*
  15. // @exclude http://flapi.nicovideo.jp/*
  16. // @match http://www.nicovideo.jp/*
  17. // @match http://ch.nicovideo.jp/*
  18. // @match http://i.nicovideo.jp/*
  19. // @match http://api.ce.nicovideo.jp/api/v1/system.unixtime?
  20. // @match http://*.nicovideo.jp/*
  21. // @match http://ext.nicovideo.jp/*
  22. // @match http://search.nicovideo.jp/*
  23. // @grant GM_xmlhttpRequest
  24. // @version 1.140811
  25. // ==/UserScript==
  26.  
  27.  
  28. //
  29. // * ver 1.140524
  30. // * ver 1.140522
  31. // - 本家のサムネイル仕様変更に対応
  32.  
  33. // * ver 1.140428
  34. // - 本家の仕様変更で使えなくなっていた、プレイリストのブックマーク保存機能を復活
  35.  
  36. // * ver 1.140319
  37. // - 謎の技術によって、ニコメンドがなくても説明文の動画リンクにサムネイルを出せるように
  38. // - 細かなスタイル調整
  39.  
  40. // * ver 1.140303
  41. // - 動画選択画面で再生リストの開閉が記憶されなくなったのに対抗
  42. // - 動画切換え時に一番上までスクロールするようになったのに対抗
  43. // - 本家の内部仕様変更(jQuery ver up等)に対応
  44.  
  45. // * ver 1.140227
  46. // - タグ検索のソート順が毎回リセットされるようになったのに対抗
  47.  
  48. // * ver 1.140218
  49. // - コメント重複を勝手に直してたけど不要になったので除去
  50. // - 二本目以降の動画だけ自動再生を追加
  51.  
  52. // * ver 1.140207
  53. // - テレビちゃんメニューの表示修正
  54. // - スレッドIDのリンクからもタグを取得できるように(watchページ内のみ)
  55. // - マイリスト選択メニュー部分の右クリックでとりマイの位置に戻る隠し機能
  56.  
  57. // * ver 1.140122
  58. // - テレビちゃんメニューをShinjukuWatch仕様に
  59.  
  60. // * ver 1.140110
  61. // - 検索フォームのオートコンプリートを調整
  62. // - ニコメンドまわりのコード除去
  63. // - 微妙にNicorenizerとの相性を改善
  64.  
  65. (function() {
  66. var isNativeGM = true;
  67. var monkey =
  68. (function(isNativeGM){
  69. var w;
  70. try { w = unsafeWindow || window; } catch (e) { w = window;}
  71. var document = w.document;
  72.  
  73. var conf = {
  74. autoBrowserFull: false, // 再生開始時に自動全画面化
  75. disableAutoBrowserFullIfNicowari: false, // ユーザーニコ割があるときは自動全画面化しない
  76. autoNotFull: true, // 再生完了時にフルスクリーン解除(原宿と同じにする)
  77. autoTagPin: false,
  78. topPager: true, // 検索ボックスのページャを上にする
  79. hideLeftIchiba: false,
  80. autoClosePlaylistInFull: true, // 全画面時にプレイリストを自動で閉じる
  81. autoOpenSearch: false, // 再生開始時に自動検索画面
  82. autoScrollToPlayer: true, // プレイヤー位置に自動スクロール(自動全画面化オフ時)
  83. hideNewsInFull: true, // 全画面時にニュースを閉じる
  84. wideCommentPanel: false, // コメントパネルをワイドにする
  85. removeLeftPanel: true, // 左パネルを消滅させる
  86. leftPanelJack: false, // 左パネルに動画情報を表示
  87. rightPanelJack: true, // 右パネルに動画情報を表示
  88. headerViewCounter: false, // ヘッダに再生数コメント数を表示
  89. popupViewCounter: 'full', // 動画切り替わり時にポップアップで再生数を表示
  90. ignoreJumpCommand: false, // @ジャンプ無効化
  91. nicoSSeekCount: -1, // @ジャンプによるシーク(ループなど)を許可する回数 -1=無限 0以上はその回数だけ許可
  92. doubleClickScroll: true, // 空白部分ををダブルクリックで動画の位置にスクロールする
  93. hidePlaylist: true, // プレイリストを閉じる
  94. hidePlaylistInVideoExplorer: true, // 動画選択画面でプレイリストを閉じる
  95. hidariue: false, // てれびちゃんメニュー内に、原宿以前のランダム画像復活
  96. videoExplorerHack: true, // 検索画面を乗っ取る
  97. squareThumbnail: true, // 検索画面のサムネを4:3にする
  98. enableFavTags: false, // 動画検索画面にお気に入りタグを表示
  99. enableFavMylists: false, // 動画検索画面にお気に入りマイリストを表示
  100. searchPageItemCount: 50, // 検索モードの1ページあたりの表示数
  101. enableMylistDeleteButton: false, // 動画検索画面で、自分のマイリストから消すボタンを追加する
  102. enableHoverPopup: true, // 動画リンクのマイリストポップアップを有効にする
  103. enableAutoTagContainerHeight: true, // タグが2行以内なら自動で高さ調節(ピン留め時のみ
  104. autoSmallScreenSearch: false, // ポップアップからのタグ検索でもプレイヤーを小さくする
  105. enableNewsHistory: false, // ニコニコニュースの履歴を保持する
  106. defaultSearchOption: '', // 検索時のデフォルトオプション
  107. autoPlayIfWindowActive: 'no', // 'yes' = ウィンドウがアクティブの時だけ自動再生する
  108. autoPlay2ndVideo: false, // 2本目以降の動画だけ自動再生
  109. enableYukkuriPlayButton: false, // スロー再生ボタンを表示する
  110. noNicoru: false, // ニコるボタンをなくす
  111. enoubleTouchPanel: false, // タッチパネルへの対応を有効にする
  112. mouseClickWheelVolume: 0, // マウスボタン+ホイールで音量調整を有効にする 1 = 左ボタン 2 = 右ボタン
  113. enableQTouch: false, // タッチパネルモード有効
  114. commentVisibility: 'visible', // 'visible', 'hidden', 'lastState'
  115. lastCommentVisibility: 'visible',
  116. controllerVisibilityInFull: '', // 全画面時に操作パネルとコメント入力欄を出す設定
  117. enableTrueBrowserFull: false, // フチなし全画面モードにする (Chromeは画面ダブルクリックで切り替え可能)
  118. enableSharedNgSetting: false, //
  119. hideNicoNews: false, // ニコニコニュースを消す
  120. hashPlaylistMode: 0, // location.hashにプレイリストを保持 0 =無効 1=連続再生時 2=常時
  121. storagePlaylistMode: '', // localStorageにプレイリストを保持
  122. compactVideoInfo: true, //
  123. hoverMenuDelay: 0.4, // リンクをホバーした時のメニューが出るまでの時間(秒)
  124. enableFullScreenMenu: true, // 全画面時にホイールでメニューを出す
  125. enableHeatMap: false, //
  126. heatMapDisplayMode: 'hover', // 'always' 'hover'
  127. replacePopupMarquee: true, //
  128. enableRelatedTag: true, // 関連タグを表示するかどうか
  129. // playerTabAutoOpenNicommend: 'enable', // 終了時にニコメンドを自動で開くかどうか 'enable' 'auto' 'disable'
  130. autoPauseInvisibleInput: true, //
  131. customPlayerSize: '', //
  132. removeCommentPanelHoverEvent: false, //
  133. disableVideoExplorer: false, //
  134. disableTagReload: false, //
  135. disableHorizontalScroll: false, // 横スクロールバーを出なくする
  136. hideCommentPanelSocialButtons: false, // コメントパネル下のソーシャルボタンを隠す
  137. mylistPanelPosition: '',
  138. enableDescriptionThumbnail: false, // 説明文の動画リンクにサムネイルとタイトル表示
  139.  
  140. enableLocalMylistCache: false,
  141.  
  142. rankingCategory_g_ent2_Close: true,
  143. rankingCategory_g_life2_Close: true,
  144. rankingCategory_g_tech_Close: true,
  145. rankingCategory_g_culture2_Close: true,
  146. rankingCategory_g_other_Close: true,
  147.  
  148. searchEngine: 'sugoi', // 'normal' 'sugoi'
  149. searchStartTimeRange: '', //
  150. searchLengthSecondsRange: '', //
  151. searchMusicDlFilter: false, //
  152.  
  153. hideVideoExplorerExpand: true, // 「動画をもっと見る」ボタンを小さくする
  154. // nicommendVisibility: 'visible', // ニコメンドの表示 'visible', 'underIchiba', 'hidden'
  155. ichibaVisibility: 'visible', // 市場の表示 '', 'visible', 'hidden'
  156. reviewVisibility: 'visible', // レビューの表示 'visible', 'hidden'
  157. bottomContentsVisibility: 'hidden', // 動画下のコンテンツ表示表示非表示
  158. hideMenuInFull: 'hide', // 全画面時にマイリストメニューを隠す '', 'hide' = 目立たなくする, 'hideAll' = 完全非表示
  159.  
  160. flatDesignMode: '', // 'on' グラデーションや角丸をなくす。 7/25からQwatchがフラットデザインになったので不要になった
  161. playerBgStyle: '', // ↑ の後継パラメータ 'gray' 'white' ''
  162.  
  163. shortcutTogglePlay: {char: 'P', shift: false, ctrl: false, alt: true, enable: false}, // 停止/再生
  164. shortcutDefMylist: {char: 'M', shift: true, ctrl: false, alt: false, enable: false}, // とりマイ登録のショートカット
  165. shortcutMylist: {char: 'M', shift: false, ctrl: true , alt: false, enable: false}, // マイリスト登録のショートカット
  166. shortcutOpenSearch: {char: 'S', shift: true, ctrl: false, alt: false, enable: false}, // 検索オープンのショートカット
  167. shortcutOpenDefMylist: {char: 'D', shift: true, ctrl: false, alt: false, enable: false}, // とりマイオープンのショートカット
  168. shortcutOpenRecommend: {char: 'R', shift: true, ctrl: false, alt: false, enable: false}, // 関連動画(オススメ)を開くショートカット
  169. shortcutCommentVisibility: {char: 'V', shift: true, ctrl: false, alt: false, enable: false}, // コメント表示ON/OFFのショートカット
  170. shortcutScrollToNicoPlayer: {char: 'P', shift: true, ctrl: false, alt: false, enable: false}, // プレイヤーまでスクロールのショートカット
  171. shortcutShowOtherVideo: {char: 'U', shift: true, ctrl: false, alt: false, enable: false}, // 投稿者の関連動画表示のショートカット
  172. shortcutMute: {char: 'T', shift: true, ctrl: false, alt: false, enable: false}, // 音量ミュートのショートカット
  173. shortcutDeepenedComment: {char: 'B', shift: true, ctrl: false, alt: false, enable: false}, // コメント背面表示
  174. shortcutToggleStageVideo: {char: 'H', shift: true, ctrl: false, alt: false, enable: false}, // ハードウェアアクセラレーション(StageVideo)のショートカット
  175.  
  176. shortcutInvisibleInput: {char: 'C', shift: false, ctrl: false, alt: true, enable: true}, // 停止/再生
  177.  
  178. initializeImmediately: false, // 動画のロードを待たずに初期化する
  179.  
  180. watchCounter: 0, // お前は今までに見た動画の数を覚えているのか?をカウントする
  181. forceEnableStageVideo: false,
  182. forceExpandStageVideo: false,
  183. enableAutoPlaybackContinue: false, // 一定時間操作しなかくても自動再生を続行
  184. lastLeftTab: 'videoInfo',
  185. lastRightTab: 'w_videoInfo',
  186. lastRightTabInExplorer: 'comment',
  187. lastControlPanelPosition: '',
  188. enableSortTypeMemory: true, // 検索のソート順を記憶する
  189. searchSortType: 'n', //
  190. searchSortOrder: 'd', // 'd'=desc 'a' = asc
  191. fxInterval: 40, // アニメーションのフレームレート 40 = 25fps
  192. enableGpuLayer: false, // 一部の要素でGPU描画を有効にしてみる?
  193. debugMode: false
  194. };
  195.  
  196.  
  197. //===================================================
  198. //===================================================
  199. //===================================================
  200.  
  201. function addStyle(styles, id) {
  202. var elm = document.createElement('style');
  203. window.setTimeout(function() {
  204. elm.type = 'text/css';
  205. if (id) { elm.id = id; }
  206.  
  207. var text = styles.toString();
  208. text = document.createTextNode(text);
  209. elm.appendChild(text);
  210. var head = document.getElementsByTagName('head');
  211. head = head[0];
  212. head.appendChild(elm);
  213. }, 0);
  214. return elm;
  215. }
  216.  
  217. if (!isNativeGM) {
  218. this.GM_xmlhttpRequest = function(options) {
  219. try {
  220. var req = new XMLHttpRequest();
  221. var method = options.method || 'GET';
  222. req.onreadystatechange = function() {
  223. if (req.readyState === 4) {
  224. if (typeof options.onload === "function") options.onload(req);
  225. }
  226. };
  227. req.open(method, options.url, true);
  228. if (options.headers) {
  229. for (var h in options.headers) {
  230. req.setRequestHeader(h, options.headers[h]);
  231. }
  232. }
  233.  
  234. req.send(options.data || null);
  235. } catch (e) {
  236. if (conf.debugMode) console.log(e);
  237. }
  238. };
  239. }
  240.  
  241. (function() { // 各ページ共通
  242. var __css__ = (function() {/*
  243. .tagItemsPopup {
  244. background: #eef;
  245. }
  246. .popupMenu {
  247. position: absolute;
  248. min-width: 200px;
  249. font-Size: 12pt;
  250. z-index: 2000000;
  251. box-shadow: 2px 2px 2px #888;
  252. }
  253. .popupMenu ul, .popupMenu ul li {
  254. position: relative;
  255. list-style-type: none;
  256. margin: 0; padding: 0;
  257. white-space: nowrap;
  258. }
  259. .tagItemsPopup .icon{
  260. width: 17px;
  261. height: 15px;
  262. }
  263. .tagItemsPopup .nicodic, .tagItemsPopup .newsearch {
  264. margin: 1px 4px 1px 1px;
  265. }
  266. .tagItemsPopup .nicodic:hover, .tagItemsPopup .newsearch:hover {
  267. margin: 0px 3px 0px 0px;
  268. border: 1px outset;
  269. }
  270.  
  271. .mylistListPopup {
  272. position:absolute;
  273. background: #fff;
  274. overflow: visible;
  275. padding: 8px;
  276. border: 1px outset #333;
  277. transition: opacity 0.3s ease;
  278. }
  279. .mylistListPopup.popupMenu ul li {
  280. position: relative;
  281. margin: 2px 8px;
  282. overflow-y: visible;
  283. }
  284. .mylistListPopup:not(.show) {
  285. left: -9999px;
  286. top: -9999px;
  287. opacity: 0;
  288. }
  289. .mylistListPopup.show {
  290. opacity: 1;
  291. }
  292. .mylistListPopup .listInner {
  293. -webkit-column-width: auto; -moz-column-width: auto;
  294. -webkit-column-count: 1 !important; {* Chromeだけバグるので *}
  295. }
  296. .mylistListPopup .icon {
  297. display: inline-block;
  298. width: 18px;
  299. height: 14px;
  300. margin: -4px 4px 0 0;
  301. vertical-align: middle;
  302. margin-right: 15px;
  303. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  304. transform: scale(1.5); -webkit-transform: scale(1.5);
  305. transform-origin: 0 0 0; -webkit-transform-origin: 0 0 0;
  306. transition: transform 0.1s ease, box-shadow 0.1s ease;
  307. -webkit-transition: -webkit-transform 0.1s ease, box-shadow 0.1s ease;
  308. cursor: pointer;
  309. }
  310. .mylistListPopup .icon:hover {
  311. background-color: #ff9;
  312. transform: scale(2); -webkit-transform: scale(2);
  313. }
  314. .mylistListPopup .icon:after {
  315. content: '開く';
  316. position: absolute;
  317. bottom: 0px;
  318. right: 12px;
  319. padding: 2px;
  320. opacity: 0;
  321. transform: scale(0.5); -webkit-transform: scale(0.5);
  322. z-index: -1;
  323. }
  324. .mylistListPopup .icon:hover:after {
  325. {*box-shadow: 2px 2px 2px #888;*}
  326. background: #fff;
  327. z-index: 100;
  328. opacity: 1;
  329. }
  330. .mylistListPopup .deflist .icon { background-position: 0 -253px; }
  331. .mylistListPopup .folder1 .icon { background-position: 0 -23px;}
  332. .mylistListPopup .folder2 .icon { background-position: 0 -46px;}
  333. .mylistListPopup .folder3 .icon { background-position: 0 -69px;}
  334. .mylistListPopup .folder4 .icon { background-position: 0 -92px;}
  335. .mylistListPopup .folder5 .icon { background-position: 0 -115px;}
  336. .mylistListPopup .folder6 .icon { background-position: 0 -138px;}
  337. .mylistListPopup .folder7 .icon { background-position: 0 -161px;}
  338. .mylistListPopup .folder8 .icon { background-position: 0 -184px;}
  339. .mylistListPopup .folder9 .icon { background-position: 0 -207px;}
  340.  
  341.  
  342. .mylistListPopup .name {
  343. display: inline-block;
  344. vertical-align: middle;
  345. font-size: 110%;
  346. color: #666;
  347. text-derocation: none !important;
  348. }
  349. .mylistListPopup .name:hover {
  350. color: #000;
  351. background-color: #ff9;
  352. }
  353. .mylistListPopup .name:after {
  354. content: ' に登録';
  355. font-size: 75%;
  356. color: #fff;
  357. }
  358. .mylistListPopup .name.exist:after {
  359. content: ' に登録済';
  360. color: #933;
  361. }
  362. .mylistListPopup .name:hover:after {
  363. color: #666;
  364. }
  365.  
  366. {* マイリスト登録パネル *}
  367. .mylistPopupPanel {
  368. height: 24px;
  369. z-index: 10000;
  370. {*border: 1px solid silver;
  371. border-radius: 3px; *}
  372. padding: 0;
  373. margin: 0;
  374. overflow: hidden;
  375. display: inline-block;
  376. background: #eee;
  377. }
  378. .mylistPopupPanel.fixed {
  379. position: fixed; right: 0; bottom: 0;
  380. transition: right 0.1s ease-out;
  381. }
  382. .mylistPopupPanel.fixed.left {
  383. right: auto;
  384. left: 0;
  385. }
  386. .mylistPopupPanel.fixed.top {
  387. bottom: auto;
  388. top: 0;
  389. }
  390.  
  391. .mylistPopupPanel.fixed>* {
  392. transition: opacity 0.1s ease-out;
  393. }
  394. .mylistPopupPanel>*>* {
  395. transition: background 0.5s ease 0.5s, color 0.5s ease 0.5s;
  396. }
  397. .full_with_browser .mylistPopupPanel, .mylistPopupPanel.black {
  398. background: #000; border: 0;
  399. }
  400. body.full_with_browser .mylistPopupPanel *,body .mylistPopupPanel.black.fixed * {
  401. background: #000; color: #888; border-color: #333;
  402. }
  403. .full_with_browser .mylistPopupPanel.hideAllInFull{
  404. display: none;
  405. }
  406. .full_with_browser.fullWithPlaylist .mylistPopupPanel.hideInFull,
  407. .full_with_browser.fullWithPlaylist .mylistPopupPanel.hideAllInFull {
  408. display: block;
  409. }
  410. .full_with_browser .mylistPopupPanel.hideInFull.fixed:not(:hover) {
  411. right: -100px;
  412. transition: right 0.8s ease-in-out 0.5s;
  413. }
  414. .full_with_browser .mylistPopupPanel.hideInFull.fixed.left:not(:hover) {
  415. left: -100px; right: auto;
  416. transition: left 0.8s ease-in-out 0.5s;
  417. }
  418. .full_with_browser .mylistPopupPanel.hideInFull:not(:hover)>*{
  419. opacity: 0;
  420. transition: opacity 0.3s ease-out 1s;
  421. }
  422. .mylistPopupPanel.w_touch {
  423. height: 40px;
  424. }
  425. iframe.popup {
  426. position: absolute;
  427. }
  428. {* マウスホバーで出るほうのマイリスト登録パネル *}
  429. .mylistPopupPanel.popup {
  430. position: absolute;
  431. z-index: 1000000;
  432. box-shadow: 2px 2px 2px #888;
  433. }
  434. {* マイリスト登録パネルの中の各要素 *}
  435. .mylistPopupPanel .mylistSelect {
  436. width: 64px;
  437. margin: 0;
  438. padding: 0;
  439. font-size: 80%;
  440. white-space: nowrap;
  441. {*background: #eee;*}
  442. border: 1px solid silver;
  443. }
  444. .mylistSelect:focus {
  445. border-style: outset;
  446. }
  447. {* 誤操作を減らすため、とりマイの時だけスタイルを変える用 *}
  448. .mylistPopupPanel.w_touch button {
  449. padding: 8px 18px;
  450. }
  451. .mylistPopupPanel.w_touch .mylistSelect {
  452. font-size: 170%; width: 130px; border: none;
  453. }
  454. .mylistPopupPanel.w_touch .mylistSelect:focus {
  455. border: 1px dotted;
  456. }
  457. .mylistPopupPanel.deflistSelected button {
  458. }
  459. .mylistPopupPanel.mylistSelected button {
  460. color: #ccf;
  461. }
  462. .mylistPopupPanel button {
  463. margin: 0;
  464. font-weight: bolder;
  465. cursor: pointer;
  466. }
  467. .mylistPopupPanel button:active, #outline .playlistToggle:active, #outline .openVideoExplorer:active, #content .openConfButton:active {
  468. border:1px inset !important
  469. }
  470. .mylistPopupPanel button:hover, #outline .playlistToggle:hover, #outline .openVideoExplorer:hover, #outline .openConfButton:hover, #yukkuriPanel .yukkuriButton:hover {
  471. border:1px outset
  472. }
  473. .mylistPopupPanel .mylistAdd, .mylistPopupPanel .tagGet, #yukkuriPanel .yukkuriButton {
  474. border:1px solid #777; cursor: pointer; font-family:arial, helvetica, sans-serif; padding: 0px 4px 0px 4px; text-shadow: -1px -1px 0 rgba(0,0,0,0.3);font-weight:bold; text-align: center; color: #eee; background-color: #888; margin: 0;
  475. }
  476. .mylistPopupPanel .mylistAdd:focus, .mylistPopupPanel .tagGet:focus, #yukkuriPanel .yukkuriButton:focus {
  477. border-style: outset; color: orange;
  478. }
  479. .mylistPopupPanel.deflistSelected {
  480. color: #ff9;
  481. }
  482. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  483. border:1px solid #ebb7b7; font-family:arial, helvetica, sans-serif; padding: 0px 6px 0px 6px; text-shadow: -1px -1px 0 rgba(0,0,0,0.3);font-weight:bold; text-align: center; color: #FFFFFF; background-color: #f7e3e3;
  484. }
  485. .mylistPopupPanel.deflistSelected {
  486. color: #ff9;
  487. }
  488. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  489. border:1px solid #ebb7b7; font-family:arial, helvetica, sans-serif; padding: 0px 6px 0px 6px; text-shadow: -1px -1px 0 rgba(0,0,0,0.3); text-align: center; color: #FFFFFF; background-color: #f7e3e3;
  490. }
  491. .mylistPopupPanel.mylistSelected .deflistRemove {
  492. display: none;
  493. }
  494. .mylistPopupPanel .closeButton{
  495. color: #339;
  496. padding: 0;
  497. margin: 0;
  498. font-size: 80%;
  499. text-decoration: none;
  500. }
  501. .mylistPopupPanel .newTabLink{
  502. padding: 0 2px; text-decoration: underline; text-shadow: -1px -1px 0px #442B2B;
  503. }
  504. .mylistPopupPanel.fixed .newTabLink, .mylistPopupPanel.fixed .closeButton {
  505. display: none;
  506. }
  507. .w_fullScreenMenu .mylistPopupPanel.fixed { bottom: 2px; }
  508.  
  509. {* ポイントが無いときは表示しない *}
  510. .item:not(.silver):not(.gold) .uadContainer {
  511. display: none !important;
  512. }
  513.  
  514. .xDomainLoaderFrame {
  515. width: 1px;
  516. height: 1px;
  517. position: fixed;
  518. top: -100px;
  519. left: -100px;
  520. border: 0;
  521. overflow: hidden;
  522. }
  523.  
  524. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  525. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  526. addStyle(__css__, 'watchItLaterCommonStyle');
  527. })(); // end of commoncss
  528.  
  529.  
  530.  
  531.  
  532. (function() { // watchページだけのstyle
  533. if (!w.WatchApp) { return; }
  534. var __css__ = (function() { /*
  535. {* 動画タグとプレイリストのポップアップ *}
  536. #videoTagPopupContainer {
  537. }
  538. #videoTagPopupContainer.w_touch {
  539. line-height: 200%; font-size: 130%;
  540. }
  541. #videoTagPopupContainer.w_touch .nicodic{
  542. margin: 4px 14px;
  543. }
  544. .playlistMenuPopup {
  545. background: #666; color: white; padding: 4px 8px;
  546. }
  547. .playlistMenuPopup.w_touch {
  548. line-height: 250%;
  549. }
  550. #playlistSaveDialog {
  551. display: none;
  552. }
  553. #playlistSaveDialog.show {
  554. display: block;
  555. }
  556. #playlistSaveDialog.show .shadow{
  557. position: fixed;
  558. top: 0; left: 0; width: 100%; height: 100%;
  559. background: #000; opacity: 0.5;
  560. z-index: 30000;
  561. }
  562. #playlistSaveDialog.show .formWindow{
  563. position: fixed;
  564. margin: 0 auto;
  565. text-align: center;
  566. width: 100%;
  567. top: 45%;
  568. z-index: 30001;
  569. }
  570. #playlistSaveDialog .formWindow .formWindowInner{
  571. -webkit-transition: opacity 1s ease-out;
  572. transition: opacity 1s ease-out;
  573. opacity: 0;
  574. }
  575. #playlistSaveDialog.show .formWindow .formWindowInner{
  576. text-align: left;
  577. opacity: 1;
  578. margin: 0 auto;
  579. background: #f4f4f4;
  580. width: 500px;
  581. padding: 8px;
  582. border: 1px solid;
  583. }
  584. #playlistSaveDialog.show .formWindow .formWindowInner a{
  585. font-weight: bolder;
  586. }
  587. #playlistSaveDialog.show .formWindow .formWindowInner a:hover{
  588. text-decoration: underline; background: white;
  589. }
  590. #playlistSaveDialog.show .formWindow .formWindowInner label{
  591. margin: 8px;
  592. }
  593. #playlistSaveDialog.show .formWindow .formWindowInner input{
  594.  
  595. }
  596. #playlistSaveDialog.show .formWindow .formWindowInner .desc{
  597. font-size: 80%;
  598. }
  599. .playlistMenuPopup ul li {
  600. cursor: pointer;
  601. }
  602. .playlistMenuPopup ul li.savelist {
  603. color: #aaa;
  604. }
  605. .playlistMenuPopup ul li:hover {
  606. text-decoration: underline; background: #888;
  607. }
  608. #yukkuriPanel .yukkuriButton.active {
  609. border:1px inset
  610. }
  611.  
  612. #content .openConfButton {
  613. border:1px solid #bbb; cursor: pointer; font-family:arial, helvetica, sans-serif; padding: 4px; text-shadow: 1px 1px 0 rgba(0,0,0,0.3); text-align: center; color: #444; background-color: #ccc; margin: 0;
  614. }
  615. #outline .playlistToggle, #outline .openVideoExplorer, #outline .openConfButton {
  616. border:1px solid #444; cursor: pointer; font-family:arial, helvetica, sans-serif; padding: 0px 4px 0px 4px; box-shadow: 1px 1px 0 rgba(0,0,0,0.3); text-align: center; color: #444; background-color: #ccc; margin: 0;
  617. height: 24px; border-radius: 0 0 8px 8px;
  618. }
  619. #outline .openConfButton { padding: 0 8px; letter-spacing: 4px; width: 60px; }
  620.  
  621. {* 全画面時にプレイリスト表示 *}
  622. body.full_with_browser.fullWithPlaylist #playlist{
  623. z-index: 100 !important;
  624. display: block !important;
  625. position: absolute;
  626. bottom: 0;
  627. }
  628. body.full_with_browser.fullWithPlaylist .browserFullOption{
  629. display: none !important;
  630. }
  631. body.full_with_browser.fullWithPlaylist #content.playlist #playerContainerWrapper {
  632. margin-bottom: 167px !important;
  633. }
  634.  
  635.  
  636. {* 少しでも縦スクロールを減らすため、動画情報を近づける。人によっては窮屈に感じるかも *}
  637. #outline {
  638. margin-top: -16px;
  639. }
  640. #outline #feedbackLink{
  641.  
  642. }
  643. #outline .videoEditMenuExpand{
  644. position: absolute;right: 0;top: 26px; z-index: 1;
  645. }
  646. {* ヘッダに表示する再生数 *}
  647. #videoCounter {
  648. color: #ff9; font-size: 70%;
  649. }
  650. {* 右に表示する動画情報 *}
  651. .sidePanel .sideVideoInfo, .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  652. padding: 0px 0px 0 0px; width: 196px; height: 100%; z-index: 10019;
  653. position:absolute; top:0; right:0; border: 1px solid #000;
  654. overflow-x: visible; overflow-y: auto;
  655. }
  656. {* 右に表示する動画情報 *}
  657. #playerTabWrapper.sidePanel .sideVideoInfo, #playerTabWrapper.sidePanel .sideIchibaPanel, #playerTabWrapper.sidePanel .sideReviewPanel {
  658. padding: 0px 0px 0 0px; width: 324px; height: 100%;
  659. position: absolute; top: 0; right:0;
  660. }
  661.  
  662. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideVideoInfo,
  663. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideIchibaPanel,
  664. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideReviewPanel,
  665. .videoExplorer #playerTabWrapper .sideVideoInfo,
  666. .videoExplorer #playerTabWrapper .sideIchibaPanel,
  667. .videoExplorer #playerTabWrapper .sideReviewPanel {
  668. width: 418px;
  669. }
  670. #playerTabWrapper.w_videoInfo #appliPanel, #playerTabWrapper.w_ichiba #appliPanel, #playerTabWrapper.w_review #appliPanel {
  671. top: -9999px;
  672. }
  673. .sidePanel .sideVideoInfo {
  674. background: #f4f4f4; text-Align: left; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%; border: 1px solid black;
  675. }
  676. .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  677. background: #f4f4f4; text-Align: center; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%;
  678. }
  679. .sidePanel .sideVideoInfo .sideVideoInfoInner {
  680. padding: 0 4px; position: relative;
  681. }
  682. .sidePanel .sideVideoInfo .videoTitleContainer {
  683. background: #ddd; text-align: center; color: #000; margin: 6px 6px 0;
  684. border: solid; border-width: 2px 2px 0; border-color: #888;
  685. }
  686. .sidePanel .sideVideoInfo .videoOwnerInfoContainer {
  687. margin: 0 6px; border: solid; border-width: 0 2px 0; border-color: #888;
  688. }
  689. .sidePanel .sideVideoInfo .videoThumbnailContainer {
  690. background: #ddd; text-align: center; color: #000; margin: 0;
  691. }
  692. .sidePanel .sideVideoInfo .videoThumbnailContainer img {
  693. cursor: pointer;
  694. }
  695. .sidePanel .sideVideoInfo .videoTitle {
  696. padding: 8px;
  697. }
  698. .sidePanel .sideVideoInfo .videoPostedAt {
  699. color: #333;
  700. }
  701. .sidePanel .sideVideoInfo .videoStats{
  702. font-size:90%;
  703. }
  704. .sidePanel .sideVideoInfo .videoStats li{
  705. display: inline-block; margin: 0 2px;
  706. }
  707. .sidePanel .sideVideoInfo .videoStats li span{
  708. font-weight: bolder;
  709. }
  710. .sidePanel .sideVideoInfo .videoStats .ranking{
  711. display: none !important;
  712. }
  713. .sidePanel .sideVideoInfo .videoInfo{
  714. background: #ddd; text-align: center; padding: 4px; margin: 0 6px 6px;
  715. border: solid; border-width: 0 2px 2px; border-color: #888;
  716. }
  717. .sideVideoInfo .sideVideoInfoInner{
  718. -webkit-transition: opacity 1s ease-out, color 3s ease-out;
  719. transition: opacity 1s ease-out, color 3s ease-out;
  720. opacity: 0;
  721. }
  722. .sideVideoInfo.show .sideVideoInfoInner{
  723. opacity: 1;
  724. }
  725. .videoCount.blink {
  726. color: #ccc;
  727. }
  728. .sidePanel .videoCountDiff {
  729. position: absolute; color: white; right: 0; opacity: 0; z-index: 100; text-shadow: 1px 1px 0 orange;
  730. }
  731. .sidePanel .videoCountDiff.blink {
  732. opacity: 1; color: white;
  733. }
  734. #siteHeader .videoCount, #siteHeader .videoCountDiff {
  735. min-width: 32px; text-align: right; display: inline-block;
  736. }
  737. #siteHeader .videoCountDiff, #fullScreenToggleContainer .videoCountDiff {
  738. position: absolute; color: yellow; opacity: 0; font-weight: bolder; text-shadow: 1px 1px 0 red;
  739. }
  740. #siteHeader .videoCountDiff.blink, #fullScreenToggleContainer .videoCountDiff.blink {
  741. opacity: 1; color: yellow;
  742. }
  743. .videoExplorer #siteHeaderNicopoPurchase,
  744. .videoExplorer #siteHeaderUserNickNameContainer,
  745. .size_medium #siteHeaderNicopoPurchase,
  746. .size_medium #siteHeaderUserNickNameContainer,
  747. #siteHeaderNotificationPremium {
  748. display: none !important;
  749. }
  750. #fullScreenToggleContainer .blink, #videoCounter .blink {
  751. color: #000;
  752. }
  753. .videoCountDiff:before {content: '+';}
  754. .videoCountDiff.down:before {content: ''; }
  755. #popupMarquee .videoCountDiff {display: none;}
  756. .sidePanel .sideVideoInfo .videoDescription{
  757. overflow-x: hidden; text-align: left;
  758. }
  759. .sidePanel .sideVideoInfo .videoDescriptionInner{
  760. margin: 0 8px;
  761. }
  762. .sidePanel .sideVideoInfo .videoDetails{
  763. min-width: 150px;
  764. }
  765. .sidePanel .sideVideoInfo .videoDetails a{
  766. margin: auto 4px;
  767. }
  768. .sidePanel .sideVideoInfo .videoDetails a:visited{
  769. color: #04618c;
  770. }
  771. .sidePanel .sideVideoInfo .videoDetails a.watch{
  772. margin: auto 30px auto 4px;
  773. display:inline-block;
  774. }
  775. .sideVideoInfo .userName, .sideVideoInfo .channelName{
  776. display: block;
  777. font-size: 120%;
  778. cursor: pointer;
  779. }
  780. .sideVideoInfo .userIconContainer, .sideVideoInfo .channelIconContainer {
  781. background: #ddd; width: 100%; text-align: center; float: none;
  782. }
  783. .sidePanel .userIcon, .sidePanel .channelIcon{
  784. min-width: 128px; max-width: 150px; width: auto; height: auto; border: 0;
  785. box-shadow: 0 0 4px #666; cursor: pointer;
  786. }
  787. .sideVideoInfo .descriptionThumbnail {
  788. text-align: left; font-size: 90%; padding: 4px; background: #ddd;
  789. min-height: 50px; margin: 4px 8px; font-weight: normal; color: black;
  790. }
  791. .sideVideoInfo .descriptionThumbnail p {
  792. margin: 0 8px;
  793. font-weight: bolder;
  794. }
  795. .sideVideoInfo .descriptionThumbnail .uploadAt {
  796. font-size: 90%;
  797. margin: 4px;
  798. color: #333;
  799. }
  800. .sideVideoInfo .descriptionThumbnail .counterContainer {
  801. text-align: center;
  802. }
  803. .sideVideoInfo .descriptionThumbnail .view,
  804. .sideVideoInfo .descriptionThumbnail .comment,
  805. .sideVideoInfo .descriptionThumbnail .mylist
  806. {
  807. font-size: 90%;
  808. white-space: nowrap;
  809. margin-right: 4px;
  810. color: #333;
  811. }
  812. .sideVideoInfo .descriptionThumbnail .count {
  813. font-weight: bolder;
  814. }
  815.  
  816. .sideVideoInfo .descriptionThumbnail.video img{
  817. height: 50px; cursor: pointer; float: left;
  818. }
  819. .sideVideoInfo .descriptionThumbnail.mylist img{
  820. height: 40px; cursor: pointer;
  821. }
  822. .sideVideoInfo .descriptionThumbnail.illust img{
  823. height: 60px; cursor: pointer;
  824. }
  825. .sideVideoInfo a.otherSite {
  826. font-weight: bolder; text-decoration: underline;
  827. }
  828. body:not(.videoExplorer) #leftPanel.removed {
  829. display: none; left: 0px;
  830. }
  831. body:not(.videoExplorer) #leftPanel.removed .sideVideoInfo {
  832. display: none; width: 0px !important; border: none; margin: 0; padding: 0; right: auto;
  833. }
  834. .sideVideoInfo .userIconContainer.isUserVideoPublic .notPublic { display: none; }
  835. .sideVideoInfo .userIconContainer .isPublic { display: none; }
  836. .sideVideoInfo .userIconContainer.isUserVideoPublic .isPublic { display: inline; }
  837. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem,
  838. .sidePanel .sideIchibaPanel .ichiba_mainitem {
  839. width: 180px; display:inline-block; vertical-align: top;
  840. margin: 4px 3px; border 1px solid silver;
  841. }
  842.  
  843. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  844. font-size: 60px;
  845. }
  846. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem>div>dt {
  847. height: 50px;position: relative;
  848. }
  849. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  850. position: absolute;width: 100%;
  851. }
  852. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  853. position: absolute;
  854. }
  855. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonShita {
  856. position: absolute;
  857. }
  858.  
  859. .sidePanel.videoInfo, .sidePanel.ichiba{
  860. background: none;
  861. }
  862.  
  863. .sideVideoInfo.isFavorite .userName:after, .sideVideoInfo.isFavorite.isChannel .videoOwnerInfoContainer .channelName:after{
  864. content: ' ★ '; color: gold; text-shadow: 1px 1px 1px black;
  865. }
  866.  
  867. .sidePanel.videoInfo #leftPanelContent, .sidePanel.ichiba #leftPanelContent {
  868. display: none;
  869. }
  870. .sidePanel.w_comment #playerTabContainer,
  871. .sidePanel.videoInfo .sideVideoInfo,
  872. .sidePanel.ichiba .sideIchibaPanel,
  873. .sidePanel.w_videoInfo .sideVideoInfo,
  874. .sidePanel.w_ichiba .sideIchibaPanel,
  875. .sidePanel.w_review .sideReviewPanel {
  876. display: block; z-index: 10060;
  877. }
  878. .sidePanel:not(.w_comment) .watchWatchContainer {
  879. display: none;
  880. }
  881.  
  882. #leftPanelTabContainer {
  883. display:none; background: #666; position: absolute; right: 4px; top: -27px; list-style-type: none; padding: 4px 6px 3px 60px; height: 20px;
  884. }
  885. #sidePanelTabContainer {
  886. display: none;
  887. position: absolute; list-style-type: none;
  888. padding: 5px 10px 0; right: -408px; top: 0; width: 350px; height: 34px;
  889. transform: rotate(90deg); transform-origin: 0 0 0;
  890. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  891. z-index: 1000;
  892. }
  893. .full_with_browser #sidePanelTabContainer {
  894. background: #000 !important;
  895. }
  896. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left {
  897. background: #000; right: auto; left: -375px; padding: 0; height: 27px;
  898. transform: rotate(-90deg); transform-origin: 100% 0 0;
  899. -webkit-transform: rotate(-90deg); -webkit-transform-origin: 100% 0 0;
  900. }
  901.  
  902. #leftPanelTabContainer.w_touch {
  903. top: -40px; height: 33px;
  904. }
  905. .sidePanel:hover #sidePanelTabContainer, .sidePanel:hover #leftPanelTabContainer {
  906. display: block;
  907. }
  908. #leftPanelTabContainer .tab{
  909. display: inline-block; cursor: pointer; background: #999; padding: 2px 4px 0px; border-width: 2px 2px 0px;
  910. }
  911. #leftPanelTabContainer.w_touch .tab, #sidePanelTabContainer.w_touch .tab {
  912. padding: 8px 12px 8px;
  913. }
  914. #sidePanelTabContainer .tab {
  915. background: none repeat scroll 0 0 #999999; border-width: 2px 2px 0; cursor: pointer;
  916. display: inline-block; font-size: 13px; padding: 5px 10px 8px;
  917. border-radius: 8px 8px 0px 0px;
  918. }
  919. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left .tab {
  920. display: inline-block; font-size: 13px; padding: 5px 10px 0px;
  921. }
  922. #leftPanel.videoInfo .tab.videoInfo, #leftPanel.ichiba .tab.ichiba {
  923. background: #f4f4f4; border-style: outset;
  924. }
  925. #playerTabWrapper.w_comment .tab.comment,
  926. #playerTabWrapper.w_videoInfo .tab.videoInfo,
  927. #playerTabWrapper.w_ichiba .tab.ichiba,
  928. #playerTabWrapper.w_review .tab.review
  929. {
  930. background: #dfdfdf; border-style: outset;
  931. }
  932.  
  933. #playerTabWrapper.w_videoInfo #playerCommentPanel,
  934. #playerTabWrapper.w_ichiba #playerCommentPanel,
  935. #playerTabWrapper.w_review #playerCommentPanel {
  936. {*display: none;*} top: -9999px;
  937. }
  938. .sidePanel.ichibaEmpty .tab.ichiba, .sidePanel.reviewEmpty .tab.review {
  939. color: #ccc;
  940. }
  941.  
  942. .sideIchibaPanel .ichibaPanelInner {
  943. margin:0; color: #666;
  944. }
  945. .sideIchibaPanel .ichibaPanelHeader .logo{
  946. text-shadow: 1px 1px 1px #666; cursor: pointer; padding: 4px 0px 4px; font-size: 125%;
  947. }
  948. .sideIchibaPanel .ichibaPanelFooter{
  949. text-align: center;
  950. }
  951. .sideIchibaPanel .ichiba_mainitem {
  952. margin: 0 0 8px 0; background: white; border-bottom : 1px dotted #ccc;
  953. }
  954. .sideIchibaPanel .ichiba_mainitem a:hover{
  955. background: #eef;
  956. }
  957. .sideIchibaPanel .ichiba_mainitem>div {
  958. max-width: 266px; margin: auto; text-align: center;
  959. }
  960. .sideIchibaPanel .ichiba_mainitem .blomagaArticleNP {
  961. background: url("http://ichiba.nicovideo.jp/embed/zero/img/bgMainBlomagaArticleNP.png") no-repeat scroll 0 0 transparent;
  962. height: 170px;
  963. margin: 0 auto;
  964. width: 180px;
  965. }
  966. .sideIchibaPanel .ichiba_mainitem .blomagaLogo {
  967. color: #FFFFFF;font-size: 9px;font-weight: bold;padding-left: 10px;padding-top: 8px;
  968. }
  969. .sideIchibaPanel .ichiba_mainitem .blomagaLogo span{
  970. background: none repeat scroll 0 0 #AAAAAA;padding: 0 3px;
  971. }
  972. .sideIchibaPanel .ichiba_mainitem .blomagaText {
  973. color: #666666;font-family: 'HGS明朝E','MS 明朝';font-size: 16px;height: 100px;
  974. padding: 7px 25px 0 15px;text-align: center;white-space: normal;word-break: break-all;word-wrap: break-word;
  975. }
  976. .sideIchibaPanel .ichiba_mainitem .blomagaAuthor {
  977. color: #666666; font-size: 11px;padding: 0 20px 0 10px;text-align: right;
  978. }
  979. .sideIchibaPanel .ichiba_mainitem .balloonUe{
  980. bottom: 12px; display: block; max-width: 266px;
  981. }
  982. .sideIchibaPanel .ichiba_mainitem .balloonUe a{
  983. background: url("/img/watch_zero/ichiba/imgMainBalloonUe.png") no-repeat scroll center top transparent;
  984. color: #666666 !important;
  985. display: block;
  986. font-size: 108%;
  987. line-height: 1.2em;
  988. margin: 0 auto;
  989. padding: 8px 15px 3px;
  990. text-align: center;
  991. text-decoration: none;
  992. word-wrap: break-word;
  993. }
  994. .sideIchibaPanel .ichiba_mainitem .balloonShita{
  995. height: 12px; bottom: 0; left: 0;
  996. }
  997. .sideIchibaPanel .ichiba_mainitem .balloonShita img{
  998. vertical-align: top !important;
  999. }
  1000. .sideIchibaPanel .ichiba_mainitem .ichibaMarquee {
  1001. display: none;
  1002. }
  1003. .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  1004. font-size: 22px; color: #0066CC;
  1005. font-family: 'ヒラギノ明朝 Pro W3','Hiragino Mincho Pro','MS P明朝','MS PMincho',serif;
  1006. }
  1007. .sideIchibaPanel .ichiba_mainitem .action {
  1008. font-size: 85%;
  1009. }
  1010. .sideIchibaPanel .ichiba_mainitem .action .buy {
  1011. font-weight: bolder; color: #f60;
  1012. }
  1013. .sideIchibaPanel .ichiba_mainitem .itemname {
  1014. font-weight: bolder;
  1015. }
  1016. .sideIchibaPanel .ichiba_mainitem .maker {
  1017. font-size: 77%; margin-bottom: 2px;
  1018. }
  1019. .sideIchibaPanel .ichiba_mainitem .price {
  1020. }
  1021. .sideIchibaPanel .ichiba_mainitem .action .click {
  1022. font-weight: bolder;
  1023. }
  1024. .sideIchibaPanel .ichiba_mainitem .goIchiba {
  1025. font-size: 77%; margin: 5px 0;
  1026. }
  1027. .sideIchibaPanel .addIchiba, .sideIchibaPanel .reloadIchiba {
  1028. cursor: pointer;
  1029. }
  1030. .sideIchibaPanel .noitem {
  1031. cursor: pointer;
  1032. }
  1033.  
  1034. #outline .bottomAccessContainer {
  1035. position: absolute; top: 12px;
  1036. }
  1037. #outline .bottomConfButtonContainer {
  1038. position: absolute; top: 12px; right: 0px;
  1039. }
  1040. body.videoExplorer .bottomAccessContainer{
  1041. display: none;
  1042. }
  1043. #outline.under960 .bottomAccessContainer{
  1044. right: 60px;
  1045. }
  1046. .watchItLaterSettingMenu {
  1047. font-weight: bolder;
  1048. white-space: nowrap;
  1049. }
  1050. #outline .sidebar {
  1051. -webkit-transition: margin-top 0.3s ease-out;
  1052. transition: margin-top 0.3s ease-out;
  1053. }
  1054. #outline.under960 .sidebar {
  1055. margin-top: 24px;
  1056. }
  1057. #videoHeader.menuClosed .watchItLaterMenu, #videoHeader.menuClosed .hidariue { display: none; }
  1058. #videoHeader .watchItLaterMenu {
  1059. position: absolute; width: 100px; left: -55px; top: 32px;
  1060. }
  1061. {* タイトルクリックでヘッダが開閉できるのをわかりやすく *}
  1062. .videoDetailToggleButton:hover {
  1063. text-decoration: underline;
  1064. }
  1065. .videoDetailToggleButton:hover:after {
  1066. content: '▼';
  1067. position: absolute;
  1068. width: 32px;
  1069. height: 20px;
  1070. top: 0;
  1071. bottom: 0;
  1072. right: -32px;
  1073. margin: auto;
  1074. color: #888;
  1075. font-size: 80%;
  1076. }
  1077. .infoActive .videoDetailToggleButton:hover:after {
  1078. content: '▲';
  1079. }
  1080.  
  1081. {* プレイリスト出したり隠したり *}
  1082. #playlist>* {
  1083. -webkit-transition: opacity 0.6s; transition: opacity 0.6s;
  1084. }
  1085. body:not(.full_with_browser):not(.videoExplorer) #playlist.w_closing>* {
  1086. opacity: 0;
  1087. }
  1088. body:not(.full_with_browser):not(.videoExplorer) #playlist:not(.w_show){
  1089. position: absolute; top: -9999px;
  1090. }
  1091. #playlist.w_show{
  1092. {*max-height: 180px;*}
  1093. }
  1094. .playlistToggle:after {
  1095. content: "▼";
  1096. }
  1097. .playlistToggle.w_show:after {
  1098. content: "▲";
  1099. }
  1100.  
  1101. body.videoExplorer #content.w_adjusted #playlist .playlistInformation {
  1102. white-space: nowrap;
  1103. }
  1104. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .playbackOption {
  1105. position: absolute;
  1106. }
  1107. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .generationMessage{
  1108. margin-left: 90px; max-width: 350px; overflow: hidden; text-overflow: ellipsis;
  1109. }
  1110. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption {
  1111. position: absolute; right: 0; top: 0;
  1112. }
  1113. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption a {
  1114. background: #444;
  1115. }
  1116. #playlistContainerInner .thumbContainer, #playlistContainerInner .balloon{
  1117. cursor: move;
  1118. }
  1119.  
  1120.  
  1121. {* ページャーの字が小さくてクリックしにくいよね *}
  1122. #resultPagination {
  1123. padding: 5px; font-weight: bolder; border: 1px dotted silter; font-size: 130%;
  1124. }
  1125.  
  1126. #playlistContainer #playlistContainerInner .playlistItem .balloon {
  1127. bottom: auto; top: -2px; padding: auto;
  1128. }
  1129.  
  1130. body.w_channel #leftPanel .userIconContainer{
  1131. display: none;
  1132. }
  1133. {* WatchItLater設定パネル *}
  1134. #watchItLaterConfigPanel {
  1135. position: fixed; bottom: 0px; right: 16px; z-index: 10001;
  1136. width: 460px; padding: 0;
  1137. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1138. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1139. transform: scaleY(0); -webkit-transform: scaleY(0);
  1140. }
  1141. #watchItLaterConfigPanel.open {
  1142. transform: scaleY(1); -webkit-transform: scaleY(1);
  1143. }
  1144. #watchItLaterConfigPanelShadow {
  1145. position: fixed; bottom: 16px; right: 16px; z-index: 10000;
  1146. width: 460px; height: 559px; padding: 0;
  1147. background: #000; {*box-shadow: 0 0 2px black; border-radius: 8px;*} -webkit-filter: opacity(70%);
  1148. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1149. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1150. transform: scaleY(0); -webkit-transform: scaleY(0);
  1151. }
  1152. #watchItLaterConfigPanelShadow.open {
  1153. transform: scaleY(1); -webkit-transform: scaleY(1);
  1154. }
  1155. #watchItLaterConfigPanelShadowTop {
  1156. position: fixed; bottom: 563px; right:0px; z-index: 10000; background: #333;
  1157. width: 492px; height: 20px; padding: 0; border-radius: 32px; -webkit-filter: opacity(90%); display: none;
  1158. }
  1159. #watchItLaterConfigPanelOverShadow {
  1160. position: fixed; bottom: 575px; right: 0px; width: 488px; height: 8px;
  1161. box-shadow: 0 4px 16px #333;z-index: 10002; display: none;
  1162. }
  1163. #watchItLaterConfigPanel .head {
  1164. background-color: #CCCCCC;border-radius: 0;color: black;height: 50px;
  1165. overflow: hidden;padding: 5px 0 0 16px;position: relative;
  1166. }
  1167. #watchItLaterConfigPanel .head h2 {
  1168. font-size: 135%;
  1169. }
  1170. #watchItLaterConfigPanel .inner{
  1171. height: 500px; overflow-y: auto;border-width: 4px 16px 16px 16px; border-radius: 0 0 16px 16px;
  1172. border-style: solid;border-color: #ccc;
  1173. }
  1174. #watchItLaterConfigPanel ul{
  1175. border-style: inset; border-color: #ccc; border-width: 0 1px 0;
  1176. }
  1177. #watchItLaterConfigPanel ul.shortcutContainer{
  1178. border-width: 0 1px 1px;
  1179. }
  1180. #watchItLaterConfigPanel ul.videoStart{
  1181. border-width: 1px 1px 0;
  1182. }
  1183. #watchItLaterConfigPanel li{
  1184. }
  1185. #watchItLaterConfigPanel li:hover{
  1186. {*background: #ddd;*}
  1187. }
  1188. #watchItLaterConfigPanel li.buggy{
  1189. color: #888;
  1190. }
  1191. #watchItLaterConfigPanel label{
  1192. margin: 0 5px;
  1193. }
  1194. #watchItLaterConfigPanel label:hover{
  1195. }
  1196. #watchItLaterConfigPanel .foot {
  1197. text-align: right; padding: 0 12px;
  1198. }
  1199. #watchItLaterConfigPanel .closeButton{
  1200. border: 0 none;border-radius: 0 0 4px 4px;box-shadow: 0 1px 2px white;color: #666; border: 1px solid #999;
  1201. cursor: pointer;float: right;margin-top: 8px;position: absolute;right: 16px;
  1202. text-shadow: 0 1px 0 white;top: -10px; width: 60px;
  1203. }
  1204. #watchItLaterConfigPanel.autoBrowserFull_false .disableAutoBrowserFullIfNicowari,
  1205. #watchItLaterConfigPanel.autoBrowserFull_true .autoScrollToPlayer,
  1206. #watchItLaterConfigPanel.autoBrowserFull_true .autoOpenSearch,
  1207. #watchItLaterConfigPanel.removeLeftPanel_true .leftPanelJack {
  1208. color: #ccc; text-shadow: -1px -1px 0 #888;
  1209. }
  1210. #watchItLaterConfigPanel .reload .title:after {
  1211. content: ' (※)'; font-size: 80%; color: #900;
  1212. }
  1213. #watchItLaterConfigPanel .debugOnly {
  1214. display: none;
  1215. }
  1216. #watchItLaterConfigPanel.debugMode .debugOnly {
  1217. display: block; background: #888;
  1218. }
  1219. #watchItLaterConfigPanel .section {
  1220. border-style: solid;border-width: 10px 12px 10px 12px;color: white; font-size: 135%; position: relative;
  1221. font-weight: bolder; cursor: pointer; {*text-shadow: 2px 2px 1px #000000;*}
  1222. transition: border-width 0.2s ease-in-out 0.4s, color 0.3s; -webkit-transition: border-width 0.2s ease-in-out 0.4s, color 0.4s;
  1223. }
  1224. #watchItLaterConfigPanel .open .section {
  1225. border-width: 20px 12px 12px 12px;
  1226. transition: border-width 0.2s ease-in-out ; -webkit-transition: border-width 0.2s ease-in-out ;
  1227. }
  1228. #watchItLaterConfigPanel .section:hover:after {
  1229. content: '▼';
  1230. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1231. transition: transform 0.2s ease-in-out 0.4s; -webkit-transition: -webkit-transform 0.2s ease-in-out 0.4s;
  1232. }
  1233. #watchItLaterConfigPanel .open .section:after {
  1234. content: '▼';
  1235. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1236. transform: rotate(180deg); -webkit-transform: rotate(180deg);
  1237. transition: transform 0.2s ease-in-out ; -webkit-transition: -webkit-transform 0.2s ease-in-out;
  1238. }
  1239. #watchItLaterConfigPanel .section > div {
  1240. padding: 8px 0 8px 12px; box-shadow: 0 0 4px black;
  1241. }
  1242. #watchItLaterConfigPanel .section > div > span {
  1243. {*background: #333;*}
  1244. }
  1245. #watchItLaterConfigPanel li:not(.section) {
  1246. background: #fff; border-width: 0px 0px 0px 24px; border-style: solid; border-color: #fff;
  1247. max-height: 0px; overflow: hidden;
  1248. transition: max-height 0.4s ease-in-out , border-width 0.4s ease-in-out;
  1249. }
  1250. #watchItLaterConfigPanel .open li:not(.section) {
  1251. max-height: 100px; border-width: 4px 0px 4px 24px;
  1252. transition: max-height 0.4s ease-in-out 0.2s, border-width 0.4s ease-in-out 0.2s;
  1253. }
  1254. #watchItLaterConfigPanel .section .description{
  1255. display: block; font-size: 80%;;
  1256. }
  1257. #watchItLaterConfigPanel .shortcutSetting:not(.enable) span :not(.enable){
  1258. color: silver;
  1259. }
  1260. #watchItLaterConfigPanel .shortcutSetting .enable {
  1261. cursor: pointer; margin: auto 10px;
  1262. }
  1263. #watchItLaterConfigPanel .shortcutSetting .enable:before {
  1264. content: '○ ';
  1265. }
  1266. #watchItLaterConfigPanel .shortcutSetting.enable .enable:before {
  1267. content: '㋹ '; color: blue;
  1268. }
  1269. #watchItLaterConfigPanel .shortcutSetting .ctrl, #watchItLaterConfigPanel .shortcutSetting .alt, #watchItLaterConfigPanel .shortcutSetting .shift {
  1270. cursor: pointer; border: 2px outset; margin: 4px 4px; padding: 2px 4px; width: 180px; border-radius: 4px;background: #eee;
  1271. }
  1272. #watchItLaterConfigPanel .shortcutSetting.ctrl .ctrl, #watchItLaterConfigPanel .shortcutSetting.alt .alt, #watchItLaterConfigPanel .shortcutSetting.shift .shift {
  1273. border: 2px inset; color: blue;
  1274. }
  1275. #watchItLaterConfigPanel .hoverMenuDelay input {
  1276. width: 50px; ime-mode: disabled; text-align: center;
  1277. }
  1278.  
  1279.  
  1280. {* 動画検索画面に出るお気に入りタグ・お気に入りマイリスト *}
  1281. .videoExplorerMenu .watchItLaterMenu.open,
  1282. .videoExplorerMenu .watchItLaterMenu.opening {
  1283. background: -moz-linear-gradient(center top , #D1D1D1, #FDFDFD) repeat scroll 0 0 transparent !important;
  1284. background: -webkit-gradient(linear, left top, left bottom, from(#D1D1D1), to(#FDFDFD)) !important;
  1285. border-bottom: 0 !important;
  1286. }
  1287. .videoExplorerMenu .watchItLaterMenu {
  1288. position: relative;
  1289. {*background: -moz-linear-gradient(center top , whitesmoke 0%, #E1E1E1 100%) repeat scroll 0 0 transparent;*}
  1290. {*box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.1) inset;*}
  1291. {*background: #f5f5f5;*}
  1292. border-bottom: 1px solid #CCCCCC;
  1293. }
  1294. .videoExplorerMenu .watchItLaterMenu:hover{
  1295. background: #dbdbdb;
  1296. }
  1297. .videoExplorerMenu .watchItLaterMenu {
  1298. padding: 0 12px; display: block; color: black;
  1299. }
  1300. .videoExplorerMenu .slideMenu{
  1301. width: 100%; height: auto !important;
  1302. overflow-x: hidden;
  1303. overflow-y: auto;
  1304. padding: 0;
  1305. background: #fdfdfd;
  1306. border-top: 0 !important;
  1307. display: block;
  1308. max-height: 0;
  1309. transition: max-height 0.5s ease-in-out;
  1310. }
  1311. .videoExplorerMenu .slideMenu.open {
  1312. max-height: 2000px;
  1313. transition: max-height 1s ease-in-out;
  1314. }
  1315. .videoExplorerMenu .toggleVideoExplorerMenu a {
  1316. color: black; display: block;
  1317. }
  1318. .videoExplorerMenu .toggleVideoExplorerMenu a:after {
  1319. content: "▼"; position: absolute; background: none; top: 0px; right: 10px; color: #ccc;
  1320. }
  1321. .videoExplorerMenu .toggleVideoExplorerMenu.open a:after {
  1322. content: "▲";
  1323. }
  1324.  
  1325. .videoRankingList .isCategory {
  1326. position: relative;
  1327. }
  1328.  
  1329. .rankingCategoryToggle {
  1330. position: absolute;
  1331. display: none;
  1332. height: 20px;
  1333. padding: 0px 8px;
  1334. right: 14px;
  1335. top: 0;
  1336. cursor: pointer;
  1337. border: 1px solid;
  1338. color: #666;
  1339. outline: none;
  1340. }
  1341. .rankingCategoryToggle::-moz-focus-inner {
  1342. border: 0px;
  1343. }
  1344. .slideMenu.open .isCategory:hover .rankingCategoryToggle {
  1345. display: block;
  1346. }
  1347. .categoryClose .rankingCategoryToggle .close, .rankingCategoryToggle .open{
  1348. display: none;
  1349. }
  1350. .categoryClose .rankingCategoryToggle .open{
  1351. display: inline;
  1352. }
  1353. .videoRankingList li:not(.isCategory) {
  1354. transition: max-height 0.5s;
  1355. max-height: 50px; overflow:hidden;
  1356. margin-left: 8px;
  1357. }
  1358. .videoRankingList .categoryClose:not(.isCategory) {
  1359. max-height: 0px;
  1360. }
  1361.  
  1362.  
  1363. .videoExplorerMenu .slideMenu ul{
  1364. }
  1365. .videoExplorerMenu .slideMenu ul li{
  1366. background: #fdfdfd; padding: 0; border: 0;font-size: 90%; height: auto !important;
  1367. }
  1368. .videoExplorerMenu .slideMenu ul li a{
  1369. line-height: 165%; background: none; display: block;
  1370. }
  1371. .videoExplorerMenu.w_touch .slideMenu ul li a{
  1372. line-height: 300%; font-size: 120%; color: black;
  1373. }
  1374. .videoExplorerMenu .slideMenu ul li a:before{
  1375. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  1376. display: inline-block;
  1377. height: 14px;
  1378. margin: -4px 4px 0 0;
  1379. vertical-align: middle;
  1380. width: 18px;
  1381. content: ""
  1382. }
  1383. .videoExplorerMenu .slideMenu ul li a.defMylist:before{ background-position: 0 -253px;}
  1384. .videoExplorerMenu .slideMenu ul li.folder0 a:before{ background-position: 0 0;}
  1385. .videoExplorerMenu .slideMenu ul li.folder1 a:before{ background-position: 0 -23px;}
  1386. .videoExplorerMenu .slideMenu ul li.folder2 a:before{ background-position: 0 -46px;}
  1387. .videoExplorerMenu .slideMenu ul li.folder3 a:before{ background-position: 0 -69px;}
  1388. .videoExplorerMenu .slideMenu ul li.folder4 a:before{ background-position: 0 -92px;}
  1389. .videoExplorerMenu .slideMenu ul li.folder5 a:before{ background-position: 0 -115px;}
  1390. .videoExplorerMenu .slideMenu ul li.folder6 a:before{ background-position: 0 -138px;}
  1391. .videoExplorerMenu .slideMenu ul li.folder7 a:before{ background-position: 0 -161px;}
  1392. .videoExplorerMenu .slideMenu ul li.folder8 a:before{ background-position: 0 -184px;}
  1393. .videoExplorerMenu .slideMenu ul li.folder9 a:before{ background-position: 0 -207px;}
  1394.  
  1395. .videoExplorerMenu .slideMenu ul li.g_ent2 a:before { background-position: 0 -23px;}
  1396. .videoExplorerMenu .slideMenu ul li.g_life2 a:before { background-position: 0 -46px;}
  1397. .videoExplorerMenu .slideMenu ul li.g_politics a:before { background-position: 0 -69px;}
  1398. .videoExplorerMenu .slideMenu ul li.g_tech a:before { background-position: 0 -92px;}
  1399. .videoExplorerMenu .slideMenu ul li.g_culture2 a:before { background-position: 0 -115px;}
  1400. .videoExplorerMenu .slideMenu ul li.g_other a:before { background-position: 0 -138px;}
  1401. .videoExplorerMenu .slideMenu ul li.r18 a:before { background-position: 0 -207px;}
  1402. .videoExplorerMenu .slideMenu ul li.all a.all,
  1403. .videoExplorerMenu .slideMenu ul li.g_ent2 a.g_ent2,
  1404. .videoExplorerMenu .slideMenu ul li.g_life2 a.g_life2,
  1405. .videoExplorerMenu .slideMenu ul li.g_politics a.g_politics,
  1406. .videoExplorerMenu .slideMenu ul li.g_tech a.g_tech,
  1407. .videoExplorerMenu .slideMenu ul li.g_culture2 a.g_culture2,
  1408. .videoExplorerMenu .slideMenu ul li.g_other a.g_other,
  1409. .videoExplorerMenu .slideMenu ul li.r18 a.r18
  1410. { font-weight: bolder; border-top: 1px dotted #ccc; }
  1411.  
  1412.  
  1413. .videoExplorerMenu .slideMenu ul li a:after{
  1414. background: none !important;
  1415. }
  1416. .videoExplorerMenu .slideMenu ul li a:hover{
  1417. background: #f0f0ff;
  1418. }
  1419. .videoExplorerMenu .slideMenu ul .reload{
  1420. cursor: pointer; border: 1px solid; padding: 0;
  1421. }
  1422.  
  1423. .videoExplorerMenu .tagSearchHistory {
  1424. border-radius: 0px; margin-top: 2px; padding: 4px; background: #ccc;
  1425. }
  1426. .videoExplorerMenu .itemList > li, #videoExplorerExpand {
  1427. background: #f5f5f5;
  1428. }
  1429. .videoExplorerMenu .itemList ul > li:hover {
  1430. background: #e7e7e7;
  1431. }
  1432. .videoExplorerMenu .itemList ul > li.active {
  1433. background: #343434;
  1434. }
  1435.  
  1436.  
  1437. {* 動画タグが1行以下の時 *}
  1438. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1439. height: 12px; padding: 6px 4px 2px;
  1440. }
  1441. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit .toggleText{
  1442. display: none;
  1443. }
  1444. {* 動画タグが2行以下の時 *}
  1445. body:not(.full_with_browser) .tag2Lines #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1446. height: 36px;
  1447. }
  1448. {* タグ領域とプレイヤーの隙間をなくす *}
  1449. body:not(.full_with_browser) #videoTagContainer, body:not(.full_with_browser) #videoHeader .videoMenuToggle {
  1450. margin-bottom: -10px;
  1451. }
  1452. #videoHeaderMenu .searchContainer .searchText {
  1453. margin-top: -8px;
  1454. }
  1455. #videoHeaderMenu .clear-button {
  1456. position: absolute;
  1457. top: -8px;
  1458. right: 0px;
  1459. }
  1460.  
  1461. body.size_small #playerContainerWrapper {
  1462. padding: 0;
  1463. }
  1464.  
  1465. {* ニュース履歴 *}
  1466. body.videoExplorer #textMarquee .openNewsHistory, body.videoExplorer #textMarquee .newsHistory {
  1467. display: none;
  1468. }
  1469. #textMarquee .openNewsHistory {
  1470. position: absolute; width: 30px;
  1471. font-size: 13px; padding: 0; margin: 0; height: 28px;
  1472. cursor: pointer;
  1473. bottom: 0;
  1474. background: none repeat scroll 0 0 transparent;
  1475. border: 1px none;
  1476. border-radius: 2px 2px 2px 2px;
  1477. cursor: pointer;
  1478. right: 18px;
  1479. z-index: 200;
  1480. }
  1481. #textMarquee .newsHistory {
  1482. position: absolute;
  1483. bottom: 0px; right: 0px; width: 100%;
  1484. max-height: 132px;
  1485. min-height: 40px;
  1486. overflow-y: auto;
  1487. overflow-x: hidden;
  1488. z-index: 1;
  1489. padding: 4px;
  1490. display: none;
  1491. background: #333;
  1492. text-align: left;
  1493. font-size: 14px;
  1494. padding: 0;
  1495. }
  1496. #textMarquee .newsHistory li{
  1497. padding: 0 2px;
  1498. }
  1499. #textMarquee .newsHistory li:nth-child(odd){
  1500. background: #444;
  1501. }
  1502. #textMarquee .newsHistory li:nth-child(even){
  1503. background: #333;
  1504. }
  1505.  
  1506. body #popupMarquee {
  1507. width: 360px;
  1508. }
  1509. {* 半透明だとflashの上に来ると描画されないので強制的に黒にする(Chromeは平気) *}
  1510. body.full_with_browser #popupMarquee.popupMarqueeBottomLeft {
  1511. background: #000 !important; width: 400px; opacity: 1;
  1512. }
  1513. body.full_with_browser #playerContainer {
  1514. margin-left: 0 !important;
  1515. }
  1516. body:not(.full_with_browser) #playerContainerWrapper {
  1517. padding: 0px;
  1518. }
  1519. body.full_with_browser #playerContainer, body.size_small #playerContainer {
  1520. top: auto;
  1521. }
  1522. body.full_with_browser.no_setting_panel .videoExplorerMenu {
  1523. display:none;
  1524. }
  1525.  
  1526.  
  1527. body:not(.videoExplorer) {*#playlist:not(.nico-bucket-videoExplorer-b)*} #videoExplorerExpand {
  1528. display: none;
  1529. }
  1530. #outline .openVideoExplorer {
  1531. display: none;
  1532. }
  1533. #outline.w_hideSearchExpand .openVideoExplorer {
  1534. display: inline-block;
  1535. }
  1536.  
  1537. .videoExplorerMenu .quickSearchInput {
  1538. background: none repeat scroll 0 0 #F4F4F4;
  1539. border: 1px inset silver;
  1540. left: 60px;
  1541. padding-left: 4px;
  1542. position: absolute;
  1543. top: 2px;
  1544. width: 180px;
  1545. }
  1546. .videoExplorerMenu.w_touch .quickSearchInput {
  1547. top: 4px; font-size: 20px;
  1548. }
  1549. .videoExplorerMenu .clear-button {
  1550. position: absolute;
  1551. width: 20px;
  1552. height: 20px;
  1553. top: 4px;
  1554. left: 250px;
  1555. line-height: 18px;
  1556. font-size: 16px;
  1557. padding: 0;
  1558. background: #e5e5e5;
  1559. text-align: center;
  1560. color: #999;
  1561. cursor: pointer;
  1562. display: none;
  1563. box-sizing: border-box;
  1564. -webkit-box-sizing: border-box;
  1565. -moz-box-sizing: border-box;
  1566. }
  1567.  
  1568. .videoExplorerContent .contentItemList .column4 {
  1569. text-align: center;
  1570. }
  1571. .videoExplorerContent .contentItemList .column4 .balloon {
  1572. bottom: auto; top: 10px;
  1573. }
  1574. .videoExplorerContent .contentItemList .column4 .videoInformation>.info {
  1575. font-size: 85%;
  1576. }
  1577. .videoExplorerContent .contentItemList .column4 .videoInformation>.info .info{
  1578. color: #000;
  1579. }
  1580. .videoExplorerContent .contentItemList .column4 .videoInformationOuter {
  1581. width: 100px; height: 48px; margin: auto; color: #666; text-align: left;
  1582. }
  1583. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  1584. height: 220px;
  1585. }
  1586. .column1 .itemMylistComment {
  1587. font-size: 85%; color: #666; display: none;
  1588. color: #400; border: 1px solid #ccc; padding: 0 4px 0px; line-height: 130%; border-radius: 4px;
  1589. }
  1590. .column1 .itemMylistComment:before {
  1591. content: 'マイリストコメント ';
  1592. background: #ccc; border-radius: 0 0 8px 0; display: inline-block; margin: 0 4px 4px -4px; padding: 2px;
  1593. min-width: 100px;
  1594. }
  1595. .log-user-video-review .column1 .itemMylistComment {
  1596. color: #004;
  1597. }
  1598. .log-user-video-review .column1 .itemMylistComment:before {
  1599. content: 'レビュー ';
  1600. }
  1601. .column1 .itemMylistComment:after {
  1602. content: '';
  1603. }
  1604. .column1 .itemMylistComment pre {
  1605. font-family: inherit;
  1606. display: inline;
  1607. white-space: pre-wrap;
  1608. }
  1609.  
  1610. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer {
  1611. display: none;
  1612. }
  1613. .videoExplorerContent .contentItemList .nicorepoResult .column1 .nicorepoOwnerIconContainer {
  1614. float: right; display: block;
  1615. padding: 24px 14px 0 4px;
  1616. }
  1617. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer img {
  1618. height: 48px;
  1619. }
  1620.  
  1621. .videoExplorerBody.dummyMylist #searchResultContainer .favMylistEditContainer,
  1622. .videoExplorerBody.dummyMylist:not(.ranking) #searchResultMylistSortOptions,
  1623. .videoExplorerBody.dummyMylist .favMylistEditContainer,
  1624. .videoExplorerBody.dummyMylist:not(.ownerNicorepo) #searchResultHeader {
  1625. display: none !important;
  1626. }
  1627.  
  1628. .videoExplorerContent .contentItemList .thumbnailHoverMenu {
  1629. position: absolute; padding: 0; z-index: 100;
  1630. display: none;
  1631. bottom: -1px; left: 0px;
  1632. }
  1633. .videoExplorerContent .contentItemList .deleteFromMyMylist {
  1634. cursor: pointer; font-size: 70%; border: 1px solid #ccc; padding: 0;
  1635. display: none;
  1636. }
  1637. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1638. cursor: pointer; font-size: 70%; border: 1px solid #ccc;;
  1639. }
  1640. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1641. padding: 0 4px;
  1642. }
  1643. .videoExplorerContent .contentItemList .item:hover .thumbnailHoverMenu {
  1644. display: block;
  1645. }
  1646. .videoExplorerContent .contentItemList .log-user-video-upload {
  1647. background: #ffe; border-radius: 4px;
  1648. }
  1649. .videoExplorerContent .contentItemList .nicorepoResult .itemVideoDescription, .videoExplorerContent .contentItemList .nicorepoResult .videoTitle{
  1650. }
  1651. .videoExplorerContent .contentItemList.channelGuideVideo {
  1652. background: #eff; {* 検索結果にチャンネル動画が紛れ込むようになったのでわかりやすく *}
  1653. }
  1654.  
  1655. #videoExplorer.w_deflist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist,
  1656. #videoExplorer.w_mylist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist
  1657. {
  1658. display: inline-block;
  1659. }
  1660.  
  1661. #playlist .generationMessage {
  1662. cursor: pointer;
  1663. }
  1664. #playlist .generationMessage:hover {
  1665. text-decoration: underline;
  1666. }
  1667. #playlist .generationMessage:after {
  1668. content: "▼";
  1669. }
  1670.  
  1671. #yukkuriPanel {
  1672. position: fixed; z-index: 1500; bottom: 0; left: 0; display: inline-block;
  1673. transition: bottom 0.2s ease;
  1674. }
  1675. #yukkuriPanel.mylistPanelLeft {
  1676. bottom: 24px;
  1677. }
  1678. body.w_noNicoru .nicoru-button{
  1679. left: -9999; display: none !important;
  1680. }
  1681. body.w_noNicoru .menuOpened #videoMenuTopList li.videoMenuListNicoru .nicoru-button{
  1682. display: block !important;
  1683. }
  1684. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li {
  1685. margin: 0 18px 4px 0;
  1686. }
  1687. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlContainer, body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlEditContainer {
  1688. padding: 1px 0;
  1689. }
  1690.  
  1691. .userProfile.w_touch {
  1692. font-size: 150%; line-height: 120%;
  1693. }
  1694. .resultPagination.w_touch {
  1695. font-size: 200%;
  1696. }
  1697. .resultPagination.w_touch li{
  1698. padding: 4px 16px;
  1699. }
  1700. select.w_touch {
  1701. font-size: 200%;
  1702. }
  1703. {* 真・browserFullモード *}
  1704. body.full_with_browser.hideCommentInput #nicoplayerContainerInner {
  1705. {* コメント入力欄は動画上表示にするのではなく、画面外に押し出す事によって見えなくする *}
  1706. margin-top: -10px; margin-bottom: -30px !important;
  1707. }
  1708.  
  1709. {*body.full_with_browser:not(.w_fullScreenMenu) .mylistPopupPanel.fixed,*}
  1710. body.full_with_browser .yukkuriButton
  1711. { display:none; }
  1712.  
  1713. #fullScreenMenuContainer {
  1714. -webkit-transition: opacity 0.2s ease-out;
  1715. position:absolute;
  1716. display: none;
  1717. }
  1718. body.full_with_browser #fullScreenToggleContainer {
  1719. background: black;
  1720. display: block;
  1721. bottom: 100px;
  1722. right: 50px;
  1723. z-index: 10000;
  1724. min-width: 400px;
  1725. cursor: nw-resize;
  1726. opacity: 0;
  1727. color: white;
  1728. box-shadow: 2px 2px 2px silver;
  1729. border-radius: 4px;
  1730. }
  1731. body.full_with_browser #fullScreenToggleContainer .title {
  1732. color: #ffc; font-size: 120%;
  1733. }
  1734. body.full_with_browser #fullScreenToggleContainer .ownerIcon {
  1735. float: left; height: 55px; padding: 8px;
  1736. }
  1737. body.full_with_browser #fullScreenToggleContainer:hover, body.full_with_browser #fullScreenToggleContainer.active, body.w_fullScreenMenu #fullScreenToggleContainer {
  1738. opacity: 1;
  1739. }
  1740. body:not(.full_with_browser) #fullScreenToggleContainer { display: none; }
  1741.  
  1742. #sharedNgSettingContainer {
  1743. display: inline-block; font-size: 80%; position: absolute; top: -18px; left: 5px;
  1744. }
  1745. #sharedNgSetting {
  1746. background: #ddd; border: 1px solid silver;
  1747. }
  1748. {* ニュース消す *}
  1749. #content.noNews #textMarquee {
  1750. display: none !important;
  1751. }
  1752. body:not(.videoExplorer):not(.full_with_browser) #content.noNews #playerContainer {
  1753. min-height: 461px;
  1754. }
  1755. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper {
  1756. height: auto !important; position: absolute; bottom: 18px;
  1757. }
  1758. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabContainer {
  1759. bottom: -17px;
  1760. }
  1761. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #playerTabContainer {
  1762. bottom: 20px;
  1763. }
  1764. #playerTabWrapper.w_videoInfo #playerTabContainer, #playerTabWrapper.w_ichiba #playerTabContainer, #playerTabWrapper.w_review #playerTabContainer {
  1765. bottom: 0px !important;
  1766. }
  1767. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_videoInfo,
  1768. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_ichiba,
  1769. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_review
  1770. {
  1771. height: auto !important; position: absolute; bottom: 2px;
  1772. }
  1773. {* body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #leftPanel {
  1774. height: auto !important; position: absolute; bottom: 2px;
  1775. }*}
  1776. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerCommentPanel {
  1777. height: 100% !important;
  1778. }
  1779. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #appliPanel {
  1780. bottom: -18px !important;
  1781. }
  1782. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer {
  1783. height: auto;
  1784. }
  1785. #outline.noIchiba #nicoIchiba, #outline.noReview #videoReview{
  1786. display: none;
  1787. }
  1788. #bottomContentTabContainer.noBottom .outer, #bottomContentTabContainer.noBottom #pageFooter {
  1789. display: none;
  1790. }
  1791. #bottomContentTabContainer.noBottom #outline {
  1792. background: #141414; padding-top: 0; padding-bottom: 35px;
  1793. }
  1794.  
  1795. #content.w_flat_gray #playerContainerWrapper {
  1796. background: #666;
  1797. }
  1798. #content.w_flat_white #playerContainerWrapper {
  1799. background: #f4f4f4;
  1800. }
  1801. #content.w_flat_gray #wallImageContainer, #content.w_flat_white #wallImageContainer,
  1802. #content.w_flat_gray #chipWallList, #content.w_flat_white #chipWallList {
  1803. display: none !important;
  1804. }
  1805. #content #chipWallList {
  1806. right: auto; left: -42px;
  1807. }
  1808. #content #playlist .playlistInformation {
  1809. background: #444;
  1810. }
  1811. #content #videoExplorerExpand a {
  1812. text-shadow: none;
  1813. }
  1814.  
  1815. .videoMenuToggle {
  1816. -webkit-transform-origin: 100% 100%; -webkit-transition: -webkit-transform 0.4s;
  1817. transform-origin: 100% 100%; transition: transform 0.4s;
  1818. z-index: 1000;
  1819. }
  1820. #content.w_compact .videoHeaderTitle {
  1821. letter-spacing: -1px;
  1822. }
  1823. #content.w_compact .videoDetailExpand .arrow {
  1824. position: absolute; top: 8px; right: -24px;
  1825. }
  1826. #content.w_compact .tag1Line .videoMenuToggle {
  1827. transform: scale(0.8, 0.41); -webkit-transform: scale(0.8, 0.41);
  1828. }
  1829. #content.w_compact .tag2Lines .videoMenuToggle {
  1830. transform: scale(0.8); -webkit-transform: scale(0.8);
  1831. }
  1832. #content.w_compact #topVideoInfo .parentVideoInfo {
  1833. margin-top: -9px; margin-bottom: 9x;
  1834. }
  1835. #content.w_compact #topVideoInfo .parentVideoInfo .cct{
  1836. margin-bottom: 0;
  1837. }
  1838. #content.w_compact #topVideoInfo .parentVideoInfo .videoThumb{
  1839. margin-top: 4px;
  1840. }
  1841. #content.w_compact #topVideoInfo .ch_prof, #content.w_compact #topVideoInfo .userProfile {
  1842. min-width: 297px; margin-top: -1px; border: 1px solid #e7e7e7;
  1843. }
  1844. #content.w_compact #videoHeaderDetail .videoDetailExpand{
  1845. height: auto; padding: 0;
  1846. }
  1847. #content.w_compact #topVideoInfo .videoDescription.description {
  1848. background: #fff; margin: 10px 0 0;padding: 4px ;width: 1000px;{* base - 8 *} {*font-size: 90%;*}
  1849. }
  1850.  
  1851. {* 本家の幅が変わったら変える必要がある。 変数化した方が楽かも base = 1008 *}
  1852. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  1853. width: 1318px; {* base + 310 *}
  1854. }
  1855. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact #topVideoInfo .videoDescription.description {
  1856. width: 1226px; {* base + 218 *}
  1857. }
  1858. body:not(.full_with_browser) #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  1859. width: 1092px; {* base + 84 *}
  1860. }
  1861. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoTagContainer {
  1862. width: 1263px; {* base + 255 *}
  1863. }
  1864. body:not(.full_with_browser) #content.w_compact.w_wide #videoTagContainer {
  1865. width: 1040px; {* base + 32 *}
  1866. }
  1867. body:not(.full_with_browser) #content.w_compact #videoTagContainer {
  1868. width: 948px; {* base - 60 *}
  1869. }
  1870. body:not(.full_with_browser) #content.w_compact #videoHeader, #foot_inner {
  1871. width: 1008px; {* base + 48 *}
  1872. }
  1873. body:not(.full_with_browser).size_normal #content.w_compact #videoHeader, .size_normal #foot_inner {
  1874. width: 1234px; {* base + 226 *}
  1875. }
  1876. body:not(.full_with_browser) #content.w_compact.w_wide #videoHeader {
  1877. width: 1100px;
  1878. }
  1879. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoHeader {
  1880. width: 1326px;
  1881. }
  1882.  
  1883. #content.w_compact #topVideoInfo .videoMainInfoContainer{
  1884. padding: 0;
  1885. }
  1886. #content.w_compact #videoDetailInformation{
  1887. border-top: 0;
  1888. }
  1889. #content.w_compact #videoHeaderMenu .searchContainer {
  1890. top: -16px;
  1891. }
  1892. #content.w_compact .videoInformation{
  1893. margin: -4px 0 ;
  1894. }
  1895. #content.w_compact #topVideoInfo .videoStats {
  1896. margin-bottom: 2px;
  1897. }
  1898. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1899. width: 72px;
  1900. }
  1901. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList {
  1902. padding-left: 85px;
  1903. }
  1904. body.full_with_browser #videoHeaderTagList { background: #fafafa; }
  1905. #content.w_compact #topVideoInfo {
  1906. margin: 4px 0 4px;
  1907. }
  1908. #content.w_compact #topVideoInfo .videoShareLinks .socialLinks {
  1909. margin-top: -6px;
  1910. }
  1911. #outline.w_compact #videoInfoHead{
  1912. margin: 0 ;
  1913. }
  1914. #outline.w_compact .videoInformation #videoTitle {
  1915. margin: -4px 0 0;
  1916. }
  1917. #outline.w_compact .videoInformation #videoStats {
  1918. margin-top: -4px;
  1919. }
  1920. #outline.w_compact .videoInformation #videoStats .ranking {
  1921. margin: 0 0 4px;
  1922. }
  1923. #outline.w_compact #videoShareLinks {
  1924. margin: 0;
  1925. }
  1926. #outline.w_compact #bottomVideoDetailInformation {
  1927. margin: -18px 0 0;
  1928. }
  1929. #outline.w_compact .infoHeadOuter .videoEditMenuExpand {
  1930. position: absolute; top: 0;
  1931. }
  1932. #outline.w_compact .videoEditMenu {
  1933. margin: 0;
  1934. }
  1935. #outline.w_compact .videoDescription {
  1936. font-size: 90%; margin-top: -8px; padding: 0 0 4px 4px;
  1937. }
  1938. #outline.w_compact #videoComment {
  1939. margin: 0px; border: 1px solid silver; border-radius: 4px 4px 4px 4px; padding: 0 4px;
  1940. }
  1941. #outline.w_compact #videoComment h4{
  1942. padding-left: 4px;
  1943. }
  1944. #outline.w_compact .videoMainInfoContainer {
  1945. border-bottom: 0; margin-bottom: 0;
  1946. }
  1947. #outline.w_compact {
  1948. border-bottom: 0; margin-bottom: 0;
  1949. }
  1950.  
  1951. #outline.w_compact .sidebar { width: 300px; }
  1952.  
  1953. #outline.w_compact #ichibaMain dl.ichiba_mainitem {
  1954. margin: 0 22px 30px 0;
  1955. }
  1956. #footer { z-index: 1; }
  1957.  
  1958. body.en-us #playerAlignmentArea, body.zh-tw #playerAlignmentArea {
  1959. {*padding-right: 0;*}
  1960. }
  1961. #footer .toggleBottom {
  1962. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  1963. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  1964. }
  1965. #footer:hover .toggleBottom {
  1966. border: 1px outset; background: #ccc;
  1967. }
  1968. #footer .toggleBottom:hover {
  1969. box-shadow: 0px 0px 8px #fff;
  1970. }
  1971. #footer.noBottom .toggleBottom {
  1972. border-radius: 0 0 16px 16px;
  1973. }
  1974. #footer .toggleBottom .openBottom, #footer.noBottom .toggleBottom .closeBottom {
  1975. display: none;
  1976. }
  1977. #footer.noBottom .toggleBottom .openBottom {
  1978. display: block;
  1979. }
  1980. #footer .toggleBottom>div {
  1981. -webkit-transform: scaleX(3); transform: scaleX(3);
  1982. }
  1983. #footer .toggleBottom {
  1984. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  1985. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  1986. }
  1987. #footer:hover .toggleBottom {
  1988. border: 1px outset; background: #ccc;
  1989. }
  1990. #footer .toggleBottom:hover {
  1991. box-shadow: 0px 0px 8px #fff;
  1992. }
  1993.  
  1994. #footer.noBottom #foot_inner { padding: 0; }
  1995. #footer.noBottom a:nth-of-type(3):after, #footer.noBottom a:nth-of-type(6):after {
  1996. content: ' | '; color: white;
  1997. }
  1998. #footer.noBottom br { display: none; }
  1999. html { background: #141414; }
  2000. .videoExplorer #videoExplorer,
  2001. .videoExplorer #videoExplorer .videoExplorerBody,
  2002. .videoExplorerContentWrapper
  2003. {
  2004. background: none;
  2005. }
  2006.  
  2007. .animateBlink {
  2008. -webkit-transition: 1s ease-in; transition: 1s ease-in;
  2009. }
  2010.  
  2011. .w_compact .toggleDetailExpand, .w_compact .shortVideoInfo {
  2012. display: none;
  2013. }
  2014. .videoDetailToggleButton {
  2015. cursor: pointer;
  2016. }
  2017. #leftPanel {
  2018. {*border-radius: 4px 4px 4px 4px;*}
  2019. display: none; padding: 0; position: absolute; text-align: left; top: 0; z-index: 101;
  2020. }
  2021. body.ja-jp #leftPanel { display: none; }
  2022. body:not(.videoExplorer) #leftPanel { display: none; }
  2023.  
  2024.  
  2025. body.full_with_browser #playerTabWrapper, body.full_with_browser:not(.videoExplorer) .w_wide #playerTabWrapper {
  2026. top: auto !important; bottom: 3000px !important; right: 50px !important;
  2027. transition: bottom 0.2s ease-out; max-height: 500px;
  2028. }
  2029.  
  2030. body.full_with_browser.w_fullScreenMenu:not(.videoExplorer) #playerTabWrapper {
  2031. top: auto !important; bottom: 200px !important; right: 50px !important;
  2032. }
  2033.  
  2034. #fullScreenMenuContainer { display: none; }
  2035. body.full_with_browser #fullScreenMenuContainer {
  2036. display: block; position: absolute; bottom: 3000px; left: 50px; z-index: 10000;
  2037. background: #fff; cursor: pointer; transition: bottom 0.2s ease-out;
  2038. }
  2039. body.full_with_browser.w_fullScreenMenu #fullScreenMenuContainer {
  2040. bottom: 100px;
  2041. }
  2042.  
  2043. #fullScreenMenuContainer .button {
  2044. cursor: pointer; transition: color 0.4s ease-out;
  2045. }
  2046. #fullScreenMenuContainer .modeStatus { display: none; font-weight: bolder; }
  2047.  
  2048. body:not(.fullWithPlaylist) #fullScreenMenuContainer .fullScreenModeSwitch .playlistClosing,
  2049. body.fullWithPlaylist #fullScreenMenuContainer .fullScreenModeSwitch .playlistOpening
  2050. { display: inline; }
  2051.  
  2052. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch { color: blue; }
  2053. #nicoplayerContainerInner:not(.stageVideo) #fullScreenMenuContainer .stageVideoSwitch .mode_off,
  2054. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch .mode_on { display: inline; }
  2055.  
  2056.  
  2057. body.full_with_browser.w_fullScreenMenu .videoHeaderOuter {
  2058. position: absolute; z-index: 1000; width: 100%;
  2059. }
  2060. body.full_with_browser.w_fullScreenMenu #videoTagContainer {
  2061. display: block;
  2062. width: 100%;
  2063. margin-top: 0;
  2064. }
  2065. body.full_with_browser.w_fullScreenMenu #videoTagContainer #videoHeaderTagList {
  2066. padding-left: 0;
  2067. }
  2068. body.full_with_browser #videoTagContainer .toggleTagEdit {
  2069. display: none !important;
  2070. }
  2071.  
  2072. .popupMarqueeContent {
  2073. background: black;
  2074. }
  2075.  
  2076. #videoExplorer, #playlist {
  2077. transition: margin-left 0.2s ease-in-out;
  2078. }
  2079.  
  2080. .dummyMylist .editFavorite {
  2081. display: none;
  2082. }
  2083.  
  2084. {* 不要な時まで横スクロールバーが出てしまうので *}
  2085. #songrium_inline { overflow: hidden; }
  2086.  
  2087. .sideVideoInfo .videoLinkContainer {
  2088. display: inline-block;
  2089. white-space: nowrap;
  2090. }
  2091. .sideVideoInfo .nextPlayButton {
  2092. position: absolute;
  2093. margin-top: -6px;
  2094. margin-left: -30px;
  2095. width: 30px;
  2096. height: 30px;
  2097. background: url(http://res.nimg.jp/img/watch_q9/icon_nextplay.png);
  2098. {*background: url("http://res.nimg.jp/img/watch_zero/videoexplorer-s90d011f9a7.png") no-repeat scroll -37px 0 rgba(0, 0, 0, 0);*}
  2099. z-index: 100;
  2100. cursor: pointer;
  2101. text-indent: -999em;
  2102. overflow: hidden;
  2103. display: inline-block;
  2104. -webkit-transform: scale(1.0); transform: scale(1.0);
  2105. }
  2106.  
  2107. .nextPlayButton {
  2108. -webkit-transform: scale(1.5); transform: scale(1.5);
  2109. transition: transform 0.1s ease; -webkit-transition: -webkit-transform 0.1s ease;
  2110. }
  2111. .sideVideoInfo .nextPlayButton:hover {
  2112. -webkit-transform: scale(1.5); transform: scale(1.5);
  2113. }
  2114. .nextPlayButton:active, .sideVideoInfo .nextPlayButton:active {
  2115. -webkit-transform: scale(1.2); transform: scale(1.2);
  2116. }
  2117.  
  2118. .sideVideoInfo .nextPlayButton:active {
  2119. background-position-y: 30px;
  2120. }
  2121.  
  2122. body.w_disableHorizontalScroll {
  2123. overflow-x: hidden !important;
  2124. }
  2125.  
  2126. #videoTagContainerPin { display: none !important; } {* タグを固定しているか4行以上の時に現われるピン *}
  2127.  
  2128. .w_adjusted #selectionSideAdAds >* {
  2129. width: 100%; height: auto; max-width: 300px; max-height: 250px;
  2130. }
  2131.  
  2132. {* *}
  2133. .w_noHover {
  2134. pointer-events: none !important;
  2135. }
  2136. .w_noHover #playlist {
  2137. pointer-events: auto !important;
  2138. }
  2139. {* ソーシャルボタン *}
  2140. .area-JP .panel_ads_shown #playerTabContainer.w_noSocial.has_panel_ads .playerTabContent {
  2141. bottom: 80px;
  2142. }
  2143. .area-JP #playerTabContainer.w_noSocial .playerTabContent {
  2144. bottom: 4px;
  2145. }
  2146. #playerTabContainer.w_noSocial .playerTabAds {
  2147. bottom: 0;
  2148. }
  2149. #playerTabContainer.w_noSocial .socialButtons{
  2150. display: none;
  2151. }
  2152. .w_noSocial .nicoSpotAds {
  2153. bottom: 8px;
  2154. }
  2155.  
  2156.  
  2157. {* テレビちゃんメニュー スライドをやめる *}
  2158. body #videoHeader #videoMenuWrapper{
  2159. position: absolute; width: 324px; height: auto !important;
  2160. opacity: 0;
  2161. transition: opacity 0.4s ease;
  2162. right: 0px;
  2163. }
  2164. body #videoHeader.menuOpened #videoMenuWrapper{
  2165. z-index: 1000 !important;
  2166. border: 1px solid #000;
  2167. background: white;
  2168. box-shadow: 0px 0px 4px #000;
  2169. top: 110px;
  2170. bottom: auto;
  2171. opacity: 1;
  2172. }
  2173. body .tag1Line #videoHeader.menuOpened #videoMenuWrapper{
  2174. top: 62px;
  2175. }
  2176. body .tag2Lines #videoHeader.menuOpened #videoMenuWrapper{
  2177. top: 86px;
  2178. }
  2179. body #videoHeader.infoActive.menuOpened #videoMenuWrapper{
  2180. top: auto;
  2181. bottom: 48px;
  2182. }
  2183. {* body #videoHeader #videoMenuWrapper .defmylistButton, body #videoHeader #videoMenuWrapper .mylistButton {
  2184. display: none !important;
  2185. } *}
  2186. body #videoHeader #videoMenuTopList{
  2187. position: relative;
  2188. width: auto;
  2189. }
  2190. body #videoHeader.menuOpened #videoMenuWrapper .videoMenuList{
  2191. display: inline-block;
  2192. width: 60px;
  2193. min-height: 72px;
  2194. }
  2195. body #videoMenuTopList li.videoMenuListNicoru {
  2196. float: right;
  2197. min-height: 72px;
  2198. }
  2199. body #videoHeader.isAdult .videoMenuToggle, body #videoHeader.noAudioDownload .downloadButton {
  2200. display: inline-block;
  2201. opacity: 0.5;
  2202. pointer-events: none !important;
  2203. }
  2204. {* テレビちゃんメニューのスライド殺す *}
  2205. body #videoHeader.menuOpened #videoMenuWrapper {
  2206. margin-bottom: 0;
  2207. }
  2208. body #videoHeader.menuOpened #videoHeaderDetail {
  2209. margin-top: 8px;
  2210. }
  2211.  
  2212.  
  2213. .largeThumbnailPopup, .largeThumbnailPopup div{
  2214. background-color: #000;
  2215. background-size: contain;
  2216. background-repeat: no-repeat;
  2217. background-position: center center;
  2218. background-size: contain;
  2219. -moz-background-size: contain;
  2220. -webkit-background-size: contain;
  2221. -o-background-size: contain;
  2222. -ms-background-size: contain;
  2223. }
  2224.  
  2225. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  2226. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  2227. addStyle(__css__, 'watchItLaterStyle');
  2228. })(); // end of watchItLaterStyle
  2229.  
  2230.  
  2231.  
  2232.  
  2233.  
  2234. conf.load = function() {
  2235. try {
  2236. function loadStorage(key, def) {
  2237. if (window.localStorage[key] === void 0) { return def; }
  2238. return JSON.parse(window.localStorage.getItem(key));
  2239. }
  2240.  
  2241. for (var v in conf) {
  2242. if (typeof conf[v] === 'function') { continue; }
  2243. conf[v] = loadStorage('watchItLater_' + v, conf[v]);
  2244. }
  2245. } catch (e) {
  2246. }
  2247. };
  2248.  
  2249. conf.getValue = function(varName) {
  2250. return conf[varName];
  2251. };
  2252. conf.setValue = function(k, v) {
  2253. var lastValue = conf[k];
  2254. if (lastValue !== v) {
  2255. conf[k] = v;
  2256. window.localStorage.setItem('watchItLater_' + k, JSON.stringify(v));
  2257. EventDispatcher.dispatch('on.config.' + k, v, lastValue);
  2258. }
  2259. };
  2260. conf.load();
  2261.  
  2262. var console = (function(conf) {
  2263. if (conf.debugMode) { return window.console; }
  2264. var noop = function() {};
  2265. return {
  2266. log: noop,
  2267. error: noop,
  2268. trace: noop,
  2269. warn: noop,
  2270. table: noop,
  2271. time: noop,
  2272. timeEnd: noop
  2273. };
  2274. })(conf);
  2275.  
  2276. var ConfigPanel = (function($, conf, w) { // SettingPanel
  2277. var pt = function(){};
  2278. var $panel = null, $shadow = null;
  2279. var menus = [
  2280. {title: '再生開始・終了時の設定', className: 'videoStart'},
  2281. {title: '自動で全画面モードにする', varName: 'autoBrowserFull',
  2282. values: {'する': true, 'しない': false}, addClass: true},
  2283. {title: '自動全画面化オンでも、ユーザーニコ割のある動画は', varName: 'disableAutoBrowserFullIfNicowari',
  2284. values: {'全画面化しない': true, '全画面化する': false}},
  2285. {title: '自動で検索モードにする(自動全画面化オフ時)', varName: 'autoOpenSearch',
  2286. values: {'する': true, 'しない': false}},
  2287. {title: '動画の位置に自動スクロール(自動全画面化オフ時)', varName: 'autoScrollToPlayer',
  2288. values: {'する': true, 'しない': false}},
  2289. // {title: '終了時に全画面モードを解除(原宿と同じにする)', varName: 'autoNotFull',
  2290. // values: {'する': true, 'しない': false},
  2291. // description: '連続再生中は解除しません'},
  2292. {title: 'ウィンドウがアクティブの時だけ自動再生する', varName: 'autoPlayIfWindowActive',
  2293. description: 'QWatch側の設定パネルの自動再生はオフにしてください。\n■こんな人におすすめ\n・自動再生ONにしたいけど別タブで開く時は自動再生したくない\n・複数タブ開いたままブラウザ再起動したら全部のタブで再生が始まって「うるせー!」という経験のある人',
  2294. values: {'する': 'yes', 'しない': 'no'}},
  2295. {title: '動画が切り替わる時、ポップアップでタイトルと再生数を表示', varName: 'popupViewCounter',
  2296. description: '全画面状態で連続再生している時などに便利です',
  2297. values: {'する': 'always', '全画面時のみ': 'full', 'しない': 'none'}},
  2298.  
  2299. {title: 'プレイヤーの設定', className: 'playerSetting'},
  2300. {title: 'コメントパネルを広くする', varName: 'wideCommentPanel',
  2301. values: {'する': true, 'しない': false}},
  2302. {title: 'コメントパネルにNG共有設定を表示', varName: 'enableSharedNgSetting',
  2303. values: {'する': true, 'しない': false}, addClass: true},
  2304. {title: 'コメントの表示', varName: 'commentVisibility',
  2305. values: {'オフ': 'hidden', '最後の状態を記憶': 'lastState', 'オン': 'visible'}},
  2306. {title: '右のパネルに動画情報・市場・レビューを表示', varName: 'rightPanelJack', reload: true,
  2307. values: {'する': true, 'しない': false}},
  2308. {title: 'ページのヘッダに再生数表示', varName: 'headerViewCounter', reload: true,
  2309. values: {'する': true, 'しない': false}},
  2310. {title: 'ニコニコニュースの履歴を保持する', varName: 'enableNewsHistory', reload: true,
  2311. values: {'する': true, 'しない': false}},
  2312. {title: 'ニコニコニュースを消す', varName: 'hideNicoNews',
  2313. values: {'消す': true, '消さない': false}},
  2314. {title: 'プレイヤーの背景', varName: 'playerBgStyle',
  2315. description: 'ウォール機能より優先されます',
  2316. values: {'白': 'white', 'グレー': 'gray', 'ウォール': ''}},
  2317. {title: 'コメントの盛り上がりをグラフ表示', varName: 'enableHeatMap', reload: true,
  2318. description: '動画のどのあたりが盛り上がっているのか、わかりやすくなります',
  2319. values: {'する': true, 'しない': false}},
  2320. {title: '大画面をもっと大画面にする', varName: 'customPlayerSize',
  2321. description: '※有効にするとニコニコニュースが表示できなくなります。',
  2322. values: {'フルHD': '1080p', '720p': '720p', '自動調整(推奨)': 'auto', 'しない': ''}},
  2323. {title: 'プレイリスト消えないモード', varName: 'storagePlaylistMode', reload: true,
  2324. description: '有効にすると、リロードしてもプレイリストが消えなくなります。',
  2325. values:
  2326. (conf.debugMode ?
  2327. {'ウィンドウを閉じるまで': 'sessionStorage', 'ずっと保持': 'localStorage', 'しない': ''} :
  2328. {'有効(ウィンドウを閉じるまで)': 'sessionStorage', '無効': ''})
  2329. },
  2330. {title: '説明文中の動画IDにサムネイル表示', varName: 'enableDescriptionThumbnail', reload: true,
  2331. // description: 'Chrome+Tampermonkeyでは動きません',
  2332. values: {'有効': true, '無効': false}},
  2333.  
  2334.  
  2335. {title: '検索モードの設定', className: 'videoExplorer'},
  2336. {title: '検索モードを無効化', varName: 'disableVideoExplorer',
  2337. description: '無効にするとタグ検索などが原宿と同じになります。\nただし、自分で検索モードにしている時は検索モードで開きます',
  2338. values: {'する': true, 'しない': false}},
  2339. {title: 'プレイヤーをできるだけ大きくする (コメントやシークも可能にする)', varName: 'videoExplorerHack',
  2340. description: '便利ですがちょっと重いです。\n大きめのモニターだと快適ですが、小さいといまいちかも',
  2341. values: {'する': true, 'しない': false}},
  2342. {title: 'お気に入りタグを表示', varName: 'enableFavTags',
  2343. values: {'する': true, 'しない': false}},
  2344. {title: 'お気に入りマイリストを表示', varName: 'enableFavMylists',
  2345. description: '更新のあったリストが上に来るので、新着動画のチェックに便利です。',
  2346. values: {'する': true, 'しない': false}},
  2347. {title: '「マイリストから外す」ボタンを表示', varName: 'enableMylistDeleteButton',
  2348. description: 'マイリストの整理に便利。\n ※ 消す時に確認ダイアログは出ないので注意',
  2349. values: {'する': true, 'しない': false}},
  2350. {title: '検索時に関連タグを表示する', varName: 'enableRelatedTag',
  2351. values: {'する': true, 'しない': false}},
  2352. // {title: 'niconico新検索βを使う', varName: 'searchEngine',
  2353. // description: '投稿期間や動画長による絞り込みができるようになります',
  2354. // values: {'使う': 'sugoi', '使わない': 'normal'}},
  2355. {title: '1ページの表示件数', varName: 'searchPageItemCount',
  2356. values: {'100件': 100, '50件': 50, '32件': 32}},
  2357.  
  2358. {title: '全画面モードの設定', className: 'fullScreen'},
  2359. {title: '操作パネルとコメント入力欄を隠す', varName: 'controllerVisibilityInFull',
  2360. description: '全画面の時は少しでも動画を大きくしたい場合に便利',
  2361. values: {'隠す': 'hidden', '隠さない': ''}},
  2362. {title: '右下のマイリストメニュー', varName: 'hideMenuInFull',
  2363. values: {'完全に消す': 'hideAll', '色だけ変える': '', '目立たなくする': 'hide'}},
  2364. {title: 'ホイールを回したら動画情報を出す', varName: 'enableFullScreenMenu',
  2365. description: 'ホイールを大きく下に回すとメニューが出ます。タッチパネルも対応',
  2366. values: {'する': true, 'しない': false}},
  2367.  
  2368. {title: 'ページ下半身の設定', className: 'playerBottom'},
  2369. {title: 'ニコニコ市場の表示', varName: 'ichibaVisibility',
  2370. values: {'非表示': 'hidden', '表示': 'visible'}},
  2371. {title: 'レビューの表示', varName: 'reviewVisibility',
  2372. values: {'非表示': 'hidden', '表示': 'visible'}},
  2373.  
  2374. {title: '省スペース/軽量化設定', className: 'compact'},
  2375. {title: 'タグが2行以内の時に高さを詰める(ピン留め時のみ)', varName: 'enableAutoTagContainerHeight', reload: true,
  2376. values: {'詰める': true, '詰めない': false}},
  2377. {title: '動画情報の空きスペースを詰める', varName: 'compactVideoInfo',
  2378. description: '原宿ぐらいの密度になります。ちょっと窮屈かも',
  2379. values: {'詰める': true, '詰めない': false}},
  2380. // {title: '背景のグラデーションをなくす', varName: 'flatDesignMode',
  2381. // description: '軽い表示になります',
  2382. // values: {'なくす': 'on', 'なくさない': ''}},
  2383. {title: '「ニコる」をなくす', varName: 'noNicoru',
  2384. description: '画面上から見えなくなります。\nまた、コメントパネルの処理が軽くなります',
  2385. values: {'なくす': true, 'なくさない': false}},
  2386. {title: 'コメントパネルのマウスオーバー処理をなくす', varName: 'removeCommentPanelHoverEvent', reload: true,
  2387. description: 'マウスオーバー時のちらちらした物がなくなり、表示が軽くなります',
  2388. values: {'なくす': true, 'なくさない': false}},
  2389. {title: 'タグの自動更新を無効化', varName: 'disableTagReload',
  2390. values: {'する': true, 'しない': false}},
  2391. {title: '横スクロールバーを出なくする', varName: 'disableHorizontalScroll',
  2392. values: {'する': true, 'しない': false}},
  2393. {title: 'コメントパネル下のソーシャルボタン', varName: 'hideCommentPanelSocialButtons',
  2394. values: {'隠す': true, '隠さない': false}},
  2395. {title: 'GPUレイヤーを使用してみる(上級者用)', varName: 'enableGpuLayer', reload: true, debugOnly: true,
  2396. description: '環境によっては軽くなる かも しれません',
  2397. values: {'する': true, 'しない': false}},
  2398.  
  2399. {title: 'その他の設定', className: 'otherSetting'},
  2400. {title: '動画リンクにカーソルを重ねたらマイリストメニューを表示', varName: 'enableHoverPopup', reload: true,
  2401. description: 'マウスカーソルを重ねた時に出るのが邪魔な人はオフにしてください',
  2402. values: {'する': true, 'しない': false}},
  2403. {title: '動画リンクにカーソルを重ねてからメニューが出るまでの時間(秒)', varName: 'hoverMenuDelay',
  2404. type: 'text', description: '単位は秒。 標準は0.4です'},
  2405. {title: 'ニコレポのポップアップを置き換える', varName: 'replacePopupMarquee', reload: true,
  2406. description: '画面隅に出るポップアップの不可解な挙動を調整します',
  2407. values: {'する': true, 'しない': false}},
  2408. {title: '検索時のデフォルトパラメータ', varName: 'defaultSearchOption', type: 'text',
  2409. description: '常に指定したいパラメータ指定するのに便利です\n例: 「-グロ -例のアレ」とすると、その言葉が含まれる動画が除外されます'},
  2410. {title: '「@ジャンプ」を無効化', varName: 'ignoreJumpCommand', reload: true,
  2411. description: '勝手に他の動画に飛ばされる機能を無効化します。',
  2412. values: {'する': true, 'しない': false}},
  2413. {title: '「@ジャンプ」によるシーク無効化(無限ループなど)', varName: 'nicoSSeekCount', reload: true,
  2414. description: '完全に無効にする以外に、一動画あたりの回数を指定できます',
  2415. values: {'2回まで有効': 2, '1回まで有効': 1, '完全無効化': 0, 'しない': -1}},
  2416. {title: 'タッチパネル向けモード(画面を右フリックで開始)', varName: 'enableQTouch',
  2417. description: '指で操作しやすいように、一部のボタンやメニューが大きくなります',
  2418. values: {'使う': true, '使わない': false}},
  2419. {title: 'マイリストメニューの位置', varName: 'mylistPanelPosition',
  2420. values: {'左下': 'left', '右下': ''}},
  2421. {title: '2本目以降の動画だけ自動再生 (※プレミアム用)', varName: 'autoPlay2ndVideo', reload: true,
  2422. values: {'する': true, 'しない': false}},
  2423. {title: 'マイリストのローカルキャッシュ', varName: 'enableLocalMylistCache', reload: true,
  2424. description: '動画がどのマイリストに登録されてるかの情報をキャッシュします。\n「my」ボタンの右クリックを活用する人はおすすめ。',
  2425. values: {'有効': true, '無効': false}},
  2426.  
  2427.  
  2428. {title: 'マウスとキーボードの設定', description: '※Chromeはコメント入力中も反応してしまいます', className: 'shortcut'},
  2429. {title: '背景ダブルクリックで動画の位置にスクロール', varName: 'doubleClickScroll',
  2430. description: 'なにもない場所をダブルクリックすると、動画の位置にスクロールします。\n 市場を見てからプレイヤーに戻りたい時などに便利',
  2431. values: {'する': true, 'しない': false}},
  2432. {title: 'マウスのボタン+ホイールでどこでも音量調整', varName: 'mouseClickWheelVolume',
  2433. description: 'とっさに音量を変えたい時に便利',
  2434. values: {'左ボタン+ホイール': 1, '右ボタン+ホイール': 2, '使わない': 0}},
  2435. {title: '停止/再生', varName: 'shortcutTogglePlay', type: 'keyInput'},
  2436. {title: 'とりあえずマイリスト登録', varName: 'shortcutDefMylist', type: 'keyInput'},
  2437. {title: 'マイリスト登録', varName: 'shortcutMylist', type: 'keyInput',
  2438. description: '右下で選択中のマイリストに登録'},
  2439. {title: 'とりあえずマイリストを開く', varName: 'shortcutOpenDefMylist', type: 'keyInput'},
  2440. {title: '動画投稿者の関連動画を開く', varName: 'shortcutShowOtherVideo', type: 'keyInput'},
  2441. {title: '検索画面を開く', varName: 'shortcutOpenSearch', type: 'keyInput'},
  2442. {title: '関連動画(オススメ)を開く', varName: 'shortcutOpenRecommend', type: 'keyInput'},
  2443. {title: 'コメント表示ON/OFF', varName: 'shortcutCommentVisibility', type: 'keyInput'},
  2444. {title: 'プレイヤーの位置までスクロール', varName: 'shortcutScrollToNicoPlayer', type: 'keyInput'},
  2445. {title: 'ミュート', varName: 'shortcutMute', type: 'keyInput'},
  2446. {title: 'コメントの背面表示ON/FF', varName: 'shortcutDeepenedComment', type: 'keyInput'},
  2447. {title: 'ハードウェアアクセラレーションON/FF', varName: 'shortcutToggleStageVideo', type: 'keyInput'},
  2448.  
  2449. {title: 'その他2(一発ネタ系)', description: 'いつのまにか消えるかもしれません', className: 'shortcut'},
  2450. {title: 'テレビちゃんメニュー内にランダム画像(左上)表示', varName: 'hidariue',
  2451. values: {'する': true, 'しない': false}},
  2452. {title: 'ゆっくり再生(スロー再生)ボタンを表示', varName: 'enableYukkuriPlayButton',
  2453. values: {'する': true, 'しない': false}},
  2454.  
  2455. {title: '実験中の設定', className: 'forDebug'},
  2456. {title: '動画のロードを待たずに初期化する', varName: 'initializeImmediately',
  2457. values: {'する': true, 'しない': false}},
  2458. // {title: 'プレイリスト消えないモード(※実験中)', varName: 'hashPlaylistMode', debugOnly: true, reload: true,
  2459. // values: {'有効(連続再生中のみ)': 1, '有効(常時)': 2, '無効': 0}},
  2460.  
  2461.  
  2462.  
  2463. ];
  2464.  
  2465. var listener = [];
  2466. function dispatchEvent(name, value, lastValue) {
  2467. for (var i = 0; i < listener.length; i++) {
  2468. (listener[i])(name, value, lastValue);
  2469. }
  2470. }
  2471. pt.createPanelDom = function() {
  2472. if ($panel === null) {
  2473. $panel = w.jQuery([
  2474. '<div id="watchItLaterConfigPanel">',
  2475. '<div class="head"><button class="closeButton" title="閉じる">▲</button><h2>WatchItLaterの設定</h2>(※)のつく項目は、リロード後に反映されます</div>',
  2476. '<div class="inner"></div></div>'
  2477. ].join(''));
  2478. $panel.on('click', function(e) { e.stopPropagation(); });
  2479.  
  2480. var scrollTo = function() {
  2481. var $target = this;
  2482. var isOpen = $target.parent().toggleClass('open').hasClass('open');
  2483. if (isOpen) {
  2484. setTimeout(function() {
  2485. var $inner = $('#watchItLaterConfigPanel .inner');
  2486. $inner.animate({
  2487. scrollTop: $inner.scrollTop() + $target.parent().position().top - 50
  2488. }, 400);
  2489. }, 200);
  2490. }
  2491. };
  2492.  
  2493. var $ul = null, $inner = $panel.find('.inner'), $item; //$panel.find('ul'), $item;
  2494. for (var i = 0, len = menus.length; i < len; i++) {
  2495. if (menus[i].varName) {
  2496. $item = this.createMenuItem(menus[i]);
  2497. } else {
  2498. if (menus[i].description) {
  2499. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span><span class="description">'+ menus[i].description + '</span></div></li>');
  2500. } else {
  2501. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span></div></li>');
  2502. }
  2503. if ($ul) $inner.append($ul);
  2504. $ul =$('<ul class="sectionContainer"/>').addClass(menus[i].className + 'Container');
  2505. $item.click($.proxy(scrollTo, $item));
  2506. }
  2507. $item.toggleClass('debugOnly', menus[i].debugOnly === true).toggleClass('reload', menus[i].reload === true);
  2508. if ($ul) $ul.append($item);
  2509. }
  2510. if ($ul) $inner.append($ul);
  2511. $panel.toggleClass('debugMode', conf.debugMode);
  2512. var $bottom = w.jQuery('<div class="foot"></div>'), self = this;
  2513. $panel.append($bottom);
  2514. $panel.find('.closeButton').click(function() {
  2515. self.close();
  2516. });
  2517. if ($shadow === null) {
  2518. $shadow = $('<div id="watchItLaterConfigPanelShadow" /><div id="watchItLaterConfigPanelShadowTop"/><div id="watchItLaterConfigPanelOverShadow"/>');
  2519. }
  2520. }
  2521. };
  2522.  
  2523. pt.refresh = function() {
  2524. var isVisible = $panel.hasClass('open');
  2525. $panel.remove().empty();
  2526. $panel = null;
  2527. this.createPanelDom();
  2528. if (isVisible) { $panel.show(); }
  2529. };
  2530.  
  2531. pt.createMenuItem = function(menu) {
  2532. if (menu.type === 'text') {
  2533. return this.createTextMenuItem(menu);
  2534. } else
  2535. if (menu.type === 'keyInput') {
  2536. return this.createKeyInputMenuItem(menu);
  2537. } else {
  2538. return this.createRadioMenuItem(menu);
  2539. }
  2540. };
  2541. pt.createRadioMenuItem = function(menu) {
  2542. var title = menu.title, varName = menu.varName, values = menu.values;
  2543. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2544. if (menu.className) { $menu.addClass(menu.className);}
  2545. if (menu.description) { $menu.attr('title', menu.description); }
  2546. var currentValue = conf.getValue(varName);
  2547. $menu.addClass(menu.varName);
  2548. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2549. for (var k in values) {
  2550. var v = values[k];
  2551. var $label = w.jQuery('<label></label>');
  2552. var $chk = w.jQuery('<input>');
  2553. $chk.attr({type: 'radio', name: varName, value: JSON.stringify(v)});
  2554.  
  2555. if (currentValue === v) {
  2556. $chk.prop('checked', 'checked');
  2557. }
  2558. $chk.click(function() {
  2559. var newValue = JSON.parse(this.value), oldValue = conf.getValue(varName);
  2560. if (oldValue !== newValue) {
  2561. if (menu.addClass) {
  2562. $panel.removeClass(menu.varName + '_' + oldValue).addClass(menu.varName + '_' + newValue);
  2563. }
  2564. conf.setValue(menu.varName, newValue);
  2565. if (typeof menu.onchange === 'function') {
  2566. menu.onchange(newValue, oldValue);
  2567. }
  2568. dispatchEvent(menu.varName, newValue, oldValue);
  2569. }
  2570. });
  2571. $label.append($chk).append(w.jQuery('<span>' + k + '</span>'));
  2572. $menu.append($label);
  2573. }
  2574. return $menu;
  2575. };
  2576. pt.createTextMenuItem = function(menu) {
  2577. var title = menu.title, varName = menu.varName;
  2578. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2579. if (menu.className) { $menu.addClass(menu.className);}
  2580. if (menu.description) { $menu.attr('title', menu.description); }
  2581. var currentValue = conf.getValue(varName);
  2582. var $input = w.jQuery('<input type="text" />');
  2583. $menu.addClass(menu.varName);
  2584. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2585. $input.val(currentValue);
  2586. $input.change(function() {
  2587. var newValue = $input.val(), oldValue = conf.getValue(varName);
  2588. if (oldValue !== newValue) {
  2589. conf.setValue(varName, newValue);
  2590. if (typeof menu.onchange === 'function') {
  2591. menu.onchange(newValue, oldValue);
  2592. }
  2593. dispatchEvent(menu.varName, newValue, oldValue);
  2594. }
  2595. });
  2596. $menu.append($input);
  2597. return $menu;
  2598. };
  2599.  
  2600. pt.createKeyInputMenuItem = function(menu) {
  2601. var title = menu.title, varName = menu.varName;
  2602. var currentValue = conf.getValue(varName), currentKey = currentValue.char;
  2603.  
  2604. function update() {
  2605. var newValue = {char: $sel.val(), ctrl: $menu.hasClass('ctrl'), alt: $menu.hasClass('alt'), shift: $menu.hasClass('shift'), enable: $menu.hasClass('enable')};
  2606. conf.setValue(varName, newValue);
  2607. if (typeof menu.onchange === 'function') {
  2608. menu.onchange(newValue);
  2609. }
  2610. dispatchEvent(menu.varName, newValue, conf.getValue(varName));
  2611. }
  2612.  
  2613. var $menu = w.jQuery('<li class="shortcutSetting"><p class="title">' + title + '</p></li>');
  2614. var sel = ['<select>'], $sel;
  2615. for (var v = 48; v <= 90; v++) {
  2616. if (v >= 0x3c && v <= 0x3f) continue;
  2617. var c = String.fromCharCode(v);
  2618. var op = ['<option value="', c, '">', c, '</option>' ].join('');
  2619. sel.push(op);
  2620. }
  2621. sel.push('</select>');
  2622. $sel = w.jQuery(sel.join(''));
  2623. var $meta = w.jQuery('<span class="enable" data-meta="enable">有効</span><span class="ctrl" data-meta="ctrl">ctrl</span><span class="alt" data-meta="alt">alt</span><span class="shift" data-meta="shift">shift</span>').on('click', function(e) {
  2624. var meta = w.jQuery(e.target).attr('data-meta');
  2625. $menu.toggleClass(meta);
  2626. update();
  2627. });
  2628. $sel.change(update);
  2629.  
  2630. $menu.toggleClass('enable', currentValue.enable).toggleClass('ctrl', currentValue.ctrl).toggleClass('alt', currentValue.alt).toggleClass('shift', currentValue.shift);
  2631. $sel.val(currentKey);
  2632.  
  2633. if (menu.className) { $menu.addClass(menu.className);}
  2634. if (menu.description) { $menu.attr('title', menu.description); }
  2635.  
  2636. $menu.append(w.jQuery('<span/>').append($meta).append($sel));
  2637.  
  2638. return $menu;
  2639. };
  2640.  
  2641. pt.toggleOpenSection = function(sectionName, toggle) {
  2642. $('#watchItLaterConfigPanel .'+ sectionName + 'Container').toggleClass('open', toggle);
  2643. $('#watchItLaterConfigPanel .inner').scrollTop($('#watchItLaterConfigPanel .' + sectionName).position().top - 50);
  2644. };
  2645.  
  2646. pt.addChangeEventListener = function(callback) {
  2647. listener.push(callback);
  2648. };
  2649. pt.open = function() {
  2650. $('body').append($shadow).append($panel);
  2651. setTimeout(function() {
  2652. $shadow.addClass('open'); $panel.addClass('open');
  2653. }, 50);
  2654. setTimeout(function() {
  2655. if (WatchController.isFullScreen()) {
  2656. pt.toggleOpenSection('fullScreen', true);
  2657. } else
  2658. if (WatchController.isSearchMode()) {
  2659. pt.toggleOpenSection('videoExplorer', true);
  2660. }
  2661. }, 1000);
  2662. };
  2663. pt.close = function() {
  2664. $shadow.removeClass('open'); $panel.removeClass('open');
  2665. setTimeout(function() {
  2666. $shadow.detach(); $panel.detach();
  2667. }, 800);
  2668. };
  2669. pt.toggle = function() {
  2670. this.createPanelDom();
  2671. if ($panel.hasClass('open')) {
  2672. this.close();
  2673. } else {
  2674. this.open();
  2675. }
  2676. };
  2677.  
  2678. return pt;
  2679. })(w.jQuery, conf, w);
  2680.  
  2681.  
  2682. /**
  2683. * 通信用
  2684. */
  2685. window.WatchItLater = {
  2686. config: {
  2687. get: function(varName) {
  2688. return conf.getValue(varName);
  2689. },
  2690. set: function(varName, value) {
  2691. conf.setValue(varName, value);
  2692. },
  2693. open: function() {
  2694. ConfigPanel.open();
  2695. }
  2696. },
  2697. loader: {},
  2698. debug: {},
  2699. init: {},
  2700. test: {
  2701. assert: function(v, m) {
  2702. if (v === true) {
  2703. window.console.log('%c OK: ', 'color: black; background: lime;', m);
  2704. } else {
  2705. window.console.log('%cFail: ', 'color: white; background: red;', m);
  2706. throw {message: 'Fail'};
  2707. }
  2708. },
  2709. expect: function(a) {
  2710. try {
  2711. var assert = window.WatchItLater.test.assert, exp = {
  2712. toBeTrue: function( desc) { assert(a === true , desc); },
  2713. toBeFalse: function( desc) { assert(a === false , desc); },
  2714. toEqual: function(b, desc) { assert(a === b , desc); },
  2715. toBeNull: function( desc) { assert(a === null , desc); },
  2716. toBeNotNull: function( desc) { assert(a !== null , desc); },
  2717. toBeDefined: function( desc) { assert(a !== void 0 , desc); },
  2718. toBeTruthy: function( desc) { assert(a ? true : false, desc); }
  2719. };
  2720. return exp;
  2721. } catch(e) {
  2722. window.console.log('%c', a);
  2723. }
  2724. },
  2725. spec: {},
  2726. run: function(name) {
  2727. var def = (new $.Deferred()), promise = def.promise();
  2728. var con = function(name) {
  2729. return function() {
  2730. var d = new $.Deferred();
  2731. setTimeout(function() {
  2732. window.console.log('%c RUN: ' + name, 'background: #8ff;');
  2733. d.resolve();
  2734. }, 100);
  2735. return d.promise();
  2736. };
  2737. };
  2738. var wrap = function(self, name) {
  2739. return function() {
  2740. var d = new $.Deferred();
  2741. setTimeout(function() {
  2742. try {
  2743. $.proxy(self.spec[name], self)(d);
  2744. } catch (e) {
  2745. window.console.log(e);
  2746. d.reject();
  2747. }
  2748. }, 0);
  2749. return d.promise();
  2750. };
  2751. };
  2752. var onFail = function(e) {
  2753. window.console.log('%c fail : ','background: red;', e);
  2754. };
  2755.  
  2756. if (name) {
  2757. promise = promise.then(con(name)).then(wrap(this, name), onFail);
  2758. } else {
  2759. for(var v in this.spec) {
  2760. if (!v.match(/^test/)) continue;
  2761. promise = promise.then(con(v)) .then(wrap(this, v), onFail);
  2762. }
  2763. }
  2764. promise.then(
  2765. function() { window.console.log('%cテスト完了', 'background: #8ff'); },
  2766. function() { window.console.log('%cテスト失敗', 'background: #f00'); }
  2767. );
  2768. def.resolve();
  2769. }
  2770. }
  2771. };
  2772. // w.WatchItLater = window.WatchItLater;
  2773.  
  2774.  
  2775. var EventDispatcher = (function(conf) {
  2776. var events = {};
  2777.  
  2778. function addEventListener(name, callback) {
  2779. name = name.toLowerCase();
  2780. if (!events[name]) {
  2781. events[name] = [];
  2782. }
  2783. events[name].push(callback);
  2784. }
  2785.  
  2786. function _dispatch(name) {
  2787. name = name.toLowerCase();
  2788. if (!events[name]) { return; }
  2789. var e = events[name];
  2790. for (var i =0, len = e.length; i < len; i++) {
  2791. try {
  2792. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  2793. } catch (ex) {
  2794. console.log('%c' + name, 'background:red; color: white;', i, e[i], ex);
  2795. }
  2796. }
  2797. }
  2798. function dispatch(name) {
  2799. console.log('%cevent:', 'background: blue; color: white;', name);//, arguments);
  2800. _dispatch.apply(null, arguments);
  2801. }
  2802. return {
  2803. addEventListener: addEventListener,
  2804. dispatch: dispatch,
  2805. _dispatch: _dispatch // コンソール汚したくない用
  2806. };
  2807. })(conf);
  2808. window.WatchItLater.event = EventDispatcher;
  2809.  
  2810. /*
  2811. * 通算視聴回数をカウント。 カウントしても意味はないけど、どれだけ無駄な時間を費やしたかを知りたくて実装。
  2812. */
  2813. var WatchCounter = (function(conf, w) {
  2814. var key = 'watchItLater_watchCounter';
  2815. function get() {
  2816. return JSON.parse(w.localStorage.getItem(key));
  2817. }
  2818. function add() {
  2819. var v = get() + 1;
  2820. w.localStorage.setItem(key, JSON.stringify(v));
  2821. console.log('%cwatchCounter: %c%d', 'color: orange;', 'font-weight: bolder;', v);
  2822. return v;
  2823. }
  2824. var self = {
  2825. get: get,
  2826. add: add
  2827. };
  2828. return self;
  2829. })(conf, w);
  2830. window.WatchItLater.counter = WatchCounter;
  2831.  
  2832. /**
  2833. * 動画タグ取得とポップアップ
  2834. *
  2835. */
  2836. var VideoTags = (function(conf, w){
  2837.  
  2838. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  2839. var pt = function(){};
  2840. var lastPopup = null;
  2841.  
  2842. pt.get = function(watchId, callback) {
  2843. var _get = function(watchId, callback) {
  2844. var url = 'http://' + host + '/tag_edit/' + watchId + '/?res_type=json&cmd=tags';
  2845. //http://www.nicovideo.jp/tag_edit/sm9/?res_type=json&cmd=tags
  2846. var req = {
  2847. method: 'GET',
  2848. url: url,
  2849. onload: function(resp) {
  2850. var result = JSON.parse(resp.responseText);
  2851. if (typeof callback === 'function') callback(result.status, result);
  2852. }
  2853. };
  2854. GM_xmlhttpRequest(req);
  2855. };
  2856.  
  2857. WatchController.getTid2Vid(watchId, function(videoId) {
  2858. _get(videoId, callback);
  2859. });
  2860. };
  2861.  
  2862. pt.hidePopup = function() {
  2863. if (lastPopup) {
  2864. lastPopup.style.display = 'none';
  2865. }
  2866. };
  2867.  
  2868. var uniq = null, $history = null, popupContainer = null;
  2869. pt.popupItems = function(watchId, baseX, baseY) {
  2870. var self = this;
  2871. popupContainer.innerHTML = '';
  2872. this.get(watchId, function(status, resp) {
  2873. if (status === 'ok') {
  2874. var tags = resp.tags;
  2875. self.hidePopup();
  2876. if (tags.length > 0) {
  2877. lastPopup = createPopup(tags, baseX, baseY);
  2878. } else {
  2879. Popup.show('この動画のタグはありません');
  2880. }
  2881. } else {
  2882. Popup.alert(resp.error_message);
  2883. }
  2884. });
  2885.  
  2886. function createPopup(tags, baseX, baseY) {
  2887. var popup = createDOM(tags, baseX, baseY);
  2888. popupContainer.appendChild(popup);
  2889. popup.style.right = null;
  2890. popup.style.left = baseX + 'px';
  2891. popup.style.top = Math.max(baseY - popup.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  2892. if (popup.offsetLeft + popup.offsetWidth > document.body.clientWidth) {
  2893. popup.style.left = null;
  2894. popup.style.right = 0;
  2895. }
  2896.  
  2897. return popup;
  2898. }
  2899.  
  2900. function createDOM(tags) {
  2901. var items = document.createElement('ul');
  2902. for (var i = 0, len = tags.length; i < len; i++) {
  2903. items.appendChild(createItemDOM(tags[i]));
  2904. }
  2905. var popup = createPopupDOM();
  2906.  
  2907. popup.appendChild(items);
  2908. return popup;
  2909. }
  2910.  
  2911. function createPopupDOM() {
  2912. var popup = document.createElement('div');
  2913. popup.className = 'tagItemsPopup popupMenu';
  2914. popup.addEventListener('click', createPopupOnClick(), false);
  2915. return popup;
  2916. }
  2917.  
  2918. function createPopupOnClick() {
  2919. return function(e) {
  2920. if (e.button !== 0 || e.shiftKey || e.ctrlKey || e.altKey || e.target.className === 'icon' || e.target.tagName === 'A') {
  2921. return;
  2922. }
  2923. this.style.display = 'none';
  2924. e.preventDefault();
  2925. e.stopPropagation();
  2926. };
  2927. }
  2928.  
  2929. function appendTagHistory(dom, text, dic) {
  2930. var $ = w.$;
  2931. if (uniq === null) {
  2932. uniq = {};
  2933. $history = $('<div class="tagSearchHistory"><h3 class="title">タグ検索履歴</h3></div>');
  2934. $history.css({width: $('.videoExplorerMenu').width() - 8, maxHeight: '300px', overflowY: 'auto'});
  2935. $('.videoExplorerMenu').append($history);
  2936. }
  2937. if (!uniq[text]) {
  2938. var a = $(dom).clone().css({marginRight: '8px', fontSize: '80%'}).click(Util.Closure.openNicoSearch(text));
  2939. dic.style.marginRight = '0';
  2940. $history.find('.title').after(a).after(dic);
  2941. }
  2942. uniq[text] = 1;
  2943. }
  2944.  
  2945. function createItemDOM(tag) {
  2946. var text = tag.tag;
  2947. var li = document.createElement('li');
  2948. li.className = 'popupTagItem';
  2949.  
  2950. // 大百科アイコン
  2951. var dic = createDicIconDOM(tag, text);
  2952. li.appendChild(dic);
  2953.  
  2954. // 新検索(search.nicovideo.jp)へのリンク
  2955. var newSearchIcon = createNewSearchIconDOM(tag, text);
  2956. li.appendChild(newSearchIcon);
  2957.  
  2958. // 本文リンク
  2959. var a = document.createElement('a');
  2960. a.appendChild(document.createTextNode(text));
  2961.  
  2962. var href = text;
  2963. if (conf.defaultSearchOption && conf.defaultSearchOption !== '' && !text.match(/(sm|nm|so)\d+/)) {
  2964. href += ' ' + conf.defaultSearchOption;
  2965. }
  2966. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  2967. a.href = 'http://' + host + '/tag/' + encodeURIComponent(href) + sortOrder;
  2968. a.addEventListener('click', createItemOnClick(text, dic), false);
  2969. li.appendChild(a);
  2970.  
  2971. return li;
  2972. }
  2973.  
  2974. function createItemOnClick(text, dic) {
  2975. return function(e) {
  2976. if (e.button !== 0 || e.metaKey) return;
  2977. if (w.WatchApp) {
  2978. WatchController.nicoSearch(text, 'tag');
  2979. e.preventDefault();
  2980. appendTagHistory(this, text, dic);
  2981. }
  2982. return false;
  2983. };
  2984. }
  2985.  
  2986. function createNewSearchIconDOM(tag, text) {
  2987. var link = document.createElement('a');
  2988. link.className = 'newsearch';
  2989. link.title = 'niconico新検索で開く';
  2990.  
  2991. // TODO: パラメータの対応表作ってあわせる
  2992. var newSortOrder = '';
  2993. link.href = 'http://search.nicovideo.jp/video/search/' + encodeURIComponent(text) + newSortOrder;
  2994. if (location.host !== 'search.nicovdieo.jp') {
  2995. link.target = '_blank';
  2996. }
  2997.  
  2998. var icon = document.createElement('img');
  2999. icon.className = 'icon';
  3000. icon.src = 'http://uni.res.nimg.jp/img/favicon.ico';
  3001. link.appendChild(icon);
  3002.  
  3003. return link;
  3004. }
  3005. function createDicIconDOM(tag, text) {
  3006. var dic = document.createElement('a');
  3007. dic.className = 'nicodic';
  3008. dic.href = 'http://dic.nicovideo.jp/a/' + encodeURIComponent(text);
  3009. dic.target = '_blank';
  3010. var icon = document.createElement('img');
  3011. icon.className = 'icon';
  3012. icon.src = tag.dic ? 'http://live.nicovideo.jp/img/2012/watch/tag_icon002.png' : 'http://live.nicovideo.jp/img/2012/watch/tag_icon003.png';
  3013. dic.appendChild(icon);
  3014. return dic;
  3015. }
  3016. };
  3017. popupContainer = document.createElement('div');
  3018. popupContainer.id = 'videoTagPopupContainer';
  3019. document.body.appendChild(popupContainer);
  3020.  
  3021. return pt;
  3022. })(conf, w);
  3023.  
  3024.  
  3025.  
  3026.  
  3027.  
  3028.  
  3029.  
  3030.  
  3031.  
  3032. /**
  3033. * マイリスト登録API
  3034. *
  3035. * (9)の頃は、iframeを作ってその中にマイリスト登録のポップアップウィンドウを開くという手抜きを行っていたが、
  3036. * ポップアップウィンドウは評判が悪いし、そのうち廃止されるだろうなと思うので、
  3037. * 真面目にAPIを叩くようにした。 (マイリストの新規作成機能は省略)
  3038. *
  3039. * …と思っていたのだが、(9)からQになった今でもポップアップウィンドウは廃止されないようだ。
  3040. */
  3041. var Mylist = window.WatchItLater.mylist = (function(){
  3042. var mylistlist = [];
  3043. var initialized = false;
  3044. var defListItems = [], mylistItems = {};
  3045. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  3046. var token = '';//
  3047.  
  3048. function Mylist() {
  3049. this.initialize();
  3050. }
  3051.  
  3052. function getToken() {
  3053. if (!isNativeGM && host !== location.host) return null; //
  3054.  
  3055. var _token = (w.NicoAPI) ? w.NicoAPI.token : '';
  3056. if (w.NicoAPI) {
  3057. return w.NicoAPI.token;
  3058. } else
  3059. if (w.WatchApp && w.WatchJsApi) {
  3060. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  3061. watchInfoModel.addEventListener('reset', function(watchInfoModel) {
  3062. token = watchInfoModel.csrfToken;
  3063. });
  3064. if (watchInfoModel.initialized) {
  3065. return watchInfoModel.csrfToken;
  3066. } else {
  3067. var dc = JSON.parse($("#watchAPIDataContainer").text());
  3068. return dc.flashvars.csrfToken;
  3069. }
  3070. } else
  3071. if (_token === null && w.FavMylist && w.FavMylist.csrf_token) {
  3072. _token = w.FavMylist.csrf_token;
  3073. }
  3074.  
  3075. if (_token !== '') {
  3076. return _token;
  3077. }
  3078. var url = 'http://' + host + '/mylist_add/video/sm9'; // マイリスト登録ウィンドウから強引にtoken取得
  3079. // var url = 'http://' + host + '/my/mylist'; // マイリスト登録ウィンドウから強引にtoken取得
  3080. GM_xmlhttpRequest({
  3081. url: url,
  3082. onload: function(resp) {
  3083. var result = resp.responseText;
  3084. if (result.match(/NicoAPI\.token = "([a-z0-9\-]+)";/)) {
  3085. token = RegExp.$1;
  3086. }
  3087. }
  3088. });
  3089. return _token;
  3090. }
  3091.  
  3092. var pt = Mylist.prototype, events = {defMylistUpdate: [], mylistUpdate: []};
  3093.  
  3094. function dispatchEvent(name) {
  3095. var e = events[name];
  3096. for (var i =0, len = e.length; i < len; i++) {
  3097. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  3098. }
  3099. }
  3100.  
  3101. pt.onDefMylistUpdate = function(callback) {
  3102. events.defMylistUpdate.push(callback);
  3103. };
  3104.  
  3105. pt.onMylistUpdate = function(callback) {
  3106. events.mylistUpdate.push(callback);
  3107. };
  3108.  
  3109. pt.getUserId = function() {
  3110. if (document.cookie.match(/user_session_(\d+)/)) {
  3111. return RegExp.$1;
  3112. } else {
  3113. return false;
  3114. }
  3115. };
  3116.  
  3117. var onInitialized = [];
  3118. pt.initialize = function() {
  3119. if (initialized) return;
  3120. var uid = this.getUserId();
  3121. if (!uid) {
  3122. return;
  3123. }
  3124. if (!isNativeGM && host !== location.host) {
  3125. initialized = true;
  3126. return;
  3127. }
  3128. token = getToken();
  3129. //var url = 'http://' + host + '/api/watch/uservideo?user_id=' + uid;
  3130. var url = 'http://' + host + '/api/mylistgroup/list';
  3131. GM_xmlhttpRequest({
  3132. url: url,
  3133. onload: function(resp) {
  3134. var result = JSON.parse(resp.responseText);
  3135. if (result.status === "ok" && result.mylistgroup) {
  3136. mylistlist = result.mylistgroup;
  3137. initialized = true;
  3138. for (var i = 0; i < onInitialized.length; i++) {
  3139. onInitialized[i](mylistlist.concat());
  3140. }
  3141. }
  3142. }
  3143. });
  3144. this.reloadDefList();
  3145. };
  3146.  
  3147. pt.loadMylistList = function(callback) {
  3148. if (initialized) {
  3149. setTimeout(function() { callback(mylistlist.concat()); }, 0);
  3150. } else {
  3151. onInitialized.push(callback);
  3152. }
  3153. };
  3154.  
  3155. pt.isMine = function(id) {
  3156. if (!initialized) { return false; }
  3157. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3158. if (mylistlist[i].id == id) { return true; }
  3159. }
  3160. return false;
  3161. };
  3162.  
  3163. pt.reloadDefList = function(callback) {
  3164. var url = 'http://' + host + '/api/deflist/list';
  3165. GM_xmlhttpRequest({
  3166. url: url,
  3167. onload: function(resp) {
  3168. try {
  3169. JSON.parse(resp.responseText);
  3170. } catch (e) {
  3171. window.console.log(e);
  3172. window.console.log(resp.responseText);
  3173. }
  3174. if (!resp.responseText) return;
  3175. var result = JSON.parse(resp.responseText);
  3176. if (result.status === "ok" && result.mylistitem) {
  3177. defListItems = result.mylistitem;
  3178. if (typeof callback === "function") callback(defListItems);
  3179. }
  3180. }
  3181. });
  3182. };
  3183.  
  3184. pt.loadMylist = function(groupId, callback) {
  3185. if (mylistItems[groupId]) {
  3186. setTimeout(function() {callback(mylistItems[groupId]); }, 0);
  3187. return;
  3188. }
  3189. var url = 'http://' + host + '/api/mylist/list?group_id=' + groupId;
  3190. GM_xmlhttpRequest({
  3191. url: url,
  3192. onload: function(resp) {
  3193. var result = JSON.parse(resp.responseText);
  3194. if (result.status === "ok" && result.mylistitem) {
  3195. mylistItems[groupId] = result.mylistitem;
  3196. if (typeof callback === "function") callback(result.mylistitem);
  3197. }
  3198. }
  3199. });
  3200. };
  3201.  
  3202. pt.clearMylistCache = function(groupId) {
  3203. delete mylistItems[groupId];
  3204. };
  3205.  
  3206. pt.reloadMylist = function(groupId, callback) {
  3207. this.clearMylistCache(groupId);
  3208. return this.loadMylist(groupId, callback);
  3209. };
  3210.  
  3211.  
  3212. pt.findDeflistByWatchId = function(watchId) {
  3213. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3214.  
  3215. for (var i = 0, len = defListItems.length; i < len; i++) {
  3216. var item = defListItems[i], wid = item.item_data.watch_id;
  3217. if (wid == watchId) return item;
  3218. }
  3219. return null;
  3220. };
  3221.  
  3222. pt.findMylistByWatchId = function(watchId, groupId) {
  3223. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3224. var items = mylistItems[groupId];
  3225. if (!items) { return null; }
  3226. for (var i = 0, len = items.length; i < len; i++) {
  3227. var item = items[i], wid = item.item_data.watch_id;
  3228. if (wid == watchId) return item;
  3229. }
  3230. return null;
  3231. };
  3232.  
  3233. // おもに参考にしたページ
  3234. // http://uni.res.nimg.jp/js/nicoapi.js
  3235. // http://d.hatena.ne.jp/lolloo-htn/20110115/1295105845
  3236. // http://d.hatena.ne.jp/aTaGo/20100811/1281552243
  3237. pt.deleteDefListItem = function(watchId, callback) {
  3238. var item = this.findDeflistByWatchId(watchId);
  3239. if (!item) return false;
  3240. var item_id = item.item_id;
  3241. var url = 'http://' + host + '/api/deflist/delete';
  3242. var data = 'id_list[0][]=' + item_id + '&token=' + token;
  3243. var req = {
  3244. method: 'POST',
  3245. data: data,
  3246. headers: {'Content-Type': 'application/x-www-form-urlencoded'}, // これを忘れて小一時間はまった
  3247. url: url,
  3248. onload: function(resp) {
  3249. var result = JSON.parse(resp.responseText);
  3250. if (typeof callback === "function") callback(result.status, result);
  3251. if (window.jQuery) {
  3252. defListItems = window.jQuery.grep(defListItems, function(item) {
  3253. return item.item_data.watch_id !== watchId;
  3254. });
  3255. }
  3256. dispatchEvent('defMylistUpdate');
  3257. }
  3258. };
  3259. GM_xmlhttpRequest(req);
  3260. return true;
  3261. };
  3262.  
  3263. pt.addDefListItem = function(watchId, callback, description) {
  3264. var url = 'http://' + host + '/api/deflist/add';
  3265.  
  3266. // 例えば、とりマイの300番目に登録済みだった場合に「登録済みです」と言われても探すのがダルいし、
  3267. // 他の動画を追加していけば、そのうち押し出されて消えてしまう。
  3268. // なので、重複時にエラーを出すのではなく、「消してから追加」することによって先頭に持ってくる。
  3269. // 「重複してたら先頭に持ってきて欲しいな~」って要望掲示板にこっそり書いたりしたけど相手にされないので自分で実装した。
  3270. var data = "item_id=" + watchId + "&token=" + token, replaced = true;
  3271. if (description) {
  3272. data += '&description='+ encodeURIComponent(description);
  3273. }
  3274.  
  3275. var _add = function(status, resp) {
  3276. var req = {
  3277. method: 'POST',
  3278. data: data,
  3279. url: url,
  3280. headers: {'Content-Type': 'application/x-www-form-urlencoded' }, // これを忘れて小一時間はまった
  3281. onload: function(resp) {
  3282. var result = JSON.parse(resp.responseText);
  3283. if (typeof callback === "function") callback(result.status, result, replaced);
  3284. }
  3285. };
  3286. GM_xmlhttpRequest(req);
  3287. };
  3288. // とりあえずマイリストにある場合はdeleteDefListItem()のcallbackで追加、ない場合は即時追加
  3289. if (!this.deleteDefListItem(watchId, _add)) {
  3290. replaced = false;
  3291. _add();
  3292. dispatchEvent('defMylistUpdate');
  3293. }
  3294. };
  3295.  
  3296. pt.addMylistItem = function(watchId, groupId, callback, description) {
  3297. var self = this;
  3298. var url = 'http://' + host + '/api/mylist/add';
  3299. var data = ['item_id=', watchId,
  3300. '&group_id=', groupId,
  3301. '&item_type=', 0, // video=0 seiga=5
  3302. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3303. '&token=', token
  3304. ].join('');
  3305. // 普通のマイリストのほうは重複しても「消してから追加」という処理を行っていない。
  3306. // とりあえずマイリストと違って登録の順番に意味があるのと、
  3307. // 古いのが押し出される心配がないため。
  3308. var _add = function() {
  3309. var req = {
  3310. method: 'POST',
  3311. data: data,
  3312. url: url,
  3313. headers: {'Content-Type': 'application/x-www-form-urlencoded' },
  3314. onload: function(resp) {
  3315. var result = JSON.parse(resp.responseText);
  3316. if (typeof callback === "function") callback(result.status, result);
  3317. if (result.status === 'ok') {
  3318. dispatchEvent('mylistUpdate', {action: 'add', groupId: groupId, watchId: watchId});
  3319. EventDispatcher.dispatch('onMylistItemAdded', groupId, watchId);
  3320. self.clearMylistCache(groupId);
  3321. }
  3322. },
  3323. error: function() {
  3324. Popup.alert('ネットワークエラー');
  3325. }
  3326. };
  3327. GM_xmlhttpRequest(req);
  3328. };
  3329. // 普通のマイリストに入れたら、とりあえずマイリストからは削除(≒移動)
  3330. if (!this.deleteDefListItem(watchId, _add)) _add();
  3331. };
  3332.  
  3333. pt.updateMylistItem = function(watchId, groupId, callback, description) {
  3334. var self = this;
  3335. this.loadMylist(groupId, function() {
  3336. var item = self.findMylistByWatchId(watchId, groupId);
  3337. if (!item) {
  3338. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3339. return;
  3340. }
  3341. var
  3342. itemId = item.item_id,
  3343. url = 'http://' + host + '/api/mylist/update',
  3344. data = ['item_id=', itemId,
  3345. '&group_id=', groupId,
  3346. '&item_type=', 0, // video=0 seiga=5
  3347. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3348. '&token=', token
  3349. ].join(''),
  3350. req = {
  3351. method: 'POST',
  3352. data: data,
  3353. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3354. url: url,
  3355. onload: function(resp) {
  3356. var result = JSON.parse(resp.responseText);
  3357. if (result.status === 'ok') {
  3358. if (typeof callback === "function") callback(result.status, result);
  3359. dispatchEvent('mylistUpdate', {action: 'update', groupId: groupId, watchId: watchId});
  3360. EventDispatcher.dispatch('onMylistItemUpdated', groupId, watchId);
  3361. }
  3362. },
  3363. error: function() {
  3364. Popup.alert('ネットワークエラー');
  3365. }
  3366. };
  3367.  
  3368. GM_xmlhttpRequest(req);
  3369. });
  3370. };
  3371.  
  3372.  
  3373. pt.deleteMylistItem = function(watchId, groupId, callback) {
  3374. var self = this;
  3375. this.loadMylist(groupId, function() {
  3376. var item = self.findMylistByWatchId(watchId, groupId);
  3377. if (!item) {
  3378. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3379. return;
  3380. }
  3381. var
  3382. item_id = item.item_id,
  3383. url = 'http://' + host + '/api/mylist/delete',
  3384. data = [
  3385. 'id_list[0][]=', item_id,
  3386. '&group_id=', groupId,
  3387. '&token=', token
  3388. ].join(''),
  3389. req = {
  3390. method: 'POST',
  3391. data: data,
  3392. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3393. url: url,
  3394. onload: function(resp) {
  3395. var result = JSON.parse(resp.responseText);
  3396. if (result.status === 'ok') {
  3397. if (typeof callback === "function") callback(result.status, result);
  3398. dispatchEvent('mylistUpdate', {action: 'delete', groupId: groupId, watchId: watchId});
  3399. EventDispatcher.dispatch('onMylistItemDeleted', groupId, watchId);
  3400. }
  3401. },
  3402. error: function() {
  3403. Popup.alert('ネットワークエラー');
  3404. }
  3405. };
  3406.  
  3407. GM_xmlhttpRequest(req);
  3408. });
  3409. };
  3410.  
  3411.  
  3412. /**
  3413. * マイリスト登録パネルを返す
  3414. */
  3415. pt.getPanel = function(watchId, videoId) {
  3416. if (isNativeGM || host === location.host) {
  3417. return this.getNativePanel(watchId, videoId);
  3418. } else {
  3419. return this.getIframePanel(watchId, videoId);
  3420. }
  3421. };
  3422.  
  3423. pt.getNativePanel = function(watchId, videoId) {
  3424. var self = this;
  3425. var _watchId = watchId, _videoId = videoId || watchId;
  3426. var body = document.createElement('div');
  3427. var mylistListPopup = null;
  3428. body.className = 'mylistPopupPanel deflistSelected';
  3429. var nobr = document.createElement('nobr');
  3430. body.appendChild(nobr);
  3431.  
  3432. var extArea = document.createElement('span');
  3433.  
  3434.  
  3435. var isWatchPage = (window.PlayerApp) ? true : false;
  3436.  
  3437. var addDeflist = function(watchId, description) {
  3438. self.addDefListItem(watchId, function(status, result, replaced) {
  3439. self.reloadDefList();
  3440. if (status !== 'ok') {
  3441. Popup.alert('とりあえずマイリストへの登録に失敗: ' + result.error.description);
  3442. } else {
  3443. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  3444. Popup.show(
  3445. torimai +
  3446. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  3447. );
  3448. }
  3449. }, description);
  3450. };
  3451. var addMylist = function(watchId, mylistId, mylistName, description) {
  3452. self.addMylistItem(watchId, mylistId, function(status, result) {
  3453. self.reloadDefList();
  3454. if (status === 'ok') {
  3455. Popup.show( '<a href="/my/mylist/#/' + mylistId + '">' + mylistName + '</a>に登録しました');
  3456. } else {
  3457. Popup.alert(mylistName + 'への登録に失敗: ' + result.error.description);
  3458. }
  3459. }, description);
  3460. };
  3461. var setButtonStyleUpdating = function(btn) {
  3462. btn.style.opacity = 0.5;
  3463. btn.style.cursor = 'pointer';
  3464. btn.disabled = true;
  3465.  
  3466. window.setTimeout(function() {
  3467. btn.disabled = false;
  3468. btn.style.opacity = 1;
  3469. btn.style.cursor = 'pointer';
  3470. btn = null;
  3471. }, 1000);
  3472. };
  3473. var onMylistListClick = function(mylistId, mylistName, type) {
  3474. if (type === 'icon') {
  3475. if (window.WatchApp) {
  3476. if (mylistId === 'default') {
  3477. WatchController.showDeflist();
  3478. } else {
  3479. WatchController.showMylist(mylistId);
  3480. }
  3481. } else {
  3482. location.href = 'http://' + host + '/my/mylist/#/' + mylistId.replace('default','home');
  3483. }
  3484. return;
  3485. }
  3486. if (mylistId === 'default') {
  3487. addDeflist(_watchId);
  3488. } else {
  3489. addMylist(_watchId, mylistId, mylistName);
  3490. }
  3491. };
  3492.  
  3493. body.watchId = function(w, v) {
  3494. if (w) {
  3495. _watchId = w;
  3496. _videoId = v || w;
  3497. var isThreadId = (/^[0-9]+$/.test(w));
  3498.  
  3499. deleteDef.disabled = false;
  3500. if (self.findDeflistByWatchId(w)) {
  3501. deleteDef.style.display = '';
  3502. } else {
  3503. deleteDef.style.display = 'none';
  3504. }
  3505. if (!isWatchPage && isThreadId) {
  3506. tagBtn.style.display = 'none'; // スレッドIDから動画IDを取る手段がないためタグ取得が難しい
  3507. } else {
  3508. tagBtn.style.display = '';
  3509. }
  3510. if (newTabLink) {
  3511. newTabLink.href = 'http://nico.ms/' + _watchId; // QWatchに乗っ取られないようにnico.msをかます(せこい)
  3512. }
  3513. if (mylistListPopup) {
  3514. mylistListPopup.hide();
  3515. }
  3516. return body;
  3517. }
  3518. return _watchId;
  3519. };
  3520.  
  3521. body.show = function() {
  3522. body.style.display = '';
  3523. if (mylistListPopup) {
  3524. mylistListPopup.hide();
  3525. }
  3526. };
  3527. body.hide = function() {
  3528. body.style.display = 'none';
  3529. if (mylistListPopup) {
  3530. mylistListPopup.hide();
  3531. }
  3532. };
  3533.  
  3534. function createSelector() {
  3535. var sel = document.createElement('select');
  3536. var lastSelect = 0;
  3537.  
  3538. sel.className = 'mylistSelect';
  3539. var appendO = function(sel, text, value) {
  3540. var opt = document.createElement('option');
  3541. opt.appendChild(document.createTextNode(text));
  3542. opt.value = value;
  3543. sel.appendChild(opt);
  3544. return opt;
  3545. },
  3546. createOptions = function() {
  3547. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3548. var mylist = mylistlist[i];
  3549. appendO(sel, (i + 1).toString(36) + ':' + mylist.name, mylist.id);
  3550. }
  3551. },
  3552. onSelect = function() {
  3553. // jQueryは全てのページにあるわけではないので気をつける。忘れると原宿が死ぬ
  3554. if (sel.selectedIndex === 0) {
  3555. body.className = body.className.replace('mylistSelected', 'deflistSelected');
  3556. } else {
  3557. lastSelect = sel.selectedIndex;
  3558. body.className = body.className.replace('deflistSelected', 'mylistSelected');
  3559. }
  3560. },
  3561. selectDeflist = function() {
  3562. sel.selectedIndex = 0;
  3563. onSelect();
  3564. },
  3565. onContextMenu = function(e) {
  3566. e.preventDefault();
  3567. e.stopPropagation();
  3568.  
  3569. if (lastSelect === 0) return;
  3570. if (sel.selectedIndex === 0) {
  3571. sel.selectedIndex = lastSelect;
  3572. } else {
  3573. sel.selectedIndex = 0;
  3574. }
  3575. onSelect();
  3576. };
  3577.  
  3578. appendO(sel, '0:とりマイ', 'default');
  3579. sel.selectedIndex = 0;
  3580. window.setTimeout(createOptions, initialized ? 0 : 3000);
  3581.  
  3582. sel.addEventListener('change', onSelect, false);
  3583. sel.addEventListener('contextmenu', onContextMenu, false);
  3584.  
  3585.  
  3586. body.addEventListener('dblclick', selectDeflist, false);
  3587. return sel;
  3588. }
  3589.  
  3590. function createSubmitButton() {
  3591. var btn = document.createElement('button');
  3592. btn.appendChild(document.createTextNode('my'));
  3593. btn.className = 'mylistAdd';
  3594. btn.title = 'マイリストに追加\n(ボタンを右クリックで詳細メニュー)';
  3595.  
  3596. var callMylistListPopup = function() {
  3597. if (!mylistListPopup) {
  3598. mylistListPopup = new MylistListPopup(mylistlist, onMylistListClick);
  3599. }
  3600. mylistListPopup.toggle(btn, _watchId);
  3601. };
  3602.  
  3603. btn.addEventListener('contextmenu', function(e) {
  3604. if (window.jQuery) {
  3605. e.preventDefault();
  3606. e.stopPropagation();
  3607. callMylistListPopup();
  3608. }
  3609. });
  3610.  
  3611. btn.addEventListener('click', function(e) {
  3612. var description = null;
  3613. if (e.shiftKey) {
  3614. description = prompt('マイリストコメントの入力');
  3615. if (!description) return;
  3616. }
  3617. setButtonStyleUpdating(btn);
  3618.  
  3619. var mylistId = sel.value, name = sel.options[sel.selectedIndex].textContent;
  3620. if (mylistId === 'default') {
  3621. addDeflist(_watchId, description);
  3622. } else {
  3623. addMylist(_watchId, mylistId, name, description);
  3624. }
  3625. } ,false);
  3626. return btn;
  3627. }
  3628.  
  3629. function createDeleteDeflistItemButton() {
  3630. var btn = document.createElement('button');
  3631. btn.appendChild(document.createTextNode('×'));
  3632. btn.className = 'deflistRemove';
  3633. btn.title = 'とりあえずマイリストから外す';
  3634.  
  3635. btn.addEventListener('click', function() {
  3636.  
  3637. setButtonStyleUpdating(btn);
  3638.  
  3639. self.deleteDefListItem(_watchId, function(status, result) {
  3640. self.reloadDefList();
  3641. btn.style.display = 'none';
  3642. if (status !== "ok") {
  3643. Popup.alert('とりあえずマイリストから削除に失敗: ' + result.error.description);
  3644. } else {
  3645. Popup.show('とりあえずマイリストから外しました');
  3646. }
  3647. });
  3648. } ,false);
  3649. return btn;
  3650. }
  3651.  
  3652. function createTagListButton() {
  3653. var btn = document.createElement('button');
  3654. btn.appendChild(document.createTextNode('tag'));
  3655. btn.className = 'tagGet';
  3656. btn.title = 'タグ取得';
  3657. btn.addEventListener('click', function(e) {
  3658. btn.disabled = true;
  3659.  
  3660. setButtonStyleUpdating(btn);
  3661.  
  3662. if (w.jQuery) {
  3663. var $btn = w.jQuery(btn), o = $btn.offset();
  3664. VideoTags.popupItems(_videoId, o.left, o.top + $btn.outerHeight());
  3665. } else {
  3666. VideoTags.popupItems(_videoId, e.pageX, e.pageY);
  3667. }
  3668. } ,false);
  3669. return btn;
  3670. }
  3671.  
  3672. function createNewTabLink() {
  3673. var a = document.createElement('a');
  3674. a.className = 'newTabLink';
  3675. a.target = '_blank';
  3676. a.title = 'この動画を新しいウィンドウで開く';
  3677. a.innerHTML = '▲';
  3678. return a;
  3679. }
  3680.  
  3681. var newTabLink = createNewTabLink();
  3682. if (w.WatchApp) {
  3683. nobr.appendChild(newTabLink);
  3684. }
  3685.  
  3686.  
  3687. var sel = createSelector();
  3688. var submit = createSubmitButton(sel);
  3689. nobr.appendChild(sel);
  3690. nobr.appendChild(submit);
  3691. if (w.jQuery) {
  3692. w.jQuery(sel).keydown(function(e) {
  3693. e.stopPropagation();
  3694. if (e.keyCode === 13) { // ENTER
  3695. submit.click();
  3696. }
  3697. });
  3698. }
  3699.  
  3700. var tagBtn = createTagListButton();
  3701. nobr.appendChild(tagBtn);
  3702.  
  3703. var deleteDef = createDeleteDeflistItemButton();
  3704. nobr.appendChild(deleteDef);
  3705.  
  3706.  
  3707.  
  3708. nobr.appendChild(extArea);
  3709.  
  3710. body.watchId(_watchId, _videoId);
  3711. return body;
  3712. };
  3713.  
  3714. // XHRでクロスドメインを超えられない場合はこちら
  3715. // 将来マイリストのポップアップウィンドウが廃止されたら使えない
  3716. // (マイページから強引に生成するか?)
  3717. pt.getIframePanel = function(watchId) {
  3718. var _watchId = watchId;
  3719. var body = document.createElement('iframe');
  3720. body.name = 'nicomylistaddDummy';
  3721. body.className = 'mylistPopupPanel';
  3722. body.setAttribute('style', 'width: 130px; height: 24px; z-index: 10000; border: 1px solid silver; padding: 0; margin: 0; overflow: hidden');
  3723.  
  3724. body.watchId = function(w) {
  3725. if (w) {
  3726. _watchId = w;
  3727. body.contentWindow.location.replace("http:/" + "/www.nicovideo.jp/mylist_add/video/" + w);
  3728. return body;
  3729. }
  3730. return _watchId;
  3731. };
  3732. if (watchId !== '') {
  3733. body.src = "http:/" + "/www.nicovideo.jp/mylist_add/video/" + _watchId;
  3734. }
  3735.  
  3736. // ダミーメソッド
  3737. body.show = function() {
  3738. body.style.display = '';
  3739. };
  3740. body.hide = function() {
  3741. body.style.display = 'none';
  3742. };
  3743.  
  3744.  
  3745. return body;
  3746. };
  3747.  
  3748. return new Mylist();
  3749. })();
  3750.  
  3751. var MylistListPopup = function() { this.initialize.apply(this, arguments); };
  3752. MylistListPopup.prototype = {
  3753. initialize: function(mylistList, onItemClick) {
  3754. this._mylistList = mylistList.concat();
  3755. this.initializeView(mylistList);
  3756. this._onItemClick = onItemClick;
  3757. },
  3758. initializeView: function() {
  3759. var $ = window.jQuery;
  3760. this._$view = $([
  3761. '<div class="mylistListPopup popupMenu">',
  3762. '<div class="listInner">',
  3763. '<ul></ul>',
  3764. '</div>',
  3765. '</div>',
  3766. ''].join(''));
  3767. this._$list = this._$view.find('ul');
  3768. this._$inner = this._$view.find('.listInner');
  3769.  
  3770.  
  3771. $('body').append(this._$view);
  3772.  
  3773. this.refresh();
  3774.  
  3775. this.initializeEvent(this._$view);
  3776. },
  3777. initializeEvent: function($view) {
  3778. $view.on('click', window.jQuery.proxy(function(e) {
  3779. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  3780. var target = e.target, $target = window.jQuery(target);
  3781. this.hide();
  3782. e.preventDefault();
  3783.  
  3784. var mylistId = $target.attr('data-mylist-id');
  3785. var mylistName = $target.attr('data-mylist-name');
  3786. if (!mylistId) { return; }
  3787. var type = target.className;
  3788.  
  3789. if (typeof this._onItemClick === 'function') {
  3790. this._onItemClick(mylistId, mylistName, type);
  3791. }
  3792. }, this));
  3793. var self = this, closeTimer = null;
  3794. $view.hover(
  3795. function() {
  3796. if (closeTimer) { window.clearTimeout(closeTimer); }
  3797. },
  3798. function() {
  3799. closeTimer = window.setTimeout(function() { self.hide(); }, 1000);
  3800. });
  3801.  
  3802. $view = null;
  3803. },
  3804. adjustColumnCount: function() {
  3805. this._$inner.css({
  3806. 'column-count': '',
  3807. 'max-height': ''
  3808. });
  3809. var height = this._$view.outerHeight(),
  3810. clientHeight = window.jQuery(window).innerHeight(),
  3811. threshold = clientHeight * 0.4;
  3812. if (threshold < height) {
  3813. var columns = parseInt( height / threshold, 10) + 1;
  3814. this._$inner.css({
  3815. 'column-count': columns,
  3816. 'max-height': clientHeight * 0.8
  3817. });
  3818. }
  3819. },
  3820. updateList: function(mylistList) {
  3821. this._mylistList = mylistList.concat();
  3822. this.refresh();
  3823. },
  3824. refresh: function() {
  3825. var mylistList = this._mylistList;
  3826. this._$list.empty();
  3827. for (var i = 0, len = mylistList.length; i < len; i++) {
  3828. var mylist = mylistList[i];
  3829. this.appendItem(mylist.id, mylist.name, mylist.icon_id);
  3830. }
  3831. this.appendItem('default', 'とりあえずマイリスト');
  3832.  
  3833. this.adjustColumnCount();
  3834. },
  3835. appendItem: function(id, name, icon_id) {
  3836. var $mylist = window.jQuery([
  3837. '<li class="folder', icon_id, '">',
  3838. '<span class="icon"></span>',
  3839. '<a href="my/mylist/#/', id.replace('default', 'home'), '" class="name">',
  3840. name,
  3841. '</a>',
  3842. '</li>',
  3843. ''].join(''));
  3844. $mylist.find('.icon, .name').attr({
  3845. 'data-mylist-id': id,
  3846. 'data-mylist-name': name
  3847. });
  3848.  
  3849. if (id === 'default') {
  3850. $mylist.addClass('deflist');
  3851. } else
  3852. if (id.indexOf('ext') === 0) {
  3853. $mylist.addClass('ext');
  3854. }
  3855. this._$list.append($mylist);
  3856. },
  3857. updateExist: function(watchId) {
  3858. if (!watchId) {
  3859. return;
  3860. }
  3861. this._$view.find('.exist').removeClass('exist');
  3862. this._$view.find('.name').each(function() {
  3863. var $this = window.jQuery(this), mylistId = $this.attr('data-mylist-id');
  3864. if (mylistId === 'default') { return; }
  3865. $this
  3866. .toggleClass('exist', window.WatchItLater.mylist.cache.hasItem(mylistId, watchId));
  3867. });
  3868.  
  3869. this._$view.find('.deflist .name')
  3870. .toggleClass('exist', window.WatchItLater.mylist.findDeflistByWatchId(watchId) !== null);
  3871. },
  3872. show: function(elm, watchId) {
  3873. this.adjustColumnCount();
  3874. this._$view.addClass('show active');
  3875.  
  3876. if (!elm) { return; }
  3877. var
  3878. $ = window.jQuery,
  3879. $elm = $(elm),
  3880. o = $elm.offset(),
  3881. $view = this._$view,
  3882. $window = $(window),
  3883. scrollLeft = $window.scrollLeft(),
  3884. scrollTop = $window.scrollTop();
  3885.  
  3886. $view.css({
  3887. top: Math.max(o.top - $view.outerHeight(), 0, scrollTop),
  3888. left: o.left
  3889. });
  3890. if ($view.offset().left + $view.outerWidth() > $window.innerWidth() + scrollLeft) {
  3891. $view.css({
  3892. left: Math.max(0, $window.innerWidth() + scrollLeft - $view.outerWidth())
  3893. });
  3894. }
  3895.  
  3896. this.updateExist(watchId);
  3897. window.jQuery('body').on('click', $.proxy(this._onBodyClick, this));
  3898. },
  3899. hide: function() {
  3900. var $view = this._$view
  3901. .removeClass('show');
  3902. window.setTimeout(function() {
  3903. $view.css({top: '', left: '', right: ''}).removeClass('active');
  3904. }, 500);
  3905. window.jQuery('body').off('click', this._onBodyClick);
  3906. },
  3907. toggle: function(elm, watchId) {
  3908. if (this._$view.hasClass('avtive')) {
  3909. this.hide();
  3910. } else {
  3911. this.show(elm, watchId);
  3912. }
  3913. },
  3914. _onBodyClick: function() {
  3915. this.hide();
  3916. }
  3917. };
  3918.  
  3919. window.WatchItLater.mylist.cache = (function() {
  3920. var CacheList = function() { this.initialize.apply(this, arguments); };
  3921. CacheList.prototype = {
  3922. initialize: function() {
  3923. this.reset();
  3924. },
  3925. reset: function() {
  3926. this._cacheList = {};
  3927. },
  3928. setCache: function(mylistId, items) {
  3929. if (!this.hasCache(mylistId)) {
  3930. this._cacheList[mylistId] = new MylistCache();
  3931. }
  3932. this._cacheList[mylistId].update(items);
  3933. },
  3934. hasCache: function(mylistId) {
  3935. if (this._cacheList[mylistId]) {
  3936. return true;
  3937. }
  3938. return false;
  3939. },
  3940. hasItem: function(mylistId, watchId) {
  3941. if (!this.hasCache(mylistId)) {
  3942. return false;
  3943. }
  3944. return this._cacheList[mylistId].hasItem(watchId);
  3945. },
  3946. addItem: function(mylistId, watchId) {
  3947. if (!this.hasCache(mylistId)) {
  3948. return false;
  3949. }
  3950. this._cacheList[mylistId].addItem(watchId);
  3951. },
  3952. removeItem: function(mylistId, watchId) {
  3953. if (!this.hasCache(mylistId)) {
  3954. return false;
  3955. }
  3956. this._cacheList[mylistId].removeItem(watchId);
  3957. },
  3958. count: function(mylistId) {
  3959. if (!this.hasCache(mylistId)) {
  3960. return NaN;
  3961. }
  3962. this._cacheList[mylistId].count();
  3963. },
  3964. toJSON: function() {
  3965. var cacheList = this._cacheList;
  3966. return this._cacheList;
  3967. },
  3968. parse: function(jsonString) {
  3969. var data;
  3970. try {
  3971. data = JSON.parse(jsonString);
  3972. } catch (e) {
  3973. data = {};
  3974. }
  3975. this.reset();
  3976. for (var mylistId in data) {
  3977. var mylistCache = data[mylistId];
  3978. this._cacheList[mylistId] = new MylistCache(mylistCache);
  3979. }
  3980. }
  3981. };
  3982.  
  3983. var MylistCache = function() { this.initialize.apply(this, arguments); };
  3984. MylistCache.prototype = {
  3985. initialize: function(mylistData) {
  3986. this._name = '';
  3987. if (mylistData) {
  3988. this.update(mylistData);
  3989. }
  3990. },
  3991. update: function(mylistData) {
  3992. this._cache = [];
  3993. this._hash = {};
  3994. var items = mylistData.items ? mylistData.items : mylistData;
  3995. for (var i = 0, len = items.length; i < len; i++) {
  3996. var
  3997. item = items[i],
  3998. watchId = typeof item.getId === 'function' ? item.getId() : item.id;
  3999. this._cache.push({id: watchId});
  4000. this._hash[watchId] = true;
  4001. }
  4002. if (mylistData.name) {
  4003. this._name = mylistData.name;
  4004. }
  4005. },
  4006. hasItem: function(watchId) {
  4007. return this._hash[watchId] === true;
  4008. },
  4009. addItem: function(watchId) {
  4010. if (this.hasItem(watchId)) {
  4011. return;
  4012. }
  4013. this._hash[watchId] = true;
  4014. this._cache.push({id: watchId});
  4015. },
  4016. removeItem: function(watchId) {
  4017. if (!this.hasItem(watchId)) {
  4018. return;
  4019. }
  4020. delete this._hash[watchId];
  4021. this._cache = $.grep(this._cache, function(item) {
  4022. return item.id !== watchId;
  4023. });
  4024. },
  4025. count: function() {
  4026. return this._cache.length;
  4027. },
  4028. toJSON: function() {
  4029. return {
  4030. name: this._name,
  4031. items: this._cache
  4032. };
  4033. },
  4034. parse: function(jsonString) {
  4035. var items;
  4036. try {
  4037. items = JSON.parse(jsonString);
  4038. } catch (e) {
  4039. items = [];
  4040. }
  4041. this.update(items);
  4042. }
  4043. };
  4044.  
  4045. var cacheList, noop = function() {};
  4046. var initialize = function() {
  4047. initialize = noop;
  4048. console.log('%cinitialize mylistCache', 'background: lightgreen;');
  4049. cacheList = new CacheList();
  4050.  
  4051. if (conf.enableLocalMylistCache) {
  4052. if (window.PlayerApp) {
  4053. $(window).on('beforeunload.watchItLater', function(e) {
  4054. window.localStorage.setItem('watchItLater_mylistCache', serialize());
  4055. });
  4056. }
  4057. var cacheData = window.localStorage.getItem('watchItLater_mylistCache');
  4058. if (cacheData) {
  4059. cacheList.parse(cacheData);
  4060. }
  4061. }
  4062. };
  4063. var hasCache = function(mylistId, watchId) {
  4064. initialize();
  4065. return cacheList.hasItem(mylistId, watchId);
  4066. };
  4067. var hasItem = function(mylistId, watchId) {
  4068. initialize();
  4069. return cacheList.hasItem(mylistId, watchId);
  4070. };
  4071. var addItem = function(mylistId, watchId) {
  4072. initialize();
  4073. cacheList.addItem(mylistId, watchId);
  4074. };
  4075. var removeItem = function(mylistId, watchId) {
  4076. initialize();
  4077. cacheList.removeItem(mylistId, watchId);
  4078. };
  4079. var setCache = function(mylistId, items) {
  4080. initialize();
  4081. cacheList.setCache(mylistId, items);
  4082. };
  4083. var serialize = function() {
  4084. initialize();
  4085. return JSON.stringify(cacheList);
  4086. };
  4087. var unserialize = function(json) {
  4088. initialize();
  4089. cacheList.parse(json);
  4090. };
  4091. var clearCache = function() {
  4092. window.localStorage.removeItem('watchItLater_mylistCache');
  4093. };
  4094.  
  4095.  
  4096. EventDispatcher.addEventListener('onMyMylistLoad', function(mylistId, list) {
  4097. setCache(mylistId, list || []);
  4098. });
  4099. EventDispatcher.addEventListener('onMylistItemAdded', function(mylistId, watchId) {
  4100. initialize();
  4101. cacheList.addItem(mylistId, watchId);
  4102. });
  4103. EventDispatcher.addEventListener('onMylistItemDeleted', function(mylistId, watchId) {
  4104. initialize();
  4105. cacheList.removeItem(mylistId, watchId);
  4106. });
  4107.  
  4108.  
  4109. return {
  4110. initialize: initialize,
  4111. hasItem: hasItem,
  4112. addItem: addItem,
  4113. setCache: setCache,
  4114. serialize: serialize,
  4115. unserialize: unserialize,
  4116. clearCache: clearCache
  4117. };
  4118. })();
  4119.  
  4120.  
  4121. var LocationHashParser = (function(conf, w) {
  4122. var self, dat = {};
  4123.  
  4124. function initialize() {
  4125. var hash = w.location.hash.toString();
  4126. var redirectedHash = window.sessionStorage.getItem('watchItLater_redirectedHash');
  4127. if (redirectedHash) {
  4128. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  4129. hash = redirectedHash;
  4130. window.sessionStorage.removeItem('watchItLater_redirectedHash');
  4131. }
  4132. try {
  4133. if (hash.indexOf('#json={') === 0) {
  4134. dat = JSON.parse(hash.substr(6));
  4135. }
  4136. } catch (e) {
  4137. try {
  4138. dat = JSON.parse(decodeURIComponent(hash.substr(6)));
  4139. } catch(ex) {
  4140. console.log(ex);
  4141. }
  4142. console.log(e);
  4143. }
  4144. }
  4145. function setValue(key, value) {
  4146. dat[key] = value;
  4147. }
  4148. function getValue(key) {
  4149. return dat[key];
  4150. }
  4151. function deleteValue(key) {
  4152. delete dat[key];
  4153. }
  4154. function updateHash() {
  4155. var loc = window.location.href.split('#')[0];
  4156. location.replace(loc + getHash());
  4157. }
  4158. function removeHash() {
  4159. if (location.hash.length <= 1) { return; }
  4160. var scrollTop = $(window).scrollTop();
  4161. var loc = window.location.href.split('#')[0];
  4162. location.replace(loc + '#');
  4163. $(window).scrollTop(scrollTop);
  4164. }
  4165. function getHash() {
  4166. var json = JSON.stringify(dat);
  4167. if (json === '{}') { return ''; }
  4168. return '#json=' + json;
  4169. }
  4170. function getUrl() {
  4171. var loc = window.location.href.split('#')[0];
  4172. return loc + getHash();
  4173. }
  4174. function clear() {
  4175. dat = {};
  4176. removeHash();
  4177. }
  4178.  
  4179. self = {
  4180. initialize: initialize,
  4181. setValue: setValue,
  4182. getValue: getValue,
  4183. deleteValue: deleteValue,
  4184. updateHash: updateHash,
  4185. removeHash: removeHash,
  4186. getHash: getHash,
  4187. getUrl: getUrl,
  4188. clear: clear
  4189. };
  4190. return self;
  4191. })(conf, w);
  4192.  
  4193. window.WatchItLater.loader.favMylists = (function() {
  4194. var lastUpdate = 0;
  4195. var favMylistList = [];
  4196. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4197. var $ = w.$;
  4198. /**
  4199. * お気に入りマイリストの取得。 jQueryのあるページでしか使えない
  4200. * マイページを無理矢理パースしてるので突然使えなくなるかも
  4201. */
  4202. var self = {
  4203. load: function(callback) {
  4204. if (!w.jQuery) return; //
  4205.  
  4206. function request(page) {
  4207. url = baseUrl + '?page=' + page;
  4208. GM_xmlhttpRequest({
  4209. url: url,
  4210. onload: function(resp) {
  4211. var $result = $(resp.responseText).find('#favMylist');
  4212.  
  4213. if ($result.length >= 1) {
  4214. updateMaxPage($result);
  4215.  
  4216. if (page === 1) { favMylistList = []; }
  4217.  
  4218. $result.find('.outer').each(function() {
  4219. favMylistList.push(readBlock(this));
  4220. });
  4221. }
  4222.  
  4223. if (page < maxPage) {
  4224. setTimeout(function() {
  4225. page++;
  4226. request(page);
  4227. }, 500);
  4228. } else {
  4229. sort();
  4230. do_callback();
  4231. }
  4232. }
  4233. });
  4234. }
  4235. function readBlock(elm) {
  4236. var
  4237. $elm = $(elm),
  4238. $a = $elm.find('h5 a'), $desc = $elm.find('.mylistDescription'),
  4239. iconType = $elm.find('.folderIcon').attr('class').split(' ')[1],
  4240. id = ($a.attr('href').split('/').reverse())[0],
  4241. $postTime = $elm.find('.postTime span'),
  4242. postTime = $.trim($postTime.text()),
  4243. postTimeData = $postTime.data(),
  4244. $videoLink = $elm.find('.videoTitle a'),
  4245. videoTitle = $videoLink.text(),
  4246. videoHref = $videoLink.attr('href'),
  4247. videoId = videoHref ? (videoHref.split('/').reverse()[0]) : '';
  4248. return {
  4249. id: id,
  4250. name: $a.text(),
  4251. description: $desc.text(),
  4252. iconType: iconType,
  4253. lastVideo: {
  4254. title: videoTitle,
  4255. videoId: videoId,
  4256. postedAt: postTime,
  4257. postTimeData: postTimeData
  4258. }
  4259. };
  4260. }
  4261.  
  4262. function updateMaxPage($result) {
  4263. var $paging = $result.find('.pagerWrap:first .pager:first a');
  4264. maxPage = Math.min(Math.max($paging.length, 1), 3);
  4265. }
  4266. function sort() {
  4267. favMylistList.sort(function(a, b) {
  4268. return (a.lastVideo.postedAt < b.lastVideo.postedAt) ? 1 : -1;
  4269. });
  4270. }
  4271. function do_callback() {
  4272. if (typeof callback === 'function') { callback(favMylistList); }
  4273. }
  4274.  
  4275. var now = Date.now();
  4276. if (now - lastUpdate < 3 * 60 * 1000) {
  4277. do_callback();
  4278. return;
  4279. }
  4280. lastUpdate = now;
  4281.  
  4282. var
  4283. baseUrl = 'http://' + host + '/my/fav/mylist',
  4284. url = baseUrl,
  4285. maxPage = 1;
  4286.  
  4287. request(1);
  4288.  
  4289. }
  4290. };
  4291. return self;
  4292. })();
  4293.  
  4294.  
  4295. window.WatchItLater.loader.favTags = (function(w) {
  4296. var lastUpdate = 0;
  4297. var favTagList = [], favTagTextList = [];
  4298. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4299. var $ = w.$;
  4300. var pt = function(){};
  4301.  
  4302. var load = function(callback) {
  4303. if (!w.jQuery) return; //
  4304. var now = Date.now();
  4305. if (now - lastUpdate < 60 * 1000) {
  4306. if (typeof callback === 'function') { callback(favTagList); }
  4307. return;
  4308. }
  4309. lastUpdate = now;
  4310. var api = 'http://' + host + '/api/favtag/list?t=' + now;
  4311. $.ajax({
  4312. url: api,
  4313. timeout: 30000,
  4314. complete: function(result) {
  4315. if (result.status !== 200) {
  4316. return;
  4317. }
  4318. try {
  4319. var json = JSON.parse(result.responseText), items = json.favtag_items;
  4320. for (var i = 0, len = items.length; i < len; i++) {
  4321. var text = items[i]['tag'];
  4322. favTagList.push({href: '/tag/' + encodeURIComponent(text), name: items[i]['tag']});
  4323. favTagTextList.push(text);
  4324. }
  4325. EventDispatcher.dispatch('onFavTagsLoad', favTagTextList.concat());
  4326. if (typeof callback === 'function') { callback(favTagList); }
  4327. } catch (e) {
  4328. console.log('tag parse error!', e);
  4329. }
  4330. }
  4331. });
  4332. };
  4333.  
  4334. pt.load = load;
  4335. return pt;
  4336. })(w);
  4337.  
  4338.  
  4339. /**
  4340. * 左下に出るポップアップメッセージ
  4341. *
  4342. */
  4343. var Popup = (function(){
  4344. function Popup() {}
  4345.  
  4346. Popup.show = function(text) {
  4347. console.log('%c' + text, 'background: cyan;');
  4348. if (w.WatchApp) {
  4349. text = text.replace(/[\n]/, '<br />');
  4350. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4351. // Firefoxではflashの上に半透明要素を重ねられないのでとりあえず黒で塗りつぶす
  4352. '<span style="background: black;">' + text + '</span>'
  4353. );
  4354. }
  4355. };
  4356.  
  4357. Popup.alert = function(text) {
  4358. console.log('%c' + text, 'background: yellow;');
  4359. if (w.WatchApp) {
  4360. text = text.replace(/[\n]/, '<br />');
  4361. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4362. '<span style="background: black; color: red;">' + text + '</span>'
  4363. );
  4364. } else {
  4365. w.alert(text);
  4366. }
  4367. };
  4368.  
  4369. Popup.hide = function() {
  4370. if (w.WatchApp) {
  4371. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.stop();
  4372. }
  4373. };
  4374. return Popup;
  4375. })();
  4376.  
  4377.  
  4378. var KeyMatch = (function() {
  4379. var self;
  4380.  
  4381. function create(def) {
  4382. var ch = def.char[0].toUpperCase();
  4383. return {
  4384. prop: {
  4385. char: ch,
  4386. code: typeof def.code === 'number' ? def.code : ch.charCodeAt(0),
  4387. shift: !!def.shift,
  4388. ctrl: !!def.ctrl,
  4389. alt: !!def.alt,
  4390. enable: !!def.enable
  4391. },
  4392. test: function(event) {
  4393. if (
  4394. this.prop.enable === true &&
  4395. this.prop.shift === event.shiftKey &&
  4396. this.prop.ctrl === event.ctrlKey &&
  4397. this.prop.alt === event.altKey &&
  4398. this.prop.code === event.which
  4399. ) {
  4400. event.preventDefault();
  4401. return true;
  4402. }
  4403. return false;
  4404. },
  4405. json: function() {
  4406. return JSON.stringify(this.prop);
  4407. }
  4408. };
  4409. }
  4410.  
  4411. self = {
  4412. create: create
  4413. };
  4414. return self;
  4415. })();
  4416.  
  4417. var TouchEventDispatcher = (function(target) {
  4418. var
  4419. self,
  4420. touchStartEvent = null,
  4421. touchEndEvent = null,
  4422. events = {
  4423. onflick: []
  4424. };
  4425. function dispatchEvent(name) {
  4426. var e = events[name];
  4427. for (var i =0, len = e.length; i < len; i++) {
  4428. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  4429. }
  4430. }
  4431.  
  4432. target.addEventListener('touchstart', function(e) {
  4433. touchStartEvent = e;
  4434. }, false);
  4435. target.addEventListener('touchcancel', function(e) {
  4436. touchStartEvent = null;
  4437. }, false);
  4438. target.addEventListener('touchend', function(e) {
  4439. touchEndEvent = e;
  4440. if (touchStartEvent !== null) {
  4441. var
  4442. sx = touchStartEvent.changedTouches[0].pageX, sy = touchStartEvent.changedTouches[0].pageY,
  4443. ex = touchEndEvent.changedTouches[0].pageX, ey = touchEndEvent.changedTouches[0].pageY,
  4444. dx = (sx - ex), dy = (sy - ey), len = Math.sqrt(dx * dx + dy * dy), s;
  4445. if (len > 150) {
  4446. s = dy / len;
  4447. var a = Math.abs(s), ss = Math.round(s);
  4448. if (a <= 0.3 || a >= 0.7) {
  4449. var d;
  4450. if (ss < 0) { d = 'down'; } else if (ss > 0) { d = 'up'; }
  4451. else if (dx < 0) { d = 'right';} else { d = 'left'; }
  4452. dispatchEvent('onflick', {
  4453. direction: d,
  4454. distance: len,
  4455. x: dx, y: dy,
  4456. startEvent: touchStartEvent,
  4457. endEvent: touchEndEvent
  4458. });
  4459. }
  4460. }
  4461. }
  4462. touchStartEvent = touchEndEvent = null;
  4463. }, false);
  4464.  
  4465. function onflick(callback) {
  4466. events.onflick.push(callback);
  4467. }
  4468.  
  4469. self = {
  4470. onflick: onflick
  4471. };
  4472. return self;
  4473. })(w.document);
  4474.  
  4475.  
  4476.  
  4477. /**
  4478. * リンクのマウスオーバーに仕込む処理
  4479. * ここの表示は再考の余地あり
  4480. */
  4481. var AnchorHoverPopup = (function(w, conf,EventDispatcher) {
  4482. var mylistPanel = Mylist.getPanel(''), hoverMenuDelay = conf.hoverMenuDelay * 1000;
  4483. mylistPanel.className += ' popup';
  4484. mylistPanel.style.display = 'none';
  4485. document.body.appendChild(mylistPanel);
  4486.  
  4487. EventDispatcher.addEventListener('on.config.hoverMenuDelay', function(delay) {
  4488. delay = parseFloat(delay, 10);
  4489. if (isNaN(delay)) { return; }
  4490. hoverMenuDelay = Math.abs(delay * 1000);
  4491. });
  4492.  
  4493. function showPanel(watchId, baseX, baseY, w_touch) {
  4494.  
  4495. var cn = mylistPanel.className.toString();
  4496. if (w_touch === true) {
  4497. cn = cn.replace(' w_touch', '') + ' w_touch';
  4498. } else {
  4499. if (cn.indexOf('w_touch') >= 0 && mylistPanel.style.display !== 'none') {
  4500. // フリック操作で表示したパネルが出ている間はそちらを優先し、なにもしない
  4501. return;
  4502. }
  4503. cn = cn.replace(' w_touch', '');
  4504.  
  4505. }
  4506. VideoTags.hidePopup();
  4507. if (mylistPanel.className !== cn) mylistPanel.className = cn;
  4508.  
  4509. mylistPanel.style.display = '';
  4510. mylistPanel.watchId(watchId);
  4511. mylistPanel.style.right = null;
  4512. mylistPanel.style.left = (baseX) + 'px';
  4513. mylistPanel.style.top = Math.max(baseY - mylistPanel.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  4514.  
  4515. if (mylistPanel.offsetLeft + mylistPanel.offsetWidth > document.body.clientWidth) {
  4516. mylistPanel.style.left = null;
  4517. mylistPanel.style.right = 0;
  4518. }
  4519.  
  4520. }
  4521.  
  4522.  
  4523. var videoReg = /(\?cc_video_id=|\?cc_id=|watch\/)([a-z0-9]+)/;
  4524. var excludeReg = /(news|live|seiga)\..*?nicovideo\.jp/;
  4525.  
  4526. function each(w, watchId) {
  4527.  
  4528. this.w_eventInit = false;
  4529.  
  4530. this.addEventListener('mouseover', function() {
  4531. var mx = 0, my = 0, self = this;
  4532. if (this.href) this.setAttribute('href', this.href.split('?')[0]);
  4533.  
  4534. self.w_mouse_in = true;
  4535. self.w_mouse_timer = null;
  4536. self.w_mouse_timer = setTimeout(function() {
  4537. self.w_mouse_timer = null;
  4538. if (!self.w_mouse_in) {
  4539. return;
  4540. }
  4541. var o;
  4542. if (w.jQuery) {
  4543. var $e = w.jQuery(self);
  4544. var t = $e.text();
  4545. o = t !== "" ? $e.offset() : $e.find('*').offset();
  4546. showPanel(watchId, o.left, o.top);
  4547. } else
  4548. if (self.getBoundingClientRect) {
  4549. o = (self.firstChild && self.firstChild.tagName === 'IMG') ? self.firstChild.getBoundingClientRect() : self.getBoundingClientRect();
  4550. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4551. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4552. showPanel(watchId, left + o.left, top + o.top);
  4553. } else {
  4554. showPanel(watchId, mx + 8, my + 8);
  4555. }
  4556. EventDispatcher.dispatch('mylistPanelOpen', mylistPanel, self, watchId);
  4557. }, hoverMenuDelay);
  4558.  
  4559. if (!this.w_eventInit) {
  4560. this.addEventListener('mouseout', function() {
  4561. self.w_mouse_in = false;
  4562. if (self.w_mouse_timer) {
  4563. clearTimeout(self.w_mouse_timer);
  4564. self.w_mouse_timer = null;
  4565. }
  4566. }, false);
  4567. if (!w.jQuery) {
  4568. this.addEventListener('mousemove', function(ev) {
  4569. mx = ev.pageX;
  4570. my = ev.pageY;
  4571. }, false);
  4572. }
  4573. this.w_eventInit = true;
  4574. }
  4575. }, false);
  4576. this.added = 1;
  4577. }
  4578.  
  4579. function bind(force, target) {
  4580. if (!conf.enableHoverPopup) { return; }
  4581.  
  4582. var a = Array.prototype.slice.apply(document.links), vreg = videoReg, ereg = excludeReg;
  4583. for (var i = 0, len = a.length; i < len; i++) {
  4584. var e = a[i];
  4585. var m, href= e.href;
  4586. if (
  4587. href &&
  4588. !e.added &&
  4589. (m = vreg.exec(href)) !== null &&
  4590. !ereg.test(href) &&
  4591. // e.className !== "itemEcoLink" &&
  4592. e.className !== "playlistSaveLink"
  4593. ) {
  4594. each.apply(e, [w, m[2]]);
  4595. }
  4596. }
  4597. }
  4598. function bindTouch() {
  4599. TouchEventDispatcher.onflick(function(e) {
  4600. var se = e.startEvent;
  4601. if (e.direction === 'right' && (se.target.tagName === 'A' || se.target.parentElement.tagName === 'A')) {
  4602. var
  4603. a = (se.target.tagName === 'A') ? e.startEvent.target : e.startEvent.target.parentElement,
  4604. href = a.href, vreg = videoReg, ereg = excludeReg, m, watchId;
  4605. if (
  4606. href &&
  4607. (m = vreg.exec(href)) !== null &&
  4608. !ereg.test(href) &&
  4609. // e.className !== "itemEcoLink" &&
  4610. true
  4611. ) {
  4612. watchId = m[2];
  4613. var o;
  4614. if (w.jQuery) {
  4615. var $a = w.jQuery(a);
  4616. var t = $a.text();
  4617. o = t !== "" ? $a.offset() : $a.find('*').offset();
  4618. showPanel(watchId, o.left, o.top, true);
  4619. } else {
  4620. o = (a.firstChild && a.firstChild.tagName === 'IMG') ? a.firstChild.getBoundingClientRect() : a.getBoundingClientRect();
  4621. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4622. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4623. showPanel(watchId, left + o.left, top + o.top, true);
  4624. }
  4625. }
  4626. }
  4627. });
  4628. }
  4629.  
  4630. var lastUpdate = 0, linksCount = document.links.length,
  4631. bindLoop = function(nextTime) {
  4632. var now = Date.now();
  4633. var updateInterval = w.document.hasFocus() ? 3000 : 15000;
  4634. if (now - lastUpdate < updateInterval) {
  4635. var len = document.links.length;
  4636. if (linksCount === len) {
  4637. return;
  4638. }
  4639. linksCount = len;
  4640. }
  4641. bind();
  4642. lastUpdate = now;
  4643. };
  4644.  
  4645. var self = {
  4646. hidePopup: function() {
  4647. VideoTags.hidePopup();
  4648. mylistPanel.hide();
  4649. return this;
  4650. },
  4651. updateNow: function() {
  4652. bind();
  4653. lastUpdate -= 1500;
  4654. return this;
  4655. }
  4656. };
  4657.  
  4658.  
  4659. if (location.host === "ext.nicovideo.jp") {
  4660. bind();
  4661. } else {
  4662. var thumbnailReg = /\.smilevideo\.jp\/smile\?i=(\d+)/;
  4663. if (location.host === 'ch.nicovideo.jp' && w.jQuery) {
  4664. w.jQuery('.lazyimage, .thumb_video.thumb_114.wide img, .itemset li .image a .item').each(function() {
  4665. var $e = w.jQuery(this).text(' ');
  4666. var src = $e.attr('data-original') || $e.attr('src');
  4667. if (typeof src === 'string' && thumbnailReg.test(src)) {
  4668. each.apply(this, [w, 'so' + RegExp.$1]);
  4669. }
  4670. });
  4671. }
  4672. bindTouch();
  4673. bind();
  4674. setInterval(bindLoop, 500);
  4675. }
  4676. return self;
  4677. })(w, conf, EventDispatcher);
  4678. window.WatchItLater.popup = AnchorHoverPopup;
  4679.  
  4680.  
  4681. //===================================================
  4682. //===================================================
  4683. //===================================================
  4684.  
  4685.  
  4686. /**
  4687. * マイリスト登録のポップアップウィンドウを乗っ取る処理
  4688. *
  4689. * iframeの子ウィンドウ内に開かれた時に実行される
  4690. * クロスドメインを越えられない環境ではこっちを使うしかない
  4691. */
  4692. (function(){ // mylist window
  4693. if (w.location.href.indexOf('/mylist_add/') < 0 || w.name === 'nicomylistadd') return;
  4694.  
  4695. var $ = w.jQuery;
  4696. $('body,table,img,td').css({border:0, margin:0, padding:0, background: "transparent", overflow: 'hidden'});
  4697. $('#main_frm').css({background: '#fff', paddig: 0, borderRadius: 0}).addClass('mylistPopupPanel');
  4698.  
  4699. if ($('#edit_description').length < 1) {
  4700. $('#main_frm .font12:first').css({position: 'absolute', margin: 0, top: 0, left: 0, padding: 0, color: 'red', fontSize: '8pt'});
  4701. // ログインしてないぽい
  4702. return;
  4703. }
  4704.  
  4705. $('#box_success').css({position: 'absolute', top: 0, left: 0});
  4706. $('#box_success h1').css({color: 'black', fontSize: '8pt', padding: 0});
  4707. $('td').css({padding: 0});
  4708.  
  4709. // 「マイリストに登録しました」
  4710. // $('.mb8p4:last').show();
  4711. // $('.mb8p4:last h1').css({fontSize : "8pt"});
  4712.  
  4713. $('table:first').css({width: '200px'});
  4714. $('table:first td.main_frm_bg').css({height: '20px'});
  4715. $('table:first table:first').hide();
  4716.  
  4717. $('select')
  4718. .css({width: '64px',position: 'absolute', top:0, left:0, margin: 0})
  4719. .addClass('mylistSelect');
  4720. $('select')[0].selectedIndex = $('select')[0].options.length - 1;
  4721. $('#select_group option:last')[0].innerHTML = 'とりマイ';
  4722.  
  4723. // var submit = document.createElement("input");
  4724. // submit.className = 'mylistAdd';
  4725. // submit.type = "submit";
  4726. // submit.value = "マ";
  4727. // $(submit).css({position: 'absolute', top: 0, left: '64px'});
  4728. // $('select')[0].parentNode.appendChild(submit);
  4729.  
  4730.  
  4731. $('#edit_description').hide();
  4732.  
  4733. w.document.documentElement.scrollTop = 0;
  4734. w.document.documentElement.scrollLeft = 0;
  4735.  
  4736.  
  4737. $($.browser.safari ? 'body' : 'html').scrollTop(0);
  4738.  
  4739. w.window.close = function()
  4740. {
  4741. return;
  4742. };
  4743. w.window.alert = function()
  4744. {
  4745. w.document.write('<span style="position:absolute;top:0;left:0;font-size:8pt;color:red;">' +
  4746. arguments[0] + '</span>');
  4747. };
  4748. })();
  4749.  
  4750.  
  4751. //===================================================
  4752. //===================================================
  4753. //===================================================
  4754.  
  4755. window.WatchItLater.loader.videoArrayAPILoader = (function() {
  4756. var sessionId = 0;
  4757. var deferredList = {};
  4758. var cacheData = {};
  4759. var currentRequestIds = '', currentPromise;
  4760. var loaderFrame, loaderWindow;
  4761. var BASE_URL = 'http://i.nicovideo.jp/v3/video.array?v=';
  4762. var isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') >= 0;
  4763.  
  4764. //WatchItLater.loader.videoArrayAPILoader.load('sm9').then(function(info) { console.log(info); });
  4765.  
  4766. var load = function(watchId) {
  4767. var ids = [], result = {}, def = new $.Deferred(), timeoutTimer = null;
  4768.  
  4769. initialize();
  4770.  
  4771. $(typeof watchId !== 'string' ? watchId : [watchId]).each(function(i, id) {
  4772. if (cacheData[id]) {
  4773. result[id] = cacheData[id];
  4774. } else {
  4775. ids.push(id);
  4776. }
  4777. });
  4778. ids = window._.unique(ids);
  4779. if (ids.length < 1) {
  4780. window.setTimeout(function() { def.resolve(result); }, 0);
  4781. return def;
  4782. }
  4783. var _ids = JSON.stringify(ids);
  4784. var onSuccess = function(infoList) {
  4785. $(ids).each(function(i, id) {
  4786. result[id] = result[id] || infoList[id] || cacheData[id];
  4787. });
  4788.  
  4789. window.clearTimeout(timeoutTimer);
  4790. currentRequestIds = ''; currentPromise = null;
  4791. def.resolve(result);
  4792. def = null;
  4793. };
  4794. var onFail = function() {
  4795. window.clearTimeout(timeoutTimer);
  4796. console.log('%cVideoArrayAPILoader.onFail', 'color: red;');
  4797. currentRequestIds = ''; currentPromise = null;
  4798. if (def) {
  4799. def.reject();
  4800. }
  4801. def = null;
  4802. };
  4803.  
  4804. sessionId++;
  4805.  
  4806. timeoutTimer = window.setTimeout(onFail, 30 * 1000);
  4807.  
  4808. if (_ids === currentRequestIds) {
  4809. currentPromise.then(onSuccess, onFail);
  4810. return def;
  4811. }
  4812.  
  4813. currentRequestIds = _ids;
  4814. sendRequest(ids, sessionId).then(onSuccess, onFail);
  4815.  
  4816. return def.promise();
  4817. };
  4818.  
  4819. var sendRequest = function(ids, sessionId) {
  4820. var def = new $.Deferred();
  4821. currentPromise = def;
  4822. deferredList[sessionId] = def;
  4823. if (isChrome && conf.debugMode) {
  4824. // 基本的に i.nicovideo.jpのほうが高機能だが、Chrome + Tampermonkeyからは使えないため回避策
  4825. window.WatchItLater.loader.ceAPIClient.videoArray(ids, 'xml').then(
  4826. function(xml) { onXmlLoad(sessionId, xml); },
  4827. function() { onXmlFail(sessionId); }
  4828. );
  4829. } else {
  4830. loaderWindow.location.replace(BASE_URL + ids.join(',') + '#' + sessionId);
  4831. }
  4832. return def.promise();
  4833. };
  4834.  
  4835. var parseVideoArray = function(xml) {
  4836. var $xml = $(xml), infoList = {};
  4837. $xml.find('video_info').each(function() {
  4838. var info = new parseVideoInfo($(this));
  4839. infoList[info.id] = cacheData[info.id] = cacheData[info.default_thread] = info;
  4840. if (info.threadIds && info.threadIds.length > 1) {
  4841. $(info.threadIds).each(function(i, threadId) {
  4842. infoList[threadId] = cacheData[threadId] = info;
  4843. });
  4844. }
  4845. });
  4846. return infoList;
  4847. };
  4848.  
  4849. var parseVideoInfo = function($xml) {
  4850. var info = {threadIds: []};
  4851. var elements = [
  4852. 'id', 'user_id', 'title', 'description', 'length_in_seconds',
  4853. 'thumbnail_url', 'first_retrieve', 'default_thread',
  4854. 'view_counter', 'mylist_counter'];
  4855.  
  4856. $(elements).each(function(i, elm) {
  4857. info[elm] = $xml.find(elm + ':first').text();
  4858. });
  4859.  
  4860. info['num_res'] = $xml.find('thread:first num_res').text();
  4861.  
  4862. var duration = parseInt(info['length_in_seconds'], 10);
  4863. info['length'] = parseInt(duration / 60, 10) + ':' + (100 + (duration % 60)).toString().substr(1);
  4864.  
  4865. info['first_retrieve_t'] = info['first_retrieve'];
  4866. info['first_retrieve'] =
  4867. window.WatchApp.ns.util.DateFormat.strftime(
  4868. '%Y-%m-%d %H:%M:%S',
  4869. new Date(info['first_retrieve'])
  4870. );
  4871.  
  4872. $xml.find('thread id, channel_thread id').each(function() {
  4873. info.threadIds.push(this.innerHTML);
  4874. });
  4875. return info;
  4876. };
  4877.  
  4878. var onXmlLoad = function(sessionId, xml) {
  4879. if (deferredList[sessionId]) {
  4880. deferredList[sessionId].resolve(parseVideoArray(xml));
  4881. delete deferredList[sessionId];
  4882. currentPromise = null;
  4883. }
  4884. };
  4885.  
  4886. var onXmlFail = function(sessionId) {
  4887. if (deferredList[sessionId]) {
  4888. deferredList[sessionId].reject();
  4889. delete deferredList[sessionId];
  4890. currentPromise = null;
  4891. }
  4892. };
  4893.  
  4894. var initialize = function() {
  4895. initialize = window._.noop;
  4896.  
  4897. loaderFrame = document.createElement('iframe');
  4898. loaderFrame.name = 'watchItLaterAPILoader';
  4899. loaderFrame.className = 'watchItLaterAPILoaderFrame xDomainLoaderFrame';
  4900. document.body.appendChild(loaderFrame);
  4901.  
  4902. loaderWindow = loaderFrame.contentWindow;
  4903.  
  4904. EventDispatcher.addEventListener('onMessage', function(data, type) {
  4905. if (type !== 'VideoArrayAPILoader') { return; }
  4906. var sessionId = data.session, xml = data.xml;
  4907. try {
  4908. //console.log('VideoArrayAPILoader.onMessage', data.session, data.xml);
  4909. onXmlLoad(sessionId, xml);
  4910. } catch (e) {
  4911. console.log('message parse error', e);
  4912. onXmlFail(sessionId);
  4913. }
  4914. });
  4915.  
  4916. };
  4917.  
  4918. // sample URL
  4919. // http://i.nicovideo.jp/v3/video.array?v=sm9,sm3393520,sm5909863,so23023492,1394173596
  4920. //initialize();
  4921. return {
  4922. load: load
  4923. };
  4924. })();
  4925.  
  4926. // 参考: http://www59.atwiki.jp/nicoapi/pages/24.html
  4927. // TampermonkeyはContent-Type: text/xmlのページで動かないため、
  4928. // 同じドメインにある適当なテキストapiを踏み台にして通信する
  4929. window.WatchItLater.loader.ceAPIClient = (function() {
  4930. var BASE_URL = 'http://api.ce.nicovideo.jp/api/v1/system.unixtime?';
  4931. var MESSAGE_ORIGIN = 'http://api.ce.nicovideo.jp/';
  4932.  
  4933. function CeAPIClient() {}
  4934.  
  4935. CeAPIClient.prototype = {
  4936. initialize: function() {
  4937. this.initialize = this.initialize_;
  4938. console.log('%cinitialize CeAPIClient', 'background: lightgreen;');
  4939. this._initialDef = new $.Deferred();
  4940.  
  4941. var sessions = this._sessions = {};
  4942. var cacheData = this._cacheData = {};
  4943.  
  4944. var loaderFrame = document.createElement('iframe');
  4945. loaderFrame.name = 'ceAPILoader';
  4946. loaderFrame.className = 'ceAPILoaderFrame xDomainLoaderFrame';
  4947. document.body.appendChild(loaderFrame);
  4948.  
  4949. this._loaderWindow = loaderFrame.contentWindow;
  4950.  
  4951. EventDispatcher.addEventListener('onMessage', $.proxy(function(data, type) {
  4952. if (type !== 'ceAPILoader') { return; }
  4953. if (data.status === 'initialized') {
  4954. return this._onInitialized();
  4955. }
  4956.  
  4957. if (data.sessionId) {
  4958. var def = sessions[data.sessionId];
  4959. delete sessions[data.sessionId];
  4960.  
  4961. if (data.status === 'ok') {
  4962. cacheData[data.url] = data.body;
  4963. return def.resolve(data.body);
  4964. } else {
  4965. cacheData[data.url] = data.body;
  4966. window.setTimeout(function() { delete cacheData[data.url]; }, 60000);
  4967. return def.reject(data.status);
  4968. }
  4969. }
  4970. }, this));
  4971.  
  4972. this._loaderWindow.location.replace(BASE_URL);
  4973.  
  4974. return this._initialDef.promise();
  4975. },
  4976. initialize_: function() {
  4977. var def = new $.Deferred();
  4978. window.setTimeout(function() { def.resolve(); }, 0);
  4979. return def.promise();
  4980. },
  4981. _onInitialized: function() {
  4982. return this._initialDef.resolve();
  4983. },
  4984. _load: function(url) {
  4985. var def = new $.Deferred(), cacheData = this._cacheData;
  4986. if (cacheData[url]) {
  4987. window.setTimeout(function() { def.resolve(cacheData[url]); }, 0);
  4988. return def.promise();
  4989. }
  4990.  
  4991. var sessionId = 'session_' + Math.random();
  4992. this._sessions[sessionId] = def;
  4993. try {
  4994. this._loaderWindow.postMessage(JSON.stringify({
  4995. sessionId: sessionId,
  4996. url: url
  4997. }),
  4998. MESSAGE_ORIGIN);
  4999. } catch (e) {
  5000. console.log('%cException!', 'background: red;', e);
  5001. delete this._sessions[sessionId];
  5002. return def.reject();
  5003. }
  5004. return def.promise();
  5005. },
  5006. videoArray: function(watchId, format) {
  5007. return this.initialize().then($.proxy(function() {
  5008. var url = '/nicoapi/v1/video.array?v=';
  5009. var ids = [], def = new $.Deferred();
  5010.  
  5011. $(typeof watchId !== 'string' ? watchId : [watchId]).each(function(i, id) {
  5012. ids.push(id);
  5013. });
  5014. ids = window._.unique(ids);
  5015. if (ids.length < 1) {
  5016. window.setTimeout(function() { def.resolve({}); }, 0);
  5017. return;
  5018. }
  5019.  
  5020. url = url + ids.join(',');
  5021. if (!format || format !== 'xml') {
  5022. url += '&__format=json';
  5023. }
  5024.  
  5025. this._load(url).then(function(result) {
  5026. try {
  5027. if (!format && format !== 'xml') {
  5028. result = JSON.parse(result).nicovideo_video_response;
  5029. result.status = result['@status'];
  5030. delete result['@status'];
  5031. }
  5032. } catch (e) {
  5033. console.log('%cJSON parse Error!', 'background: red;', e);
  5034. def.reject({});
  5035. }
  5036. return def.resolve(result);
  5037. }, function() {
  5038. return def.reject();
  5039. });
  5040.  
  5041. return def.promise();
  5042. }, this));
  5043. }
  5044. };
  5045.  
  5046. return new CeAPIClient();
  5047. })();
  5048.  
  5049.  
  5050. //===================================================
  5051. //===================================================
  5052. //===================================================
  5053.  
  5054. var _watchController = function(w) {
  5055. var WatchApp = w.WatchApp, _false = function() { return false; };
  5056. var
  5057. watch = (WatchApp && WatchApp.ns.init) || {},
  5058. watchInfoModel = (watch.CommonModelInitializer && WatchApp.ns.model.WatchInfoModel.getInstance()) || {},
  5059. nicoPlayer = (watch.PlayerInitializer && watch.PlayerInitializer.nicoPlayerConnector) || {},
  5060. videoExplorerController = watch.VideoExplorerInitializer.videoExplorerController,
  5061. videoExplorer = videoExplorerController.getVideoExplorer(),
  5062. videoExplorerContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  5063. $ = w.$, WatchJsApi = w.WatchJsApi;
  5064. return {
  5065. isZeroWatch: function() {
  5066. return (window.PlayerApp) ? true : false;
  5067. },
  5068. getWatchInfoModel: function() {
  5069. return watchInfoModel;
  5070. },
  5071. nicoSearch: function(word, search_type) {
  5072. if (!search_type) {
  5073. try {
  5074. var type = videoExplorerContentType.SEARCH;
  5075. search_type = videoExplorer.getContentList().getContent(type).getSearchType();
  5076. } catch(e) {
  5077. search_type = search_type || 'tag';
  5078. }
  5079. }
  5080. videoExplorerController.searchVideo(word, search_type);
  5081. AnchorHoverPopup.hidePopup();
  5082. },
  5083. showMylist: function(mylistId) {
  5084. videoExplorerController.showMylist(mylistId.toString());
  5085. },
  5086. clearDeflistCache: function() {
  5087. watch.VideoExplorerInitializer.deflistVideoAPILoader._cache.clear();
  5088. },
  5089. clearMylistCache: function(id) {
  5090. if (id) {
  5091. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.deleteElement(
  5092. 'http://riapi.nicovideo.jp/api/watch/mylistvideo?id=' + id.toString()
  5093. );
  5094. } else {
  5095. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.clear();
  5096. }
  5097. },
  5098. showDeflist: function() {
  5099. videoExplorerController.showDeflist();
  5100. },
  5101. changeScreenMode: function(mode) {
  5102. WatchJsApi.player.changePlayerScreenMode(mode);
  5103. setTimeout(function(){$(window).resize();}, 3000);
  5104. },
  5105. isFixedHeader: function() {
  5106. return !$('body').hasClass('nofix');
  5107. },
  5108. // ヘッダー追従かどうかを考慮したscrollTop
  5109. scrollTop: function(top, dur) {
  5110. var header = (this.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  5111.  
  5112. if (top !== void 0) {
  5113. return $(window).scrollTop(top - header, dur);
  5114. } else {
  5115. return $(window).scrollTop() + header;
  5116. }
  5117. },
  5118. scrollToVideoPlayer: function(force) {
  5119. // 縦解像度がタグ+プレイヤーより大きいならタグの開始位置、そうでないならプレイヤーの位置にスクロール
  5120. // ただし、該当部分が画面内に納まっている場合は、勝手にスクロールするとかえってうざいのでなにもしない
  5121. var $body = $('body'), isContentFix = $body.hasClass('content-fix');
  5122. $body.addClass('w_noHover').removeClass('content-fix');
  5123. var h = $('#playerContainer').outerHeight() + $('#videoTagContainer').outerHeight();
  5124. var top = $(window).height() >= h ? '#videoTagContainer, #playerContainer' : '#playerContainer';
  5125.  
  5126.  
  5127. if (force) {
  5128. // 要素が画面内に納まっている場合でも、その要素の位置までスクロール
  5129. WatchApp.ns.util.WindowUtil.scrollFit(top, 600);
  5130. } else {
  5131. // 要素が画面内に収まっている場合はスクロールしない
  5132. WatchApp.ns.util.WindowUtil.scrollFitMinimum(top, 600);
  5133. }
  5134. $(window).scrollLeft(0);
  5135. $body.toggleClass('content-fix', isContentFix);
  5136. setTimeout(function() {
  5137. $body.removeClass('w_noHover');
  5138. }, 800);
  5139. },
  5140. play: function() {
  5141. nicoPlayer.playVideo();
  5142. },
  5143. pause: function() {
  5144. nicoPlayer.stopVideo();
  5145. },
  5146. togglePlay: function() {
  5147. var status = $("#external_nicoplayer")[0].ext_getStatus();
  5148. if (status === 'playing') {
  5149. this.pause();
  5150. } else {
  5151. this.play();
  5152. }
  5153. },
  5154. isPlaying: function() {
  5155. var status = $("#external_nicoplayer")[0].ext_getStatus();
  5156. return status === 'playing';
  5157. },
  5158. nextVideo: function() {
  5159. return nicoPlayer.playNextVideo();
  5160. },
  5161. prevVideo: function() {
  5162. return nicoPlayer.playPreviousVideo();
  5163. },
  5164. vpos: function(v) {
  5165. if (typeof v === 'number') {
  5166. return nicoPlayer.seekVideo(v);
  5167. } else {
  5168. return nicoPlayer.getVpos();
  5169. }
  5170. },
  5171. openSearch: function() {
  5172. WatchApp.ns.init.VideoExplorerInitializer.expandButtonView.open();
  5173. // videoExplorer.openByCurrentCondition();
  5174. // videoExplorer.changeState(true);
  5175. },
  5176. closeSearch: function() {
  5177. videoExplorer.changeState(false);
  5178. videoExplorer.close();
  5179. },
  5180. openVideoOwnersVideo: function() {
  5181. if (this.isChannelVideo()) {
  5182. this.openChannelOwnersVideo();
  5183. } else {
  5184. this.openUpNushiVideo();
  5185. }
  5186. },
  5187. openUpNushiVideo: function() {
  5188. videoExplorerController.showOwnerVideo();
  5189. },
  5190. openChannelOwnersVideo: function() {
  5191. videoExplorerController.showMylist('-3');
  5192. },
  5193. openUserVideo: function(userId, userNick) {
  5194. videoExplorerController.showOtherUserVideos(userId, userNick);
  5195. },
  5196. openRecommend: function() {
  5197. var
  5198. type = videoExplorerContentType.RELATED_VIDEO,
  5199. open = function() {
  5200. var rel = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer._menu.getItemByContentType(type);
  5201. rel.select();
  5202. };
  5203. if (videoExplorer.isOpen()) {
  5204. open();
  5205. } else {
  5206. this.openSearch();
  5207. setTimeout(open, 500);
  5208. }
  5209. },
  5210. getVideoExplorerCurrentItems: function(format) {
  5211. var ac = videoExplorer._contentList.getActiveContent();
  5212. if (!ac || !ac.getItems) return [];
  5213. var items = ac.getItems();
  5214.  
  5215. if (!format) {
  5216. return items;
  5217. } else
  5218. if (format === 'playlist') {
  5219. var result = [];
  5220. for (var i = items.length - 1; i >= 0; i--) {
  5221. result.unshift(
  5222. videoExplorerController._item2playlistItem(items[i])
  5223. );
  5224. }
  5225. return result;
  5226. }
  5227. },
  5228. getWatchId: function() {// スレッドIDだったりsmXXXXだったり
  5229. return watchInfoModel.v;
  5230. },
  5231. getVideoId: function() {// smXXXXXX, soXXXXX など
  5232. return watchInfoModel.id;
  5233. },
  5234. getMyNick: function() {
  5235. return watch.CommonModelInitializer.viewerInfoModel.nickname;
  5236. },
  5237. getMyUserId: function() {
  5238. return watch.CommonModelInitializer.viewerInfoModel.userId;
  5239. },
  5240. getPlaylistItems: function() {
  5241. return watch.PlaylistInitializer.playlist.items || watch.PlaylistInitializer.playlist.currentItems;
  5242. },
  5243. setPlaylistItems: function(items, currentItem) {
  5244. var playlist = watch.PlaylistInitializer.playlist;
  5245. // var isAutoPlay = playlist.isContinuous();//.isAutoPlay();
  5246. playlist.reset(
  5247. items,
  5248. 'WatchItLater',
  5249. playlist.type,
  5250. playlist.option
  5251. );
  5252. if (currentItem) { playlist.playingItem = currentItem; }
  5253. else { playlist.playingItem = items[0]; }
  5254. // if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  5255. // playlist.disableContinuous();
  5256. // }
  5257. },
  5258. shufflePlaylist: function(target) {
  5259. var x = this.getPlaylistItems(), items = [], i, currentIndex = -1, currentItem = null;
  5260. if (target === 'right') {
  5261. for (i = 0; i < x.length;) {
  5262. if (x[0]._isPlaying) {
  5263. currentIndex = i;
  5264. currentItem = x.shift();
  5265. items.push(currentItem);
  5266. break;
  5267. } else {
  5268. items.push(x.shift());
  5269. }
  5270. }
  5271. }
  5272.  
  5273. x = x.map(function(a){return {weight:Math.random(), value:a};})
  5274. .sort(function(a, b){return a.weight - b.weight;})
  5275. .map(function(a){return a.value;});
  5276. for (i = 0; i < x.length; i++) {
  5277. if (x[i]._isPlaying) {
  5278. items.unshift(x[i]);
  5279. } else {
  5280. items.push(x[i]);
  5281. }
  5282. }
  5283. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView;
  5284. var left = pm.getLeftSideIndex();
  5285. this.setPlaylistItems(items, currentItem);
  5286. pv.scroll(left);
  5287. },
  5288. clearPlaylist: function(target) {
  5289. var x = this.getPlaylistItems(), items = [], i, currentItem = null;
  5290. if (target === 'left') {
  5291. for (i = x.length - 1; i >= 0; i--) {
  5292. items.unshift(x[i]);
  5293. if (x[i]._isPlaying) {
  5294. currentItem = x[i];
  5295. break;
  5296. }
  5297. }
  5298. } else
  5299. if (target === 'right') {
  5300. for (i = 0; i < x.length ; i++) {
  5301. items.push(x[i]);
  5302. if (x[i]._isPlaying) {
  5303. currentItem = x[i];
  5304. break;
  5305. }
  5306. }
  5307. }
  5308. else {
  5309. for (i = 0; i < x.length; i++) {
  5310. if (x[i]._isPlaying) {
  5311. currentItem = x[i];
  5312. items.unshift(x[i]);
  5313. }
  5314. }
  5315. }
  5316. this.setPlaylistItems(items, currentItem);
  5317. },
  5318. appendSearchResultToPlaylist: function(mode) {
  5319. var
  5320. items = this.getPlaylistItems(),
  5321. searchItems = this.getVideoExplorerCurrentItems('playlist'),
  5322. uniq = {}, i, playingIndex = 0, c, len, currentItem = null;
  5323. if (!searchItems || searchItems.length < 1) {
  5324. return;
  5325. }
  5326. for (i = 0, len = items.length; i < len; i++) {
  5327. uniq[items[i].id] = true;
  5328. if (items[i]._isPlaying) { playingIndex = i; currentItem = items[i]; }
  5329. }
  5330. if (mode === 'next') {
  5331. for (i = searchItems.length - 1; i >= 0; i--) {
  5332. c = searchItems[i];
  5333. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === void 0 && items.splice(playingIndex + 1, 0, c);
  5334. }
  5335. } else {
  5336. for (i = 0, len = searchItems.length; i < len; i++) {
  5337. c = searchItems[i];
  5338. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === void 0 && items.push(c);
  5339. }
  5340. }
  5341. this.setPlaylistItems(items, currentItem);
  5342. },
  5343. insertVideoToPlaylist: function(id) {
  5344. WatchItLater.VideoInfoLoader.load(id).then(function(info) {
  5345. var item = new WatchApp.ns.model.playlist.PlaylistItem(info);
  5346. watch.PlaylistInitializer.playlist.insertNextPlayingItem(item);
  5347. }, function(err) {
  5348. Popup.alert(err.message);
  5349. });
  5350. },
  5351. addDefMylist: function(description) {
  5352. var watchId = watchInfoModel.id;
  5353. setTimeout(function() {
  5354. Mylist.addDefListItem(watchId, function(status, result, replaced) {
  5355. Mylist.reloadDefList();
  5356. if (status !== "ok") {
  5357. Popup.alert('とりあえずマイリストの登録に失敗: ' + result.error.description);
  5358. } else {
  5359. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  5360. Popup.show(
  5361. torimai +
  5362. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  5363. );
  5364. }
  5365. }, description);
  5366. }, 0);
  5367. },
  5368. commentVisibility: function(v) {
  5369. if (v === 'toggle') {
  5370. return this.commentVisibility(!this.commentVisibility());
  5371. } else
  5372. if (typeof v === 'boolean') {
  5373. nicoPlayer.playerConfig.set({commentVisible: v});
  5374. return this;
  5375. } else {
  5376. var pc = nicoPlayer.playerConfig.get();
  5377. return pc.commentVisible;
  5378. }
  5379. },
  5380. deepenedComment: function(v) {
  5381. if (v === 'toggle') {
  5382. return this.deepenedComment(!this.deepenedComment());
  5383. } else
  5384. if (typeof v === 'boolean') {
  5385. nicoPlayer.playerConfig.set({deepenedComment: v});
  5386. return this;
  5387. } else {
  5388. var pc = nicoPlayer.playerConfig.get();
  5389. return pc.deepenedComment;
  5390. }
  5391. },
  5392. allowStageVideo: function(v) {
  5393. if (v === 'toggle') {
  5394. return this.allowStageVideo(!this.allowStageVideo());
  5395. } else
  5396. if (typeof v === 'boolean') {
  5397. nicoPlayer.playerConfig.set({allowStageVideo: v});
  5398. return this;
  5399. } else {
  5400. var pc = nicoPlayer.playerConfig.get();
  5401. return pc.allowStageVideo;
  5402. }
  5403. },
  5404. isStageVideoSupported: function() {
  5405. try {
  5406. var exp = w.document.getElementById('external_nicoplayer');
  5407. return exp.isStageVideoSupported();
  5408. } catch(e) {
  5409. console.log(e);
  5410. return false;
  5411. }
  5412. },
  5413. isStageVideoAvailable: function() {
  5414. try {
  5415. var exp = w.document.getElementById('external_nicoplayer');
  5416. return exp.isStageVideoAvailable();
  5417. } catch(e) {
  5418. console.log(e);
  5419. return false;
  5420. }
  5421. },
  5422. toggleStageVideo: function() {
  5423. if (!this.isStageVideoSupported()) {
  5424. Popup.alert('ハードウェアアクセラレーションを使用できない状態か、未対応の環境です');
  5425. return;
  5426. }
  5427. var isAllowed = this.allowStageVideo(), exp = $('#external_nicoplayer')[0];
  5428. exp.setIsForceUsingStageVideo(!isAllowed && conf.forceEnableStageVideo);
  5429. this.allowStageVideo(!isAllowed);
  5430. setTimeout($.proxy(function() {
  5431. isAllowed = this.allowStageVideo();
  5432. var isAvailable = this.isStageVideoAvailable();
  5433. Popup.show('ハードウェアアクセラレーション:' +
  5434. (isAllowed ? '設定ON' : '設定OFF') + ' / ' +
  5435. (isAvailable ? '使用可能' : '使用不能')
  5436. );
  5437. }, this), 100);
  5438. },
  5439. mute: function(v) {
  5440. var exp = w.document.getElementById('external_nicoplayer');
  5441.  
  5442. if (v === 'toggle') {
  5443. return this.mute(!this.mute());
  5444. } else
  5445. if (typeof v === 'boolean') {
  5446. exp.ext_setMute(v);
  5447. return this;
  5448. } else {
  5449. return exp.ext_isMute();
  5450. }
  5451. },
  5452. volume: function(v) {
  5453. var exp = w.document.getElementById('external_nicoplayer');
  5454. if (typeof v === 'string' && v.match(/^[+-]\d+$/)) {
  5455. this.volume(this.volume() + v * 1);
  5456. } else
  5457. if (typeof v === 'number' || (typeof v === 'string' && v.match(/^\d+$/))) {
  5458. exp.ext_setVolume(Math.max(0, Math.min(v * 1, 100)));
  5459. }
  5460. return exp.ext_getVolume();
  5461. },
  5462. isWide: function() {
  5463. var exp = w.document.getElementById('external_nicoplayer');
  5464. return exp.ext_isWide();
  5465. },
  5466. isPlaylistActive: function() {
  5467. return watch.PlaylistInitializer.playlist.getPlaybackMode() !== 'normal';
  5468. },
  5469. isPlaylistRandom: function() {
  5470. return watch.PlaylistInitializer.playlist.isShuffle();
  5471. },
  5472. isPlaylistContinuous: function() {
  5473. return watch.PlaylistInitializer.playlist.getPlaybackMode() === 'continuous';
  5474. },
  5475. getOwnerIcon: function() {
  5476. try {
  5477. return this.isChannelVideo() ? watchInfoModel.channelInfo.iconUrl : watchInfoModel.uploaderInfo.iconUrl;
  5478. } catch (e) {
  5479. return 'http://uni.res.nimg.jp/img/user/thumb/blank_s.jpg';
  5480. }
  5481. },
  5482. getOwnerName: function() {
  5483. try {
  5484. return this.isChannelVideo() ? watchInfoModel.channelInfo.name : watchInfoModel.uploaderInfo.nickname;
  5485. } catch (e) {
  5486. return '';
  5487. }
  5488. },
  5489. getOwnerId: function() {
  5490. try {
  5491. return this.isChannelVideo() ? watchInfoModel.channelInfo.id : watchInfoModel.uploaderInfo.id;
  5492. } catch (e) {
  5493. return '0';
  5494. }
  5495. },
  5496. getOwnerType: function() {
  5497. try {
  5498. return this.isChannelVideo() ? 'channel' : 'user';
  5499. } catch (e) {
  5500. return 'channel';
  5501. }
  5502. },
  5503. getOwnerPage: function() {
  5504. try {
  5505. if (this.isChannelVideo()) {
  5506. return $('#ch_prof').find('.symbol').attr('href');
  5507. } else {
  5508. return '/user/' + this.getOwnerId();
  5509. }
  5510. } catch (e) {
  5511. return '';
  5512. }
  5513. },
  5514. isFavoriteOwner: function() {
  5515. try {
  5516. return this.isChannelVideo() ?
  5517. !!(watchInfoModel.channelInfo && watchInfoModel.channelInfo.isFavorited) :
  5518. watchInfoModel.uploaderInfo.isFavorited;
  5519. } catch (e) {
  5520. return false;
  5521. }
  5522. },
  5523. isVideoPublic: function() { // 投稿動画一覧を公開しているか? 公開マイリストがあるかどうかとは別なのでややこしい
  5524. return this.isChannelVideo() ? true : watchInfoModel.uploaderInfo.isUserVideoPublic;
  5525. },
  5526. isChannelVideo: function() {
  5527. return watchInfoModel.isChannelVideo();
  5528. },
  5529. getOwnerInfo: function() {
  5530. return {
  5531. type: this.getOwnerType(),
  5532. name: this.getOwnerName(),
  5533. icon: this.getOwnerIcon(),
  5534. id: this.getOwnerId(),
  5535. page: this.getOwnerPage(),
  5536. isFavorite: this.isFavoriteOwner(),
  5537. isVideoPublic: this.isVideoPublic()
  5538. };
  5539. },
  5540. isSearchMode: function() {
  5541. return videoExplorer.isOpen(); ////return $('body').hasClass('videoExplorer');
  5542. },
  5543. isFullScreen: function() {
  5544. return $('body').hasClass('full_with_browser');
  5545. },
  5546. // フルスクリーンの時にタグとかプレイリストを表示する設定かどうか
  5547. isFullScreenContentAll: function() {
  5548. try {
  5549. var content = localStorage.BROWSER_FULL_OPTIONS;
  5550. if (typeof content !== 'string') return false;
  5551. var isAll = JSON.parse(content).content === 'all';
  5552. return isAll;
  5553. } catch(e) {
  5554. console.log('%cexception', 'background: red; color: white;', e);
  5555. return false;
  5556. }
  5557. },
  5558. isIchibaEmpty: function() {
  5559. return $('#ichibaMain') .find('.ichiba_mainitem').length < 1;
  5560. },
  5561. postComment: function(comment, command) {
  5562. comment = $.trim(comment);
  5563. if (comment.length <= 0) { return; }
  5564. if (!command) { command = ''; }
  5565.  
  5566. setTimeout(function() {
  5567. try {
  5568. var exp = w.document.getElementById('external_nicoplayer');
  5569. console.log('postComment: ', [comment, command]);
  5570. if (!exp.externalPostChat(comment, command)) {
  5571. Popup.alert('コメント投稿に失敗しました');
  5572. }
  5573. } catch(e) {
  5574. Popup.alert('コメント投稿に失敗しました');
  5575. }
  5576. }, 0);
  5577. },
  5578. // スレッドIDから動画IDに変換。出来なかった時はそのまま返す
  5579. getTid2Vid: function(watchId, callback) {
  5580. if (!watchId.match(/^[0-9]+$/)) {
  5581. return callback(watchId);
  5582. }
  5583. WatchItLater.VideoInfoLoader.load(watchId).then(function(info) {
  5584. callback(info.id);
  5585. },
  5586. function() {
  5587. callback(watchId);
  5588. });
  5589. }
  5590. };
  5591. }; // end _watchController
  5592.  
  5593. (function() {
  5594. window.WatchItLater.WatchController =
  5595. window.WatchController = {
  5596. isZeroWatch: function() { return window.PlayerApp ? true : false; },
  5597. isFullScreen: function() { return false; },
  5598. isSearchMode: function() { return false; },
  5599. getTid2Vid: function(threadId, callback) { return callback(threadId);}
  5600. };
  5601. })();
  5602.  
  5603.  
  5604.  
  5605. var Util = (function() {
  5606. var Cache = {
  5607. storage: {},
  5608. get: function(key) {
  5609. if (!this.storage[key]) {
  5610. console.log('no cache');
  5611. return false;
  5612. } else
  5613. if (this.storage[key].cachedUntil <= Date.now()){
  5614. console.log('cache timeout');
  5615. delete this.storage[key];
  5616. return false;
  5617. } else {
  5618. console.log('cache exist');
  5619. return this.storage[key].data;
  5620. }
  5621. },
  5622. set: function(key, data, cacheTimeMs) {
  5623. cacheTimeMs = cacheTimeMs || 1000 * 60 * 10;
  5624. console.log('set cache', key, cacheTimeMs);
  5625. this.storage[key] = {
  5626. data: data,
  5627. cachedUntil: Date.now() + cacheTimeMs
  5628. };
  5629. return data;
  5630. }
  5631. };
  5632. var Browser = {
  5633. isWebkit: function() {
  5634. return navigator.userAgent.toLowerCase().indexOf('webkit') >= 0;
  5635. },
  5636. isFx: function() {
  5637. return navigator.userAgent.toLowerCase().indexOf('firefox') >= 0;
  5638. }
  5639. };
  5640. var
  5641. isMetaKey = function(e) {
  5642. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return true; }
  5643. return false;
  5644. },
  5645. prevent = function(e) {
  5646. e.preventDefault(); e.stopPropagation();
  5647. },
  5648. scrollToVideoExplorer = function() {
  5649. if (!WatchController.isSearchMode()) { return; }
  5650. window.setTimeout(function() {
  5651. window.WatchApp.ns.util.WindowUtil.scrollFit($('#videoExplorer'));
  5652. }, 100);
  5653. };
  5654.  
  5655. var Closure = {
  5656. outScope: function(func) {
  5657. return new Function([
  5658. '(' + func.toString() + ').apply(this, arguments);'
  5659. ].join(''));
  5660. },
  5661. openVideoOwnersVideo: function() {
  5662. return function(e) {
  5663. if (isMetaKey(e)) { return; }
  5664. prevent(e);
  5665. WatchController.openVideoOwnersVideo();
  5666. scrollToVideoExplorer();
  5667. };
  5668. },
  5669. openVideoOwnersNicorepo: function() {
  5670. return function(e) {
  5671. if (isMetaKey(e)) { return; }
  5672. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  5673. return;
  5674. }
  5675. prevent(e);
  5676. //WatchController.showMylist(NicorepoVideo.REPO_OWNER);
  5677. WatchController.showMylist('repo-owner-' + WatchController.getOwnerId());
  5678. scrollToVideoExplorer();
  5679. };
  5680. },
  5681. openDefMylist: function() {
  5682. return function(e) {
  5683. if (isMetaKey(e)) { return; }
  5684. prevent(e);
  5685. WatchController.showDeflist();
  5686. scrollToVideoExplorer();
  5687. };
  5688. },
  5689. openMylist: function(id) {
  5690. return function(e) {
  5691. if (isMetaKey(e)) { return; }
  5692. prevent(e);
  5693. WatchController.showMylist(id);
  5694. scrollToVideoExplorer();
  5695. };
  5696. },
  5697. openNicoSearch: function(word, type) {
  5698. return function(e) {
  5699. if (isMetaKey(e)) { return; }
  5700. if (WatchController.isZeroWatch()) {
  5701. prevent(e);
  5702. WatchController.nicoSearch(word, type);
  5703. scrollToVideoExplorer();
  5704. }
  5705. };
  5706. },
  5707. seekVideo: function(vpos) {
  5708. return function(e) {
  5709. if (isMetaKey(e)) { return; }
  5710. prevent(e);
  5711. WatchController.vpos(vpos);
  5712. };
  5713. },
  5714. showLargeThumbnail: function(url) {
  5715. return function() {
  5716. WatchController.showLargeThumbnail(url);
  5717. };
  5718. },
  5719. commentPanelContextMenu: function() {
  5720. return function(a) {
  5721. a.preventDefault(); a.stopPropagation();
  5722. var c = this.commentListModel.getComment(this.parseResNo(jQuery(a.currentTarget)));
  5723. var WatchApp = null, WatchController = null;
  5724. if (c) {
  5725. var
  5726. $d = this.CommentContextMenu.$contextMenuContainer,
  5727. $e = jQuery("#playerCommentPanel"),
  5728. left = this.$commentTableHeaderOuter.position().left,
  5729. top = a.pageY - $e.offset().top,
  5730. f = Math.min($e.offset().top + $e.outerHeight(), jQuery(window).scrollTop() + jQuery(window).outerHeight());
  5731. if (f < a.pageY + $d.outerHeight()) top -= a.pageY + $d.outerHeight() - f;
  5732. this.CommentContextMenu.show(c, left, top);
  5733. }
  5734. };
  5735. }
  5736. };
  5737. var Deferred = {
  5738. wait: function(msec) {
  5739. return function() {
  5740. var args = Array.prototype.slice.call(arguments, 0);
  5741. var d = new $.Deferred();
  5742. setTimeout(function() {
  5743. d.resolve.apply(d, args);
  5744. }, msec);
  5745. return d.promise();
  5746. };
  5747. }
  5748. };
  5749.  
  5750.  
  5751. var self = {
  5752. Cache: Cache,
  5753. Closure: Closure,
  5754. Deferred: Deferred,
  5755. Browser: Browser,
  5756. here: function(func) { // えせヒアドキュメント
  5757. return func.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  5758. }
  5759. };
  5760. return self;
  5761. })();
  5762. window.WatchItLater.util = Util;
  5763.  
  5764. var NicoNews = (function() {
  5765. var WatchApp = null, watch = null, $ = null, WatchJsApi = null, initialized = false;
  5766. var $button = null, $history = null, $ul = null, deteru = {}, $textMarquee, $textMarqueeInner;
  5767. var isHover = false;
  5768.  
  5769. function onNewsUpdate(news) {
  5770. var type = news.data.type, $current = null,
  5771. newsText = $textMarqueeInner.find('.categoryOuter:last').text() +
  5772. $textMarqueeInner.find('.item .title, .item .header, .item .bannertext, .item .text').text(),
  5773. newsHref = $textMarqueeInner.find('a').attr('href');
  5774. if (deteru[newsHref]) {
  5775. $current = deteru[newsHref].remove();
  5776. } else {
  5777. $current = deteru[newsHref] = makeTopic(newsText, newsHref, type);
  5778. }
  5779. $ul.append($current);
  5780. $current.show(200, scrollToBottom);
  5781. }
  5782. function makeTopic(title, url, type) {
  5783. return $([
  5784. '<li style="display: none;">',
  5785. '<a href="', url , '" target="_blank" class="', type, ' title="', escape(title),'">', title, '</a>',
  5786. '</li>',
  5787. ''].join(''));
  5788. }
  5789. function scrollToBottom() {
  5790. if (!isHover) {
  5791. $history.animate({scrollTop: $('.newsHistory ul').innerHeight()}, 200);
  5792. }
  5793. }
  5794.  
  5795. var self = {
  5796. initialize: function(w) {
  5797. WatchApp = w.WatchApp;
  5798. if (!WatchApp || initialized) { return; }
  5799. watch = WatchApp.ns.init;
  5800. $ = w.$;
  5801. WatchJsApi = w.WatchJsApi;
  5802. $textMarquee = $('#textMarquee');
  5803. $textMarqueeInner = $textMarquee.find('.textMarqueeInner');
  5804.  
  5805. watch.TextMarqueeInitializer.textMarqueeViewController.scheduler.addEventListener(
  5806. 'schedule',
  5807. onNewsUpdate);
  5808.  
  5809. $button = $('<button class="openNewsHistory" title="ニコニコニュースの履歴を開く">▲</button>');
  5810. $history = $('<div class="newsHistory" style="display: none;"><ul></ul></div>');
  5811. $history.hover(
  5812. function() { isHover = true; },
  5813. function() { isHover = false; }
  5814. );
  5815. $ul = $history.find('ul');
  5816. $button.click(function() { self.toggle(); });
  5817.  
  5818. $textMarquee.append($button).append($history);
  5819. initialized = true;
  5820. },
  5821. open: function() {
  5822. $history.show(200, function() {
  5823. scrollToBottom();
  5824. WatchApp.ns.util.WindowUtil.scrollFitMinimum('.newsHistory', 200);
  5825. });
  5826. },
  5827. close: function() {
  5828. $history.hide(200);
  5829. isHover = false;
  5830. },
  5831. toggle: function() {
  5832. if ($history.is(':visible')) {
  5833. $button.text('▲');
  5834. this.close();
  5835. } else {
  5836. $button.text('▼');
  5837. this.open();
  5838. }
  5839. }
  5840. };
  5841. return self;
  5842. })();
  5843.  
  5844.  
  5845.  
  5846. /**
  5847. * マイリストや検索API互換形式のデータを返すやつ
  5848. */
  5849. var DummyMylist = function() { this.initialize.apply(this, arguments); };
  5850. DummyMylist.prototype = {
  5851. banner: '',
  5852. id: '-100',
  5853. sort: '4',
  5854. isDeflist: -1,
  5855. isWatchngCountFull: false,
  5856. isWatchngThisMylist: false,
  5857. itemCount: 0,
  5858. items: [],
  5859. rawData: {},
  5860. page: 1,
  5861. perPage: 32,
  5862. type: 2, // 2: MYLIST_VIDEO
  5863. //
  5864. // ver130726より新規追加 defineGetterのほうがいいかも
  5865.  
  5866. status: 'ok',
  5867. name: '',
  5868. description: '',
  5869. user_id: '',
  5870. user_nickname: 'ニコニコ動画',
  5871. default_sort: '1',
  5872. is_watching_this_mylist: false,
  5873. is_watching_count_full: false,
  5874. list: [],
  5875. // ここまで
  5876. initialize: function(param) {
  5877. this.rawData = {
  5878. status: 'ok',
  5879. list: [],
  5880. name: '総合ランキング',
  5881. description: '',
  5882. is_watching_count_full: false,
  5883. is_watching_this_mylist: false,
  5884. user_nickname: '',
  5885. user_id: '',
  5886. sort: '1'
  5887. };
  5888. this._baseCreateTime = Date.now();//new Date();
  5889. this.rawData.user_nickname = param.user_nickname || WatchController.getMyNick();
  5890. this.rawData.user_id = param.user_id || WatchController.getMyUserId();
  5891. this.rawData.name = param.name || this.rawData.name;
  5892. this.rawData.description = param.description || '';
  5893.  
  5894. this.type = param.type || WatchApp.ns.components.videoexplorer.model.ContentType.MYLIST_VIDEO;
  5895. this.sort = this.rawData.sort = param.sort || this.sort;
  5896. this.id = param.id || '-100';
  5897.  
  5898. this.status = this.rawData.status;
  5899. this.list = this.rawData.list;
  5900. this.name = this.rawData.name;
  5901. this.description = this.rawData.description;
  5902. this.default_sort = this.rawData.sort || this.sort;
  5903. this.user_nickname = this.rawData.user_nickname || this.user_nickname;
  5904. this.user_id = this.rawData.user_id;
  5905.  
  5906.  
  5907. },
  5908. setName: function(name) {
  5909. this.rawData.name = name;
  5910. },
  5911. getName: function() {
  5912. return this.rawData.name || '';
  5913. },
  5914. setPage: function(page) {
  5915. this.page = page;
  5916. this.items = this.rawData.list.slice(page * this.perPage - this.perPage, page * this.perPage);
  5917. },
  5918. push: function(item) {
  5919. if (!item.create_time) {
  5920. var tm = this._baseCreateTime - 60000 * this.itemCount;
  5921. item.create_time = tm;
  5922. }
  5923. this.rawData.list.push(item);
  5924. this.itemCount = this.rawData.list.length;
  5925. this.setPage(this.page);
  5926. },
  5927. unshift: function(item) {
  5928. if (!item.create_time) {
  5929. var tm = this._baseCreateTime + 60000 * this.itemCount;
  5930. item.create_time = tm;
  5931. }
  5932. this.rawData.list.unshift(item);
  5933. this.itemCount = this.rawData.list.length;
  5934. this.setPage(this.page);
  5935. },
  5936. slice: function(b, e) {
  5937. this.rawData.list = this.rawData.list.slice(b, e);
  5938. this.itemCount = this.rawData.list.length;
  5939. this.setPage(this.page);
  5940. },
  5941. sortItem: function(sortId, force) {
  5942. sortId = parseInt(sortId, 10);
  5943. if (!!!force && (sortId < 0 || sortId === parseInt(this.sort, 10)) ) { return; }
  5944. var sortKey = ([
  5945. 'create_time', 'create_time',
  5946. 'mylist_comment', 'mylist_comment',
  5947. 'title', 'title',
  5948. 'first_retrieve', 'first_retrieve',
  5949. 'view_counter', 'view_counter',
  5950. 'thread_update_time', 'thread_update_time',
  5951. 'num_res', 'num_res',
  5952. 'mylist_counter', 'mylist_counter',
  5953. 'length_seconds', 'length_seconds'
  5954. ])[sortId],
  5955. order = (sortId % 2 === 0) ? 'asc' : 'desc';
  5956.  
  5957. if (!sortKey) { return; }
  5958. var compare= {
  5959. asc: function(a, b) { return (a[sortKey] > b[sortKey] ) ? 1 : -1; },
  5960. desc: function(a, b) { return (a[sortKey] < b[sortKey] ) ? 1 : -1; },
  5961. iasc: function(a, b) { return (a[sortKey]*1 > b[sortKey]*1) ? 1 : -1; },
  5962. idesc: function(a, b) { return (a[sortKey]*1 < b[sortKey]*1) ? 1 : -1; }
  5963. };
  5964. // 偶数がascで奇数がdescかと思ったら特に統一されてなかった
  5965. if (
  5966. sortKey === 'first_retrieve' ||
  5967. sortKey === 'thread_update_time'
  5968. ) {
  5969. order = (sortId % 2 === 1) ? 'asc' : 'desc';
  5970. } else
  5971. // 数値系は偶数がdesc
  5972. if (sortKey === 'view_counter' ||
  5973. sortKey === 'num_res' ||
  5974. sortKey === 'mylist_counter' ||
  5975. sortKey === 'length_seconds'
  5976. ) {
  5977. order = (sortId % 2 === 1) ? 'iasc' : 'idesc';
  5978. }
  5979. this.sort = this.rawData.sort = sortId.toString();
  5980. this.rawData.list.sort(compare[order]);
  5981. this.items = this.rawData.list.slice(0, 32);
  5982. this.list = this.rawData.list.slice(0);
  5983. }
  5984. };
  5985.  
  5986. var DummyMylistVideo = function() { this.initialize.apply(this, arguments); };
  5987. DummyMylistVideo.prototype = {
  5988. id: 0,
  5989. title: '',
  5990. length: 0,
  5991. view_counter: 0,
  5992. num_res: 0,
  5993. mylist_counter: 0,
  5994. description_short: '',
  5995. first_retrieve: null,
  5996. thumbnail_url: null,
  5997. mylist_comment: '',
  5998. create_time: null,
  5999. type: 0, //'video',
  6000. _info: {},
  6001.  
  6002.  
  6003. initialize: function(info) {
  6004. this._info = info._info || this;
  6005. this.id = info.id;
  6006. this.length = info.length;
  6007. this.mylist_counter = info.mylist_counter || 0;
  6008. this.view_counter = info.view_counter || 0;
  6009. this.num_res = info.num_res || 0;
  6010. this.first_retrieve = info.first_retrieve || '2000-01-01 00:00:00';
  6011. this.create_time = info.create_time || null;
  6012. this.thumbnail_url = info.thumbnail_url || 'http://res.nimg.jp/img/common/video_deleted_ja-jp.jpg' /* 「視聴できません」 */;
  6013. this.title = info.title || '';
  6014. this.type = info.type || 'video';
  6015. this.description_short = info.description_short;
  6016. this.length = info.length || '00:00';
  6017. this.length_seconds = parseInt(info.length_seconds || 0, 10);
  6018. this.mylist_comment = info.mylist_comment || '';
  6019. this.type = info.type || WatchApp.ns.components.videoexplorer.model.ContentItemType.VIDEO;
  6020.  
  6021. if (this.length_seconds === 0 && this.length && this.length.indexOf(':') >= 0) {
  6022. var sp = this.length.split(':');
  6023. this.length_seconds = sp[0] * 60 + sp[1] * 1;
  6024. } else
  6025. if (this.length === '00:00' && this.length_seconds > 0) {
  6026. this.length = parseInt(this.length_seconds / 60, 10) + ':' + (this.length_seconds % 60);
  6027. }
  6028.  
  6029. if (typeof info.is_middle_thumbnail !== 'boolean') {
  6030. if (this.thumbnail_url.indexOf('.M') >= 0) {
  6031. this.thumbnail_url = this.thumbnail_url.replace(/\.M$/, '');
  6032. this.is_middle_thumbnail = true;
  6033. } else
  6034. if (this.thumbnail_url.indexOf('.M') < 0 &&
  6035. this.id.indexOf('sm') === 0) {
  6036. var threshold = 23608629, // .Mのついた最小ID?
  6037. _id = _.parseInt(this.id.substr(2));
  6038. if (_id >= threshold) {
  6039. this.is_middle_thumbnail = true;
  6040. }
  6041. }
  6042. }
  6043. },
  6044. getType: function() { return this.type; },
  6045. getInfo: function() { return this; }, // 手抜き
  6046. getName: function() { return this.title; },
  6047. getId: function() { return this.id; },
  6048. getDescription: function() { return this.description_short; },
  6049.  
  6050.  
  6051. length_seconds: 0, // TODO:
  6052. thread_update_time: '2000-01-01 00:00:00' // TODO: 「コメントが新しい順でソート」に必要?
  6053. };
  6054.  
  6055. // 参考:
  6056. // http://looooooooop.blog35.fc2.com/blog-entry-1146.html
  6057. // http://toxy.hatenablog.jp/entry/2013/07/25/200645
  6058. // http://ch.nicovideo.jp/pita/blomaga/ar297860
  6059. // http://search.nicovideo.jp/docs/api/ma9.html
  6060. var NewNicoSearch = function() { this.initialize.apply(this, arguments); };
  6061. NewNicoSearch.API_BASE_URL = 'http://api.search.nicovideo.jp/api/';
  6062. NewNicoSearch.PAGE_BASE_URL = 'http://search.nicovideo.jp/video/';
  6063. NewNicoSearch.prototype = {
  6064. _u: '', // 24h, 1w, 1m, ft 期間指定
  6065. _ftfrom: '', // YYYY-MM-DD
  6066. _ftto: '', // YYYY-MM-DD
  6067. _l: '', // short long
  6068. _m: false, // true=音楽ダウンロード
  6069. _sort: '', // last_comment_time, last_comment_time_asc,
  6070. // view_counter, view_counter_asc,
  6071. // comment_counter, comment_counter_asc,
  6072. // mylist_counter, mylist_counter_asc,
  6073. // upload_time, upload_time_asc,
  6074. // length_seconds, length_seconds_asc
  6075. _size: 32, // 一ページの件数 maxは100
  6076. _issuer: 'watch-it-later',
  6077. _base_url: NewNicoSearch.API_BASE_URL,
  6078. initialize: function(params) {
  6079.  
  6080. },
  6081. load: function(params, callback) {
  6082. var url = this._base_url;
  6083. var data = {};
  6084. data.query = params.query || 'Qwatch';
  6085. data.service = params.service || ['video']; // video video_tag
  6086. data.search = params.search || ['title', 'tags', 'description'];
  6087. data.join = params.join || [
  6088. // TODO:投稿者IDを取得する方法がないか?
  6089. 'cmsid', 'title', 'description', 'thumbnail_url', 'start_time',
  6090. 'view_counter', 'comment_counter', 'mylist_counter', 'length_seconds', 'last_res_body'
  6091. // 'user_id', 'channel_id', 'main_community_id', 'ss_adlut'
  6092. ];
  6093. data.filters = params.filters || [{}];
  6094. data.sort_by = params.sort_by || 'start_time';
  6095. data.order = params.order || 'desc';
  6096. data.timeout = params.timeout || 10000;
  6097. data.issuer = params.issuer || 'watch-it-later';
  6098. data.reason = params.reason || 'video-explorer'; // 'watchItLater';
  6099. data.size = params.size || 32;
  6100. data.from = params.from || 0;
  6101.  
  6102. if (params.sort_by === '_hot') { // 人気順ソートのパラメータ
  6103. data.hot_field = params.hot_field;
  6104. data.hot_from = params.hot_from;
  6105. data.hot_to = params.hot_to;
  6106. }
  6107.  
  6108. var cache_key = JSON.stringify({url: url, data: data}), cache = Util.Cache.get(cache_key);
  6109. if (cache) {
  6110. setTimeout(function() { callback(null, cache); }, 0);
  6111. return;
  6112. }
  6113.  
  6114. $.ajax({
  6115. url: url,
  6116. type: 'POST',
  6117. data: JSON.stringify(data),
  6118. timeout: 30000,
  6119. complete: function(result) {
  6120. console.log('result', result);
  6121. if (result.status !== 200) {
  6122. callback('fail', 'HTTP status:' + result.status);
  6123. return;
  6124. }
  6125. var data;
  6126. try {
  6127. var lines = result.responseText.split('\n'), head = JSON.parse(lines[0]);
  6128. if (head.values[0].total > 0) {
  6129. data = [head];
  6130. for (var i = 1, len = lines.length; i < len - 1; i++) {
  6131. data.push(JSON.parse(lines[i]));
  6132. }
  6133. } else {
  6134. data = [head, JSON.parse(lines[1]), {type: 'hits', values: []}, JSON.parse(lines[2])];
  6135. }
  6136. Util.Cache.set(cache_key, data);
  6137. } catch(e) {
  6138. console.log('Exception: ', e, result);
  6139. callback('fail', 'JSON syntax');
  6140. return;
  6141. }
  6142. callback(null, data);
  6143. },
  6144. error: function(req, status, thrown) {
  6145. if (status === 'parsererror') {
  6146. return;
  6147. }
  6148. console.log('%c ajax error: ' + status, 'background: red', arguments);
  6149. callback('fail', status);
  6150. }
  6151. });
  6152. }
  6153. };
  6154.  
  6155.  
  6156.  
  6157. /**
  6158. * niconico新検索の検索結果を既存の検索API互換形式に変換して返すやつ
  6159. */
  6160. var NewNicoSearchWrapper = function() { this.initialize.apply(this, arguments); };
  6161. NewNicoSearchWrapper.prototype = {
  6162. _search: null,
  6163. sortTable: {f: 'start_time', v: 'view_counter', r: 'comment_counter', m: 'mylist_counter', l: 'length_seconds',
  6164. '_hot': '_hot', // 人気が高い順
  6165. '_explore': '_explore', // 新着優先
  6166. '_popular': '_popular' // 並び順指定なし
  6167. },
  6168. initialize: function(params) {
  6169. this._search = params.search;
  6170. },
  6171. _buildSearchQuery: function(params) {
  6172. var query = {filters: []};
  6173. var sortTable = this.sortTable;
  6174. query.query = params.searchWord;
  6175. query.search = params.searchType === 'tag' ? ['tags'] : ['tags', 'title', 'description'];
  6176. query.sort_by = params.sort && sortTable[params.sort] ? sortTable[params.sort] : 'last_comment_time';
  6177. query.order = params.order === 'd' ? 'desc' : 'asc';
  6178. query.size = params.size || 32;
  6179. query.from = params.page ? Math.max(parseInt(params.page, 10) - 1, 0) * query.size : 0;
  6180.  
  6181. var n = new Date();
  6182. var now = n.getTime();
  6183. switch (params.u) {
  6184. case '1h':
  6185. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 1 * 60 * 60 * 1000)));
  6186. break;
  6187. case '24h': case '1d':
  6188. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 24 * 60 * 60 * 1000)));
  6189. break;
  6190. case '1w': case '7d':
  6191. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 7 * 24 * 60 * 60 * 1000)));
  6192. break;
  6193. case '1m':
  6194. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 30 * 24 * 60 * 60 * 1000)));
  6195. break;
  6196. case '3m':
  6197. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 90 * 24 * 60 * 60 * 1000)));
  6198. break;
  6199. case '6m':
  6200. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 180 * 24 * 60 * 60 * 1000)));
  6201. break;
  6202. default:
  6203. break;
  6204. }
  6205.  
  6206. if (query.sort_by === '_hot') {
  6207. // 人気が高い順ソート
  6208. (function() {
  6209. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6210. query.hot_field = 'mylist_counter';
  6211. query.hot_from = format(new Date(now - 1 * 24 * 60 * 60 * 1000));
  6212. query.hot_to = format(n);
  6213.  
  6214. query.order = 'desc';
  6215. })();
  6216. }
  6217.  
  6218. if (typeof params.userId === 'string' && params.userId.match(/^\d+$/)) {
  6219. query.filters.push({type: 'equal', field: 'user_id', value: params.userId});
  6220. }
  6221. if (typeof params.channelId === 'string' && params.channelId.match(/^\d+$/)) {
  6222. query.filters.push({type: 'equal', field: 'channel_id', value: params.channelId});
  6223. }
  6224.  
  6225. if (params.l === 'short') { // 5分以内
  6226. query.filters.push(this._buildLengthSecondsRangeFilter(0, 60 * 5));
  6227. } else
  6228. if (params.l === 'long' ) { // 20分以上
  6229. query.filters.push(this._buildLengthSecondsRangeFilter(60 * 20));
  6230. }
  6231.  
  6232. if (params.m === true) { // 音楽ダウンロード
  6233. query.filters.push({type: 'equal', field: 'music_download', value: true});
  6234. }
  6235.  
  6236. // TODO: これの調査 → {field: 'ss_adult', type: 'equal', value: false}
  6237.  
  6238. return query;
  6239. },
  6240. _buildStartTimeRangeFilter: function(from, to) {
  6241. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6242. var range = {field: 'start_time', type: 'range', include_lower: true, };
  6243. range.from = format(from);
  6244. if (to) range.to = format(to);
  6245. return range;
  6246. },
  6247. _buildLengthSecondsRangeFilter: function(from, to) {
  6248. var range = {field: 'length_seconds', type: 'range'};
  6249. if (to) { // xxx ~ xxx
  6250. range.from = Math.min(from, to);
  6251. range.to = Math.max(from, to);
  6252. range.include_lower = range.include_upper = true;
  6253. } else { // xxx以上
  6254. range.from = from;
  6255. range.include_lower = true;
  6256. }
  6257. return range;
  6258. },
  6259. load: function(params, callback) {
  6260. var query = this._buildSearchQuery(params);
  6261. this._search.load(query, $.proxy(function(err, result) {
  6262. this.onLoad(err, result, params, query, callback);
  6263. }, this));
  6264. },
  6265. onLoad: function(err, result, params, query, callback) {
  6266. if (err) {
  6267. console.log('load fail', err, result);
  6268. callback('fail', {message: '通信に失敗しました1'});
  6269. return;
  6270. }
  6271. var searchResult;
  6272. searchResult = {
  6273. status: 'ok',
  6274. count: result[0].values[0].total,
  6275. page: params.page,
  6276. list: []
  6277. };
  6278. var pushItems = function(items) {
  6279. var len = items.length;
  6280. for (var i = 0; i < len; i++) {
  6281. var item = items[i], description = item.description ? item.description.replace(/<.*?>/g, '') : '';
  6282.  
  6283. item.id = item.cmsid;
  6284. if (item.thumbnail_url.indexOf('.M') >= 0) {
  6285. item.thumbnail_url = item.thumbnail_url.replace(/\.M$/, '');
  6286. item.is_middle_thumbnail = true;
  6287. } else
  6288. if (item.thumbnail_url.indexOf('.M') < 0 &&
  6289. item.id.indexOf('sm') === 0) {
  6290. var threshold = 23608629, // .Mのついた最小ID?
  6291. _id = _.parseInt(item.id.substr(2));
  6292. if (_id >= threshold) {
  6293. item.is_middle_thumbnail = true;
  6294. }
  6295. }
  6296.  
  6297. searchResult.list.push({
  6298. id: item.cmsid,
  6299. type: 0, // 0 = VIDEO,
  6300. length: item.length_seconds ?
  6301. Math.floor(item.length_seconds / 60) + ':' + (item.length_seconds % 60 + 100).toString().substr(1) : '',
  6302. mylist_counter: item.mylist_counter,
  6303. view_counter: item.view_counter,
  6304. num_res: item.comment_counter,
  6305. first_retrieve: item.start_time,
  6306. create_time: item.start_time,
  6307. thumbnail_url: item.thumbnail_url,
  6308. title: item.title,
  6309. description_short: description.substr(0, 150),
  6310. description_full: description,
  6311. length_seconds: item.length_seconds,
  6312. last_res_body: item.last_res_body,
  6313. is_middle_thumbnail: item.is_middle_thumbnail
  6314. // channel_id: item.channel_id,
  6315. // main_community_id: item.main_community_id
  6316. });
  6317. }
  6318. };
  6319. for (var i = 1; i < result.length; i++) {
  6320. if (result[i].type === 'hits' && result[i].endofstream) { break; }
  6321. if (result[i].type === 'hits' && result[i].values) {
  6322. pushItems(result[i].values);
  6323. }
  6324. }
  6325. callback(null, searchResult);
  6326. }
  6327. };
  6328.  
  6329. // sug.search.nicovideo.jpはリアルタイムの入力補完用? で、関連タグはhttp://api.search.nicovideo.jp/api/tag/ っぽい
  6330. var NicoSearchSuggest = function() { this.initialize.apply(this, arguments); };
  6331. NicoSearchSuggest.API_BASE_URL = 'http://sug.search.nicovideo.jp/'; //'/suggestion/complete';
  6332. NicoSearchSuggest.prototype = {
  6333. _base_url: NicoSearchSuggest.API_BASE_URL,
  6334. initialize: function(params) {
  6335. },
  6336. load: function(word, callback) {
  6337. if (typeof word !== 'string' || word.length <= 0) {
  6338. throw new Error('wordが設定されてない!');
  6339. }
  6340. var url = this._base_url + 'suggestion/complete',
  6341. cache_key = JSON.stringify({url: url, word: word}),
  6342. cache_time = 60 * 1000 * 1,
  6343. cache = Util.Cache.get(cache_key);
  6344. if (cache) {
  6345. setTimeout(function() { callback(null, cache); }, 0);
  6346. return;
  6347. }
  6348. $.ajax({
  6349. url: url,
  6350. type: 'POST',
  6351. data: word,
  6352. timeout: 30000,
  6353. complete: function(result) {
  6354. if (result.status !== 200) {
  6355. callback('fail', 'HTTP status:' + result.status);
  6356. return;
  6357. }
  6358. var data;
  6359. try {
  6360. data = JSON.parse(result.responseText);
  6361. } catch(e) {
  6362. console.log('Exception: ', e, result);
  6363. callback('fail', 'JSON syntax');
  6364. }
  6365. Util.Cache.set(cache_key, data, cache_time);
  6366. callback(null, data);
  6367. },
  6368. error: function(req, status, thrown) {
  6369. if (status === 'parsererror') {
  6370. return;
  6371. }
  6372. callback('fail', status);
  6373. }
  6374. });
  6375. }
  6376. };
  6377.  
  6378. var NicoSearchRelatedTag = function() { this.initialize.apply(this, arguments); };
  6379. NicoSearchRelatedTag.API_BASE_URL = 'http://api.search.nicovideo.jp/';
  6380. NicoSearchRelatedTag.prototype = {
  6381. _base_url: NicoSearchRelatedTag.API_BASE_URL,
  6382. initialize: function(params) {
  6383. },
  6384. load: function(word, callback) {
  6385. var url = this._base_url + 'api/tag/',
  6386. cache_key = JSON.stringify({url: url, word: word}),
  6387. cache_time = 60 * 1000 * 10,
  6388. cache = Util.Cache.get(cache_key);
  6389. if (cache) {
  6390. setTimeout(function() { callback(null, cache); }, 0);
  6391. return;
  6392. }
  6393. var query = {query: word, service: ['tag_video'], from: 0, size: 100, timeout: 10000, reason: 'user'};
  6394. $.ajax({
  6395. url: url,
  6396. type: 'POST',
  6397. data: JSON.stringify(query),
  6398. timeout: 30000,
  6399. complete: function(result) {
  6400. if (result.status !== 200) {
  6401. callback('fail', 'HTTP status:' + result.status);
  6402. return;
  6403. }
  6404. var data;
  6405. try {
  6406. var lines = result.responseText.split('\n');
  6407. data = JSON.parse(lines[0]);
  6408. } catch(e) {
  6409. console.log('Exception: ', e, result);
  6410. callback('fail', 'JSON syntax');
  6411. return;
  6412. }
  6413. Util.Cache.set(cache_key, data, cache_time);
  6414. callback(null, data);
  6415. },
  6416. error: function(req, status, thrown) {
  6417. if (status === 'parsererror') {
  6418. return;
  6419. }
  6420. callback('fail', status);
  6421. }
  6422. });
  6423. }
  6424. };
  6425.  
  6426.  
  6427. var VideoInfoLoader = function() { this.initialize.apply(this, arguments); };
  6428. VideoInfoLoader.BASE_URL = "http://riapi.nicovideo.jp/api/search/tag";
  6429. VideoInfoLoader.prototype = {
  6430. initialize: function(params) {
  6431. },
  6432. load: function(id, callback) {
  6433. var def = new $.Deferred();
  6434.  
  6435. var cache_key = JSON.stringify({'VideoInfoLoaderCache': id}), cacheData = Util.Cache.get(cache_key);
  6436. if (cacheData) {
  6437. return def.resolve(cacheData);
  6438. }
  6439.  
  6440. if (id.toString().match(/^\d+$/)) { // watchId
  6441. WatchApp.ns.init.PlaylistInitializer.videoInfoAPILoader.load(
  6442. [id],
  6443. function(err, resp) {
  6444. if (err !== null) {
  6445. return def.reject({message: '通信に失敗しました(1)', status: 'fail'});
  6446. }
  6447. if (resp.items && resp.items[id] && resp.items[id].id) {
  6448. if (typeof callback === 'function') { callback(null, resp.items[id]); }
  6449. return def.resolve(Util.Cache.set(cache_key, resp.items[id]));
  6450. }
  6451. err = {message: '動画が見つかりませんでした(1): ' + id, status: 'fail'};
  6452. if (typeof callback === 'function') { callback(err, null); }
  6453. return def.reject(err);
  6454. }
  6455. );
  6456. return def.promise();
  6457. }
  6458.  
  6459. // タグ検索APIの「もしかして: xxx」を使って動画情報を取得する
  6460. WatchApp.ns.util.HTTPUtil.loadXDomainAPI({ // videoId
  6461. url: VideoInfoLoader.BASE_URL,
  6462. type: 'GET',
  6463. dataType: 'json',
  6464. xhrFields: {
  6465. withCredentials: true
  6466. },
  6467. data: {
  6468. words: 'watch/' + id,
  6469. sort: 'f',
  6470. order: 'd',
  6471. page: '1',
  6472. mode: 'watch'
  6473. },
  6474. success: function(result) {
  6475. if (result.suggest_video && result.suggest_video.id) {
  6476. if (typeof callback === 'function') { callback(null, result.suggest_video); }
  6477. def.resolve(Util.Cache.set(cache_key, result.suggest_video));
  6478. } else {
  6479. var err = {message: '動画が見つかりませんでした(2): ' + id, status: 'fail'};
  6480. if (typeof callback === 'function') { callback(err, null); }
  6481. def.reject(err);
  6482. }
  6483. },
  6484. error: function(resp) {
  6485. var err = {message: '通信に失敗しました(2)', status: 'fail'};
  6486. if (typeof callback === 'function') { callback(err, null); }
  6487. def.reject(err);
  6488. }
  6489. });
  6490. return def.promise();
  6491. }
  6492. };
  6493. WatchItLater.VideoInfoLoader = new VideoInfoLoader({});
  6494.  
  6495. var RelatedVideo = function() { this.initialize.apply(this, arguments); }
  6496. RelatedVideo.prototype = {
  6497. initialize: function(params) {
  6498. },
  6499. load: function(watchId) {
  6500. var def = new $.Deferred;
  6501. window.WatchApp.ns.init.VideoExplorerInitializer.relatedVideoAPILoader.load(
  6502. {'video_id': watchId},
  6503. function(err, result) {
  6504. if (err !== null) {
  6505. return def.reject({message: '通信に失敗しました(1)', status: 'fail', err: err});
  6506. }
  6507. return def.resolve(result);
  6508. }
  6509. );
  6510. return def.promise();
  6511. }
  6512. };
  6513. WatchItLater.RelatedVideo = new RelatedVideo({});
  6514.  
  6515. /**
  6516. * 動画視聴履歴をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  6517. */
  6518. var VideoWatchHistory = (function(w, Util){
  6519. function load(callback) {
  6520. var watch, $, myNick, myId, url;
  6521. try{
  6522. watch = w.WatchApp.ns.init;
  6523. $ = w.$; url = '/my/history';
  6524. myNick = WatchController.getMyNick(); myId = WatchController.getMyUserId();
  6525. } catch (e) {
  6526. console.log(e);
  6527. throw { message: 'エラーが発生しました', status: 'fail'};
  6528. }
  6529.  
  6530. var CACHE_KEY = 'videohistory', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6531. if (cacheData) {
  6532. setTimeout(function() {callback(cacheData);}, 0);
  6533. return;
  6534. }
  6535.  
  6536. var result = new DummyMylist({
  6537. id: '-1',
  6538. sort: '1',
  6539. name: myNick + 'の視聴履歴',
  6540. user_id: myId,
  6541. user_name: 'ニコニコ動画'
  6542. });
  6543. GM_xmlhttpRequest({
  6544. url: url,
  6545. onload: function(resp) {
  6546. var $dom = $(resp.responseText), $list = $dom.find('#historyList');
  6547. $list.find('.outer').each(function() {
  6548. var
  6549. $item = $(this), $meta = $item.find('.metadata'), $title = $item.find('.section h5 a'),
  6550. id = $title.attr('href').split('/').reverse()[0], title = $title.text(),
  6551. duration = $item.find('.videoTime').text(),
  6552. viewCnt = $meta.find('.play') .text().split(':')[1].replace(/,/g, ''),
  6553. resCnt = $meta.find('.comment').text().split(':')[1].replace(/,/g, ''),
  6554. mylistCnt = $meta.find('.mylist') .text().split(':')[1].replace(/,/g, ''),
  6555. postedAt = '20' + $meta.find('.posttime').text().replace(/(年|月)/g, '-').replace(/(日| *投稿)/g, ''),
  6556. thumbnail = $item.find('.thumbContainer a .video').attr('src');
  6557.  
  6558. var item = new DummyMylistVideo({
  6559. id: id,
  6560. length: duration,
  6561. mylist_counter: mylistCnt,
  6562. view_counter: viewCnt,
  6563. num_res: resCnt,
  6564. first_retrieve: postedAt,
  6565. thumbnail_url: thumbnail,
  6566. title: title,
  6567. _info: {first_retrieve: postedAt},
  6568. description_short: $item.find('.section .posttime span').text()
  6569. });
  6570. result.push(item);
  6571. });
  6572. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6573. },
  6574. onerror: function() {
  6575. Popup.alert('視聴履歴の取得に失敗しました');
  6576. }
  6577. });
  6578.  
  6579. }
  6580. var self = {
  6581. load : load
  6582. };
  6583. return self;
  6584. })(w, Util);
  6585.  
  6586.  
  6587.  
  6588. var VideoRecommendations = (function(w, Util){
  6589. var histories = {};
  6590. function request(callback) {
  6591. var watch, $, url, myNick, myId;
  6592. try{
  6593. watch = w.WatchApp.ns.init;
  6594. $ = w.$;
  6595. url = '/recommendations';
  6596. myNick = WatchController.getMyNick();
  6597. myId = WatchController.getMyUserId();
  6598. } catch (e) {
  6599. console.log(e);
  6600. throw { message: 'エラーが発生しました', status: 'fail'};
  6601. }
  6602. var CACHE_KEY = 'recommend', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6603. if (cacheData) {
  6604. setTimeout(function() {callback(cacheData); }, 0);
  6605. return;
  6606. }
  6607.  
  6608. var result = new DummyMylist({
  6609. id: '-2',
  6610. sort: '1',
  6611. name: 'あなたにオススメの動画'
  6612. });
  6613. GM_xmlhttpRequest({
  6614. url: url,
  6615. onload: function(resp) {
  6616. var text = resp.responseText, lines = text.split(/[\r\n]/), found = false, data, i, len;
  6617. for (i = 0, len = lines.length; i < len; i++) {
  6618. var line = lines[i];
  6619. if (line.indexOf('var Nico_RecommendationsParams') >= 0 &&
  6620. lines[i + 5] && lines[i + 5].indexOf('first_data') >= 0) {
  6621. data = JSON.parse(lines[i + 5].replace(/^.*?:/, ''));
  6622. if (data && data.videos) {
  6623. found = true;
  6624. break;
  6625. }
  6626. }
  6627. }
  6628. if (!found) {
  6629. throw { message: '取得に失敗しました', status: 'fail'};
  6630. }
  6631.  
  6632. for (i = 0, len = data.videos.length; i < len; i++) {
  6633. var video = data.videos[i];
  6634. if (histories[video.id]) {
  6635. delete histories[video.id];
  6636. }
  6637. var item = new DummyMylistVideo({
  6638. id: video.id,
  6639. length: video.length,
  6640. mylist_counter: video.mylist_counter,
  6641. view_counter: video.view_counter,
  6642. num_res: video.num_res,
  6643. first_retrieve: video.first_retrieve,
  6644. thumbnail_url: video.thumbnail_url,
  6645. title: video.title_short,
  6646. _info: video,
  6647. description_short: '関連タグ: ' + video.recommend_tag
  6648. });
  6649. histories[video.id] = item;
  6650. }
  6651. for (var v in histories) {
  6652. result.unshift(histories[v]);
  6653. }
  6654. result.slice(0, 128);
  6655. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6656. },
  6657. onerror: function() {
  6658. throw { message: '取得に失敗しました', status: 'fail'};
  6659. }
  6660. });
  6661.  
  6662. }
  6663. function load(callback, param) {
  6664. request(function(result) {
  6665. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6666. result.setPage(viewPage);
  6667. callback(result);
  6668. });
  6669. }
  6670. var self = {
  6671. load : load
  6672. };
  6673. return self;
  6674. })(w, Util);
  6675.  
  6676.  
  6677. var NicorepoVideo = (function(w, Util) {
  6678. if (!window.PlayerApp) return {};
  6679.  
  6680. var CACHE_TIME = 1000 * 60 * 10;
  6681. var WatchApp = w.WatchApp;
  6682.  
  6683. var getNicorepoTitle = function(type, param) {
  6684. var base = '【ニコレポ】';
  6685. if (type === 'all') {
  6686. return base + 'すべての動画';
  6687. } else
  6688. if (type === 'chcom') {
  6689. return base + 'お気に入りチャンネル&コミュニティの動画';
  6690. } else
  6691. if (type === 'mylist') {
  6692. return base + 'お気に入りマイリストの動画';
  6693. } else
  6694. if (type === 'owner') {
  6695. return WatchController.getOwnerName() + 'のニコレポ';
  6696. }
  6697. return base + 'お気に入りユーザーの動画';
  6698. };
  6699.  
  6700. var parseItemList = function($dom) {
  6701. var $list = $dom.find('.timeline');
  6702. return $list.find([
  6703. '.log-user-mylist-add',
  6704. '.log-user-uad-advertise',
  6705. '.log-user-video-upload',
  6706. '.log-user-video-review',
  6707. '.log-mylist-added-video',
  6708. '.log-community-video-upload',
  6709. '.log-user-video-round-number-of-view-counter',
  6710. '.log-user-video-round-number-of-mylist-counter'
  6711. ].join(', '));
  6712. };
  6713.  
  6714. var ownerReg = /\/(community|user|channel)\/((co|ch)?\d+)\??/;
  6715. var parseNicorepoItem = function(src) {
  6716. var
  6717. $item = $(src), $title = $item.find('.log-content .log-target-info a'),
  6718. id = $title.attr('href').split('/').reverse()[0].replace(/\?.*$/, ''), title = $title.text(),
  6719. duration = '--:--',
  6720. viewCnt = '-',
  6721. resCnt = '-',
  6722. mylistCnt = '-',
  6723. postedAt = WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', new Date($item.find('.log-footer-date time').attr('datetime'))),
  6724. thumbnail = $item.find('.log-target-thumbnail .video').attr('data-src'),
  6725. description_short = $.trim($item.find('.log-body').text()).replace(/(しました|されました)。/g, ''),
  6726. $owner = $item.find('.author-user, .author-community'),
  6727. ownerPage = $owner.attr('href'),
  6728. ownerMatch = ownerReg.exec(ownerPage),
  6729. ownerName = $owner.text(),
  6730. ownerId = (ownerMatch !== null && ownerMatch.length >= 3) ? ownerMatch[2] : null,
  6731. ownerIcon = $item.find('.log-author img').attr('data-src'),
  6732. mylistComment = $item.find('.log-content .log-subdetails').text().trim()
  6733. ;
  6734.  
  6735. $item.removeClass('log').removeClass('passive').removeClass('first');
  6736. if (src.className === 'log-mylist-added-video') {
  6737. ownerName = $item.find('.log-body a:first').text();
  6738. ownerPage = $item.find('.log-body a:last').attr('href');
  6739. }
  6740.  
  6741. var item = new DummyMylistVideo({
  6742. id: id,
  6743. length: duration,
  6744. mylist_counter: mylistCnt,
  6745. view_counter: viewCnt,
  6746. num_res: resCnt,
  6747. first_retrieve: postedAt,
  6748. thumbnail_url: thumbnail,
  6749. mylist_comment: mylistComment,
  6750. title: title,
  6751. _info: {
  6752. first_retrieve: postedAt,
  6753. nicorepo_className: src.className,
  6754. nicorepo_log: [window._.escape(description_short)],
  6755. nicorepo_owner: {
  6756. id: ownerId,
  6757. icon: ownerIcon,
  6758. page: ownerPage,
  6759. name: ownerName
  6760. }
  6761. },
  6762. description_short: description_short
  6763. });
  6764. return item;
  6765. };
  6766.  
  6767. var loadPage = function(baseUrl, result, nextLink, type) {
  6768. var def = new $.Deferred();
  6769. if (nextLink === null) {
  6770. return def.resolve(baseUrl, result, null, null);
  6771. }
  6772. var url = baseUrl;
  6773. if (type === 'offset') {
  6774. url += nextLink ? ('&offset=' + nextLink) : '';
  6775. } else {
  6776. url += nextLink ? ('&last_timeline=' + nextLink) : '';
  6777. }
  6778. console.log('load Url=', url);
  6779.  
  6780. $.ajax({
  6781. url: url,
  6782. timeout: 30000
  6783. }).then(
  6784. function(resp) {
  6785. var $dom = $(resp),
  6786. $nextPageLink = $dom.find('.next-page-link'),
  6787. hasNextPage = $nextPageLink.length > 0;
  6788.  
  6789. parseItemList($dom).each(function() {
  6790. result.push(parseNicorepoItem(this));
  6791. });
  6792.  
  6793. var nextLinkReg = /(last_timeline|offset)=(\d+)/;
  6794. if (hasNextPage) {
  6795. var href = $nextPageLink.attr('href');
  6796. if (nextLinkReg.test(href)) {
  6797. def.resolve(baseUrl, result, RegExp.$2, RegExp.$1);
  6798. } else {
  6799. def.resolve(baseUrl, result, null, null);
  6800. }
  6801. } else {
  6802. def.resolve(baseUrl, result, null, null);
  6803. }
  6804. },
  6805. function() {
  6806. def.reject();
  6807. });
  6808.  
  6809. return def.promise();
  6810. };
  6811.  
  6812. var pipeRequest = function(baseUrl, result, maxPages, callback) {
  6813. var def = new $.Deferred(), p = def.promise();
  6814.  
  6815. for (var i = maxPages; i >= 0; i--) {
  6816. p = p.then(loadPage);
  6817. if (i > 0) p = p.then(Util.Deferred.wait(300));
  6818. }
  6819.  
  6820. p.then(
  6821. function() {
  6822. var uniq = {}, uniq_items = [];
  6823. for (var i = result.rawData.list.length - 1; i >= 0; i--) {
  6824. var item = result.rawData.list[i], id = item.id, mc = item.mylist_comment;
  6825. if (uniq[id + mc]) {
  6826. uniq[id + mc]._info.nicorepo_log.push(item.first_retrieve + ' ' + item._info.nicorepo_log[0].replace(/^.*?さん(の|が)動画(が|を) ?/, ''));
  6827. } else {
  6828. uniq[id + mc] = item;
  6829. }
  6830. }
  6831. for (var v in uniq) {
  6832. uniq_items.unshift(uniq[v]);
  6833. }
  6834. result.rawData.list = uniq_items;
  6835. callback(result);
  6836. }
  6837. );
  6838. def.resolve(baseUrl, result, '', '');
  6839.  
  6840. };
  6841.  
  6842. var request = function(param) {
  6843. var url, nickname, userId, type, baseUrl;
  6844. var def = new $.Deferred();
  6845. try {
  6846. url = '';
  6847. nickname = param.nickname || window.WatchController.getMyNick();
  6848. userId = param.userId || window.WatchController.getMyUserId();
  6849. type = param.type || 'user';
  6850. baseUrl = '/my/top/' + type + '?innerPage=1&mode=next_page';
  6851. if (param.userId) {
  6852. baseUrl = '/user/'+ param.userId +'/top?innerPage=1&mode=next_page';
  6853. }
  6854. } catch (e) {
  6855. console.log(e);
  6856. return def.reject({message: 'エラーが発生しました', status: 'fail'});
  6857. }
  6858.  
  6859. var cacheData = Util.Cache.get(baseUrl);
  6860. if (cacheData) {
  6861. return def.resolve(cacheData);
  6862. }
  6863.  
  6864. var
  6865. result = new DummyMylist({
  6866. id: '-10',
  6867. sort: '1',
  6868. default_sort: '1',
  6869. name: getNicorepoTitle(type, param),
  6870. user_id: type === 'owner' ? WatchController.getOwnerId() : userId,
  6871. user_nickname: type === 'owner' ? WatchController.getOwnerName() : nickname
  6872. });
  6873.  
  6874. pipeRequest(baseUrl, result, 2, function(result) {
  6875. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  6876. });
  6877.  
  6878. return def.promise();
  6879. };
  6880.  
  6881. var load = function(callback, param) {
  6882. return request(param)
  6883. .then(function(result) {
  6884. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6885. result.sortItem(param.sort || 1, true);
  6886. result.setPage(viewPage);
  6887. if (typeof result === 'function') { callback(result); }
  6888. return this.done(result);
  6889. }, function() {
  6890. return this.fail({message: 'ニコレポの取得に失敗しました', status: 'fail'});
  6891. });
  6892. };
  6893.  
  6894. var self = {
  6895. load: load,
  6896. REPO_ALL: -10,
  6897. REPO_USER: -11,
  6898. REPO_CHCOM: -12,
  6899. REPO_MYLIST: -13,
  6900. REPO_OWNER: -14,
  6901. loadAll: function(callback, p) {
  6902. p = p || {};
  6903. p.type = 'all';
  6904. return self.load(callback, p);
  6905. },
  6906. loadUser: function(callback, p) {
  6907. p = p || {};
  6908. p.type = 'user';
  6909. return self.load(callback, p);
  6910. },
  6911. loadChCom: function(callback, p) {
  6912. p = p || {};
  6913. p.type = 'chcom';
  6914. return self.load(callback, p);
  6915. },
  6916. loadMylist: function(callback, p) {
  6917. p = p || {};
  6918. p.type = 'mylist';
  6919. return self.load(callback, p);
  6920. },
  6921. loadOwner: function(callback, p) {
  6922. p = p || {};
  6923. p.type = 'owner';
  6924. p.userId = window.WatchController.getOwnerId();
  6925. return self.load(callback, p);
  6926. }
  6927. };
  6928. window.WatchItLater.NicorepoVideo = self;
  6929.  
  6930. return self;
  6931. })(w, Util);
  6932.  
  6933.  
  6934.  
  6935. /**
  6936. * ランキングのRSSをマイリストAPIと互換のある形式に変換することで、ダミーマイリストとして表示してしまう作戦
  6937. */
  6938. var VideoRanking = (function(w, Util) {
  6939. if (!window.PlayerApp) return {};
  6940. var $ = w.jQuery;
  6941.  
  6942. var
  6943. genreIdTable = {
  6944. all: -100,
  6945. g_ent2: -110,
  6946. ent: -111,
  6947. music: -112,
  6948. sing: -113,
  6949. play: -114,
  6950. dance: -115,
  6951. vocaloid: -116,
  6952. nicoindies: -117,
  6953. g_life2: -120,
  6954. animal: -121,
  6955. cooking: -122,
  6956. nature: -123,
  6957. travel: -124,
  6958. sport: -125,
  6959. lecture: -126,
  6960. drive: -127,
  6961. history: -128,
  6962. g_politics: -130,
  6963. g_tech: -140,
  6964. science: -141,
  6965. tech: -142,
  6966. handcraft: -143,
  6967. make: -144,
  6968. g_culture2: -150,
  6969. anime: -151,
  6970. game: -152,
  6971. toho: -153,
  6972. imas: -154,
  6973. radio: -155,
  6974. draw: -156,
  6975. g_other: -160,
  6976. are: -161,
  6977. diary: -162,
  6978. other: -163
  6979. // r18: -170
  6980. },
  6981. genreNameTable = {
  6982. all: 'カテゴリ合算',
  6983. g_ent2: 'エンタメ・音楽',
  6984. ent: 'エンターテイメント',
  6985. music: '音楽',
  6986. sing: '歌ってみた',
  6987. play: '演奏してみた',
  6988. dance: '踊ってみた',
  6989. vocaloid: 'VOCALOID',
  6990. nicoindies: 'ニコニコインディーズ',
  6991. g_life2: '生活・一般・スポ',
  6992. animal: '動物',
  6993. cooking: '料理',
  6994. nature: '自然',
  6995. travel: '旅行',
  6996. sport: 'スポーツ',
  6997. lecture: 'ニコニコ動画講座',
  6998. drive: '車載動画',
  6999. history: '歴史',
  7000. g_politics: '政治',
  7001. g_tech: '科学・技術',
  7002. science: '科学',
  7003. tech: 'ニコニコ技術部',
  7004. handcraft: 'ニコニコ手芸部',
  7005. make: '作ってみた',
  7006. g_culture2: 'アニメ・ゲーム・絵',
  7007. anime: 'アニメ',
  7008. game: 'ゲーム',
  7009. toho: '東方',
  7010. imas: 'アイドルマスター',
  7011. radio: 'ラジオ',
  7012. draw: '描いてみた',
  7013. g_other: 'その他',
  7014. are: '例のアレ',
  7015. diary: '日記',
  7016. other: 'その他',
  7017. r18: 'R-18'
  7018. },
  7019. termIdTable = {
  7020. 'hourly': 0,
  7021. 'daily': -1000,
  7022. 'weekly': -2000,
  7023. 'monthly': -3000,
  7024. 'total': -4000
  7025. },
  7026. termNameTable = {
  7027. 'hourly': '(毎時)',
  7028. 'daily': '(24時間)',
  7029. 'weekly': '(週間)',
  7030. 'monthly': '(月間)',
  7031. 'total': '(合計)'
  7032. },
  7033. idTermTable = {},
  7034. idGenreTable = {}
  7035. ;
  7036. if (conf.debugMode) { genreIdTable['r18'] = -170; }
  7037. for (var genre in genreIdTable) { idGenreTable[genreIdTable[genre]] = genre;}
  7038. for (var term in termIdTable ) { idTermTable [termIdTable [term ]] = term; }
  7039.  
  7040. /**
  7041. * ニコニコ動画ランキングのRSSをマイリストAPI互換のデータ形式に変換
  7042. */
  7043. var rss2mylist = function(xml) {
  7044. var
  7045. $x = $(xml),
  7046. title = $x.find('channel title:first').text(),
  7047. $items = $x.find('channel item'),
  7048. result = new DummyMylist({
  7049. name: title,
  7050. id: '-100'
  7051. });
  7052. $items.each(function() {
  7053. var video = parseRssItem($(this));
  7054. var item = new DummyMylistVideo({
  7055. id: video.id,
  7056. length: video.duration,
  7057. mylist_counter: video.mylistCnt,
  7058. view_counter: video.viewCnt,
  7059. num_res: video.resCnt,
  7060. first_retrieve: video.postedAt,
  7061. thumbnail_url: video.thumbnail,
  7062. title: video.title.replace(/^.*?第(\d+)位/, '第000$1位').replace(/^第\d+(\d{3})位/, '第$1位'),
  7063. _info: {first_retrieve: video.postedAt},
  7064. description_short: video.description.substring(0, 50)
  7065. });
  7066. result.push(item);
  7067. });
  7068. return result;
  7069. };
  7070.  
  7071. var parseRssItem = function($item) {
  7072. var
  7073. desc_cdata = $item.find('description').text(),
  7074. $desc = $('<div>' + desc_cdata + '</div>');
  7075. return {
  7076. title : $item.find('title') .text(),
  7077. id : $item.find('guid') .text().split('/').reverse()[0],
  7078. duration : $desc.find('.nico-info-length') .text(),
  7079. viewCnt : $desc.find('.nico-info-total-view') .text().replace(/,/g, ''),
  7080. resCnt : $desc.find('.nico-info-total-res') .text().replace(/,/g, ''),
  7081. mylistCnt : $desc.find('.nico-info-total-mylist').text().replace(/,/g, ''),
  7082. postedAt : $desc.find('.nico-info-date') .text()
  7083. .replace(/(年|月)/g, '-')
  7084. .replace(/:/g, ':')
  7085. .replace(/(日)/g, ''),
  7086. description : $desc.find('.nico-description') .text(),
  7087. thumbnail : $desc.find('.nico-thumbnail img').attr('src')
  7088. };
  7089. };
  7090.  
  7091. var pipeRequest = function(baseUrl, result, page, maxPage) {
  7092. var def = new $.Deferred(), p = def.promise();
  7093.  
  7094. var getPipe = function(result, url, page) {
  7095. return function() {
  7096. console.log('load RSS', url, page);
  7097. return $.ajax({
  7098. url: url,
  7099. timeout: 30000,
  7100. data: {rss: '2.0', lang: 'ja-jp', page: page},
  7101. beforeSend: function(xhr) {
  7102. xhr.setRequestHeader('X-Requested-With', {toString: function(){ return ''; }});
  7103. }
  7104. }).then(function(resp) {
  7105. var res = rss2mylist(resp);
  7106. for (var i = 0, len = res.rawData.list.length; i < len; i++) {
  7107. result.push(res.rawData.list[i]);
  7108. }
  7109. });
  7110. };
  7111. };
  7112.  
  7113. for (var i = page; i <= maxPage; i++) {
  7114. p = p.then(getPipe(result, baseUrl, i));
  7115. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  7116. }
  7117.  
  7118. def.resolve();
  7119.  
  7120. return p;
  7121. };
  7122.  
  7123. var CACHE_TIME = 1000 * 60 * 30;
  7124. var request = function(baseUrl, page, maxPage) {
  7125. var def = new $.Deferred();
  7126. var cacheData = Util.Cache.get(baseUrl);
  7127. if (cacheData) {
  7128. return def.resolve(cacheData);
  7129. }
  7130.  
  7131. var result = new DummyMylist({
  7132. name: '総合ランキング',
  7133. id: '-100'
  7134. });
  7135.  
  7136. pipeRequest(baseUrl, result, page, maxPage).then(
  7137. function() {
  7138. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  7139. },
  7140. function() {
  7141. def.reject();
  7142. });
  7143.  
  7144. return def.promise();
  7145. };
  7146.  
  7147. var parseParam = function(param) {
  7148. var
  7149. id = parseInt(param.id || -100, 10),
  7150. genreId = getGenreId(id),
  7151. termId = getTermId(id),
  7152. category = idGenreTable[genreId] || 'all', type = 'fav', term = 'daily', lang= 'ja-jp',
  7153. viewPage = (param && typeof param.page === 'number') ? param.page : 1,
  7154. genreName = genreNameTable[category] || genreNameTable['all'],
  7155. maxRssPage = 1, sort = param.sort || '4';
  7156.  
  7157. term = idTermTable[termId] || idTermTable[0];
  7158. maxRssPage = (category === 'all' && term !== 'hourly') ? 3 : 1;
  7159. return {
  7160. genreId: genreId,
  7161. genreName: genreName,
  7162. category: category,
  7163. type: type,
  7164. term: term,
  7165. lang: lang,
  7166. viewPage: viewPage,
  7167. sort: sort,
  7168. maxRssPage: maxRssPage,
  7169. baseUrl:
  7170. '/ranking/'+ type +'/'+ term + '/'+ category //+'?rss=2.0&lang=' + lang
  7171. };
  7172. };
  7173.  
  7174. var loadRanking = function(param) {
  7175. var p = parseParam(param);
  7176. return request(p.baseUrl, 1, p.maxRssPage)
  7177. .then(function(result) {
  7178. result.name = p.genreName;
  7179. result.setPage(p.viewPage);
  7180.  
  7181. this.done(result);
  7182. });
  7183. };
  7184.  
  7185. var load = function(onload, param) {
  7186. var p = parseParam(param);
  7187. return request(p.baseUrl, 1, p.maxRssPage)
  7188. .then(function(result) {
  7189. result.name = p.genreName;
  7190. result.setPage(p.viewPage);
  7191.  
  7192. if (typeof onload === 'function') {
  7193. onload(result);
  7194. }
  7195. return this.done(result);
  7196. }, function() {
  7197. return this.fail({message: 'ランキングの取得に失敗しました', status: 'fail'});
  7198. });
  7199. };
  7200.  
  7201. var getTermId = function(t) {
  7202. if (typeof t === 'string') {
  7203. return termIdTable[t] || 0;
  7204. } else
  7205. if (typeof t === 'number'){
  7206. return (t - (t % 1000)) % 10000;
  7207. }
  7208. return 0;
  7209. };
  7210. var getGenreId = function(g, term) {
  7211. if (typeof g === 'string') {
  7212. return (genreIdTable[g] || 0) + getTermId(term);
  7213. } else
  7214. if (typeof g === 'number'){
  7215. return g % 1000;
  7216. } else {
  7217. return genreIdTable;
  7218. }
  7219. };
  7220. var getGenreName = function(g) {
  7221. if (typeof g === 'number' || (typeof g === 'string' && g.match(/^-?[0-9]+$/))) {
  7222. g = g % 1000;
  7223. var genre = idGenreTable[g];
  7224. return genreNameTable[genre];
  7225. } else
  7226. if (typeof g === 'string') {
  7227. return genreNameTable[g];
  7228. } else {
  7229. return genreNameTable;
  7230. }
  7231. };
  7232. var getCategory = function(g) {
  7233. if (typeof g === 'number') {
  7234. g = g % 1000;
  7235. return idGenreTable[g - (g %10)];
  7236. } else
  7237. if (typeof g === 'string') {
  7238. g = genreIdTable[g];
  7239. return idGenreTable[g - (g %10)];
  7240. } else {
  7241. return 'all';
  7242. }
  7243. };
  7244.  
  7245. var self = {
  7246. load: load,
  7247. getTermId: getTermId,
  7248. getGenreId: getGenreId,
  7249. getGenreName: getGenreName,
  7250. getCategory: getCategory
  7251. };
  7252. WatchItLater.VideoRanking = self;
  7253. return self;
  7254. })(w, Util);
  7255.  
  7256.  
  7257.  
  7258. /**
  7259. * チャンネル動画一覧をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  7260. */
  7261. var ChannelVideoList = (function(w, Util){
  7262. if (!window.PlayerApp) return {};
  7263. var
  7264. CACHE_TIME = 1000 * 60 * 1, MAX_PAGE = 3,
  7265. getPipe = function(baseUrl, result, page) {
  7266. return function(hasPage) {
  7267. var def = new $.Deferred();
  7268. if (!hasPage) return def.resolve(hasPage);
  7269. var url = baseUrl + '?page=' + page;
  7270. console.log('load page', url);
  7271.  
  7272. $.ajax({url: url, timeout: 30000}).then(function(resp) {
  7273. var hasNextPage = parseItems(resp, result);
  7274. def.resolve(hasNextPage);
  7275. }, function(err) {
  7276. def.reject(err);
  7277. });
  7278. return def.promise();
  7279. };
  7280. },
  7281. pipeRequest = function(baseUrl, result) {
  7282. var def = new $.Deferred(), p = def.promise();
  7283.  
  7284. var maxPage = MAX_PAGE;
  7285. for (var i = 1; i <= maxPage; i++) {
  7286. p = p.then(getPipe(baseUrl, result, i));
  7287. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  7288. }
  7289.  
  7290. p.then(function() {
  7291. this.done(result);
  7292. });
  7293.  
  7294. def.resolve(true);
  7295. return p;
  7296. },
  7297. load = function(callback, params) {
  7298. var myId, url, id, ownerName, def = new $.Deferred();
  7299. try{
  7300. id = params.id.toString().replace(/^ch/, '');
  7301. ownerName = params.ownerName;
  7302. url = 'http://ch.nicovideo.jp/channel/ch'+ id + '/video';
  7303. myId = WatchController.getMyUserId();
  7304. } catch (e) {
  7305. console.log(e);
  7306. throw { message: 'エラーが発生しました', status: 'fail'};
  7307. }
  7308.  
  7309. var CACHE_KEY = 'ch-' + id, cacheData = Util.Cache.get(CACHE_KEY);
  7310. if (cacheData) {
  7311. if (typeof callback === 'function') {
  7312. setTimeout(function() { callback(cacheData); } , 0);
  7313. }
  7314. return def.resolve(cacheData);
  7315. }
  7316.  
  7317.  
  7318. var result = new DummyMylist({
  7319. id: 'ch' + id,
  7320. sort: '1',
  7321. name: ownerName + 'の動画',
  7322. user_id: myId,
  7323. user_name: 'ニコニコ動画'
  7324. });
  7325.  
  7326. pipeRequest(url, result).then(function() {
  7327. Util.Cache.set(CACHE_KEY, result, CACHE_TIME);
  7328. if (typeof callback === 'function') callback(result);
  7329. def.resolve(result);
  7330. });
  7331.  
  7332. return def.promise();
  7333. },
  7334. parseItems = function(html, result) {
  7335. var $html = $(html), $list = $html.find('.contents_list .item');
  7336. var hasNextPage = false;
  7337. $list.each(function() {
  7338. var $item = $(this);
  7339. var id = $item.find('.title a').attr('href').split('/').reverse()[0];
  7340. var $counts = $item.find('.counts'), first_retrieve = $item.find('.time var').attr('title');
  7341. w.$item = $item;
  7342. result.push(new DummyMylistVideo({
  7343. id: id,
  7344. length: $item.find('.length').text(),
  7345. mylist_counter: $counts.find('.mylist var').text().split(',').join(''),
  7346. view_counter: $counts.find('.view var').text().split(',').join(''),
  7347. num_res: $counts.find('.comment var').text().split(',').join(''),
  7348. first_retrieve: first_retrieve,
  7349. thumbnail_url: $item.find('.lazyimage').data('original'),
  7350. title: $item.find('.title').text().trim(),
  7351. _info: {first_retrieve: first_retrieve, is_channel: true},
  7352. description_short: $item.find('.description').text().trim()
  7353. }));
  7354. });
  7355. if ($html.find('.pager .next:not(.disabled)').length > 0) {
  7356. hasNextPage = true;
  7357. }
  7358. return hasNextPage;
  7359. },
  7360. loadOwnerVideo = function(callback) {
  7361. if (!WatchController.isChannelVideo()) {
  7362. throw {message: 'チャンネル情報の取得に失敗しました', status: 'fail'};
  7363. }
  7364. var params = {
  7365. id: WatchController.getOwnerId(),
  7366. ownerName: WatchController.getOwnerName()
  7367. };
  7368. var def = new $.Deferred();
  7369. load(callback, params).then(function(result) {
  7370. if (typeof callback === 'function') callback(result);
  7371. def.resolve(result);
  7372. }, function() {
  7373. def.reject({message: 'チャンネル動画の取得に失敗しました', status: 'fail'});
  7374. });
  7375. return def.promise();
  7376. };
  7377.  
  7378. var self = {
  7379. load: load,
  7380. loadOwnerVideo: loadOwnerVideo
  7381. };
  7382. WatchItLater.ChannelVideo = self;
  7383. return self;
  7384. })(w, Util);
  7385.  
  7386.  
  7387.  
  7388. var niconicodoRedirect = function() {
  7389. // www.nicovideo.jp/stampを watchにパラメータを中継するためのクッションページとして使う。
  7390. // watchと同じドメインならどこでもいいけど、ここはDBアクセスもなさそうな静的ページため採用
  7391. var hash = location.hash.toString();
  7392. if (hash.indexOf('#json={') !== 0) {
  7393. return;
  7394. }
  7395. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  7396.  
  7397. LocationHashParser.initialize();
  7398. var blankWatchId = 'sm20353707';
  7399. var redirectWatchId = LocationHashParser.getValue('redirectWatchId');
  7400. // 見た目が残念なので消す
  7401. document.body.innerHTML = '';
  7402.  
  7403. window.sessionStorage.setItem('watchItLater_redirectedHash', location.hash);
  7404. var redirectTo = '/watch/' + (redirectWatchId ? redirectWatchId : blankWatchId);
  7405. location.replace(redirectTo);
  7406. };
  7407.  
  7408.  
  7409. /**
  7410. * GINZAwatch上でのあれこれ
  7411. * 無計画に増築中
  7412. *
  7413. * watch.jsを解析すればわかる
  7414. *
  7415. */
  7416. var ZeroFunc = function(w) { // Zero Watch
  7417. var
  7418. video_id = '', watch_id = '',
  7419. // WatchApp = w.WatchApp, WatchJsApi = w.WatchJsApi,
  7420. isTouchActive = false,
  7421. console = conf.debugMode ? window.console : {log: _.noop, trace: _.noop, time: _.noop, timeEnd: _.noop},
  7422. watch = WatchApp.ns.init,
  7423. watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  7424. if (!WatchApp.mixin) {
  7425. WatchApp.mixin = _.mixin;
  7426. }
  7427.  
  7428. console.log('%cGinza', 'background: lightgreen;');
  7429.  
  7430. /**
  7431. * ゆっくり再生(スロー再生)メニュー
  7432. */
  7433. var Yukkuri = (function($, conf, w) {
  7434. var self, $content = null, $button = null, timer = null, cnt = 0, isActive = false;
  7435.  
  7436. function createDom() {
  7437. $content = $('<div id="yukkuriPanel" />');
  7438. $button = $('<button>yu</button>').addClass('yukkuriButton').attr({title: 'ゆっくり(スロー再生)'});
  7439. $button.click(function() {
  7440. toggleActive();
  7441. });
  7442. $content.append($button);
  7443.  
  7444. $('body').append($content);
  7445. }
  7446.  
  7447. function show() {
  7448. if ($content === null) {
  7449. createDom();
  7450. }
  7451. updateView();
  7452. $content.show();
  7453. }
  7454. function hide() {
  7455. $content.hide();
  7456. }
  7457. function updateView() {
  7458. $button.toggleClass('active', isActive);
  7459. }
  7460.  
  7461. function start() {
  7462. if (timer !== null) {
  7463. clearInterval(timer);
  7464. }
  7465. isActive = true;
  7466. updateView();
  7467. timer = setInterval(function() {
  7468. var v = cnt++ % 4;
  7469. if (v === 0) {
  7470. WatchController.play();
  7471. } else
  7472. if (v === 1) {
  7473. WatchController.pause();
  7474. }
  7475. }, 20);
  7476. }
  7477. function stop() {
  7478. if (timer !== null) {
  7479. clearInterval(timer);
  7480. timer = null;
  7481. }
  7482. isActive = false;
  7483. updateView();
  7484. WatchController.pause();
  7485. }
  7486.  
  7487. function toggleActive() {
  7488. if (isActive) {
  7489. stop();
  7490. } else {
  7491. start();
  7492. }
  7493. return isActive;
  7494. }
  7495.  
  7496. self = {
  7497. show: show,
  7498. hide: hide,
  7499. start: start,
  7500. stop: stop
  7501. };
  7502. return self;
  7503. })($, conf, w);
  7504.  
  7505. function onWindowResizeEnd() {
  7506. setTimeout(function() {
  7507. EventDispatcher.dispatch('onWindowResizeEnd');
  7508. }, 1000);
  7509. }
  7510.  
  7511. /**
  7512. * デフォルトの市場貼付ボタンはなぜかページの一番上までスクロールするという意地悪な仕様だが、
  7513. * こっちはがんばって見やすい位置に調整して開く
  7514. */
  7515. function ichibaSearch(word, shopCode) {
  7516. var wait = 10, opened = false;
  7517. //shopCode = shopCode || 'az'; // az = amazon
  7518. var search = function() {
  7519. if ($('#ichibaConsole').is(':visible')) {
  7520. setTimeout(function() {
  7521. w.WatchApp.ns.util.WindowUtil.scrollFitMinimum('#ichibaConsole', 300);
  7522. }, 1000);
  7523. if (!word) {
  7524. return;
  7525. }
  7526. if ($('#ichiba_search_form_query').is(':visible')) {
  7527. $('#ichiba_search_form_query').val(word);
  7528. w.ichiba.search(shopCode, 0, 'all');
  7529. setTimeout(function() {$('#ichiba_search_form_query').focus();}, 1000);
  7530. } else {
  7531. if (!opened) {
  7532. if(shopCode) { w.ichiba.showRelatedTagItems(shopCode, 0, 'all'); }
  7533. opened = true;
  7534. }
  7535. if (wait-- > 0) setTimeout(search, 1000);
  7536. }
  7537. } else {
  7538. if (wait-- > 0) setTimeout(search, 1000);
  7539. }
  7540. };
  7541. search();
  7542. w.ichiba.showConsole();
  7543. }
  7544. WatchController.ichibaSearch = ichibaSearch;
  7545.  
  7546. function initVideoCounter() {
  7547. var
  7548. playerAreaConnector = watch.PlayerInitializer.playerAreaConnector,
  7549. counter = {mylistCount: 0, viewCount: 0, commentCount: 0},
  7550. blinkItem = function($elm) {
  7551. $elm.removeClass('animateBlink').addClass('blink');
  7552. setTimeout(function() {
  7553. $elm.addClass('animateBlink').removeClass('blink');
  7554. $elm = null;
  7555. }, 500);
  7556. };
  7557. var setVideoCounter = function(watchId, title) {
  7558. var $tpl = $(
  7559. '<span>再生: <span class="viewCountDiff videoCountDiff"></span><span class="viewCount videoCount"></span> コメ: <span class="commentCountDiff videoCountDiff"></span><span class="commentCount videoCount"></span> マイ: <span class="mylistCountDiff videoCountDiff"></span><span class="mylistCount videoCount"></span></span>'
  7560. );
  7561. assignVideoCountToDom($tpl, counter);
  7562.  
  7563. if ((conf.popupViewCounter === 'always') ||
  7564. (conf.popupViewCounter === 'full' && $('body').hasClass('full_with_browser'))
  7565. ) {
  7566. Popup.show(
  7567. $('<div/>')
  7568. .append(
  7569. $('<a/>')
  7570. .text(window._.unescape(title))
  7571. .attr('href', 'http://nico.ms/' + watchId)
  7572. )
  7573. .html() +
  7574. '<br/><span style="margin-left:10px; font-size: 90%;">'+ $tpl.html() + '</span>'
  7575. );
  7576. }
  7577. $('#fullScreenToggleContainer').html([
  7578. '<img class="ownerIcon" src="', WatchController.getOwnerIcon(), '">',
  7579. '<div class="title">', title, '</div>',
  7580. '<p class="postedAt">',$('.videoPostedAt:last').text(), '</p>',
  7581. '<p class="videoCounter">', $tpl.html(), '</p>',
  7582. ''].join(''))
  7583. .toggleClass('favorite', WatchController.isFavoriteOwner())
  7584. .find('img').attr('title', WatchController.getOwnerName());
  7585.  
  7586. if (conf.headerViewCounter) {
  7587. var vc = $('#videoCounter');
  7588. if (vc.length < 1) {
  7589. var li = $('<li></li>')[0];
  7590. li.id = 'videoCounter';
  7591. $('#siteHeaderLeftMenu').after(li);
  7592. vc = $('#videoCounter');
  7593. }
  7594. vc.empty().append($tpl);
  7595. }
  7596. };
  7597.  
  7598. playerAreaConnector.addEventListener('onWatchCountUpdated', function(c) {
  7599. var diff = c - counter.viewCount;
  7600. if (diff === 0) return;
  7601. counter.viewCount = c;
  7602. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'viewCount', diff);
  7603. });
  7604. playerAreaConnector.addEventListener('onCommentCountUpdated', function(c) {
  7605. var diff = c - counter.commentCount;
  7606. if (diff === 0) return;
  7607. counter.commentCount = c;
  7608. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'commentCount', diff);
  7609. });
  7610. playerAreaConnector.addEventListener('onMylistCountUpdated', function(c) {
  7611. var diff = c - counter.mylistCount;
  7612. if (diff === 0) return;
  7613. counter.mylistCount = c;
  7614. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'mylistCount', diff);
  7615. });
  7616.  
  7617. EventDispatcher.addEventListener('onWatchInfoReset', function(watchInfoModel){
  7618. counter.mylistCount = watchInfoModel.mylistCount;
  7619. counter.viewCount = watchInfoModel.viewCount;
  7620. counter.commentCount = watchInfoModel.commentCount;
  7621.  
  7622. setVideoCounter(watchInfoModel.v, watchInfoModel.title);
  7623. });
  7624. EventDispatcher.addEventListener('onVideoCountUpdated', function(c, type, diff) {
  7625. var $target = $('.sidePanel .videoInfo, #fullScreenMenuContainer, #videoCounter');
  7626. assignVideoCountToDom($target, c);
  7627. $target.find('.' + type + 'Diff').text(diff).toggleClass('down', diff < 0);
  7628. blinkItem($target.find('.' + type + ', .' + type + 'Diff'));
  7629. });
  7630.  
  7631. } //
  7632.  
  7633. var isFirst = true;
  7634. function onVideoInitialized() {
  7635. watch = WatchApp.ns.init;
  7636. AnchorHoverPopup.hidePopup().updateNow();
  7637. tagv = watch.TagInitializer.tagViewController;
  7638. WatchCounter.add();
  7639.  
  7640. if (isFirst) {
  7641. if (conf.autoPlayIfWindowActive === 'yes' && w.document.hasFocus()) {
  7642. // ウィンドウがアクティブの時だけ自動再生する。 複数タブ開いてるときは便利
  7643. setTimeout(function() { WatchController.play(); }, 2000);
  7644. }
  7645.  
  7646. if (isFirst && conf.commentVisibility !== 'visible') {
  7647. if (conf.commentVisibility === 'hidden') {
  7648. console.log('comment off');
  7649. WatchController.commentVisibility(false);
  7650. } else {
  7651. console.log('last state', conf.lastCommentVisibility);
  7652. WatchController.commentVisibility(conf.lastCommentVisibility === 'visible');
  7653. }
  7654. }
  7655. EventDispatcher.dispatch('onFirstVideoInitialized');
  7656. }
  7657.  
  7658. EventDispatcher.dispatch('onVideoInitialized', isFirst);
  7659. isFirst = false;
  7660. } //
  7661.  
  7662. function onVideoChangeStatusUpdated(isChanging) {
  7663. AnchorHoverPopup.hidePopup();
  7664. if (isChanging) {
  7665. $('.sidePanel .sideVideoInfo').removeClass('show');
  7666. }
  7667. if ((conf.enableAutoPlaybackContinue || conf.debugMode) && watch.PlayerInitializer.noUserOperationController.autoPlaybackModel._isAutoPlayback) {
  7668. watch.PlayerInitializer.noUserOperationController.autoPlaybackModel.setCount(0);
  7669. }
  7670. EventDispatcher.dispatch('onVideoChangeStatusUpdated', isChanging);
  7671. }
  7672.  
  7673. var $sideInfoPanelTemplate = $([
  7674. '<div class="sideVideoInfoInner">',
  7675.  
  7676. '<div class="videoTitleContainer"><h3 class="videoTitle"></h3></div>',
  7677. '<div class="videoOwnerInfoContainer">',
  7678. '<div class="channelIconContainer"><a target="_blank" class="channelIconLink">',
  7679. '<img class="channelIcon"></a>',
  7680. '<span class="channelName">提供: ',
  7681. '<a class="showOtherVideos" target="_blank"><span class="channelNameInner"></span></a></span>',
  7682. '</span>',
  7683. '</div>',
  7684. '<div class="userIconContainer"><a target="_blank" class="userIconLink">',
  7685. '<img class="userIcon"></a>',
  7686. '<span class="userName">投稿者: ',
  7687. '<span class="userNameInner notPublic"></span>',
  7688. '<span class="isPublic"><a class="showOtherVideos"><span class="userNameInner"></span></a></span>',
  7689. '</span>',
  7690. '</div>',
  7691. '</div>',
  7692. '<div class="videoInfo">',
  7693. '<span class="videoPostedAt"></span>',
  7694. '<ul class="videoStats">',
  7695. '<li style="position: relative;">再生: <span class="viewCountDiff videoCountDiff"></span><span class="videoCount viewCount"></span></li>',
  7696. '<li style="position: relative;">コメント: <span class="commentCountDiff videoCountDiff"></span><span class="videoCount commentCount"></span></li>',
  7697. '<li style="position: relative;">マイリスト: <span class="mylistCountDiff videoCountDiff"></span><span class="videoCount mylistCount"></span></li>',
  7698. '</ul>',
  7699. '</div>',
  7700.  
  7701.  
  7702. '<div class="videoThumbnailContainer" style="display: none;">',
  7703. '<img class="videoThumbnailImage">',
  7704. '</div>',
  7705. '<div class="videoDetails">',
  7706. '<div class="videoDescription">',
  7707. '<div class="videoDescriptionInner">',
  7708. '</div>',
  7709. '</div>',
  7710. '</div>',
  7711. '</div>',
  7712. ''].join(''));
  7713.  
  7714.  
  7715. // - 左パネル乗っ取る
  7716. function initLeftPanel($, conf, w) {
  7717.  
  7718. var $tab = $([
  7719. '<ul id="leftPanelTabContainer">',
  7720. '<li class="tab ichiba" data-selection="ichiba" >市場</li>',
  7721. '<li class="tab videoInfo" data-selection="videoInfo">情報</li>',
  7722. '</ul>'].join(''));
  7723.  
  7724. var
  7725. $sidePanel = $('<div id="leftPanel" />').addClass('sidePanel'),
  7726. $infoPanel = $('<div/>').attr({'id': 'leftVideoInfo', 'class': 'sideVideoInfo sidePanelInner'}),
  7727. $ichibaPanel = $('<div/>').attr({'id': 'leftIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7728. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel);
  7729. $('#playerTabWrapper').after($sidePanel);
  7730.  
  7731. var
  7732. onTabSelect = function(e) {
  7733. e.preventDefault();
  7734. AnchorHoverPopup.hidePopup();
  7735. var selection = $(e.target).attr('data-selection');
  7736. if (typeof selection === 'string') {
  7737. conf.setValue('lastLeftTab', selection);
  7738. changeTab(selection);
  7739. }
  7740. },
  7741. changeTab = function(selection) {
  7742. $sidePanel.removeClass('videoInfo ichiba').addClass(selection);
  7743. if (selection === 'ichiba') {
  7744. resetIchiba(false);
  7745. }
  7746. },
  7747. lastIchibaVideoId = '',
  7748. resetIchiba = function(force) {
  7749. var videoId = watchInfoModel.id;
  7750. if (lastIchibaVideoId === videoId && !force) {
  7751. return;
  7752. }
  7753. lastIchibaVideoId = videoId;
  7754. resetSideIchibaPanel($ichibaPanel, true);
  7755. },
  7756. resetScroll = function() {
  7757. $(this).animate({scrollTop: 0}, 600);
  7758. };
  7759.  
  7760. $infoPanel .on('dblclick', resetScroll);
  7761. $ichibaPanel.on('dblclick', resetScroll);
  7762.  
  7763. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7764. changeTab(conf.lastLeftTab);
  7765.  
  7766. var refreshPanel = function(isFirst) {
  7767. if (isFirst) { return; }
  7768.  
  7769. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  7770. if ($ichibaPanel.is(':visible')) {
  7771. resetIchiba(true);
  7772. }
  7773. };
  7774. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  7775. refreshPanel();
  7776.  
  7777. } // end of initLeftPanel
  7778.  
  7779. function initRightPanel($, conf, w) {
  7780. var $rightPanel = $('#playerTabWrapper').addClass('sidePanel');
  7781. initRightPanelVerticalTab($rightPanel);
  7782. initRightPanelHorizontalTab($, conf, w);
  7783. var $playerTabWrapper = $rightPanel, wideCss = null;
  7784. var
  7785. createWideCommentPanelCss = function (targetWidth) {
  7786. var px = targetWidth - $rightPanel.outerWidth();
  7787. var elms = [
  7788. '#playerTabWrapper', //'#playerTabWrapper',
  7789. '#commentDefaultHeader',
  7790. '#playerCommentPanel .commentTable',
  7791. '#playerCommentPanel .commentTable .commentTableContainer'
  7792. ];
  7793. var css = [
  7794. 'body.videoExplorer #content.w_adjusted #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7795. 'body:not(.full_with_browser) .w_wide #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7796. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }\n', // 960 + 140
  7797. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }\n\n' // 1186 + 140
  7798. ];
  7799. for (var v in elms) {
  7800. var $e = $(elms[v]), newWidth = $e.width() + px;
  7801. css.push([
  7802. '.w_wide #playerTabWrapper ', elms[v],
  7803. ' , body.videoExplorer #content.w_adjusted ',
  7804. elms[v], '\n{ width: ', newWidth,'px !important; }\n\n'
  7805. ].join(''));
  7806. }
  7807. wideCss = addStyle(css.join(''), 'wideCommentPanelCss');
  7808. console.log(css.join(''));
  7809. },
  7810. toggleWide = function(v) {
  7811. $('#content').toggleClass('w_wide', v);
  7812. EventDispatcher.dispatch('onWindowResizeEnd');
  7813. };
  7814.  
  7815. var wideCommentPanelCss = Util.here(function() {/*
  7816. body.videoExplorer #content.w_adjusted #playerTabWrapper { width: 420px; }
  7817. body:not(.full_with_browser) .w_wide #playerTabWrapper { width: 420px; }
  7818.  
  7819. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }
  7820. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }
  7821.  
  7822. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerTabWrapper,
  7823. body.videoExplorer #content.w_adjusted #playerTabWrapper
  7824. { width: 420px !important; }
  7825.  
  7826. body:not(.full_with_browser) .w_wide #playerTabWrapper #commentDefaultHeader,
  7827. body.videoExplorer #content.w_adjusted #commentDefaultHeader
  7828. { width: 408px !important; }
  7829.  
  7830. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable,
  7831. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable
  7832. { width: 406px !important; }
  7833.  
  7834. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable .commentTableContainer,
  7835. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable .commentTableContainer
  7836. { width: 406px !important; }
  7837. */});
  7838. addStyle(wideCommentPanelCss, 'wideCommentPanelCss');
  7839.  
  7840. EventDispatcher.addEventListener('on.config.wideCommentPanel', toggleWide);
  7841. toggleWide(!!conf.wideCommentPanel);
  7842.  
  7843. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  7844.  
  7845. //EventDispatcher.dispatch('onWindowResizeEnd');
  7846. //createWideCommentPanelCss(420);
  7847.  
  7848. var $div = $([
  7849. '<div id="sharedNgSettingContainer" style="display: none;">NG共有: ',
  7850. '<select id="sharedNgSetting">',
  7851. '<option value="HIGH">高</option>',
  7852. '<option value="MIDDLE">中</option>',
  7853. '<option value="LOW">低</option>',
  7854. '<option value="NONE">無</option>',
  7855. '</select>',
  7856. '</div>',
  7857. ''].join('')), $ngs = $div.find('select');
  7858.  
  7859. $ngs
  7860. .val(watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get().ngScoringFilteringLevel)
  7861. .on('change', function() {
  7862. var val = this.value;
  7863. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({ngScoringFilteringLevel: this.value});
  7864. });
  7865. $('#commentDefaultHeader').append($div);
  7866.  
  7867. EventDispatcher.addEventListener('on.config.enableSharedNgSetting', function(newValue, oldValue) {
  7868. if (newValue) {
  7869. $div.show();
  7870. } else {
  7871. $div.hide();
  7872. }
  7873. });
  7874. if (conf.enableSharedNgSetting) { $div.show(); }
  7875. });
  7876.  
  7877. if (conf.removeCommentPanelHoverEvent) {
  7878. $("#commentDefault").find(".commentTableContainerInner") .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7879. $('#playerCommentPanel .section .commentTable .commentTableContainer') .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7880. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7881. .off('contextmenu')
  7882. .on('contextmenu', '.cell',
  7883. $.proxy(Util.Closure.commentPanelContextMenu(), watch.PlayerInitializer.commentPanelViewController.commentContent)
  7884. );
  7885. }
  7886. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function(isChanging) {
  7887. if (isChanging) {
  7888. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7889. .find('.cell').off();
  7890. }
  7891. });
  7892. } // end initRightPanel
  7893.  
  7894. function initRightPanelHorizontalTab($, conf, w) {
  7895. } //
  7896.  
  7897. function initRightPanelVerticalTab($sidePanel) {
  7898. if (!conf.rightPanelJack) { return; }
  7899.  
  7900. var $tab = $([
  7901. '<ul id="sidePanelTabContainer">',
  7902. '<li class="tab comment" data-selection="w_comment" >コメント</li>',
  7903. '<li class="tab videoInfo" data-selection="w_videoInfo">動画情報</li>',
  7904. '<li class="tab ichiba" data-selection="w_ichiba" >ニコニコ市場</li>',
  7905. '<li class="tab review" data-selection="w_review" >レビュー</li>',
  7906. '</ul>'].join(''));
  7907.  
  7908. var $infoPanel = $('<div/>').attr({'id': 'rightVideoInfo', 'class': 'sideVideoInfo sidePanelInner'});
  7909. var $ichibaPanel = $('<div/>').attr({'id': 'rightIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7910. var $reviewPanel = $('<div/>').attr({'id': 'rightReviewPanel', 'class': 'sideReviewPanel sidePanelInner'});
  7911. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel).append($reviewPanel);
  7912.  
  7913. var
  7914. onTabSelect = function(e) {
  7915. e.preventDefault();
  7916. AnchorHoverPopup.hidePopup();
  7917. var selection = $(e.target).attr('data-selection');
  7918. if (typeof selection === 'string') {
  7919. if (WatchController.isSearchMode()) {
  7920. conf.setValue('lastRightTabInExplorer', selection);
  7921. } else {
  7922. conf.setValue('lastRightTab', selection);
  7923. }
  7924. changeTab(selection);
  7925. }
  7926. },
  7927. $videoReview = $('#videoReview'),
  7928. toggleReview = function(f) {
  7929. if (f) {
  7930. $reviewPanel.append($videoReview);
  7931. } else {
  7932. $('#playerBottomAd').after($videoReview);
  7933. }
  7934. },
  7935. changeTab = function(selection) {
  7936. if ($sidePanel.hasClass('w_review') && selection !== 'w_review') {
  7937. toggleReview(false);
  7938. }
  7939. $sidePanel.removeClass('w_videoInfo w_comment w_ichiba w_review').addClass(selection);
  7940. if (selection === 'w_ichiba') {
  7941. resetIchiba(false);
  7942. } else
  7943. if (selection === 'w_review') {
  7944. toggleReview(true);
  7945. } else
  7946. if (selection === 'w_comment') {
  7947. setTimeout(function() {
  7948. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  7949. }, 500);
  7950. }
  7951. return changeTab;
  7952. },
  7953. lastIchibaVideoId = '', resetIchiba = function(force) {
  7954. var videoId = watchInfoModel.id;
  7955. if (lastIchibaVideoId === videoId && !force) {
  7956. return;
  7957. }
  7958. lastIchibaVideoId = videoId;
  7959. resetSideIchibaPanel($ichibaPanel, true);
  7960. },
  7961. resetScroll = function() {
  7962. $(this).animate({scrollTop: 0}, 600);
  7963. };
  7964.  
  7965. $infoPanel .on('dblclick', resetScroll);
  7966. $ichibaPanel.on('dblclick', resetScroll);
  7967. $reviewPanel.on('dblclick', resetScroll);
  7968.  
  7969. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7970. changeTab(conf.lastRightTab);
  7971.  
  7972. EventDispatcher.addEventListener('onVideoExplorerOpening', function() {
  7973. changeTab('w_comment');
  7974. });
  7975. EventDispatcher.addEventListener('onVideoExplorerClosing', function() {
  7976. changeTab(conf.lastRightTab);
  7977. });
  7978.  
  7979. var onOuterResize = function() {
  7980. var $body = $('body'), $right = $('#playerTabWrapper');
  7981. if (WatchController.isSearchMode() || $body.hasClass('full_with_browser')) { return; }
  7982. var w = $('#external_nicoplayer').outerWidth(), margin = 124;
  7983. w += $right.is(':visible') ? $right.outerWidth() : 0;
  7984. $('#sidePanelTabContainer').toggleClass('left', (window.innerWidth - w - margin < 0));
  7985. };
  7986. EventDispatcher.addEventListener('onWindowResizeEnd', onOuterResize);
  7987. EventDispatcher.addEventListener('onPlayerAlignmentAreaResize', onOuterResize);
  7988.  
  7989. var refreshPanel = function(isFirst) {
  7990.  
  7991. window.setTimeout(function() {
  7992. $sidePanel
  7993. .toggleClass('reviewEmpty', $('#videoReview').find('.stream').length < 1)
  7994. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  7995. }, 2000);
  7996.  
  7997. if (isFirst) { return; }
  7998.  
  7999. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  8000. if ($ichibaPanel.is(':visible')) {
  8001. resetIchiba(true);
  8002. }
  8003. };
  8004. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  8005.  
  8006. refreshPanel();
  8007.  
  8008. } // end of initRightPanelVerticalTab
  8009.  
  8010.  
  8011. function assignVideoCountToDom($tpl, count) {
  8012. var addComma = WatchApp.ns.util.StringUtil.addComma;
  8013. $tpl
  8014. .find('.viewCount' ).text(addComma(count.viewCount )).end()
  8015. .find('.commentCount').text(addComma(count.commentCount)).end()
  8016. .find('.mylistCount' ).text(addComma(count.mylistCount ));
  8017. return $tpl;
  8018. } //
  8019.  
  8020. function sidePanelRefresh($sideInfoPanel, $ichibaPanel, $sidePanel, $template) {
  8021. var isFavorite = WatchController.isFavoriteOwner();
  8022. //var h = $sideInfoPanel.innerHeight() - 100;
  8023.  
  8024. $template.find('.videoTitle').html(watchInfoModel.title);
  8025.  
  8026. assignVideoCountToDom($template, watchInfoModel);
  8027. $template.find('.videoPostedAt').text($('.videoPostedAt:last').text());
  8028.  
  8029. var $videoDescription = $template.find('.videoDescription');
  8030.  
  8031. $videoDescription.find('.videoDescriptionInner').append(create$videoDescription(watchInfoModel.description));
  8032.  
  8033. var $userIconContainer = $template.find('.userIconContainer');
  8034. var $channelIconContainer = $template.find('.channelIconContainer');
  8035.  
  8036. var info = WatchController.getOwnerInfo();
  8037. if (info.type === 'channel') {
  8038. if (info.id && info.id !== '0') {
  8039. $channelIconContainer
  8040. .find('.channelIcon')
  8041. .attr({'src': info.icon}).end()
  8042. .find('.channelIconLink')
  8043. .attr({'href': info.page})
  8044. .on('click', Util.Closure.openVideoOwnersVideo()).end()
  8045. .find('.channelNameInner')
  8046. .text(info.name).end()
  8047. .find('.showOtherVideos')
  8048. .attr({'href': info.page})
  8049. .on('click', Util.Closure.openVideoOwnersVideo());
  8050. }
  8051. $userIconContainer.remove();
  8052. } else {
  8053. if (info.id && info.id !== '0') { // ユーザーが退会してたりすると情報が無いのでチェックしてから
  8054. $userIconContainer
  8055. .find('.userIcon')
  8056. .attr({'src': info.icon}).end()
  8057. .find('.userIconLink')
  8058. .attr({'href': info.page})
  8059. .on('click', Util.Closure.openVideoOwnersNicorepo()).end()
  8060. .find('.userNameInner')
  8061. .text(info.name).end()
  8062. .find('.showOtherVideos')
  8063. .attr({'href': info.page + '/video'})
  8064. .on('click', Util.Closure.openVideoOwnersVideo() ).end()
  8065. .toggleClass('isUserVideoPublic', info.isVideoPublic);
  8066. $channelIconContainer.remove();
  8067. } else {
  8068. $userIconContainer.remove();
  8069. $channelIconContainer.remove();
  8070. }
  8071. }
  8072.  
  8073. $sideInfoPanel.find('*').unbind();
  8074.  
  8075. $sidePanel
  8076. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  8077.  
  8078. $sideInfoPanel
  8079. .empty()
  8080. .scrollTop(0)
  8081. .toggleClass('isFavorite', isFavorite)
  8082. .toggleClass('isChannel', WatchController.isChannelVideo())
  8083. .append($template);
  8084.  
  8085. window.setTimeout(function() {
  8086. $sideInfoPanel.addClass('show');
  8087. $sideInfoPanel = $ichibaPanel = $sidePanel = $template =
  8088. $videoDescription = $userIconContainer =
  8089. $channelIconContainer = null;
  8090. }, 100);
  8091.  
  8092. } // end of sidePanelRefresh
  8093.  
  8094. /**
  8095. * 説明文中の動画リンク類を加工
  8096. */
  8097. function decorateVideoDescriptionLink($description) {
  8098. var watchLinks = [], watchIds = [];
  8099. var videoReg = /\/watch\/((sm|nm|so|)\d+)$/;
  8100. var seigaReg = /seiga\/im(\d+)/;
  8101. $description.find('a').each(function() {
  8102. var url = this.href, text, $this = $(this);
  8103. if (videoReg.test(url)) {
  8104. var watchId = RegExp.$1;
  8105. var $videoLinkContainer = $([
  8106. '<div class="videoLinkContainer"></div>',
  8107. ''].join(''));
  8108. var $nextButton = $([
  8109. '<div class="nextPlayButton" title="次に再生" onclick="WatchItLater.WatchController.insertVideoToPlaylist(\'', watchId, '\')">次に再生</div>',
  8110. ''].join(''));
  8111. $this.after($videoLinkContainer);
  8112. $videoLinkContainer.append($this).append($nextButton);
  8113.  
  8114. watchLinks.push({id: watchId, $target: $videoLinkContainer});
  8115. watchIds.push(watchId);
  8116. } else if (seigaReg.test(url)) {
  8117. var illustId = RegExp.$1;
  8118. var $thumbnail = $([
  8119. '<div class="descriptionThumbnail illust">',
  8120. '<img src="http://lohas.nicoseiga.jp/thumb/',
  8121. illustId,
  8122. 'z" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  8123. '</div>',
  8124. ''].join(''));
  8125. $this.after($thumbnail);
  8126. }
  8127. });
  8128.  
  8129. if (conf.enableDescriptionThumbnail && watchIds.length > 0) {
  8130. var ac = function(s) {
  8131. s = parseInt(s, 10);
  8132. s = s < 1 ? '-' : s;
  8133. return '<span class="count">' + WatchApp.ns.util.StringUtil.addComma(s) + '</span>';
  8134. };
  8135. var onWatchIdInfoReady = function(result) {
  8136. $(watchLinks).each(function(i, watchLink) {
  8137. var id = watchLink.id, $target = watchLink.$target;
  8138. if (result[id]) {
  8139. var info = result[id];
  8140. var $thmb = $([
  8141. '<div class="descriptionThumbnail video">',
  8142. '<img src="', info.thumbnail_url, '" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  8143. '<span class="uploadAt">', info.first_retrieve ,' 投稿</span>',
  8144. '<p>', info.title, ' (', info.length, ')</p>',
  8145. '<div class="counterContainer">',
  8146. '<span class="view">再生: ', ac(info.view_counter) ,'</span> ',
  8147. '<span class="comment">コメ: ', ac(info.num_res) ,'</span> ',
  8148. '<span class="mylist">マイ: ', ac(info.mylist_counter),'</span>',
  8149. '</div>',
  8150. '</div>'].join(''));
  8151. $target.after($thmb);
  8152. }
  8153. $target = watchLink = null;
  8154. });
  8155. watchIds = watchLinks = null;
  8156. };
  8157. var onWatchIdInfoFail = function() {
  8158. watchIds = watchLinks = null;
  8159. };
  8160. window.setTimeout(function() {
  8161. window.WatchItLater.loader.videoArrayAPILoader.load(watchIds).then(onWatchIdInfoReady, onWatchIdInfoFail);
  8162. }, 1000);
  8163. } else {
  8164. watchIds = watchLinks = null;
  8165. }
  8166. $description = null;
  8167. }
  8168.  
  8169. /**
  8170. * 動画説明文のクリックイベント類を割り当てる
  8171. */
  8172. function bindDescriptionEvents($description) {
  8173. $description.on('click', function(e) {
  8174. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8175.  
  8176. var elm = e.target;
  8177. if (elm.tagName !== 'A') { return; }
  8178. if (elm.className === 'otherSite') return;
  8179.  
  8180. var $elm = $(elm);
  8181.  
  8182. if (elm.textContent.indexOf('mylist/') === 0) {
  8183. e.preventDefault(); e.stopPropagation();
  8184. var mylistId = elm.textContent.split('/').reverse()[0];
  8185.  
  8186. WatchController.showMylist(mylistId);
  8187. } else
  8188. if (elm.className === 'seekTime') {
  8189. e.preventDefault(); e.stopPropagation();
  8190. var data = $elm.attr('data-seekTime').split(":"),
  8191. vpos = (data[0] * 60 + parseInt(data[1], 10)) * 1000;
  8192. WatchController.vpos(vpos);
  8193. }
  8194. });
  8195. $description.find('.watch').unbind('click');
  8196. $description = null;
  8197. }
  8198.  
  8199. function create$videoDescription(html) {
  8200. var linkmatch = /<a.*?<\/a>/, links = [], n;
  8201. html = html.split('<br />').join(' <br /> ');
  8202. while ((n = linkmatch.exec(html)) !== null) {
  8203. links.push(n);
  8204. html = html.replace(n, ' <!----> ');
  8205. }
  8206.  
  8207. // (htttp://example.com) -> ( htttp://example.com ) にして、 閉じカッコがリンクされるのを抑止
  8208. html = html.replace(/\((https?:\/\/[\x21-\x3b\x3d-\x7e]+)\)/gi, '( $1 )');
  8209. html = html.replace(/(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi, '<a href="$1" target="_blank" class="otherSite">$1</a>');
  8210. for (var i = 0, len = links.length; i < len; i++) {
  8211. html = html.replace(' <!----> ', links[i]);
  8212. }
  8213. html = html.split(' <br /> ').join('<br />');
  8214. var $description = $('<p class="videoDescription description">' + html + '</p>');
  8215.  
  8216. bindDescriptionEvents($description);
  8217. decorateVideoDescriptionLink($description);
  8218. return $description;
  8219. } //
  8220.  
  8221. function resetSideIchibaPanel($ichibaPanel, force) {
  8222.  
  8223. $ichibaPanel.scrollTop(0).find('*').unbind().empty();
  8224. var $inner = $('<div class="ichibaPanelInner" />');
  8225. var $header = $('<div class="ichibaPanelHeader"><p class="logo">ニコニコ市場出張所</p></div>');
  8226. $inner.append($header);
  8227.  
  8228.  
  8229. var items = [];
  8230. $('#ichibaMain').find('.ichiba_mainitem>div').each(function() {
  8231. var $item = $(this).clone().attr('id', null);
  8232. var $dl = $('<dl class="ichiba_mainitem" />').append($item);
  8233. $item.find('.thumbnail span').css({fontSize: ''});
  8234. // 誤クリックしやすいのでサムネはリンクを外す
  8235. $item.find('.thumbnail a img, .blomagaThumbnail, .blomagaText')
  8236. .parent().attr('href', null).attr('style', null).css({'text-decoration': 'none'});
  8237. $item.find('a').attr('onclick', null);
  8238. items.push($dl);
  8239. $inner.append($dl);
  8240. });
  8241. if (items.length > 0) {
  8242. for (var i = items.length -1; i >= 0; i--) {
  8243. $inner.find('#watch' + i + '_mq').attr('id', null).addClass('ichibaMarquee');
  8244. }
  8245. }
  8246. $inner.find('.nicoru').remove();
  8247.  
  8248. var $footer = $('<div class="ichibaPanelFooter"></div>');
  8249.  
  8250. var $addIchiba = $('<button class="addIchiba">商品を貼る</button>');
  8251. $addIchiba.click(function() {
  8252. AnchorHoverPopup.hidePopup();
  8253. ichibaSearch();
  8254. });
  8255. $footer.append($addIchiba);
  8256.  
  8257. var $reloadIchiba = $('<button class="reloadIchiba">リロード</button>');
  8258. $reloadIchiba.click(function() {
  8259. resetSideIchibaPanel($ichibaPanel, true);
  8260. $ichibaPanel = null;
  8261. });
  8262. $footer.append($reloadIchiba);
  8263.  
  8264. $inner.append($footer);
  8265. $inner.hide();
  8266. $ichibaPanel.append($inner);
  8267. $inner.fadeIn();
  8268. $inner = $header = $footer = $addIchiba = $reloadIchiba = null;
  8269. } //
  8270.  
  8271. function initHidariue() {
  8272. // 再生終了時に勝手にメニューが開閉するのを止める
  8273. window.WatchApp.ns.init.PlayerInitializer.videoendViewController.videoHeaderViewController = {openVideoMenu: function(){}, closeVideoMenu: function() {}};
  8274. var hidariue = null;
  8275. var resetHidariue = function() {
  8276. // var dt = new Date();
  8277. // if (dt.getMonth() < 1 && dt.getDate() <=1) {
  8278. // $('#videoMenuTopList').append('<li style="font-size:50%"> \ │ /<br>  /‾\   /‾‾‾‾‾‾‾‾‾<br>─( ゜ ∀ ゜ )< しんねんしんねん!<br>  \_/   \_________<br> / │ \</li>');
  8279. // }
  8280. if (!conf.hidariue) { return; }
  8281. if (!hidariue) {
  8282. $('#videoMenuTopList').append('<li class="hidariue" style="text-align: center;"><a href="http://userscripts.org/scripts/show/151269" target="_blank" style="color:black;"><img id="hidariue" style="border-radius: 8px; box-shadow: 1px 1px 2px #ccc;"></a><p id="nicodou" style="padding-left: 4px; display: inline-block"><a href="http://www.nicovideo.jp/video_top" target="_top"><img src="http://res.nimg.jp/img/base/head/logo/q.png" alt="ニコニコ動画:GINZA"></a></p></li>');
  8283. hidariue = $('#hidariue')[0];
  8284. }
  8285. hidariue.src = 'http://res.nimg.jp/img/base/head/icon/nico/' +
  8286. (1000 + Math.floor(Math.random() * 1000)).toString().substr(1) + '.gif';
  8287. };
  8288. EventDispatcher.addEventListener('onVideoInitialized', resetHidariue);
  8289. } //
  8290.  
  8291.  
  8292. var VideoExplorerToggleMenu = function(title, titleLink) {
  8293. this.initializeBaseDom(title, titleLink);
  8294. };
  8295. WatchApp.mixin(VideoExplorerToggleMenu.prototype, {
  8296. initializeBaseDom: function(title, titleLink) {
  8297.  
  8298. this._$toggle = $('<li style="display:none;" class="toggleVideoExplorerMenu watchItLaterMenu"></li>');
  8299. this._$menu = $('<li class="slideMenu"><ul></ul></li>');
  8300. this._$list = this._$menu.find('ul');
  8301.  
  8302. var $a = $('<a/>').text(title).attr('href', titleLink);
  8303. this._$toggle.append($a);
  8304.  
  8305. this._initializeToggleEvent();
  8306. this._initializeItemEvent();
  8307. },
  8308. _initializeToggleEvent: function() {
  8309. this._$toggle.on('click', $.proxy(function(e) {
  8310. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8311. e.preventDefault(); e.stopPropagation();
  8312.  
  8313. var isVisible = this._$menu.hasClass('open');
  8314. this._$toggle.addClass('opening');
  8315. this._$menu.toggleClass('open', !isVisible);
  8316.  
  8317. window.setTimeout($.proxy(function() {
  8318. this._$toggle.toggleClass('open', !isVisible);
  8319. this._$toggle.removeClass('opening');
  8320. }, this), 500);
  8321. }, this));
  8322. },
  8323. _initializeItemEvent: function() {
  8324. this._$menu.on('click', function(e) {
  8325. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8326.  
  8327. var elm = e.target;
  8328. if (elm.tagName !== 'A') { return; }
  8329. var $elm = $(elm);
  8330. var type = $elm.attr('data-menu-type');
  8331.  
  8332. if (type === 'mylist') {
  8333. e.preventDefault(); e.stopPropagation();
  8334. var mylistId = $elm.attr('data-mylist-id');
  8335. WatchController.showMylist(mylistId);
  8336. } else
  8337. if (type === 'deflist') {
  8338. e.preventDefault(); e.stopPropagation();
  8339. WatchController.showDeflist();
  8340. } else
  8341. if (type === 'tag') {
  8342. e.preventDefault(); e.stopPropagation();
  8343. var tag = $elm.attr('data-search-tag');
  8344. WatchController.nicoSearch(tag, 'tag');
  8345. }
  8346. });
  8347. },
  8348. attach: function() {
  8349. $('.videoExplorerMenu').find('ul:first li:first').after(this._$menu).after(this._$toggle);
  8350. },
  8351. detach: function() {
  8352. this._$toggle.detach();
  8353. this._$menu.detach();
  8354. },
  8355. add$listItem: function($item) {
  8356. this._$list.append($item);
  8357. },
  8358. addItem: function(name, title, attr) {
  8359. var
  8360. $a = $('<a/>')
  8361. .attr(attr)
  8362. .text(title),
  8363. $li = $('<li/>').addClass(iconType);
  8364. $li.append($a);
  8365. this._$list.append($li);
  8366. return $a;
  8367. },
  8368. addMylistItem: function(name, mylistId, title, iconType) {
  8369. var
  8370. $a = $('<a/>')
  8371. .text(name)
  8372. .attr({
  8373. href: '/mylist/' + mylistId,
  8374. title: title,
  8375. 'data-menu-type': 'mylist',
  8376. 'data-mylist-id': mylistId,
  8377. }),
  8378. $li = $('<li/>').addClass(iconType || '');
  8379. $li.append($a);
  8380. this._$list.append($li);
  8381. return $a;
  8382. },
  8383. show: function() {
  8384. this._$toggle.fadeIn(500);
  8385. }
  8386. });
  8387.  
  8388. WatchItLater.videoExplorerMenu = {};
  8389. WatchItLater.videoExplorerMenu.favMylists = (function() {
  8390. var toggleMenu;
  8391.  
  8392. var initialize = function() {
  8393. initialize = window._.noop;
  8394. console.log('%cinitialize WatchItLater.videoExplorerMenu.favMylists', 'background: lightgreen;');
  8395.  
  8396. toggleMenu = new VideoExplorerToggleMenu('お気に入りマイリスト', '/my/fav/mylist');
  8397.  
  8398. window.WatchItLater.loader.favMylists.load(function(mylists) {
  8399. if (mylists.length < 1) {
  8400. return;
  8401. }
  8402. for (var i = 0, len = mylists.length; i < len; i++) {
  8403. var mylist = mylists[i], lastVideo = mylist.lastVideo, $li = $('<li/>'),
  8404. title = [
  8405. '/mylist/', mylist.id, '\n',
  8406. mylist.description, '\n',
  8407. '最新動画: ', lastVideo.title, '\n',
  8408. '投稿日時: ', lastVideo.postedAt, '\n',
  8409. ''].join('');
  8410. toggleMenu.addMylistItem(
  8411. mylist.name,
  8412. mylist.id,
  8413. title,
  8414. mylist.iconType
  8415. );
  8416. }
  8417. toggleMenu.show();
  8418. });
  8419. };
  8420.  
  8421. return {
  8422. attach: function() {
  8423. initialize();
  8424. toggleMenu.attach();
  8425. },
  8426. detach: function() {
  8427. if (!toggleMenu) {
  8428. return;
  8429. }
  8430. console.log(toggleMenu);
  8431. toggleMenu.detach();
  8432. }
  8433. };
  8434. })();
  8435.  
  8436. WatchItLater.videoExplorerMenu.favTags = (function() {
  8437. var toggleMenu;
  8438.  
  8439. var initialize = function() {
  8440. initialize = window._.noop;
  8441. console.log('%cinitialize WatchItLater.videoExplorerMenu.favTags', 'background: lightgreen;');
  8442.  
  8443. toggleMenu = new VideoExplorerToggleMenu('お気に入りタグ', '/my/fav/tag');
  8444.  
  8445. window.WatchItLater.loader.favTags.load(function(tags) {
  8446. if (tags.length < 1) {
  8447. $toggle.remove();
  8448. return;
  8449. }
  8450. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  8451. for (var i = 0, len = tags.length; i < len; i++) {
  8452. var tag = tags[i], $li = $('<li/>'),
  8453. $a = $('<a/>')
  8454. .attr({
  8455. href: '/tag/' + encodeURIComponent(tag.name + ' ' + conf.defaultSearchOption) + sortOrder,
  8456. 'data-menu-type': 'tag',
  8457. 'data-search-tag': tag.name
  8458. })
  8459. .text(tag.name);
  8460. toggleMenu.add$listItem($li.append($a));
  8461. }
  8462. toggleMenu.show();
  8463. });
  8464. };
  8465.  
  8466. return {
  8467. attach: function() {
  8468. initialize();
  8469. toggleMenu.attach();
  8470. },
  8471. detach: function() {
  8472. if (!toggleMenu) {
  8473. return;
  8474. }
  8475. toggleMenu.detach();
  8476. }
  8477. };
  8478. })();
  8479.  
  8480.  
  8481. WatchItLater.videoExplorerMenu.myShortcuts = (function() {
  8482. var toggleMenu;
  8483.  
  8484. var initialize = function() {
  8485. initialize = window._.noop;
  8486. console.log('%cinitialize WatchItLater.videoExplorerMenu.myShortcuts', 'background: lightgreen;');
  8487.  
  8488. toggleMenu = new VideoExplorerToggleMenu('マイショートカット', '/my/mylist');
  8489. toggleMenu.attach();
  8490.  
  8491. window.WatchItLater.mylist.loadMylistList(function(mylistList) {
  8492. toggleMenu.add$listItem(
  8493. $('<li/>').append(
  8494. $('<a/>')
  8495. .addClass('defMylist')
  8496. .attr({href: '/my/mylist', 'data-menu-type': 'deflist'})
  8497. .text('とりあえずマイリスト')
  8498. ));
  8499. var items = [
  8500. {id: -1, href: '/my/history', name: '視聴履歴'},
  8501. {id: -2, href: '/recommendations', name: 'あなたにオススメの動画'},
  8502. {id: NicorepoVideo.REPO_ALL, href: '/my/top/all', name: '【ニコレポ】すべての動画'},
  8503. {id: NicorepoVideo.REPO_USER, href: '/my/top/user', name: '【ニコレポ】お気に入りユーザー'},
  8504. {id: NicorepoVideo.REPO_CHCOM, href: '/my/top/chcom', name: '【ニコレポ】チャンネル&コミュニティ'},
  8505. {id: NicorepoVideo.REPO_MYLIST, href: '/my/top/mylist', name: '【ニコレポ】お気に入りマイリスト'}
  8506. ];
  8507. for (var v in items) {
  8508. var item = items[v];
  8509. toggleMenu
  8510. .addMylistItem(item.name, item.id)
  8511. .addClass('defMylist')
  8512. .attr({
  8513. href: item.href
  8514. });
  8515. }
  8516. for (var i = 0, len = mylistList.length; i < len; i++) {
  8517. var mylist = mylistList[i];
  8518. toggleMenu.addMylistItem(mylist.name, mylist.id, '', 'folder' + mylist.icon_id);
  8519. }
  8520.  
  8521. toggleMenu.show();
  8522. });
  8523. };
  8524.  
  8525. return {
  8526. attach: function() {
  8527. initialize();
  8528. toggleMenu.attach();
  8529. },
  8530. detach: function() {
  8531. if (!toggleMenu) {
  8532. return;
  8533. }
  8534. toggleMenu.detach();
  8535. }
  8536. };
  8537. })();
  8538.  
  8539. WatchItLater.videoExplorerMenu.videoRanking = (function() {
  8540. var toggleMenu;
  8541.  
  8542. var VideoRankingToggleMenu = function(title, titleLink) {
  8543. WatchApp.extend(this, VideoRankingToggleMenu, VideoExplorerToggleMenu, [title, titleLink]);
  8544.  
  8545. this._$menu.addClass('videoRankingList');
  8546. this.initializeCategoryToggleEvents();
  8547. };
  8548. WatchApp.mixin(VideoRankingToggleMenu.prototype, {
  8549. initializeCategoryToggleEvents: function() {
  8550. this._$menu.on('click', '.rankingCategoryToggle', function(e) {
  8551. e.preventDefault(); e.stopPropagation();
  8552.  
  8553. var $target = $(e.currentTarget), category = $target.attr('data-category');
  8554. var $popup = $target.closest('.slideMenu');
  8555. var isClose = $popup.find('li.' + category).toggleClass('categoryClose').hasClass('categoryClose');
  8556.  
  8557. conf.setValue('rankingCategory_' + category + '_Close', isClose);
  8558. });
  8559. },
  8560. addRankingItem: function($, genre, id, name, category, term) {
  8561. var $a =
  8562. $('<a/>')
  8563. .attr({
  8564. href: '/ranking/fav/' + term + '/' + genre,
  8565. 'data-menu-type': 'mylist',
  8566. 'data-mylist-id': id
  8567. })
  8568. .text(name)
  8569. .addClass(genre);
  8570. var $li = $('<li/>');
  8571.  
  8572. if (genre === category) {
  8573. $li.addClass('isCategory'); // nameと同じならカテゴリランキング、違うならジャンルランキング
  8574. if (genre !== 'all' && genre !== 'g_politics' && genre !== 'r18') {
  8575. var $button = $([
  8576. '<button class="rankingCategoryToggle">',
  8577. '<span class="open" title="サブカテゴリを開く">▼</span>',
  8578. '<span class="close" title="サブカテゴリを閉じる">▲</span>',
  8579. '</button>'
  8580. ].join(''));
  8581. $button.attr('data-category', category);
  8582. $li.append($button);
  8583. }
  8584. }
  8585. var isClose = conf.getValue('rankingCategory_' + category + '_Close');
  8586. $li
  8587. .toggleClass('categoryClose', isClose)
  8588. .attr({'data-genre': genre, 'data-category': category})
  8589. .addClass(category).addClass(genre)
  8590. .append($a);
  8591.  
  8592. this._$list.append($li);
  8593. return $a;
  8594. }
  8595. });
  8596.  
  8597.  
  8598. var initialize = function() {
  8599. initialize = window._.noop;
  8600. console.log('%cinitialize WatchItLater.videoExplorerMenu.videoRanking', 'background: lightgreen;');
  8601.  
  8602. toggleMenu = new VideoRankingToggleMenu('動画ランキング', '/ranking');
  8603. toggleMenu.attach();
  8604.  
  8605. // TODO: マジックナンバーを
  8606. toggleMenu.addRankingItem($, 'all', -100, 'カテゴリ合算(毎時)', 'all', 'hourly');
  8607. toggleMenu.addRankingItem($, 'all', -1100, 'カテゴリ合算(24時間)', 'all', 'daily');
  8608. // toggleMenu.addRankingItem($, 'all', -4100, 'カテゴリ合算(合計)', 'all', 'total');
  8609.  
  8610. var genreId = VideoRanking.getGenreId();
  8611. for (var genre in genreId) {
  8612. if (genre === 'all') { continue; }
  8613. var id = genreId[genre], name = VideoRanking.getGenreName(genre), category = VideoRanking.getCategory(id);
  8614. toggleMenu.addRankingItem($, genre, id, name, category, 'hourly');
  8615. }
  8616.  
  8617. window.setTimeout(function() { toggleMenu.show(); }, 100);
  8618. };
  8619.  
  8620. return {
  8621. attach: function() {
  8622. initialize();
  8623. toggleMenu.attach();
  8624. },
  8625. detach: function() {
  8626. if (!toggleMenu) {
  8627. return;
  8628. }
  8629. toggleMenu.detach();
  8630. }
  8631. };
  8632. })();
  8633.  
  8634.  
  8635. var WatchingVideoView = function() { this.initialize.apply(this, arguments); };
  8636. WatchingVideoView.prototype = {
  8637. _params: null,
  8638. _$view: null,
  8639. _watchInfoModel: null,
  8640. _type: null,
  8641. initialize: function(params) {
  8642. this._content = params.content;
  8643. this._watchInfoModel = params.watchInfoModel;
  8644. this._$view = params.$view;
  8645. this._mylistController = params.mylistController;
  8646. this._type = params.type;
  8647.  
  8648. this._$title = this._$view.find('.title');
  8649. this._$thumb = this._$view.find('.thumbnail');
  8650. this._$add = this._$view.find('.add');
  8651. this._$remove = this._$view.find('.remove');
  8652.  
  8653. this._$add .on('click', $.proxy(this._onAddClick, this));
  8654. this._$remove.on('click', $.proxy(this._onRemoveClick, this));
  8655.  
  8656. EventDispatcher.addEventListener('onWatchInfoReset', $.proxy(this.onVideoChange, this));
  8657. },
  8658. getView: function() {
  8659. return this._$view;
  8660. },
  8661. detach: function() {
  8662. this._$view.detach();
  8663. },
  8664. update: function() {
  8665. $('.videoExplorerBody').toggleClass('containsWatchingVideo', this._content.containsWatchId());
  8666. },
  8667. onVideoChange: function() {
  8668. this._$title.html(this._watchInfoModel.title);
  8669. this._$thumb
  8670. .attr('src', this._watchInfoModel.thumbnail)
  8671. .off('click').on('click', Util.Closure.showLargeThumbnail(this._watchInfoModel.thumbnail));
  8672. if (this._content.isActive()) {
  8673. this.update();
  8674. }
  8675. },
  8676. _setIsUpdating: function() {
  8677. this._$view.addClass('updating');
  8678. setTimeout($.proxy(this._clearIsUpdating, this), 3000);
  8679. },
  8680. _clearIsUpdating: function() {
  8681. this._$view.removeClass('updating');
  8682. },
  8683. _getIsUpdating: function() {
  8684. return this._$view.hasClass('updating');
  8685. },
  8686. _onAddClick: function() {
  8687. var watchId = WatchController.getWatchId();
  8688. this._setIsUpdating();
  8689. if (this._type === 'deflist') {
  8690. this._mylistController.addDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8691. } else {
  8692. var mylistId = this._content.getMylistId();
  8693. this._mylistController.addMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8694. }
  8695. },
  8696. _onRemoveClick: function() {
  8697. var watchId = WatchController.getWatchId();
  8698. this._setIsUpdating();
  8699. if (this._type === 'deflist') {
  8700. this._mylistController.deleteDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8701. } else {
  8702. var mylistId = this._content.getMylistId();
  8703. this._mylistController.deleteMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8704. }
  8705. },
  8706. _onMylistUpdate: function(status, result) {
  8707. if (status === 'ok') {
  8708. if (this._type === 'deflist') {
  8709. WatchController.clearDeflistCache();
  8710. }
  8711. } else {
  8712. Popup.alert('更新に失敗: ' + result.error.description);
  8713. }
  8714. this._content.setFilter(null);
  8715. setTimeout(
  8716. $.proxy(function() {
  8717. //this._content.changeState({page: 1});
  8718. this.contentRefresh();
  8719. this._clearIsUpdating();
  8720. }, this), 500);
  8721. },
  8722. contentRefresh: function() {
  8723. var params = this._content.getParams();
  8724. params.page = 1;
  8725. this._content.changeState(params);
  8726. this._content.refresh({page: 1});
  8727. }
  8728. }; // end WatchingVideoView.prototype
  8729.  
  8730.  
  8731. var GrepOptionView = function() { this.initialize.apply(this, arguments); };
  8732. GrepOptionView.prototype = {
  8733. _params: null,
  8734. _$view: null,
  8735. initialize: function(params) {
  8736. this._content = params.content;
  8737. this._$view = params.$view;
  8738. this._$form = this._$view.find('form');
  8739. this._$input = this._$view.find('.grepInput').attr('list', params.listName);
  8740. this._$community = this._$view.find('.community');
  8741. this._$alive = this._$view.find('.alive');
  8742. this._$duration = this._$view.find('.duration');
  8743. this._$invert = this._$view.find('.invert');
  8744. this._$checkboxes = this._$view.find('input[type=checkbox]');
  8745. this._$selectors = this._$view.find('select');
  8746.  
  8747. this._not = false;
  8748.  
  8749. this._$view.toggleClass('debug', !!conf.debugMode);
  8750.  
  8751. this._$list = $('<datalist />').attr('id', params.listName);
  8752. $('body').append(this._$list);
  8753.  
  8754. this._$form.on('submit', $.proxy(this._onFormSubmit, this));
  8755. this._$checkboxes.on('click', $.proxy(this._onCheckClick, this));
  8756. this._$selectors
  8757. .on('click', $.proxy(this._onSelectorClick, this))
  8758. .on('change', $.proxy(this._onSelectorChange, this));
  8759.  
  8760. this._$input.on('click', $.proxy(function(e) {
  8761. e.stopPropagation();
  8762. }, this)) .on('focus', $.proxy(function(e) {
  8763. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  8764. }, this));
  8765.  
  8766. this._$view .on('click', $.proxy(function(e) {
  8767. this._$input.focus();
  8768. }, this));
  8769. },
  8770. getView: function() {
  8771. return this._$view;
  8772. },
  8773. detach: function() {
  8774. this._$view.detach();
  8775. },
  8776. clear: function() {
  8777. this._$input.val('');
  8778. this._$checkboxes.prop('checked', false);
  8779. this._$view.removeClass('active');
  8780. this._$selectors.val('');
  8781. },
  8782. update: function() {
  8783. var list = this._content.getRawList();
  8784. var tmp = [];
  8785. for (var i = list.length -1; i >= 0; i--) {
  8786. tmp.push('<option>');
  8787. tmp.push(list[i].title); // 既にエスケープされてる
  8788. tmp.push('</option>');
  8789. }
  8790. this._$list.html(tmp.join(''));
  8791.  
  8792. if (this._getWord().length > 0) {
  8793. window.setTimeout($.proxy(function() { this._$input.focus(); }, this), 100);
  8794. }
  8795. },
  8796. _isActive: function() {
  8797. return (this._$input.val().length > 0 ||
  8798. !!this._$community.prop('checked') ||
  8799. !!this._$duration.val() ||
  8800. !!this._$alive .prop('checked'));
  8801. },
  8802. _getWord: function() {
  8803. return $.trim(this._$input.val());
  8804. },
  8805. _onCheckClick: function(e) {
  8806. e.stopPropagation();
  8807. this._submit();
  8808. },
  8809. _onSelectorClick: function(e) {
  8810. e.stopPropagation();
  8811. },
  8812. _onSelectorChange: function(e) {
  8813. e.stopPropagation();
  8814. this._submit();
  8815. },
  8816. _onFormSubmit: function(e) {
  8817. e.preventDefault();
  8818. e.stopPropagation();
  8819. this._submit();
  8820. },
  8821. _submit: function() {
  8822. var isActive = this._isActive();
  8823. this._$view.toggleClass('active', isActive);
  8824.  
  8825. if (isActive) {
  8826. this._content.setFilter(this._getFilter());
  8827. } else {
  8828. this._content.setFilter(null);
  8829. }
  8830.  
  8831. this.contentRefresh();
  8832. },
  8833. contentRefresh: function() {
  8834. var params = this._content.getParams();
  8835. params.page = 1;
  8836. this._content.changeState(params);
  8837. this._content.refresh({page: 1});
  8838. },
  8839. _getFilter: function() {
  8840. var to_h = function(str) {
  8841. return str.replace(/[A-Za-z0-9]/g, function(s) {
  8842. return String.fromCharCode(s.charCodeAt(0) - 65248);
  8843. }).toLowerCase();
  8844. };
  8845. var word = to_h(this._getWord());
  8846. var communityReg = /^so|^\d+$/;
  8847. var wordFilter = word.length > 0;
  8848. var durationVal = parseInt(this._$duration.val(), 10);
  8849. var communityFilter = !!this._$community.prop('checked');
  8850. var aliveFilter = !!this._$alive.prop('checked');
  8851. var durationFilter = !isNaN(durationVal);
  8852. var isInvert = !!this._$invert.prop('checked');
  8853.  
  8854.  
  8855. var isCommunity = function(item) {
  8856. return communityReg.test(item.id);
  8857. };
  8858. var isMatch = function(item) {
  8859. var title = item.title;
  8860. var desc = item.description_full || item.description_short || '';
  8861. var mc = item.mylist_comment || '';
  8862. var text = to_h([title, desc, mc].join('\n'));
  8863.  
  8864. return text.indexOf(word) >= 0;
  8865. };
  8866. var isAlive = function(item) {
  8867. var thumbnail = item.thumbnail_url || '';
  8868. if (thumbnail.indexOf('http://res.nimg.jp/img/common/video_deleted') < 0) {
  8869. return true;
  8870. }
  8871. return false;
  8872. };
  8873.  
  8874. var durationMatch = function(item) {
  8875. var itemDuration;
  8876. if (item.length_seconds) {
  8877. itemDuration = item.length_seconds;
  8878. } else {
  8879. var tmp = item.length.split(':');
  8880. itemDuration = parseInt(tmp[0], 10) * 60 + parseInt(tmp[1], 10);
  8881. }
  8882.  
  8883. if (durationVal < 0) {
  8884. return itemDuration <= Math.abs(durationVal);
  8885. } else {
  8886. return itemDuration >= durationVal;
  8887. }
  8888. };
  8889.  
  8890. var grepFilter = function(item) {
  8891. var result = true, i = func.length, f;
  8892. while (--i >= 0 && (result || isInvert)) {
  8893. f = func[i];
  8894. result &= f(item);
  8895. }
  8896. return isInvert ? !result : result;
  8897. };
  8898.  
  8899. var func = [], f;
  8900. if (wordFilter) { func.push(isMatch); }
  8901. if (communityFilter) { func.push(isCommunity); }
  8902. if (aliveFilter) { func.push(isAlive); }
  8903. if (durationFilter) { func.push(durationMatch); }
  8904.  
  8905. if (func.length < 1) { return null; }
  8906.  
  8907. return grepFilter;
  8908. }
  8909. }; // end GrepOptionView.prototype
  8910.  
  8911. function initMylistContent($, conf, w) {
  8912. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  8913. var ContentView = WatchApp.ns.components.videoexplorer.view.content.MylistVideoContentView;
  8914. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  8915. var explorer = vec.getVideoExplorer();
  8916. var myUserId = WatchController.getMyUserId();
  8917. var content = explorer.getContentList().getContent(ContentType.MYLIST_VIDEO);
  8918. var loader = content._mylistVideoAPILoader;
  8919. var pager = content._pager;
  8920. var watchingVideoView = new WatchingVideoView({
  8921. content: content,
  8922. watchInfoModel: watchInfoModel,
  8923. mylistController: Mylist,
  8924. type: 'mylist',
  8925. $view: $([
  8926. '<div class="watchingVideo">',
  8927. '<img class="thumbnail">',
  8928. '<p class="title"></p>',
  8929. '<span class="contains" >この動画はリストに登録されています</span>',
  8930. '<span class="not_contains">この動画はリストにありません</span>',
  8931. '<span class="edit">',
  8932. '<button class="add" >登録</button>',
  8933. '<button class="remove">外す</button>',
  8934. '</span>',
  8935. '</div>',
  8936. ''].join(''))
  8937. });
  8938.  
  8939. var grepOptionView = new GrepOptionView({
  8940. content: content,
  8941. listName: 'suggestMylistTitle',
  8942. $view: $([
  8943. '<div class="grepOption">',
  8944. '<form>',
  8945. '<input type="search" class="grepInput" autocomplete="on" placeholder="タイトル・説明文で絞り込む(G)" accesskey="g">',
  8946. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  8947. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  8948. '<label class="durationFilter filter">',
  8949. '動画時間<select class="duration">',
  8950. '<option value="">指定無し</option>',
  8951. '<option value="-180">3分以内</option>',
  8952. '<option value="180" >3分以上</option>',
  8953. '<option value="-300">5分以内</option>',
  8954. '<option value="300" >5分以上</option>',
  8955. '<option value="-600">10分以内</option>',
  8956. '<option value="600" >10分以上</option>',
  8957. '<option value="-1800">30分以内</option>',
  8958. '<option value="1800" >30分以上</option>',
  8959. '</select>',
  8960. '</label>',
  8961. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  8962. '</form>',
  8963. '</div>',
  8964. ].join(''))
  8965. });
  8966.  
  8967.  
  8968. pager._pageItemCount = conf.searchPageItemCount;
  8969. pager._displayPageCount = 5;
  8970. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  8971. pager._pageItemCount = v;
  8972. });
  8973.  
  8974. content._isOwnerNicorepo = false;
  8975. content._isRanking = false;
  8976. content.getIsMine = $.proxy(function() {
  8977. return parseInt(this.getUserId(), 10) === parseInt(myUserId, 10) && parseInt(this.getMylistId(), 10) > 0;
  8978. }, content);
  8979. content.getIsDummy = $.proxy(function() {
  8980. var id = this.getMylistId();
  8981. return parseInt(id, 10) <= 0 || id.toString().indexOf('repo') === 0;
  8982. }, content);
  8983. content.getIsOwnerNicorepo = $.proxy(function() { return this._isOwnerNicorepo; }, content);
  8984. content.getIsRanking = $.proxy(function() { return this._isRanking; }, content);
  8985.  
  8986. // grep対応のための拡張
  8987. content._rawList = [];
  8988. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  8989. content._filter = null;
  8990. content.setFilter = $.proxy(function(filter) {
  8991. this._filter = filter;
  8992. }, content);
  8993. content.getFilter = $.proxy(function() { return this._filter; }, content);
  8994.  
  8995.  
  8996. content.clear_org = content.clear;
  8997. content.clear = $.proxy(function() {
  8998. this.setFilter(null);
  8999. this.clear_org();
  9000. grepOptionView.clear();
  9001. }, content);
  9002.  
  9003. content.getNickname = $.proxy(function() {
  9004. if (this._nickname && this._nickname.length > 0) {
  9005. return this._nickname;
  9006. }
  9007. return 'no-name';
  9008. }, content);
  9009.  
  9010. content.onLoad_org = content.onLoad;
  9011. content.onLoad = $.proxy(function(err, result) {
  9012. this._isOwnerNicorepo = result.isOwnerNicorepo;
  9013. this._isRanking = result.isRanking;
  9014.  
  9015. var filter = this.getFilter();
  9016. if (err === null && result.list && result.list.length) {
  9017. if (!result.rawList) result.rawList = result.list.concat();
  9018. if (filter) {
  9019. var list = [];
  9020.  
  9021. for (var i = result.rawList.length - 1; i >= 0; i--) {
  9022. var item = result.rawList[i];
  9023. if (item.title && filter(item)) {
  9024. list.unshift(item);
  9025. }
  9026. }
  9027. result.list = list;
  9028. } else {
  9029. result.list = result.rawList.concat();
  9030. }
  9031. } else
  9032. if (result.rawList) {
  9033. result.list = result.rawList.concat();
  9034. }
  9035.  
  9036. this._rawList = result.rawList || [];
  9037. this.onLoad_org(err, result);
  9038. if (this.getIsMine()) {
  9039. EventDispatcher.dispatch('onMyMylistLoad', this.getMylistId(), {
  9040. name: this.getName(),
  9041. items: result.rawList
  9042. });
  9043. }
  9044. }, content);
  9045.  
  9046. content.containsWatchId = $.proxy(function(watchId) {
  9047. var list = this.getRawList();
  9048. if (!watchId) { watchId = WatchController.getWatchId(); }
  9049.  
  9050. for (var i = list.length - 1; i >= 0; i--) {
  9051. if (list[i].id === watchId) { return true; }
  9052. }
  9053. return false;
  9054. }, content);
  9055.  
  9056. loader.load_org = loader.load;
  9057. loader.load = $.proxy(function(params, callback) {
  9058. var isOwnerNicorepo = false, isRanking = false;
  9059. var id = params.id;
  9060. if (typeof id === 'string' && id.indexOf('repo-owner-') === 0) {
  9061. id = NicorepoVideo.REPO_OWNER;
  9062. }
  9063.  
  9064. var applyFilter = function(err, result) {
  9065. result.isOwnerNicorepo = isOwnerNicorepo;
  9066. result.isRanking = isRanking;
  9067. callback(err, result);
  9068. };
  9069. if (id < 0) {
  9070. var timeoutTimer = null;
  9071. var onload = function(result) {
  9072. window.clearTimeout(timeoutTimer);
  9073. // 投稿者ニコレポが0件で、投稿動画一覧を公開していたらそっちを開くタイマーをセット
  9074. if (result.list.length < 1 &&
  9075. parseInt(id, 10) === NicorepoVideo.REPO_OWNER &&
  9076. WatchController.isVideoPublic()) {
  9077. window.setTimeout(function() {
  9078. WatchController.openVideoOwnersVideo();
  9079. }, 500);
  9080. }
  9081. applyFilter(null, result);
  9082. };
  9083. var onerror = function(result) {
  9084. window.clearTimeout(timeoutTimer);
  9085. callback('error', result);
  9086. };
  9087. timeoutTimer = window.setTimeout(function() {
  9088. onload = onerror = window._.noop;
  9089. onerror({message: '通信がタイムアウトしました:' + id, status: 'fail'});
  9090. }, 30 * 1000);
  9091.  
  9092. // マイリストIDに負の数字(通常ないはず)が来たら乗っ取るサイン
  9093. // そもそもマイリストIDはstringのようなので数字にこだわる必要なかったかも
  9094. //
  9095. try {
  9096. if (typeof VideoRanking.getGenreName(id) === 'string') {
  9097. isRanking = true;
  9098. VideoRanking.load(null, {id: id}).then(onload, onerror);
  9099. return;
  9100. }
  9101. // TODO: マジックナンバーを
  9102. switch (parseInt(id, 10)) {
  9103. case -1:
  9104. VideoWatchHistory.load(onload);
  9105. break;
  9106. case -2:
  9107. VideoRecommendations.load(onload);
  9108. break;
  9109. case -3:
  9110. ChannelVideoList.loadOwnerVideo(null).then(onload, onerror);
  9111. break;
  9112. case NicorepoVideo.REPO_ALL:
  9113. NicorepoVideo.loadAll() .then(onload, onerror);
  9114. break;
  9115. case NicorepoVideo.REPO_USER:
  9116. NicorepoVideo.loadUser() .then(onload, onerror);
  9117. break;
  9118. case NicorepoVideo.REPO_CHCOM:
  9119. NicorepoVideo.loadChCom() .then(onload, onerror);
  9120. break;
  9121. case NicorepoVideo.REPO_MYLIST:
  9122. NicorepoVideo.loadMylist().then(onload, onerror);
  9123. break;
  9124. case NicorepoVideo.REPO_OWNER:
  9125. isOwnerNicorepo = true;
  9126. NicorepoVideo.loadOwner() .then(onload, onerror);
  9127. break;
  9128. default:
  9129. throw {message: '未定義のIDです:' + id, status: 'fail'};
  9130. }
  9131. } catch(e) {
  9132. // TODO: ここのエラーをちゃんと投げる
  9133. if (e.message && e.status) {
  9134. onerror({
  9135. status: e.status,
  9136. message: e.message
  9137. });
  9138. } else {
  9139. console.log(e); console.trace();
  9140. onerror({message: 'エラーが発生しました:' + id, status: 'fail'});
  9141. }
  9142. }
  9143. } else {
  9144. this.load_org(params, applyFilter);
  9145. }
  9146. }, loader);
  9147.  
  9148.  
  9149. var __css__ = Util.here(function() {/*
  9150. #videoExplorer .watchingVideo { display: none; }
  9151. #videoExplorer .watchingVideo .title { display: none; }
  9152. #videoExplorer .watchingVideo.updating * {
  9153. cursor: wait; opacity: 0.5;
  9154. }
  9155. #videoExplorer .watchingVideo button {
  9156. padding: 2px 12px; margin: 12px 24px;
  9157. }
  9158. #videoExplorer .isMine .watchingVideo {
  9159. display: block; background: #f4f4f4; border: 1px solid #ccc;
  9160. margin: auto; width: 500px; min-height: 48px; padding: 16px;
  9161. }
  9162.  
  9163. #videoExplorer .watchingVideo .thumbnail {
  9164. float: left; width: 72px; margin-right: 24px; cursor: pointer;
  9165. }
  9166.  
  9167.  
  9168. #videoExplorer .watchingVideo .title {
  9169. font-weight: bolder;
  9170. }
  9171. #videoExplorer .watchingVideo .title:before { content: ''; }
  9172. #videoExplorer .watchingVideo .title:after { content: ' '; }
  9173.  
  9174. #videoExplorer .watchingVideo .contains { display: none; }
  9175. #videoExplorer .containsWatchingVideo .watchingVideo .contains { display: inline; }
  9176. #videoExplorer .watchingVideo .not_contains { display: inline; }
  9177. #videoExplorer .containsWatchingVideo .watchingVideo .not_contains { display: none; }
  9178.  
  9179. #videoExplorer .watchingVideo .edit { display: none; }
  9180. #videoExplorer .isMine .watchingVideo .edit { display: inline-block; }
  9181.  
  9182. #videoExplorer .watchingVideo .add { display: inline-block; }
  9183. #videoExplorer .containsWatchingVideo .watchingVideo .add { display: none; }
  9184.  
  9185. #videoExplorer .watchingVideo .remove{ display: none; }
  9186. #videoExplorer .containsWatchingVideo .watchingVideo .remove{ display: inline-block; }
  9187.  
  9188. .isMine .editFavorite {
  9189. display: none; {* 自分のマイリストにはお気に入り登録ボタンを出さない *}
  9190. }
  9191. .watchingVideo button {
  9192. cursor: pointer;
  9193. }
  9194.  
  9195. .grepOption {
  9196. padding: 16px;
  9197. width: 500px;
  9198. margin: 16px auto;
  9199. background: #f4f4f4; border: 1px solid #ccc;
  9200. }
  9201. .grepOption .grepInput {
  9202. font-size: 120%;
  9203. width: 100%;
  9204. }
  9205.  
  9206. .grepOption .filter {
  9207. display: block; margin: 8px;
  9208. }
  9209. .grepOption .filter:hover {
  9210. background: #ccc;
  9211. }
  9212. .grepOption .filter.invertFilter {
  9213. display: none;
  9214. }
  9215. .grepOption.active .filter.invertFilter {
  9216. display: block; text-align: right;
  9217. }
  9218.  
  9219.  
  9220. */});
  9221. addStyle(__css__, 'mylistContentCss');
  9222.  
  9223. var MylistDetailView = WatchApp.ns.components.videoexplorer.view.content.parts.MylistDetailView;
  9224. MylistDetailView.prototype.update_org = MylistDetailView.prototype.update;
  9225. MylistDetailView.prototype.update = function(id, name, description, count) {
  9226. this.update_org(id, name, description, count);
  9227. if (id.toString().match(/repo-owner-(\d+)/)) {
  9228. this._$name.attr('href', '/user/' + RegExp.$1);
  9229. } else
  9230. if (parseInt(id, 10) <= 0) {
  9231. this._$name.attr('href', '');
  9232. }
  9233. };
  9234.  
  9235. var
  9236. overrideContentView = function(proto, watchingVideoView, grepOptionView) {
  9237. var updateCssClass = function(content) {
  9238. $('.videoExplorerBody')
  9239. .toggleClass('dummyMylist', content.getIsDummy())
  9240. .toggleClass('isMine', content.getIsMine())
  9241. .toggleClass('ownerNicorepo', content.getIsOwnerNicorepo())
  9242. .toggleClass('ranking', content.getIsRanking())
  9243. ;
  9244. };
  9245.  
  9246. proto.detach_org = proto.detach;
  9247. proto.detach = function() {
  9248. this.detach_org();
  9249. watchingVideoView.detach();
  9250. grepOptionView.detach();
  9251. };
  9252.  
  9253. proto.onUpdate_org = proto.onUpdate;
  9254. proto.onUpdate = function() {
  9255. this.onUpdate_org();
  9256. updateCssClass(this._content);
  9257. watchingVideoView.update();
  9258. grepOptionView.update();
  9259. this._$content.find('.mylistSortOrder').before(watchingVideoView.getView());
  9260. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9261. };
  9262.  
  9263. proto.onError_org = proto.onError;
  9264. proto.onError = function() {
  9265. this.onError_org();
  9266. updateCssClass(this._content);
  9267. watchingVideoView.update();
  9268. grepOptionView.update();
  9269. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9270. };
  9271.  
  9272. };
  9273.  
  9274. overrideContentView(ContentView.prototype, watchingVideoView, grepOptionView);
  9275. } // end initMylistContent
  9276.  
  9277. function initDeflistContent($, conf, w) {
  9278. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  9279. var ContentView = WatchApp.ns.components.videoexplorer.view.content.DeflistVideoContentView;
  9280. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  9281. var explorer = vec.getVideoExplorer();
  9282. var content = explorer.getContentList().getContent(ContentType.DEFLIST_VIDEO);
  9283. var loader = content._deflistVideoAPILoader;
  9284. var pager = content._pager;
  9285. var watchingVideoView = new WatchingVideoView({
  9286. content: content,
  9287. watchInfoModel: watchInfoModel,
  9288. mylistController: Mylist,
  9289. type: 'deflist',
  9290. $view: $([
  9291. '<div class="watchingVideo">',
  9292. '<img class="thumbnail">',
  9293. '<p class="title"></p>',
  9294. '<span class="contains" >この動画はリストに登録されています</span>',
  9295. '<span class="not_contains">この動画はリストにありません</span>',
  9296. '<span class="edit">',
  9297. '<button class="add" >登録</button>',
  9298. '<button class="remove">外す</button>',
  9299. '</span>',
  9300. '</div>',
  9301. ''].join(''))
  9302. });
  9303. var grepOptionView = new GrepOptionView({
  9304. content: content,
  9305. listName: 'suggestDeflistTitle',
  9306. $view: $([
  9307. '<div class="grepOption">',
  9308. '<form>',
  9309. '<input type="search" class="grepInput" autocomplete="on" placeholder="とりマイをタイトル・説明文で絞り込む(G)" accesskey="g">',
  9310. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  9311. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  9312. '<label class="durationFilter filter">',
  9313. '動画長<select class="duration">',
  9314. '<option value="">指定無し</option>',
  9315. '<option value="-180">3分以内</option>',
  9316. '<option value="180" >3分以上</option>',
  9317. '<option value="-300">5分以内</option>',
  9318. '<option value="300" >5分以上</option>',
  9319. '<option value="-600">10分以内</option>',
  9320. '<option value="600" >10分以上</option>',
  9321. '<option value="-1800">30分以内</option>',
  9322. '<option value="1800" >30分以上</option>',
  9323. '</select>',
  9324. '</label>',
  9325. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  9326. '</form>',
  9327. '</div>',
  9328. ].join(''))
  9329. });
  9330.  
  9331.  
  9332.  
  9333. pager._pageItemCount = conf.searchPageItemCount;
  9334. pager._displayPageCount = 5;
  9335. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  9336. pager._pageItemCount = v;
  9337. });
  9338.  
  9339.  
  9340. content.changeState_org = content.changeState;
  9341. content.changeState = $.proxy(function(params, callback) {
  9342. console.log('deflist refresh! ', params, callback);
  9343. if (!this.isActive()) {
  9344. WatchController.clearDeflistCache();
  9345. }
  9346. this.changeState_org(params, callback);
  9347. }, content);
  9348.  
  9349. content.getIsMine = function() { return true; };
  9350.  
  9351. // grep対応のための拡張
  9352. content._rawList = [];
  9353. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  9354. content._filter = null;
  9355. content.setFilter = $.proxy(function(filter) {
  9356. this._filter = filter;
  9357. }, content);
  9358. content.getFilter = $.proxy(function() { return this._filter; }, content);
  9359.  
  9360. content.onLoad_org = content.onLoad;
  9361. content.onLoad = $.proxy(function(err, result) {
  9362. var filter = this.getFilter();
  9363. if (err === null && result.list && result.list.length) {
  9364. if (!result.rawList) result.rawList = result.list.concat();
  9365. if (filter) {
  9366. var list = [];
  9367.  
  9368. for (var i = result.rawList.length - 1; i >= 0; i--) {
  9369. var item = result.rawList[i];
  9370. if (item.title && filter(item)) {
  9371. list.unshift(item);
  9372. }
  9373. }
  9374. result.list = list;
  9375. } else {
  9376. result.list = result.rawList.concat();
  9377. }
  9378. } else
  9379. if (result.rawList) {
  9380. result.list = result.rawList.concat();
  9381. }
  9382. this._rawList = result.rawList || [];
  9383.  
  9384. this.onLoad_org(err, result);
  9385. }, content);
  9386.  
  9387. content.clear_org = content.clear;
  9388. content.clear = $.proxy(function() {
  9389. this.setFilter(null);
  9390. this.clear_org();
  9391. grepOptionView.clear();
  9392. }, content);
  9393.  
  9394. content.containsWatchId = $.proxy(function(watchId) {
  9395. var list = this.getRawList();
  9396. if (!watchId) { watchId = WatchController.getWatchId(); }
  9397.  
  9398. for (var i = list.length - 1; i >= 0; i--) {
  9399. if (list[i].id === watchId) { return true; }
  9400. }
  9401. return false;
  9402. }, content);
  9403.  
  9404. var
  9405. overrideContentView = function(proto, watchingVideoView) {
  9406. var updateCssClass = function(content) {
  9407. $('.videoExplorerBody').toggleClass('isMine', true);
  9408. };
  9409.  
  9410. proto.detach_org = proto.detach;
  9411. proto.detach = function() {
  9412. this.detach_org();
  9413. watchingVideoView.detach();
  9414. grepOptionView.detach();
  9415. };
  9416.  
  9417. proto.onUpdate_org = proto.onUpdate;
  9418. proto.onUpdate = function() {
  9419. this.onUpdate_org();
  9420. updateCssClass(this._content);
  9421. watchingVideoView.update();
  9422. grepOptionView.update();
  9423. this._$content.find('.deflistSortOrder').before(watchingVideoView.getView());
  9424. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9425. };
  9426.  
  9427. proto.onError_org = proto.onError;
  9428. proto.onError = function() {
  9429. this.onError_org();
  9430. updateCssClass(this._content);
  9431. watchingVideoView.update();
  9432. grepOptionView.update();
  9433. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9434. };
  9435.  
  9436. };
  9437.  
  9438. overrideContentView(ContentView.prototype, watchingVideoView);
  9439. } // end initDeflistContent
  9440.  
  9441.  
  9442.  
  9443.  
  9444. function showLargeThumbnail(baseUrl) {
  9445. var largeUrl = baseUrl, size;
  9446. if (baseUrl.indexOf('smilevideo.jp') >= 0) {
  9447. largeUrl = baseUrl.replace(/\.([LM])/, '') + '.L';
  9448. size = 'width: 360px; height: 270px; max-height: 500px;';
  9449. } else {
  9450. largeUrl = baseUrl.replace(/z$/, 'l');
  9451. size = 'width: 360px; max-height: 500px;';
  9452. }
  9453. var
  9454. html = [
  9455. '<div class="largeThumbnailPopup" onmousedown="if (event.button == 0) { $(\'#popupMarquee\').removeClass(\'show\'); event.preventDefault(); }" style="width: 360px; height: 270px; background-image: url(' , largeUrl, ')">',
  9456. '<img src="', largeUrl, '" style="display: none;" onload="$(\'#popupMarquee .largeThumbnailPopup *\').hide()">',
  9457. // '<img src="', baseUrl, '" style="', size, ' z-index: 2;">',
  9458. '<div style="', size, ' background-image: url(' + baseUrl + '); "></div>',
  9459. '</div>',
  9460. ''].join('');
  9461. Popup.show(html);
  9462. } //
  9463. WatchController.showLargeThumbnail = showLargeThumbnail;
  9464.  
  9465. function onVideoStopped() {
  9466. EventDispatcher.dispatch('onVideoStopped');
  9467. }
  9468.  
  9469. function onVideoEnded() {
  9470. EventDispatcher.dispatch('onVideoEnded');
  9471. }
  9472.  
  9473. var videoExplorerOpenCount = 0;
  9474. function onVideoExplorerOpened(params) {
  9475. window.console.timeEnd('onVideoExplorerOpen');
  9476. var target = params.target, contentList = params.contentList, content = params.content;
  9477. if (videoExplorerOpenCount++ === 0) {
  9478. EventDispatcher.dispatch('onFirstVideoExplorerOpened', content);
  9479. }
  9480. EventDispatcher.dispatch('onVideoExplorerOpened', content);
  9481.  
  9482. AnchorHoverPopup.hidePopup().updateNow();
  9483. }
  9484.  
  9485. function onVideoExplorerOpening(params) {
  9486. window.console.time('onVideoExplorerOpen');
  9487. var target = params.target, contentList = params.contentList, content = params.content;
  9488. if (videoExplorerOpenCount === 0) {
  9489. EventDispatcher.dispatch('onFirstVideoExplorerOpening', params);
  9490. }
  9491. EventDispatcher.dispatch('onVideoExplorerOpening', params);
  9492. }
  9493.  
  9494. function onVideoExplorerClosing(params) {
  9495. var target = params.target, contentList = params.contentList, content = params.content;
  9496. EventDispatcher.dispatch('onVideoExplorerClosing', content);
  9497. }
  9498.  
  9499. function onVideoExplorerClosed(params) {
  9500. var target = params.target, contentList = params.contentList, content = params.content;
  9501. AnchorHoverPopup.hidePopup().updateNow();
  9502. EventDispatcher.dispatch('onVideoExplorerClosed', content);
  9503. setTimeout(function() {
  9504. watch.PlaylistInitializer.playlistView.resetView();
  9505. }, 1000);
  9506. }
  9507.  
  9508. function onVideoExplorerRefreshStart(params) {
  9509. window.console.time('videoExplorerRefresh');
  9510. var target = params.target, contentList = params.contentList, content = params.content;
  9511. var
  9512. ContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  9513. type = content.getType(),
  9514. $ve = $('#videoExplorer')
  9515. .removeClass('w_user').removeClass('w_upload').removeClass('w_mylist')
  9516. .removeClass('w_deflist').removeClass('w_related').removeClass('w_search'),
  9517. $body = $ve.find('.videoExplorerBody')
  9518. .removeClass('isMine').removeClass('dummyMylist')
  9519. .removeClass('isRanking').removeClass('isOwnerNicorepo'),
  9520. className = 'w_user';
  9521. switch (type) {
  9522. case ContentType.USER_VIDEO:
  9523. className = 'w_user';
  9524. break;
  9525. case ContentType.UPLOADED_VIDEO:
  9526. className = 'w_uploaded';
  9527. break;
  9528. case ContentType.MYLIST_VIDEO:
  9529. className = 'w_mylist';
  9530. break;
  9531. case ContentType.DEFLIST_VIDEO:
  9532. className = 'w_deflist';
  9533. break;
  9534. case ContentType.RELATED_VIDEO:
  9535. className = 'w_related';
  9536. break;
  9537. case ContentType.SEARCH:
  9538. className = 'w_search';
  9539. break;
  9540. }
  9541. $ve.addClass(className);
  9542.  
  9543. EventDispatcher.dispatch('onVideoExplorerRefreshStart', content);
  9544. }
  9545. function onVideoExplorerRefreshEnd(params) {
  9546. window.console.timeEnd('videoExplorerRefresh');
  9547. var target = params.target, contentList = params.contentList, content = params.content;
  9548. EventDispatcher.dispatch('onVideoExplorerRefreshEnd', content);
  9549. }
  9550. function onVideoExplorerChangePage(params) {
  9551. var target = params.target, contentList = params.contentList, content = params.content;
  9552. EventDispatcher.dispatch('onVideoExplorerChangePage', content);
  9553. }
  9554.  
  9555. /**
  9556. * 検索中の動画サイズを無理矢理でっかくするよ。
  9557. */
  9558. var videoExplorerStyle = null, lastAvailableWidth = 0, lastBottomHeight = 0;
  9559. function adjustVideoExplorerSize(force) {
  9560. if (force !== true && (!conf.videoExplorerHack || !WatchController.isSearchMode())) { return; }
  9561. $('#videoExplorer, #content, #bottomContentTabContainer').toggleClass('w_adjusted', conf.videoExplorerHack);
  9562. var
  9563. isWindows = window.navigator.platform.toLowerCase().indexOf('win') >= 0,
  9564. scrollBarMargin = isWindows ? 16 : 0,
  9565. rightAreaWidth = $('.videoExplorerBody').outerWidth(), // 592
  9566. availableWidth = Math.max($(window).innerWidth() - rightAreaWidth, 300),
  9567. commentInputHeight = $('#playerContainer').hasClass('oldTypeCommentInput') ? 30 : 0,
  9568. controlPanelHeight = $('#playerContainer').hasClass('controll_panel') ? 46 : 0;
  9569.  
  9570. var
  9571. defPlayerWidth = 300, otherPluginsHeight = 0,
  9572. defPlayerHeight = (defPlayerWidth - 32) * 9 / 16 + 10,
  9573. ratio = availableWidth / defPlayerWidth , availableHeight = defPlayerHeight * ratio + commentInputHeight + controlPanelHeight,
  9574. xdiff = (availableWidth - defPlayerWidth /*- 20 */), windowHeight = $(window).innerHeight(),
  9575. bottomHeight = windowHeight - availableHeight - (WatchController.isFixedHeader() ? $('#siteHeader').outerHeight() : 0) - otherPluginsHeight;
  9576.  
  9577. if (ratio < 1 || availableWidth <= 0 || bottomHeight <= 0 || (lastAvailableWidth === availableWidth && lastBottomHeight === bottomHeight)) { return; }
  9578.  
  9579. var seekbarWidth = 675, scaleX = (availableWidth) / seekbarWidth;
  9580.  
  9581. lastAvailableWidth = availableWidth;
  9582. lastBottomHeight = bottomHeight;
  9583.  
  9584. // コメントパネル召喚
  9585. var commentPanelWidth = 420;
  9586. var dynamic_css = [//'<style type="text/css" id="explorerHack">',
  9587. 'body.videoExplorer #content.w_adjusted #playerContainerWrapper, \n',
  9588. 'body.videoExplorer #content.w_adjusted #playerAlignmentArea, \n',
  9589. 'body.videoExplorer #content.w_adjusted #playerContainer, \n',
  9590. 'body.videoExplorer #content.w_adjusted #nicoplayerContainer ,\n',
  9591. 'body.videoExplorer #content.w_adjusted #external_nicoplayer \n',
  9592. '{',
  9593. 'width: ', availableWidth, 'px !important; height: ', availableHeight, 'px !important;padding: 0; margin: 0; ',
  9594. '}\n',
  9595. 'body.videoExplorer #content.w_adjusted .videoExplorerMenu { ',
  9596. 'position: absolute; width: 300px;',
  9597. 'margin-top: ', availableHeight, 'px !important; left: ', (xdiff - 2), 'px; ',
  9598. 'max-height: ', bottomHeight + 'px; overflow-y: auto; overflow-x: hidden; height: auto;',
  9599. '}\n',
  9600. 'body.videoExplorer #videoExplorer.w_adjusted { ',
  9601. 'margin-left: ', availableWidth, 'px !important;',
  9602. 'min-height: ', (windowHeight + 200) ,'px !important;',
  9603. 'overflow-x: hidden;',
  9604. ' }\n',
  9605. 'body.videoExplorer #content.w_adjusted #playlist { margin-left: ', xdiff, 'px; }\n',
  9606.  
  9607. 'body.videoExplorer #content.w_adjusted #nicoHeatMap {',
  9608. '-webkit-transform: scaleX(', availableWidth / 100, ');',
  9609. 'transform: scaleX(', availableWidth / 100, ');',
  9610. '}\n',
  9611. 'body.videoExplorer #content.w_adjusted #nicoHeatMapContainer {',
  9612. 'width: ', availableWidth, 'px;',
  9613. '}\n',
  9614. 'body.videoExplorer #content.w_adjusted #smart_music_kiosk {',
  9615. '-webkit-transform: scaleX(', scaleX, ');',
  9616. 'left: ', ((availableWidth - seekbarWidth) / 2) ,'px !important;',
  9617. '}\n',
  9618. 'body.videoExplorer #content.w_adjusted #songrium_logo_mini {',
  9619. 'left: ', (availableWidth + 5) ,'px !important;',
  9620. '}\n',
  9621. 'body.videoExplorer #content.w_adjusted #inspire_category {',
  9622. 'left: ', (availableWidth + 32) ,'px !important;',
  9623. '}\n',
  9624.  
  9625.  
  9626. 'body.videoExplorer #content.w_adjusted #leftPanel {',
  9627. ' display: block !important; top: ', (availableHeight + otherPluginsHeight - 1), 'px; max-height: ', bottomHeight, 'px; width: ', (xdiff - 4 + 1), 'px; left: 0;',
  9628. ' height:', bottomHeight, 'px; display: block; border-radius: 0;',
  9629. '}',
  9630. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo, body.size_small.no_setting_panel.videoExplorer #content.w_adjusted #leftPanel .sideIchibaPanel {',
  9631. 'width: ', Math.max((xdiff - 4), 130), 'px; border-radius: 0;',
  9632. '}',
  9633.  
  9634. ((xdiff >= 400) ?
  9635. [
  9636. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoTitleContainer{',
  9637. 'margin-left: 158px; border-radius: 0 0 ;background: #ddd; border: solid; border-color: #ccc; border-width: 1px 1px 0;',
  9638. '}',
  9639. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoThumbnailContainer{',
  9640. 'position: absolute; max-width: 150px; top: 0; ',
  9641. '}',
  9642. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoInfo{',
  9643. 'background: #ddd; margin-left: 158px; border-radius: 0 0; border: solid; border-color: #ccc; border-width: 0 1px 1px;',
  9644. '}',
  9645. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoDetails{',
  9646. 'margin-left: 158px; height: 100%; ',
  9647. '}',
  9648. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoOwnerInfoContainer{',
  9649. 'position: absolute; width: 150px; top: 0; border: 1px solid #ccc; margin: 0;',
  9650. '}',
  9651. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .userIconContainer, body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .ch_profile{',
  9652. 'background: #ddd; max-width: 150px; float: none; border-radius: 0;',
  9653. '}',
  9654. 'body.videoExplorer:not(.content-fix) .w_adjusted .videoDetails, ',
  9655. 'body.videoExplorer:not(.content-fix) .w_adjusted #videoExplorerMenu {',
  9656. // タグ領域三行分 bodyのスクロール位置をタグの場所にしてる時でもパネルは文章の末端までスクロールできるようにするための細工
  9657. // (四行以上あるときは表示しきれないが)
  9658. 'padding-bottom: 72px; ',
  9659. '}',
  9660. ].join('') :
  9661. (
  9662. (xdiff >= 154) ?
  9663. ['body.videoExplorer #content.w_adjusted #leftPanel #leftPanelTabContainer { padding: 4px 2px 3px 2px; }'].join('') :
  9664. ['body.videoExplorer #content.w_adjusted #leftPanel { display: none !important;}'].join('')
  9665. )
  9666. ),
  9667. 'body.videoExplorer #bottomContentTabContainer.w_adjusted .videoExplorerFooterAdsContainer { width: 520px; }\n',
  9668.  
  9669. 'body.videoExplorer #content.w_adjusted #playerTabWrapper {',
  9670. 'height: ', availableHeight, 'px !important;',
  9671. '}',
  9672.  
  9673. 'body.videoExplorer #content.w_adjusted #playerTabWrapper .sidePanelInner {',
  9674. 'height: ', (availableHeight - 2), 'px;',
  9675. '}',
  9676. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active {',
  9677. 'right: -',(commentPanelWidth - 2), 'px !important; top: 0 !important; background: transparent; border: 0; margin-top: 0px;',
  9678. '}',
  9679. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active #playerCommentPanel {',
  9680. 'display: block; background: #dfdfdf; border: 1px solid;',
  9681. '}',
  9682. ''].join('');
  9683.  
  9684. if (videoExplorerStyle) {
  9685. videoExplorerStyle.innerHTML = dynamic_css;
  9686. } else {
  9687. videoExplorerStyle = addStyle(dynamic_css, 'videoExplorerStyle');
  9688. }
  9689.  
  9690. } // end adjustVideoExplorerSize
  9691.  
  9692. function setupVideoExplorerStaticCss() {
  9693. var __css__ = Util.here(function() {/*
  9694. body.videoExplorerOpening {
  9695. overflow-y: scroll;
  9696. }
  9697. body.videoExplorerOpening .videoExplorerMenu {
  9698. {* display: none; *}
  9699. }
  9700. body.videoExplorerOpening #playerTabWrapper {
  9701. visibility: hidden;
  9702. }
  9703. #videoExplorer {
  9704. transition: margin-left 0.4s ease 0.4s; overflow-x: hidden;
  9705. }
  9706. #videoExplorer.w_adjusted .videoExplorerBody, #videoExplorer.w_adjusted .videoExplorerContent .contentItemList {
  9707. width: 592px; padding-left: 0; min-width: 592px; max-width: auto;
  9708. }
  9709. #videoExplorer.w_adjusted .searchBox {
  9710. width: 574px;\
  9711. }
  9712. #videoExplorer.w_adjusted .videoExplorerContent {
  9713. width: 592px;
  9714. }
  9715. #videoExplorer.w_adjusted .videoExplorerBody .resultContentsWrap {
  9716. width: 592px; padding: 16px 0px;
  9717. }
  9718. #videoExplorer.w_adjusted .videoExplorerMenu, #content .videoExplorerMenu:not(.initialized) { display: none; }
  9719. .videoExplorerMenu {
  9720. transition: margin-top 0.4s ease 0.4s; {*, left 0.4s ease-in-out*};
  9721. }
  9722. #leftPanel {
  9723. {* transition: width 0.4s ease 0.4s, height 0.4s ease 0.4s, top 0.4s ease 0.4s, left 0.4s ease 0.4s;*}
  9724. transition: width 0.4s ease 0.4s, height 0.4s ease 0.4s, left 0.4s ease 0.4s;
  9725. }
  9726. #content.w_adjusted #playlist {
  9727. min-width: 592px;
  9728. }
  9729. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li, body.videoExplorer #content.w_adjusted #videoExplorerExpand {
  9730. height: 26px;
  9731. }
  9732. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li>a,body.videoExplorer #content.w_adjusted #videoExplorerExpand a{
  9733. line-height: 26px; font-size: 100%;
  9734. }
  9735. .errorMessage {
  9736. max-height: 0; line-height: 30px; overflow: hidden; text-align: center; color: #f88; cursor: pointer;
  9737. transition: max-height 0.8s ease;
  9738. }
  9739. .videoErrorOccurred .errorMessage {
  9740. max-height: 100px;
  9741. }
  9742.  
  9743. .w_adjusted .videoExplorerMenu .itemList li .arrow {
  9744. top: 8px;
  9745. }
  9746.  
  9747.  
  9748. .w_adjusted .videoExplorerMenu .closeVideoExplorer {
  9749. width: 300px; position: relative; padding: 2px 10px; background: #f5f5f5;
  9750. }
  9751. .w_adjusted .videoExplorerMenu .closeVideoExplorer:hover {
  9752. background: #dbdbdb;
  9753. }
  9754. .w_adjusted .videoExplorerMenu .closeVideoExplorer a{
  9755. display: block;line-height: 26px; {*color: #CC0000;*}
  9756. }
  9757.  
  9758. #searchResultNavigation > ul > li a:after, #content.w_adjusted #videoExplorerExpand a#closeSearchResultExplorer:after {
  9759. top: 8px;
  9760. }
  9761.  
  9762.  
  9763.  
  9764.  
  9765. #content.w_adjusted #playerContainerWrapper, #content.w_adjusted #playlist { box-shadow: none; }
  9766. #content.w_adjusted #videoExplorerExpand .arrow { display: none; }
  9767.  
  9768. body.videoExplorer #footer.w_adjusted {
  9769. display: none;
  9770. }
  9771. .w_adjusted .uadTagRelated .default .itemList .item .videoTitleContainer {
  9772. width: 130px;
  9773. text-align: center;
  9774. }
  9775. .w_adjusted .uadTagRelated .uadTagRelated {
  9776. margin-bottom: 30px;
  9777. }
  9778. .w_adjusted .uadTagRelated .itemList .item,
  9779. .w_adjusted .uadTagRelated .emptyItem,
  9780. .w_adjusted .uadTagRelated .default .landing {
  9781. width: 130px; margin: 0 10px 0 8px;
  9782. }
  9783. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper .itemImage {
  9784. width: 130px; height: auto; top: 0; left: 0;
  9785. }
  9786. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper {
  9787. width: 130px; height: 100px;
  9788. }
  9789. .w_adjusted .uadTagRelated .emptyItem .emptyMessageContainer {
  9790. width: 130px; height: 100px;
  9791. }
  9792. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link,
  9793. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link .title {
  9794. display: inline;
  9795. }
  9796.  
  9797. #videoExplorer.w_adjusted .videoExplorerContent .column1 .commentBlank {
  9798. width: 96%;
  9799. }
  9800. #videoExplorer.w_adjusted .videoExplorerContent .column4 .commentBlank {
  9801. width: 24%;
  9802. }
  9803. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item .createdTime .submit
  9804. {
  9805. display: none !important;
  9806. }
  9807. .nicorepoResult .column4 .videoInformation {
  9808. display: none;
  9809. }
  9810.  
  9811. #videoExplorer .pager { margin-right: 20px; }
  9812. #videoExplorer .contentItemList { clear: both; }
  9813.  
  9814. body.videoExplorer #content.w_adjusted #playerContainerWrapper { overflow: visible; }
  9815. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContent { padding: 20px 0px; }
  9816. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9817. { margin-left: 0; padding: 20px 340px 20px 0px; }
  9818. body.videoExplorer.playlist #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9819. { margin-left: 0; padding: 164px 340px 20px 0px; }
  9820.  
  9821. {* 謎のスペーサー *}
  9822. {*body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:first *}
  9823. {* body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:not(.videoExplorerMenuInner) { display: none; } *}
  9824. body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:nth-child(1) { display: none; }
  9825.  
  9826. body.videoExplorer #content.w_adjusted .videoExplorerMenu
  9827. { width: 300px; }
  9828.  
  9829. body.videoExplorer #content.w_adjusted .videoExplorerMenuInner
  9830. { position: static !important; top: 0 !important; left: 0 !important; }
  9831.  
  9832. body.videoExplorer #bottomContentTabContainer.w_adjusted { {*background: #ccc;*} }
  9833. body:not(.videoExplorer) .videoExplorerMenu { display: none; }
  9834.  
  9835. body.videoExplorer #content.w_adjusted #nicoplayerContainer {
  9836. z-index: 100;
  9837. }
  9838. body.videoExplorer #content.w_adjusted #playerTabWrapper {
  9839. top: 0px !important; background: #dfdfdf; border-radius: 4px;
  9840. z-index: 99;
  9841. transition: right 0.3s ease-out;
  9842. }
  9843. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column1 .item {
  9844. margin-left: 8px;
  9845. }
  9846. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  9847. width: 130px; margin-left: 8px; margin-right: 10px;
  9848. }
  9849. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .videoInformationOuter {
  9850. width: 414px;
  9851. }
  9852. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .nicorepoResult .videoInformationOuter {
  9853. width: auto;
  9854. }
  9855. #videoExplorer.w_adjusted .contentItemList .folder .column1 .description,
  9856. #videoExplorer.w_adjusted .suggestVideo .folder .column1 .description,
  9857. #videoExplorer.w_adjusted .descriptionShort {
  9858. width: 410px;
  9859. }
  9860.  
  9861. #videoExplorer .descriptionShort {
  9862. line-height: 1.5;
  9863. margin: 0 0 4px;
  9864. word-break: break-all;
  9865. clear: both;
  9866. color: #666;
  9867. font-size: 93%;
  9868. }
  9869.  
  9870. .w_adjusted .column1 .smallThumbnail .createdTime.at {
  9871. width: 130px;
  9872. }
  9873. .w_adjusted .column1 .createdTime.at {
  9874. width: 160px; text-align: center;
  9875. }
  9876. .w_adjusted .createdTime {
  9877. white-space: nowrap;
  9878. }
  9879. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList .folder .container,
  9880. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .suggestVideo .folder .container {
  9881. background-position: -15px -270px; width: 130px; height: 100px;
  9882. }
  9883.  
  9884. body.size_small.no_setting_panel.videoExplorer #content #videoExplorerExpand { {*「閉じる」ボタン *}
  9885. position: static; top: auto; left: auto; margin-top: 0;',
  9886. }
  9887. body.videoExplorer #content.w_adjusted #playerTabWrapper #playerCommentPanel {
  9888. display: none;
  9889. }
  9890.  
  9891. .videoExplorerMenu .item:hover {
  9892. background: #dbdbdb; text-decoration: underline;
  9893. }
  9894. .videoExplorerMenu .item {
  9895. position: relative; border-bottom: 1px solid #CCCCCC; background: #f2f2f2;
  9896. text-decoration: none; cursor: pointer;
  9897. }
  9898. .videoExplorerMenu .item .arrow {
  9899. display: block; position: absolute; top: 14px; right: 12px; width: 9px; height: 12px;
  9900. background: url("http://res.nimg.jp/img/watch_q9/video_explorer/icon_normal.png") no-repeat 0 0;
  9901. }
  9902. .videoExplorerMenu .item .text {
  9903. position: relative; width: 100%; height: 100%; display: block; text-align: left;
  9904. text-decoration: none; padding: 0 12px; color: #000; box-sizing: border-box;
  9905. line-height: 26px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box;
  9906. }
  9907. .videoExplorerMenu .itemList>li:first {
  9908. position: relative;
  9909. }
  9910.  
  9911. #videoExplorer .videoExplorerMenu .closeVideoExplorer, #videoExplorer .quickSearchInput {
  9912. display: none;
  9913. }
  9914.  
  9915. #videoExplorer .videoExplorerBody .videoExplorerContent .contentItemList.column1 .video .column1 .videoInformationOuter .title
  9916. {
  9917. white-space: normal;
  9918. }
  9919.  
  9920. .videoExplorer #playlist {
  9921. transition: margin-left 0.4s ease 0.4s;
  9922. }
  9923.  
  9924. #playerAlignmentArea .toggleCommentPanel {
  9925. display: none;
  9926. position: absolute;
  9927. right: -119px;
  9928. bottom: 70px;
  9929. width: 100px;
  9930. height: 30px;
  9931. cursor: pointer;
  9932. outline: none;
  9933. background: #ccc;
  9934. border-radius: 16px 16px 0 0;
  9935. border: solid 1px black;
  9936. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  9937. transform: rotate(90deg); transform-origin: 0 0 0;
  9938. transition: 0.4s ease-in-out;
  9939. -webkit-transition: 0.4s ease-in-out;
  9940. }
  9941. #playerAlignmentArea .toggleCommentPanel::-moz-focus-inner {
  9942. border: 0px;
  9943. }
  9944. body.videoExplorer .w_adjusted #playerAlignmentArea .toggleCommentPanel { display: block; }
  9945. #playerAlignmentArea .toggleCommentPanel:before {
  9946. content: '↑ ';
  9947. }
  9948. #playerAlignmentArea .toggleCommentPanel:hover {
  9949. right: -129px;
  9950. }
  9951. #playerAlignmentArea .toggleCommentPanel.w_active {
  9952. right: -418px;
  9953. bottom: -29px;
  9954. border-radius: 0 0 16px 16px;
  9955. z-index: 10000;
  9956. -webkit-transform: rotate(360deg);
  9957. transform: rotate(360deg);
  9958. transition: none;
  9959. -webkit-transition: 0.4s ease-in-out;
  9960. {*transition: 0.4s ease-in-out;*}
  9961. }
  9962. #playerAlignmentArea .toggleCommentPanel.w_active:hover {
  9963. {*box-shadow: 2px 2px 2px #888;*}
  9964. right: -418px;
  9965. }
  9966. #playerAlignmentArea .toggleCommentPanel.w_active:before {
  9967. content: '← ';
  9968. }
  9969.  
  9970. .videoExplorerOpening .videoExplorerBody .videoExplorerConfig {
  9971. display: none;
  9972. }
  9973. .videoExplorerBody .videoExplorerConfig {
  9974. cursor: pointer;
  9975. width: 80px; margin-left: -36px; white-space: nowrap;
  9976. border-radius: 0 32px 0 0; border: solid 1px #666; border-width: 1px 1px 0;
  9977. color: #fff; background: #aaa;
  9978. }
  9979. #videoExplorer.w_adjusted .videoExplorerConfig .open,
  9980. #videoExplorer:not(.w_adjusted) .videoExplorerConfig .close {
  9981. display: none;
  9982. }
  9983. .videoExplorerConfig::-moz-focus-inner { border: 0px; }
  9984.  
  9985. .videoExplorer #playerContainer.appli_panel #appliPanel {
  9986. width: auto !important; background: #333;
  9987. }
  9988. .videoExplorerContent {
  9989. background: #fff;
  9990. }
  9991. */});
  9992. return addStyle(__css__, 'videoExplorerStyleStatic');
  9993. } // end setupVideoExplorerStaticCss
  9994.  
  9995. function initAutoComplete($searchInput) {
  9996. var
  9997. $suggestList = $('<datalist id="quickSearchSuggestList"></datalist>'),
  9998. wordSuggest = '',
  9999. favTagsSuggest = '',
  10000. loading = false,
  10001. val = '',
  10002. suggestLoader = new NicoSearchSuggest({}),
  10003. update = _.debounce(function() {
  10004. if (loading) {
  10005. return;
  10006. }
  10007. var value = $searchInput.val();
  10008. if (value.length >= 1 && val !== value) {
  10009. val = $searchInput.val();
  10010. loading = true;
  10011. suggestLoader.load(val, onSuggestLoaded);
  10012. } else {
  10013. loading = false;
  10014. }
  10015. }, 300),
  10016. onSuggestLoaded = function(err, result) {
  10017. if (err) {
  10018. return;
  10019. }
  10020. if (result.candidates) {
  10021. console.log(result.candidates);
  10022. var candidates = result.candidates;
  10023. var options = [];
  10024. for (var i = candidates.length - 1; i >= 0; i--) {
  10025. options.unshift(['<option value="', candidates[i], '"></option>'].join(''));
  10026. }
  10027. wordSuggest = options.join('');
  10028. refresh();
  10029. }
  10030. loading = false;
  10031. },
  10032. refresh = function() {
  10033. $suggestList.html(wordSuggest + favTagsSuggest);
  10034. },
  10035. bind = function($elm) {
  10036. $elm
  10037. .on('focus', update)
  10038. .on('keydown', update)
  10039. .on('keyup', update)
  10040. .on('keypress', update)
  10041. .on('click', update)
  10042. .on('mousedown', update)
  10043. .on('mouseup', update)
  10044. .attr({'autocomplete': 'on', 'list': 'quickSearchSuggestList', 'placeholder': '検索ワードを入力(Q)'});
  10045. // try {
  10046. // //$elm.attr('type', 'search');
  10047. // //$elm[0].setAttribute('type', 'search');//.attr('type', 'search');
  10048. // } catch (e) {
  10049. // console.log(e);
  10050. // }
  10051. };
  10052.  
  10053. EventDispatcher.addEventListener('onFavTagsLoad', function(tags) {
  10054. var options = [];
  10055. for (var i = tags.length - 1; i >= 0; i--) {
  10056. options.unshift(['<option value="', tags[i], '"></option>'].join(''));
  10057. }
  10058. favTagsSuggest = options.join('');
  10059. refresh();
  10060. });
  10061. $('body').append($suggestList);
  10062.  
  10063. bind($searchInput);
  10064. } //
  10065.  
  10066. function initVideoExplorer($, conf, w) {
  10067. setupVideoExplorerStaticCss();
  10068.  
  10069. var
  10070. _vp = WatchApp.ns.components.videoexplorer,
  10071. initializer = watch.VideoExplorerInitializer,
  10072. controller = initializer.videoExplorerController,
  10073. explorer = controller.getVideoExplorer(),
  10074. explorerConfig = _vp.config.VideoExplorerConfig,
  10075. menu = explorer.getMenu(),
  10076. ContentItemType = _vp.model.ContentItemType,
  10077. ContentType = _vp.model.ContentType,
  10078. watchPageRouter = WatchApp.ns.model.state.WatchPageRouter.getInstance(),
  10079. playerConnector = watch.PlayerInitializer.nicoPlayerConnector,
  10080. searchType = 'tag',
  10081. $menu = $('.videoExplorerMenu'),
  10082. $searchInput = $('<input class="quickSearchInput" type="search" name="q" accesskey="q" required="required" x-webkit-speech />')
  10083. .attr({'title': '検索ワードを入力', 'placeholder': '検索ワードを入力(Q)'}),
  10084. $closeExplorer = $('<div class="closeVideoExplorer"><a href="javascript:;">▲ 画面を戻す</a></div>'),
  10085. $inputForm = $('<form action="javascript:void(0);" />').append($searchInput),
  10086. $toggleCommentPanel = $('<button class="toggleCommentPanel">コメント</button>');
  10087.  
  10088. // init search menu
  10089. $searchInput.on('keyup', function(e) {
  10090. $('.searchText input').val(this.value);
  10091. }).on('click', function(e) {
  10092. e.stopPropagation();
  10093. });
  10094. $inputForm.on('submit', function(e) {
  10095. //e.preventDefault();
  10096. var val = $.trim($searchInput.val());
  10097. if (val.length > 0) {
  10098. if (val.match(/(sm|nm|so)\d+/)) {
  10099. WatchController.nicoSearch(val, 'tag');
  10100. } else {
  10101. WatchController.nicoSearch(val, 'keyword');
  10102. }
  10103. }
  10104. });
  10105.  
  10106. var clearButton = new window.Nico.ClearButton({targetInput: $searchInput});
  10107. EventDispatcher.addEventListener('onSearchStart', function(word, type) {
  10108. searchType = type.replace(/^.*\./, '');
  10109. $searchInput.val(word);
  10110. window.setTimeout(function() {clearButton.refresh(); }, 0);
  10111. });
  10112. initAutoComplete($searchInput);
  10113.  
  10114. $closeExplorer.find('a').on('click', function() {
  10115. WatchController.closeSearch();
  10116. });
  10117.  
  10118. // メニュー拡張
  10119. var
  10120. detachMenuItems = function() {
  10121. WatchItLater.videoExplorerMenu.myShortcuts .detach();
  10122. WatchItLater.videoExplorerMenu.videoRanking.detach();
  10123. WatchItLater.videoExplorerMenu.favTags .detach();
  10124. WatchItLater.videoExplorerMenu.favMylists .detach();
  10125.  
  10126. $inputForm.detach();
  10127. $closeExplorer.detach();
  10128. },
  10129. attachMenuItems = function() {
  10130. if (conf.enableFavTags ) { WatchItLater.videoExplorerMenu.favTags.attach(); }
  10131. if (conf.enableFavMylists) { WatchItLater.videoExplorerMenu.favMylists.attach(); }
  10132. WatchItLater.videoExplorerMenu.videoRanking.attach();
  10133. WatchItLater.videoExplorerMenu.myShortcuts.attach();
  10134.  
  10135.  
  10136. $('.videoExplorerMenu')
  10137. .find('.itemList>li:first').append($inputForm)
  10138. .end().find('.errorMessage').after($closeExplorer);
  10139. };
  10140. controller._refreshMenu_org = controller._refreshMenu;
  10141. controller._refreshMenu = $.proxy(function() {
  10142. detachMenuItems();
  10143. this._refreshMenu_org();
  10144. attachMenuItems();
  10145. }, controller);
  10146. controller.showDeflist_org = controller.showDeflist;
  10147. controller.showMylist_org = controller.showMylist;
  10148. controller.showOtherUserVideos_org = controller.showOtherUserVideos;
  10149. controller.showOwnerVideo_org = controller.showOwnerVideo;
  10150. controller.searchVideo_org = controller.searchVideo;
  10151. controller.showDeflist = $.proxy(function() {
  10152. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10153. location.href = "/my/mylist";
  10154. return;
  10155. }
  10156. this.showDeflist_org();
  10157. }, controller);
  10158. controller.showMylist = $.proxy(function(id) {
  10159. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10160. location.href = "/mylist/" + id;
  10161. return;
  10162. }
  10163. this.showMylist_org(id);
  10164. }, controller);
  10165. controller.showOtherUserVideos = $.proxy(function(id, name) {
  10166. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10167. location.href = "/user/" + id;
  10168. return;
  10169. }
  10170. this.showOtherUserVideos_org(id, name);
  10171. }, controller);
  10172. controller.showOwnerVideo = $.proxy(function() {
  10173. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10174. location.href = "/user/" + WatchController.getOwnerId();
  10175. return;
  10176. }
  10177. this.showOwnerVideo_org();
  10178. }, controller);
  10179. controller.searchVideo = $.proxy(function(word, type) {
  10180. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10181. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  10182. location.href = (type === 'tag' ? 'tag' : 'search') + "/" + encodeURIComponent(word) + sortOrder;
  10183. return;
  10184. }
  10185. this.searchVideo_org(word, type);
  10186. }, controller);
  10187.  
  10188. EventDispatcher.addEventListener('onBottomContentTabViewReset', function(name) {
  10189. if (name === 'outline') {
  10190. WatchController.closeSearch();
  10191. }
  10192. });
  10193.  
  10194.  
  10195. EventDispatcher.addEventListener('on.config.enableFavTags',
  10196. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  10197. EventDispatcher.addEventListener('on.config.enableFavMylists',
  10198. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  10199.  
  10200. EventDispatcher.addEventListener('onVideoExplorerOpened', function(content) {
  10201. setTimeout(function() {
  10202. if (conf.videoExplorerHack) {
  10203. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  10204. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  10205. }
  10206. }, 100);
  10207. clearButton.refresh();
  10208.  
  10209. $('body').removeClass('videoExplorerOpening');
  10210. $('.videoExplorerMenu').addClass('initialized');
  10211. });
  10212. EventDispatcher.addEventListener('onVideoExplorerRefreshEnd', function(content) {
  10213. if (content.getType() === ContentType.USER_VIDEO) {
  10214. var items = content.getItems();
  10215. if (items.length === 1 && items[0].getContentItemType() !== ContentItemType.VIDEO) {
  10216. // ユーザーの投稿動画一覧が公開マイリスト一つだけだったら自動でそれを開く
  10217. items[0].stepIn();
  10218. return;
  10219. }
  10220. }
  10221. });
  10222. EventDispatcher.addEventListener('onVideoExplorerRefreshStart', function(content) {
  10223. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  10224. });
  10225.  
  10226. EventDispatcher.addEventListener('onVideoExplorerOpening', function(content) {
  10227. $('body').addClass('videoExplorerOpening');
  10228. adjustVideoExplorerSize(true);
  10229. });
  10230. EventDispatcher.addEventListener('onVideoExplorerClosing', function(content) {
  10231. });
  10232.  
  10233. EventDispatcher.addEventListener('onBeforeVideoExplorerMenuClear', function() {
  10234. detachMenuItems();
  10235. });
  10236.  
  10237. EventDispatcher.addEventListener('onUpdateSettingPanelVisible', function(isVisible, panel) {
  10238. if (isVisible && WatchController.isSearchMode()) {
  10239. setTimeout(function() {
  10240. WatchController.closeSearch();
  10241. setTimeout(function() {
  10242. playerConnector.updateSettingsPanelVisible(true, panel);
  10243. }, 800);
  10244. }, 100);
  10245. }
  10246. });
  10247.  
  10248. EventDispatcher.addEventListener('onFirstVideoExplorerOpened', function() {
  10249. window.console.time('onFirstVideoExplorerOpen');
  10250. EventDispatcher.addEventListener('onWindowResizeEnd', adjustVideoExplorerSize);
  10251. EventDispatcher.addEventListener('onVideoInitialized', adjustVideoExplorerSize);
  10252. adjustVideoExplorerSize(true);
  10253. window.console.timeEnd('onFirstVideoExplorerOpen');
  10254. });
  10255.  
  10256. var duration_match = /^([0-9]+):([0-9]+)/;
  10257. controller._item2playlistItem = function (item) {
  10258. // 動画長が入るようにする
  10259. var length_seconds = 0, len = item.getLength ? item.getLength() : '', m;
  10260. if (typeof len === 'string' && (m = duration_match.exec(len)) !== null) {
  10261. length_seconds = m[1] * 60 + m[2] * 1;
  10262. }
  10263. return new WatchApp.ns.model.playlist.PlaylistItem({
  10264. id : item.getId(),
  10265. title : item.getTitle(),
  10266. thumbnail_url : item.getThumbnailUrl(),
  10267. view_counter : item.getViewCounter(),
  10268. num_res : item.getNumRes(),
  10269. mylist_counter: item.getMylistCounter(),
  10270. mylist_comment: item.getMylistComment(),
  10271. first_retrieve: item.getFirstRetrieve(),
  10272. ads_counter : item.getUadCounter(),
  10273. length_seconds: length_seconds
  10274. });
  10275. };
  10276.  
  10277. initVideoExplorerItemContent();
  10278.  
  10279. $('#playerAlignmentArea').append($toggleCommentPanel);
  10280. $toggleCommentPanel.on('click', function() {
  10281. AnchorHoverPopup.hidePopup();
  10282. $('#playerTabWrapper').toggleClass('w_active');
  10283. $toggleCommentPanel.toggleClass('w_active', $('#playerTabWrapper').hasClass('w_active'));
  10284. setTimeout(function() {
  10285. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  10286. }, 1000);
  10287. }).on('mouseover', function() {
  10288. AnchorHoverPopup.hidePopup();
  10289. });
  10290.  
  10291. var toggleVideoExplorerHack = function(v) {
  10292. $('#videoExplorer, #content, #footer, #bottomContentTabContainer').toggleClass('w_adjusted', v);
  10293. if (v) {
  10294. $('#content').append($('.videoExplorerMenu'));
  10295. if (WatchController.isSearchMode()) {
  10296. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  10297. adjustVideoExplorerSize(true);
  10298. }
  10299. } else {
  10300. $('.videoExplorerContentWrapper').before($('.videoExplorerMenu'));
  10301. setTimeout(function() {
  10302. if (WatchController.isSearchMode()) {
  10303. playerConnector.updatePlayerConfig({playerViewSize: 'small'});
  10304. WatchApp.ns.util.WindowUtil.shake();
  10305. }
  10306. }, 1000);
  10307. }
  10308. };
  10309. EventDispatcher.addEventListener('on.config.videoExplorerHack', toggleVideoExplorerHack);
  10310. toggleVideoExplorerHack(conf.videoExplorerHack);
  10311.  
  10312.  
  10313. watchPageRouter._prepareState_org = watchPageRouter._prepareState;
  10314. watchPageRouter._prepareState = $.proxy(function(state) {
  10315. if (
  10316. conf.videoExplorerHack &&
  10317. WatchController.isSearchMode() &&
  10318. state.getVideoId() !== this._currentState.getVideoId()
  10319. ) {
  10320. state.prepare({
  10321. video: {id: this._watchInfoModel.v}
  10322. });
  10323. return state;
  10324. } else {
  10325. return this._prepareState_org(state);
  10326. }
  10327. }, watchPageRouter);
  10328. window.WatchApp.ns.model.state.WatchPageState.prototype.isVideoStateChange =
  10329. function(state) {
  10330. return this.getVideoId() !== state.getVideoId();
  10331. };
  10332.  
  10333.  
  10334. } // end initVideoExplorer
  10335.  
  10336. function initVideoExplorerItemContent() {
  10337.  
  10338. // 動画情報表示のテンプレートを拡張
  10339. var
  10340. overrideItemTemplate = function() {
  10341. var menu =
  10342. '<div class="thumbnailHoverMenu">' +
  10343. '<button class="showLargeThumbnail" onclick="WatchItLater.onShowLargeThumbnailClick(this);" title="大きいサムネイルを表示">+</button>' +
  10344. '<button class="deleteFromMyMylist" onclick="WatchItLater.onDeleteFromMyMylistClick(this);">マイリスト外す</button>' +
  10345. '</div>', $menu = $(menu);
  10346.  
  10347. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html());
  10348. $template.find('.column1 .thumbnailContainer').append($menu).end()
  10349. .find('.column4 .balloon').before($menu.clone()).end()
  10350. .find('.column4 .balloon').remove().end()
  10351. .find('.messageContainer').remove().end()
  10352. .find('.lastResBody')
  10353. .before($('<p class="descriptionShort"/><p class="itemMylistComment mylistComment"/>')).end()
  10354. .find('.noImage').remove().end()
  10355. //.find.remove('div.descriptionShort').end()
  10356. // .before($('<p class="descriptionShort"/>')).end()
  10357. // .find('.descriptionShort')
  10358. // .after($('<p class="itemMylistComment mylistComment"/>')).end()
  10359. .find('.createdTime').after($('<div class="nicorepoOwnerIconContainer"><a target="_blank"><img /></a></div>'));
  10360. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html($template.html());
  10361. $template = $menu = null;
  10362.  
  10363. },
  10364. onDeleteFromMyMylistClick = function(elm) {
  10365. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  10366. var contentList = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer.getContentList();
  10367. var
  10368. $elm = $(elm),
  10369. $item = $elm.closest('.item'),
  10370. $videoItem = $item, //.find(''),//$elm.parent().parent(),
  10371. watchId = $item.attr('data-watch-id'),
  10372. ac = contentList.getActiveContent(),
  10373. type = contentList.getActiveContentType(),
  10374. onUpdate = function(status, result) {
  10375. if (status !== "ok") {
  10376. Popup.alert('削除に失敗: ' + result.error.description);
  10377. } else {
  10378. $videoItem.animate({opacity: 0.3}, 500);
  10379. }
  10380. };
  10381.  
  10382. if (type === ContentType.MYLIST_VIDEO) {
  10383. if (!ac.getIsMine()) { return; }
  10384. Mylist.deleteMylistItem(watchId, ac.getMylistId(), onUpdate, 0);
  10385. } else
  10386. if (type === ContentType.DEFLIST_VIDEO) {
  10387. Mylist.deleteDefListItem(watchId, onUpdate, 0);
  10388. }
  10389. },
  10390. onShowLargeThumbnailClick = function (elm) {
  10391. var $item = $(elm).closest('.item');
  10392. var src = $item.attr('data-thumbnail');
  10393. if (!src) { return; }
  10394. showLargeThumbnail(src);
  10395. };
  10396. overrideItemTemplate();
  10397. WatchItLater.onDeleteFromMyMylistClick = onDeleteFromMyMylistClick;
  10398. WatchItLater.onShowLargeThumbnailClick = onShowLargeThumbnailClick;
  10399.  
  10400. // 動画情報表示の拡張
  10401. var ItemView = WatchApp.ns.components.videoexplorer.view.content.item.AbstractVideoContentItemView;
  10402. ItemView.prototype._setView_org = ItemView.prototype._setView;
  10403.  
  10404. ItemView.prototype.update_org = ItemView.prototype.update;
  10405. ItemView.prototype.update = function() {
  10406. // 動画情報表示をゴリゴリいじる場所
  10407. var item = this._item, $item = this._$item;
  10408. this.update_org(item);
  10409.  
  10410. this._$item.find('.deleteFromMyMylist').data('watchId', this._item.getId());
  10411. if (item._mylistComment) { // マイリストコメント
  10412. $item.find('.mylistComment').css({display: 'block'});
  10413. } else {
  10414. $item.find('.mylistComment').remove();
  10415. }
  10416.  
  10417. var lastResBody = item.getLastResBody();
  10418. if (lastResBody.length > 0) {
  10419. this._$lastResBody.css('cssText', 'display: block !important').text(lastResBody);
  10420. } else {
  10421. this._$lastResBody.remove();
  10422. }
  10423.  
  10424. var thumbnail = this._$thumbnail.attr('src').replace('.jpg.M', '');
  10425. if (item.isMiddleThumbnail()) {
  10426. this._$item.find('.thumbnailContainer')
  10427. .css('background-image', 'url(' + thumbnail + ')');
  10428. this._$thumbnail.remove();
  10429. } else {
  10430. this._$item
  10431. .addClass('smallThumbnail')
  10432. .find('.thumbnailContainer')
  10433. .css('background-image', 'url(' + thumbnail + ')');
  10434. }
  10435. $item.attr({
  10436. 'data-thumbnail': thumbnail,
  10437. 'data-watch-id': item.getId()
  10438. });
  10439.  
  10440.  
  10441. if (item._seed && item._seed._info) {
  10442. var info = item._seed._info;
  10443. if (info.nicorepo_owner) { // ニコレポ
  10444. $item.addClass(info.nicorepo_className).addClass('nicorepoResult');
  10445. var owner = info.nicorepo_owner;
  10446. var $iconContainer = $item.find('.nicorepoOwnerIconContainer'), $icon = $iconContainer.find('img'), $link = $iconContainer.find('a');
  10447. $icon.attr('src', owner.icon);
  10448. $link.attr({'href': owner.page, 'data-ownerid': owner.id, 'title': owner.name + ' さん'});
  10449. if (info.nicorepo_className.indexOf('log-user-') >= 0) {
  10450. $link.attr(
  10451. 'onclick',
  10452. 'if (arguments[0].button > 0) return; arguments[0].preventDefault();' +
  10453. 'WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController.showOtherUserVideos(this.dataset.ownerid, this.title);' +
  10454. 'WatchApp.ns.util.WindowUtil.scrollFit($("#videoExplorer"));'
  10455. );
  10456. }
  10457. if (info.nicorepo_log.length > 1) {
  10458. $item.find('.descriptionShort').html(info.nicorepo_log.join('<br>'));
  10459. }
  10460. // ニコレポは再生数が取れないので-で埋める
  10461. this._$viewCount .html('-');
  10462. this._$commentCount.html('-');
  10463. this._$mylistCount .html('-');
  10464. $iconContainer = $icon = $link = null;
  10465. }
  10466. }
  10467. if (item._seed && typeof item._seed.description_full === 'string' && item._seed.description_full.length > 150) {
  10468. this._$descriptionShort.attr('title', item._seed.description_full);
  10469. }
  10470. $item = null;
  10471.  
  10472. };
  10473. ItemView = null;
  10474.  
  10475. } // end initVideoExplorerItemContent
  10476.  
  10477.  
  10478.  
  10479. var lastVideoOwnerJson = '';
  10480. function onWatchInfoReset(watchInfoModel) {
  10481. $('body').toggleClass('w_channel', watchInfoModel.isChannelVideo());
  10482. EventDispatcher.dispatch('onWatchInfoReset', watchInfoModel);
  10483. var owner = WatchController.getOwnerInfo(), owner_json = JSON.stringify(owner);
  10484. if (lastVideoOwnerJson.length > 0 && lastVideoOwnerJson !== owner_json) {
  10485. EventDispatcher.dispatch('onVideoOwnerChanged', owner);
  10486. }
  10487. lastVideoOwnerJson = owner_json;
  10488. }
  10489.  
  10490. function onScreenModeChange(sc) {
  10491. setTimeout(function() {
  10492. EventDispatcher.dispatch('onScreenModeChange', sc);
  10493. }, 500);
  10494. }
  10495.  
  10496. function initMylistPanel($, conf, w) {
  10497. var iframe = Mylist.getPanel('');
  10498. iframe.id = "mylist_add_frame";
  10499. iframe.className += " fixed";
  10500. w.document.body.appendChild(iframe);
  10501. iframe.hide(); // ページの初期化が終わるまでは表示しない
  10502.  
  10503. var $iframe = $(iframe);
  10504. $iframe.find('.mylistSelect').attr('accesskey', ':');
  10505.  
  10506. var toggleMylistMenuInFull = function(v) {
  10507. $('.mylistPopupPanel')
  10508. .toggleClass('hideInFull', v === 'hide')
  10509. .toggleClass('hideAllInFull', v === 'hideAll');
  10510. };
  10511. EventDispatcher.addEventListener('on.config.hideMenuInFull', toggleMylistMenuInFull);
  10512. toggleMylistMenuInFull(conf.hideMenuInFull);
  10513.  
  10514. var setMylistPanelPosition = function(v) {
  10515. $iframe
  10516. .toggleClass('left', v.indexOf('left') >= 0)
  10517. .toggleClass('top', v.indexOf('top') >= 0);
  10518. setTimeout(function() {
  10519. $('#yukkuriPanel')
  10520. .toggleClass('mylistPanelLeft', v.indexOf('left') >= 0 && v.indexOf('top') < 0);
  10521. }, 500);
  10522. };
  10523. EventDispatcher.addEventListener('on.config.mylistPanelPosition', setMylistPanelPosition);
  10524. if (conf.mylistPanelPosition !== '') setMylistPanelPosition(conf.mylistPanelPosition);
  10525.  
  10526. var $footer = $('#footer'), $window = $(window);
  10527. var toggleMylistPanelStyle = function() {
  10528. if ($footer.is(':visible')) {
  10529. $iframe.toggleClass('black', $window.scrollTop() + $window.innerHeight() - $footer.offset().top >= 0);
  10530. } else {
  10531. $iframe.removeClass('black');
  10532. }
  10533. };
  10534.  
  10535. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10536. toggleMylistPanelStyle();
  10537. var newVideoId = watchInfoModel.id;
  10538. var newWatchId = watchInfoModel.v;
  10539. iframe.watchId(newVideoId, newWatchId);
  10540. if (isFirst) iframe.show();
  10541. });
  10542. EventDispatcher.addEventListener('onScrollEnd', toggleMylistPanelStyle);
  10543. EventDispatcher.addEventListener('onWindowResizeEnd', toggleMylistPanelStyle);
  10544. EventDispatcher.addEventListener('onScreenModeChange', toggleMylistPanelStyle);
  10545. EventDispatcher.addEventListener('on.config.hidePlaylist', toggleMylistPanelStyle);
  10546. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', toggleMylistPanelStyle);
  10547. } //
  10548.  
  10549. function initScreenMode() {
  10550. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10551. if (conf.autoBrowserFull) {
  10552. setTimeout(function() {
  10553. if ($('body').hasClass('up_marquee') && conf.disableAutoBrowserFullIfNicowari) {
  10554. // ユーザーニコ割があるときは自動全画面にしない
  10555. return;
  10556. }
  10557. if (WatchController.isSearchMode()) { // TODO: localStorageに直接アクセスすんな
  10558. var settingSize = (localStorage["PLAYER_SETTINGS.LAST_PLAYER_SIZE"] === '"normal"') ? 'normal' : 'medium';
  10559. WatchController.changeScreenMode(settingSize);
  10560. }
  10561. WatchController.changeScreenMode('browserFull');
  10562. onWindowResizeEnd(); // TODO:;;;;
  10563. }, 100);
  10564. } else {
  10565. if (conf.autoOpenSearch && !WatchController.isSearchMode() && !$('body').hasClass('full_with_browser')) {
  10566. WatchController.openSearch();
  10567. }
  10568. if (conf.autoScrollToPlayer) {
  10569. // 初回のみ、プレイヤーが画面内に納まっていてもタグの位置まで自動スクロールさせる。(ファーストビューを固定するため)
  10570. // 二回目以降は説明文や検索結果からの遷移なので、必要最小限の動きにとどめる
  10571. if (!WatchController.isSearchMode() || isFirst) {
  10572. WatchController.scrollToVideoPlayer(isFirst);
  10573. }
  10574. }
  10575. }
  10576. });
  10577.  
  10578.  
  10579. var lastPlayerConfig = null, lastScreenMode = '';
  10580.  
  10581. function hideIfNeed() {
  10582. if (conf.controllerVisibilityInFull === 'hidden') {
  10583. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({oldTypeCommentInput: true, oldTypeControlPanel: false});
  10584. $('body').addClass('hideCommentInput');
  10585. } else {
  10586. var $w = $(window), iw = $w.innerWidth(), ih = $w.innerHeight();
  10587. var controllerH = 46, inputH = 30;
  10588. }
  10589. }
  10590.  
  10591. function restoreVisibility() {
  10592. if (lastPlayerConfig !== null) {
  10593. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10594. {oldTypeCommentInput: !!lastPlayerConfig.oldTypeCommentInput, oldTypeControlPanel: !!lastPlayerConfig.oldTypeControlPanel}
  10595. );
  10596. $(window).resize();
  10597. }
  10598. $('body').removeClass('hideCommentInput');
  10599. }
  10600.  
  10601. function togglePlaylist(v) {
  10602. v = (typeof v === 'boolean') ? v : !$('body').hasClass('fullWithPlaylist');
  10603. $('body').toggleClass('fullWithPlaylist', v);
  10604. if (v) {
  10605. watch.PlaylistInitializer.playlistView.resetView();
  10606. }
  10607. return v;
  10608. }
  10609.  
  10610. function initShield() {
  10611. var shield = $('<div id="fullScreenMenuContainer" />');
  10612. shield.click(function(e) {
  10613. e.stopPropagation();
  10614. togglePlaylist();
  10615. });
  10616. $('#external_nicoplayer').after(shield);
  10617. shield = null;
  10618. }
  10619. initShield();
  10620.  
  10621. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  10622. var mode = sc.mode;
  10623. $('body').removeClass('w_fullScreenMenu');
  10624. if (mode === 'browserFull' && lastScreenMode !== mode) {
  10625. lastPlayerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get();
  10626. conf.setValue('lastControlPanelPosition', lastPlayerConfig.oldTypeControlPanel ? 'bottom' : 'over');
  10627.  
  10628. hideIfNeed();
  10629. //if (conf.enableTrueBrowserFull) togglePlaylist(conf.enableTrueBrowserFull);
  10630. } else
  10631. if (lastScreenMode === 'browserFull' && mode !== 'browserFull') {
  10632. conf.setValue('lastControlPanelPosition', '');
  10633. $('#playerContainerSlideArea').css({height: ''}); // wall bug fix
  10634. restoreVisibility();
  10635. }
  10636. lastScreenMode = mode;
  10637. });
  10638.  
  10639. $(window).on('beforeunload.watchItLater', function(e) {
  10640. conf.setValue('lastControlPanelPosition', '');
  10641. restoreVisibility();
  10642. });
  10643.  
  10644. var wheelCounter = 0, wheelTimer = null;
  10645. EventDispatcher.addEventListener('onWheelNoButton', function(e, delta) {
  10646. if (!conf.enableFullScreenMenu) return;
  10647. if ((e.target.tagName !== 'OBJECT' && e.target.tagName !== 'HTML') ||
  10648. !WatchController.isFullScreen()) return;
  10649. if (wheelTimer) {
  10650. wheelCounter += delta;
  10651. } else {
  10652. wheelCounter = 0;
  10653. wheelTimer = setTimeout(function() {//
  10654. wheelTimer = null;
  10655. if (Math.abs(wheelCounter) > 3) {
  10656. EventDispatcher
  10657. .dispatch('onToggleFullScreenMenu',
  10658. $('body').toggleClass('w_fullScreenMenu', wheelCounter < 0).hasClass('w_fullScreenMenu')
  10659. );
  10660. AnchorHoverPopup.hidePopup();
  10661. }
  10662. }, 500);
  10663. }
  10664. });
  10665.  
  10666. TouchEventDispatcher.onflick(function(e) {
  10667. if (!conf.enableFullScreenMenu) return;
  10668. if ((e.direction !=='up' && e.direction !=='down') || e.startEvent.target.tagName !== 'OBJECT' || !WatchController.isFullScreen()) return;
  10669. if (wheelTimer) {
  10670. clearTimeout(wheelTimer);
  10671. wheelTimer = null;
  10672. }
  10673. EventDispatcher
  10674. .dispatch('onToggleFullScreenMenu',
  10675. $('body').toggleClass('w_fullScreenMenu', e.direction === 'down').hasClass('w_fullScreenMenu')
  10676. );
  10677. AnchorHoverPopup.hidePopup();
  10678. });
  10679.  
  10680. var $fullScreenMenuContainer = $('<div id="fullScreenMenuContainer"/>');
  10681. var $fullScreenModeSwitch = $([
  10682. '<button class="fullScreenModeSwitch button">',
  10683. 'プレイリスト: ',
  10684. '<span class="modeStatus playlistOpening">▼</span>',
  10685. '<span class="modeStatus playlistClosing">▲</span>',
  10686. '</button>'
  10687. ].join('')).click(togglePlaylist);
  10688. var $toggleStageVideo = $([
  10689. '<button class="stageVideoSwitch button">',
  10690. 'アクセラレーション: ',
  10691. '<span class="modeStatus mode_off">OFF</span>',
  10692. '<span class="modeStatus mode_on">ON</span>',
  10693. '</button>'
  10694. ].join('')).attr('title', 'ハードウェアアクセラレーションのON/OFF').click(function() { WatchController.toggleStageVideo(); });
  10695. var $toggleSetting = $([
  10696. '<button class="toggleSetting button">',
  10697. '</button>'
  10698. ].join('')).text('⛭設定').attr('title', '設定パネルを開閉します').click(function() { ConfigPanel.toggle();});
  10699. $fullScreenMenuContainer.append($fullScreenModeSwitch).append($toggleStageVideo).append($toggleSetting);
  10700. $('#nicoplayerContainerInner').append($fullScreenMenuContainer);
  10701.  
  10702.  
  10703. if (conf.lastControlPanelPosition === 'bottom' || conf.lastControlPanelPosition === 'over') {
  10704. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  10705. console.log('restore oldTypeControlPanel ? ', conf.lastControlPanelPosition === 'bottom');
  10706. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10707. {oldTypeControlPanel: conf.lastControlPanelPosition === 'bottom'}
  10708. );
  10709. });
  10710. }
  10711.  
  10712. } // end initScreenMode()
  10713.  
  10714.  
  10715.  
  10716.  
  10717. function initPlaylist($, conf, w) {
  10718. var
  10719. playlist = watch.PlaylistInitializer.playlist,
  10720. blankVideoId = 'sm20353707', blankVideoUrl = 'http://www.nicovideo.jp/watch/' + blankVideoId + '?',
  10721. redirectPageUrl = 'http://www.nicovideo.jp/stamp',
  10722. items = {},
  10723. toCenter = function() { // 表示位置調整
  10724. var
  10725. pm = WatchApp.ns.view.playlist.PlaylistManager,
  10726. pv = watch.PlaylistInitializer.playlistView,
  10727. pl = playlist,
  10728. current = pl.getPlayingIndex(),
  10729. cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()),
  10730. center = Math.round(cols / 2);
  10731.  
  10732. if (cols < 1) { return; }
  10733. var currentLeft = pm.getLeftSideIndex();
  10734. pv.scroll(Math.max(0, current - center + 1));
  10735. },
  10736. scroll = function(d) {
  10737. var isEffectEnabled = watch.PlaylistInitializer.playlistView.isEffectEnabled;
  10738. var left = WatchApp.ns.view.playlist.PlaylistManager.getLeftSideIndex();
  10739. watch.PlaylistInitializer.playlistView.isEffectEnabled = false;
  10740. watch.PlaylistInitializer.playlistView.scroll(Math.max(0, left + d));
  10741. watch.PlaylistInitializer.playlistView.isEffectEnabled = isEffectEnabled;
  10742. };
  10743.  
  10744. playlist.isAutoPlay = playlist.isContinuous; // 互換用
  10745. playlist.enableAutoPlay = playlist.enableContinuous;
  10746. playlist.disableAutoPlay = playlist.disableContinuous;
  10747.  
  10748. $('#playlist').find('.playlistInformation').on('dblclick.watchItLater', function(e) {
  10749. e.preventDefault();
  10750. e.stopPropagation();
  10751. toCenter();
  10752. });
  10753.  
  10754. EventDispatcher.addEventListener('onVideoInitialized', function() {
  10755. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView, pl = watch.PlaylistInitializer.playlist;
  10756. var current = pl.getPlayingIndex(), cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()), center = Math.floor(cols / 2);
  10757. if (pm.getLeftSideIndex() + cols <= pl.getNextPlayingIndex()) { toCenter(); }
  10758. });
  10759.  
  10760. $('#playlistContainer .prevArrow, #playlistContainer .nextArrow').on('mousewheel.watchItLater', function(e, delta) {
  10761. if (WatchController.isFullScreen()) { return; }
  10762. e.preventDefault();
  10763. e.stopPropagation();
  10764. scroll(delta *-1);
  10765. }).attr('title', 'ホイールで左右にスクロール');
  10766.  
  10767. // フルスクリーン中はプレイリストのどこでもスクロールできたほうがいいね
  10768. $('#playlist').on('mousewheel.watchItLater', function(e, delta) {
  10769. if (WatchController.isFullScreen() || WatchController.isSearchMode() || $('#footer').hasClass('noBottom')) {
  10770. e.preventDefault();
  10771. e.stopPropagation();
  10772. scroll(delta *-1);
  10773. }
  10774. });
  10775.  
  10776. EventDispatcher.addEventListener('onWheelAndButton', function(e, delta, button) {
  10777. if (WatchController.isFullScreen()) { return; }
  10778. if ($('#playlist').hasClass('dragging')) {
  10779. e.preventDefault();
  10780. scroll(delta *-1);
  10781. }
  10782. });
  10783.  
  10784. var
  10785. updatePos = function() {
  10786. if (
  10787. conf.hashPlaylistMode === 2 || (conf.hashPlaylistMode === 1 && WatchController.isPlaylistActive())) {
  10788. LocationHashParser.setValue('playlist', exportPlaylist());
  10789. LocationHashParser.updateHash();
  10790. }
  10791. if (conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') {
  10792. setTimeout(function() {
  10793. w[conf.storagePlaylistMode].setItem('watchItLater_playlist', JSON.stringify(exportPlaylist()));
  10794. }, 0);
  10795. }
  10796.  
  10797. var pos = Math.max((playlist.getPlayingIndex() + 1), 1) + '/' + Math.max(playlist.getItems().length, 1);
  10798. $('.generationMessage').text(pos + " - \n" + $('.generationMessage').text().replace(/^.*\n/, ''));
  10799. },
  10800. resetView = function() {
  10801. watch.PlaylistInitializer.playlistView.resetView();
  10802. },
  10803. exportPlaylist = function(option, type, continuous, shuffle) {
  10804. var
  10805. items = playlist.currentItems,
  10806. list = [],
  10807. current = 0,
  10808. len = conf.debugMode ? Math.min(600, items.length) : Math.min(300, items.length);
  10809.  
  10810. for (var i = 0; i < len; i++) {
  10811. var item = items[i];
  10812. if (item._isPlaying) current = i;
  10813. list.push([
  10814. item.id,
  10815. parseInt(item.mylistCounter, 10).toString(36),
  10816. parseInt(item.viewCounter, 10).toString(36),
  10817. parseInt(item.numRes, 10).toString(36),
  10818. (item.thumbnailUrl ? parseInt(item.thumbnailUrl.split('?i=')[1], 10).toString(36) : 'c490r'),
  10819. ].join(',') + ':' + item.title
  10820. );
  10821. }
  10822. return {
  10823. a: (typeof continuous === 'boolean') ? continuous : WatchController.isPlaylistContinuous(),
  10824. r: (typeof shuffle === 'boolean') ? shuffle : WatchController.isPlaylistRandom(),
  10825. o: option || playlist.option,
  10826. t: type || playlist.type,
  10827. i: list,
  10828. c: current
  10829. };
  10830. },
  10831. importPlaylist = function(list) {
  10832. var PlaylistItem = WatchApp.ns.model.playlist.PlaylistItem, newItems = [], uniq = {}, currentIndex = -1;
  10833.  
  10834. WatchController.clearPlaylist();
  10835. var currentItem = playlist.currentItems[0];
  10836. if (!currentItem) {
  10837. var wm = watchInfoModel;
  10838. currentItem = new PlaylistItem({
  10839. id: wm.v,
  10840. title: wm.title,
  10841. mylist_counter: wm.mylistCount,
  10842. view_counter: wm.viewCount,
  10843. num_res: wm.commentCount,
  10844. thumbnail_url: wm.thumbnail,
  10845. first_retriee: wm.postedAt
  10846. });
  10847. }
  10848.  
  10849. for (var i = 0, len = list.i.length; i < len; i++) {
  10850. var
  10851. dat = list.i[i],
  10852. c = dat.split(':')[0].split(','),
  10853. title = dat.replace(/^.*:/, ''),
  10854. id = c[0],
  10855. thumbnailId = parseInt(c[4], 36);
  10856.  
  10857. if (uniq[id] || typeof id !== 'string') { continue; }
  10858. uniq[id] = true;
  10859. if (id === watchInfoModel.v) {
  10860. currentIndex = i;
  10861. newItems.push(currentItem);
  10862. } else {
  10863. var item = new PlaylistItem({
  10864. id: id,
  10865. title: title.replace('<', '&lt;').replace('>', '&gt;'), // ないはずだけど一応
  10866. mylist_counter: parseInt(c[1], 36),
  10867. view_counter: parseInt(c[2], 36),
  10868. num_res: parseInt(c[3], 36),
  10869. thumbnail_url: 'http://tn-skr' + ((thumbnailId % 4) + 1) + '.smilevideo.jp/smile?i=' + thumbnailId,
  10870. first_retrieve: null
  10871. });
  10872. newItems.push(item);
  10873. }
  10874. }
  10875. // 復元するリストの中に現在の動画がなかった
  10876. if (currentIndex === -1) {
  10877. if (typeof list.c === 'number') {
  10878. if (list.c < newItems.length) {
  10879. currentIndex = list.c + 1;
  10880. newItems.splice(currentIndex, 0, currentItem);
  10881. } else {
  10882. currentIndex = list.length;
  10883. newItems.push(currentItem);
  10884. }
  10885. } else {
  10886. newItems.unshift(currentItem);
  10887. currentIndex = 0;
  10888. }
  10889. }
  10890.  
  10891. var isAutoPlay = playlist.isContinuous();//isAutoPlay();
  10892. playlist.reset(newItems, 'WatchItLater', list.t, list.o);
  10893. if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  10894. playlist.disableContinuous();
  10895. }
  10896. if (currentIndex >= 0) { playlist.playingItem = newItems[currentIndex]; }
  10897. if (list.a) { playlist.enableContinuous(); }
  10898. if (list.r) {
  10899. if (watchInfoModel.id === blankVideoId) {
  10900. setTimeout(function() {
  10901. WatchController.shufflePlaylist();
  10902. }, 3000);
  10903. } else {
  10904. playlist.enableContinuous();
  10905. }
  10906. }
  10907. },
  10908. $dialog = null, $savelink = null, $continuous, $shuffle,
  10909. openSaveDialog = function() {
  10910. function resetLink() {
  10911. var playlist = exportPlaylist(null, null, $continuous.is(':checked'), $shuffle.is(':checked'));
  10912. playlist.o = playlist.o || [];
  10913. playlist.o.name = $savelink.text();
  10914. playlist.t = 'mylist';
  10915. LocationHashParser.setValue('playlist', playlist);
  10916. if (!playlist.r) {
  10917. LocationHashParser.setValue('redirectWatchId', watchInfoModel.id);
  10918. } else {
  10919. LocationHashParser.deleteValue('redirectWatchId');
  10920. }
  10921. $savelink
  10922. //.attr('href', blankVideoUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10923. .attr('href', redirectPageUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10924. .unbind();
  10925. }
  10926. function closeDialog() {
  10927. $dialog.removeClass('show');
  10928. }
  10929.  
  10930. if (!$dialog) {
  10931. $dialog = $('<div id="playlistSaveDialog" />');
  10932. $dialog.append($([
  10933. '<div class="shadow"></div>',
  10934. '<div class="formWindow"><div class="formWindowInner">',
  10935. '<h3>プレイリスト保存用リンク(実験中)</h3>',
  10936. '<p class="link"><a target="_blank" class="playlistSaveLink">保存用リンク</a><button class="editButton">編集</button></p>',
  10937. '<label><input type="checkbox" class="continuous">開始時に連続再生をONにする</label><br>',
  10938. '<label><input type="checkbox" class="shuffle">開始時にリストをシャッフルする</label>',
  10939. '<p class="desc">リンクを右クリックしてコピーやブックマークする事で、現在のプレイリストを保存する事ができます。</p>',
  10940. '<button class="closeButton">閉じる</button>',
  10941. '</div></div>',
  10942. ''].join('')));
  10943. $savelink = $dialog.find('a').attr('added', 1);
  10944. $continuous = $dialog.find('.continuous');
  10945. $shuffle = $dialog.find('.shuffle');
  10946. $dialog.find('.shadow').on('click', closeDialog);
  10947. $dialog.find('.editButton').on('click', function() {
  10948. var newTitle = prompt('タイトルを編集', $savelink.text());
  10949. if (newTitle) {
  10950. $savelink.text(newTitle);
  10951. resetLink();
  10952. }
  10953. });
  10954. $continuous.on('click', resetLink);
  10955. $shuffle .on('click', resetLink);
  10956. $dialog.find('.closeButton').on('click', closeDialog);
  10957.  
  10958. $('body').append($dialog);
  10959. }
  10960. $savelink.text(
  10961. $('#playlist .generationMessage')
  10962. .text()
  10963. .replace(/^.*?\n/, '')
  10964. .replace(/^.*「/, '')
  10965. .replace(/」.*?$/, '')
  10966. .replace(/ *- \d{4}-\d\d-\d\d \d\d:\d\d$/, '') +
  10967. ' - ' + WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M', new Date())
  10968. );
  10969. $continuous.attr('checked', WatchController.isPlaylistActive());
  10970. $shuffle .attr('checked', WatchController.isPlaylistRandom());
  10971. resetLink();
  10972. $dialog.addClass('show');
  10973. },
  10974. PlaylistMenu = (function($, conf, w, playlist) {
  10975. var $popup = null, $generationMessage = $('#playlist').find('.generationMessage'), self;
  10976.  
  10977. var
  10978. enableContinuous = function() {
  10979. playlist.enableContinuous();
  10980. },
  10981. createDom = function() {
  10982. $popup = $('<div class="playlistMenuPopup popupMenu"/>')
  10983. .addClass('pop')
  10984. .toggleClass('w_touch', isTouchActive);
  10985. var $ul = $('<ul/>');
  10986. $popup.click(function() {
  10987. self.hide();
  10988. });
  10989. var $shuffle = $('<li>シャッフル: 全体</li>').click(function(e) {
  10990. WatchController.shufflePlaylist();
  10991. enableContinuous();
  10992. });
  10993. $ul.append($shuffle);
  10994. var $shuffleR = $('<li>シャッフル: 右</li>').click(function(e) {
  10995. WatchController.shufflePlaylist('right');
  10996. enableContinuous();
  10997. });
  10998. $ul.append($shuffleR);
  10999.  
  11000. var $next = $('<li>検索結果を追加: 次に再生</li>').click(function() {
  11001. WatchController.appendSearchResultToPlaylist('next');
  11002. enableContinuous();
  11003. });
  11004. $ul.append($next);
  11005.  
  11006. var $insert = $('<li>検索結果を追加: 末尾</li>').click(function() {
  11007. WatchController.appendSearchResultToPlaylist();
  11008. enableContinuous();
  11009. });
  11010. $ul.append($insert);
  11011.  
  11012. var $clear = $('<li>リストを消去: 全体</li>').click(function() {
  11013. WatchController.clearPlaylist();
  11014. //watch.PlaylistInitializer.playlist.setPlaybackMode('normal');
  11015. });
  11016. $ul.append($clear);
  11017.  
  11018. var $clearLeft = $('<li>リストを消去: 左</li>').click(function() {
  11019. WatchController.clearPlaylist('left');
  11020. });
  11021. $ul.append($clearLeft);
  11022. var $clearRight = $('<li>リストを消去: 右</li>').click(function() {
  11023. WatchController.clearPlaylist('right');
  11024. });
  11025. $ul.append($clearRight);
  11026.  
  11027. var $saver = $('<li>リストを保存(実験中)</li>').click(function() {
  11028. openSaveDialog();
  11029. });
  11030.  
  11031. $ul.append($saver);
  11032. $popup.append($ul);
  11033. $('body').append($popup);
  11034. },
  11035. show = function() {
  11036. if ($popup === null) { createDom(); }
  11037. var offset = $generationMessage.offset(), $window = $(window) , pageBottom = $window.scrollTop() + $window.innerHeight();
  11038. $popup.css({
  11039. left: offset.left,
  11040. top: Math.min(offset.top + 24, pageBottom - $popup.outerHeight())
  11041. }).show();
  11042. },
  11043. hide = function() {
  11044. if ($popup) { $popup.hide(); }
  11045. },
  11046. toggle = function() {
  11047. if ($popup === null || !$popup.is(':visible')) {
  11048. show();
  11049. } else {
  11050. hide();
  11051. }
  11052. };
  11053.  
  11054. $generationMessage.click(function(e) {
  11055. e.preventDefault();
  11056. self.toggle();
  11057. });
  11058.  
  11059. $('body').on('click.watchItLater', function(e) {
  11060. var tagName = e.target.tagName, className = e.target.className;
  11061. if (className !== 'generationMessage') {
  11062. self.hide();
  11063. }
  11064. });
  11065. self = {
  11066. show: show,
  11067. hide: hide,
  11068. toggle: toggle
  11069. };
  11070. return self;
  11071. })($, conf, w, playlist);
  11072.  
  11073.  
  11074. var hashlist = LocationHashParser.getValue('playlist');
  11075. if (hashlist && hashlist.i && hashlist.i.length > 0) {
  11076. try {
  11077. console.log('restore playlist!!');
  11078. importPlaylist(hashlist);
  11079. if (conf.hashPlaylistMode < 1) {
  11080. LocationHashParser.removeHash();
  11081. }
  11082. setTimeout(function() { resetView(); } , 3000);
  11083. } catch (e) {
  11084. console.log(e);
  11085. console.trace();
  11086. }
  11087. } else
  11088. if ((conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') && w[conf.storagePlaylistMode] && !playlist.isContinuous()) {
  11089. try {
  11090. console.log('restore playlist:' + conf.storagePlaylistMode);
  11091. var list = JSON.parse(w[conf.storagePlaylistMode].getItem('watchItLater_playlist'));
  11092. if (list !== null) { importPlaylist(list); }
  11093. setTimeout(function() { resetView(); } , 3000);
  11094. } catch (e) {
  11095. console.log('プレイリストの復元に失敗!', e);
  11096. }
  11097. } else {
  11098. updatePos();
  11099. }
  11100.  
  11101.  
  11102. // EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  11103. // if ($('body').hasClass('full_with_browser')) {
  11104. // // フル画面時プレイリストを閉じる
  11105. // if (conf.autoClosePlaylistInFull) { $('#content').find('.browserFullPlaylistClose:visible').click(); }
  11106. // }
  11107. // });
  11108.  
  11109. EventDispatcher.addEventListener('onVideoExplorerOpened', function() {
  11110. // 2013/09/26 本家側で開閉を記録するようになった -> 2014/03/03 また記憶しなくなった
  11111.  
  11112. // 通常画面でプレイリストを表示にしてるなら、開いた状態をデフォルトにする
  11113. if (conf.hidePlaylistInVideoExplorer === false) {
  11114. playlist.open();
  11115. }
  11116. });
  11117. $('#playlist .browserFullOption a').on('click', function() {
  11118. if (WatchController.isSearchMode()) {
  11119. conf.setValue('hidePlaylistInVideoExplorer', !conf.hidePlaylistInVideoExplorer);
  11120. }
  11121. });
  11122.  
  11123. EventDispatcher.addEventListener('on.config.hashPlaylistMode', function(v) {
  11124. if (v === 0) {
  11125. LocationHashParser.deleteValue('playlist');
  11126. LocationHashParser.removeHash();
  11127. } else
  11128. if (v === 1 || v === 2) {
  11129. var msg = [
  11130. '【警告】「プレイリストが消えないモード」は実験中の機能です。',
  11131. '',
  11132. 'この機能を使うと、ページをリロードしたりブックマークしてもプレイリストが消えなくなりますが、',
  11133. 'データを力技で保持するため、ページのURLがものすごく長く(※)なります。',
  11134. '',
  11135. 'そのため、ブラウザのパフォーマンスが低下したり、未知の不具合が発生する可能性があります。',
  11136. 'それでもこの機能を使ってみたい!という方だけ「OK」を押してください。',
  11137. '',
  11138. '※ 数千~数万文字くらい!',
  11139. ''].join('\n');
  11140. if (confirm(msg)) {
  11141. LocationHashParser.setValue('playlist', exportPlaylist());
  11142. LocationHashParser.updateHash();
  11143. } else {
  11144. conf.setValue('hashPlaylistMode', 0);
  11145. ConfigPanel.refresh();
  11146. }
  11147. }
  11148. });
  11149.  
  11150. $('#playlist .browserFullOption').on('click.bugfix', resetView);
  11151.  
  11152. $('.generationMessage, .prevArrow, .nextArrow, .playbackOption').on('mouseover', function() {
  11153. AnchorHoverPopup.hidePopup();
  11154. });
  11155.  
  11156. playlist.addEventListener('changePlaybackMode', function(mode) {
  11157. console.log('changePlaybackMode', mode, conf.hashPlaylistMode);
  11158. if (mode === 'normal' && conf.hashPlaylistMode < 2) {
  11159. LocationHashParser.removeHash();
  11160. } else {
  11161. updatePos();
  11162. }
  11163. });
  11164.  
  11165. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11166. updatePos();
  11167. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  11168. resetView();
  11169. });
  11170. EventDispatcher.addEventListener('onWatchInfoReset', function() {
  11171. updatePos();
  11172. });
  11173. playlist.addEventListener('reset', function() {
  11174. EventDispatcher.dispatch('onPlaylistReset');
  11175. updatePos();
  11176. });
  11177. playlist.addEventListener('update', function() {
  11178. EventDispatcher.dispatch('onPlaylistUpdate');
  11179. updatePos();
  11180. });
  11181. });
  11182.  
  11183. var togglePlaylistDisplay = function(v) {
  11184. var $playlist = $('#playlist');
  11185. if (!v) {
  11186. $playlist.addClass('w_show').removeClass('w_closing');
  11187. } else {
  11188. $playlist.addClass('w_closing');
  11189. setTimeout(function() { $playlist.removeClass('w_show');}, 500);
  11190. }
  11191. };
  11192. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  11193. togglePlaylistDisplay(conf.hidePlaylist);
  11194.  
  11195. // プレイリスト消えないモードの時はプレイリストを勝手におすすめに置き換える機能をキャンセル
  11196. (function() {
  11197. var ld = WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController._videoExplorerPlaylistResetArgumentsLoader;
  11198. ld.load_org = ld.load;
  11199. ld.load = $.proxy(function(a, b, c) {
  11200. if (conf.storagePlaylistMode !== '') {
  11201. return;
  11202. }
  11203. this.load_org(a, b, c);
  11204. }, ld);
  11205. ld = null;
  11206. })();
  11207.  
  11208. } // end initPlaylist
  11209.  
  11210.  
  11211. function initPageHeader($, conf, w) {
  11212. $('.videoDetailExpand h2').addClass('videoDetailToggleButton');
  11213. } // end initPageHeader
  11214.  
  11215.  
  11216. function initVideoTagContainer($, conf, w) {
  11217. var $videoHeaderTagEditLinkArea = null, $toggleTagEditText = null, baseTagHeight = 72, currentHeight = 72;
  11218. var tagListView = watch.TagInitializer.tagViewController.tagListView, $videoHeader = $('.videoHeaderOuter');
  11219.  
  11220. tagListView.getCurrentDefaultHeight_org = tagListView.getCurrentDefaultHeight;
  11221. tagListView.getCurrentDefaultHeight = function() {
  11222. if ($('body').hasClass('full_with_browser')) {
  11223. return tagListView.getCurrentDefaultHeight_org();
  11224. }
  11225. return currentHeight;
  11226. };
  11227.  
  11228. $videoHeaderTagEditLinkArea = $('.toggleTagEditInner .videoHeaderTagEditLinkArea');
  11229. $('.toggleTagEdit').append($videoHeaderTagEditLinkArea);
  11230. $toggleTagEditText = $('<span class="toggleText">' + $('.toggleTagEditInner').text() + '</span>');
  11231. $('.toggleTagEditInner').empty().append($toggleTagEditText).append($videoHeaderTagEditLinkArea);
  11232.  
  11233. var onTagReset = function() {
  11234. try {
  11235. // タグが2行以下だったら自動的に狭くする処理
  11236. if (!conf.enableAutoTagContainerHeight) { return; }
  11237. currentHeight = Math.min(baseTagHeight, $('#videoTagContainer').find('.tagInner').innerHeight());
  11238.  
  11239. if (baseTagHeight !== currentHeight) {
  11240. var $toggle = $('#videoTagContainer').find('.toggleTagEdit');
  11241. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  11242.  
  11243. if (currentHeight < 36) { // 1行以下の時
  11244. $videoHeader.addClass('tag1Line');
  11245. } else {
  11246. if (currentHeight <= 60) { // 2行以下の時
  11247. $videoHeader.addClass('tag2Lines');
  11248. }
  11249. }
  11250. watch.TagInitializer.tagViewController.tagListView.fit();
  11251. } else {
  11252. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  11253. watch.TagInitializer.tagViewController.tagListView.fit();
  11254. }
  11255. } catch (e) {
  11256. console.log(e);
  11257. }
  11258. };
  11259.  
  11260. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11261. EventDispatcher.addEventListener('onVideoInitialized', onTagReset);
  11262. });
  11263. watch.TagInitializer.tagList.addEventListener('reset', onTagReset);
  11264. if (conf.enableAutoTagContainerHeight) {
  11265. watch.TagInitializer.tagViewController.tagViewPinStatus.changeStatus(true);
  11266. }
  11267. window.setTimeout(onTagReset, 1000);
  11268.  
  11269. $videoHeaderTagEditLinkArea = $toggleTagEditText = null;
  11270.  
  11271.  
  11272. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org = WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived;
  11273. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived = function(a) {
  11274. //console.log('onTagDataReceived', a);
  11275. if (conf.disableTagReload) {
  11276. return;
  11277. }
  11278. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org(a);
  11279. };
  11280.  
  11281. } // end initVideoTagContainer
  11282.  
  11283.  
  11284.  
  11285. function initVideoReview($, conf, w) {
  11286. var __css__ = Util.here(function() {/*
  11287. .sidePanel #videoReview { margin: 0 auto; }
  11288. #outline.w_compact #videoReview { width: 300px; }
  11289. #outline.w_compact textarea.newVideoReview { width: 277px; }
  11290. #outline.w_compact #videoReviewHead { width: 283px; }
  11291. #outline.w_compact #videoReview .stream { width: 300px; }
  11292. #outline.w_compact #videoReview .inner { width: 300px; }
  11293. #outline.w_compact .commentContent { width: 278px; }
  11294. #outline.w_compact .commentContentBody { width: 232px; }
  11295. .sidePanel.w_review #videoReview { width: 308px; }
  11296. .sidePanel.w_review textarea.newVideoReview { width: 286px; }
  11297. .sidePanel.w_review #videoReviewHead { width: 291px; }
  11298. .sidePanel.w_review #videoReview .stream { width: 308px; }
  11299. .sidePanel.w_review #videoReview .inner { width: 308px; }
  11300. .sidePanel.w_review .commentContent { width: 286px; }
  11301. .sidePanel.w_review .commentContentBody { width: 240px; }
  11302. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview { width: 400px; }
  11303. body:not(.full_with_browser) .w_wide .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11304. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReviewHead { width: 383px; }
  11305. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .stream { width: 400px; }
  11306. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .inner { width: 400px; }
  11307. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContent { width: 378px; }
  11308. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContentBody { width: 332px; }
  11309. body.videoExplorer .sidePanel.w_review #videoReview { width: 400px; }
  11310. body.videoExplorer .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11311. body.videoExplorer .sidePanel.w_review #videoReviewHead { width: 383px; }
  11312. body.videoExplorer .sidePanel.w_review #videoReview .stream { width: 400px; }
  11313. body.videoExplorer .sidePanel.w_review #videoReview .inner { width: 400px; }
  11314. body.videoExplorer .sidePanel.w_review .commentContent { width: 378px; }
  11315. body.videoExplorer .sidePanel.w_review .commentContentBody { width: 332px; }
  11316.  
  11317. body:not(.videoExplorer) .sidePanel .commentUserProfile, body:not(.videoExplorer) .sidePanel .panelTrigger {
  11318. display: none !important;
  11319. }
  11320. body.videoExplorer .sidePanel .commentUserProfile {
  11321. position: fixed;
  11322. top: 36px !important;
  11323. left: auto !important;
  11324. right: 0 !important;
  11325. z-index: 11000;
  11326. }
  11327.  
  11328. .sidePanel .getMoreReviewComment {
  11329. margin-bottom: 256px;
  11330. }
  11331. */});
  11332. var reviewCss = addStyle(__css__, 'videoReviewCss');
  11333.  
  11334. /*
  11335. EventDispatcher.addEventListener('onFirstVideoInitialized', function() { setTimeout(function() {
  11336. var elms = [
  11337. '#videoReview',
  11338. 'textarea.newVideoReview',
  11339. '#videoReviewHead',
  11340. '#videoReview .stream',
  11341. '#videoReview .inner',
  11342. '.commentContent',
  11343. '.commentContentBody'
  11344. ];
  11345. var css = [], $baseElement = $('#videoReview');
  11346. var makeCss = function (targetWidth, preSel) {
  11347. var px = targetWidth - $baseElement.outerWidth();
  11348. for (var v in elms) {
  11349. var $e = $(elms[v]), newWidth = $e.width() + px;
  11350. css.push([
  11351. preSel, elms[v], ' { width: ', newWidth,'px; }\n'
  11352. ].join(''));
  11353. }
  11354. };
  11355. makeCss(300, '#outline.w_compact ');
  11356. makeCss(308, '.sidePanel.w_review ');
  11357. makeCss(400, '.sidePanel.w_review.w_wide ');
  11358. makeCss(400, 'body.videoExplorer .sidePanel.w_review ');
  11359. console.log(css.join(''));
  11360. var reviewCss = addStyle(css.join(''), 'videoReviewCss');
  11361. }, 3000);});
  11362. */
  11363. } // end initVideoReview
  11364.  
  11365. function initNews() {
  11366. var stopNicoNewsPolling = function() {
  11367. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemDispatcher.stop();
  11368. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemList.list.length = 0;
  11369. };
  11370. var toggleNoNews = function() {
  11371. $('#content').toggleClass('noNews', conf.hideNicoNews || conf.customPlayerSize !== '');
  11372. if ($('#content').hasClass('noNews')) {
  11373. stopNicoNewsPolling();
  11374. }
  11375. };
  11376.  
  11377. EventDispatcher.addEventListener('on.config.hideNicoNews', toggleNoNews);
  11378. EventDispatcher.addEventListener('on.config.customPlayerSize', toggleNoNews);
  11379.  
  11380. toggleNoNews();
  11381.  
  11382. if (conf.enableNewsHistory) { NicoNews.initialize(w); }
  11383. } //
  11384.  
  11385.  
  11386. function initEvents() {
  11387. var pac = watch.PlayerInitializer.playerAreaConnector;
  11388.  
  11389. pac.addEventListener("onVideoInitialized", onVideoInitialized);
  11390. pac.addEventListener("onVideoEnded", onVideoEnded);
  11391. pac.addEventListener("onVideoStopped", onVideoStopped);
  11392. // pac.addEventListener('onSystemMessageFatalErrorSended', onSystemMessageFatalErrorSended);
  11393. // watch.WatchInitializer.watchModel.addEventListener('error', function() {console.log(arguments);});
  11394.  
  11395. pac.addEventListener('updateSettingsPanelVisible', function(isVisible, panel) {
  11396. EventDispatcher.dispatch('onUpdateSettingPanelVisible', isVisible, panel);
  11397. });
  11398.  
  11399. watchInfoModel.addEventListener('reset', onWatchInfoReset);
  11400. watchInfoModel.addEventListener('beforeReset', function() {
  11401. window.console.time('watchInfoModelReset');
  11402. EventDispatcher.dispatch('onWatchInfoBeforeReset');
  11403. });
  11404. watchInfoModel.addEventListener('afterReset', function() {
  11405. window.console.timeEnd('watchInfoModelReset');
  11406. EventDispatcher.dispatch('onWatchInfoAfterReset');
  11407. });
  11408. watch.PlayerInitializer.playerScreenMode.addEventListener('change', onScreenModeChange);
  11409.  
  11410. var explorer = watch.VideoExplorerInitializer.videoExplorer;
  11411. explorer.addEventListener('openStart', onVideoExplorerOpening);
  11412. explorer.addEventListener('openEnd', onVideoExplorerOpened);
  11413. explorer.addEventListener('closeStart', onVideoExplorerClosing);
  11414. explorer.addEventListener('closeEnd', onVideoExplorerClosed);
  11415. explorer.addEventListener('refreshStart', onVideoExplorerRefreshStart);
  11416. explorer.addEventListener('refreshEnd', onVideoExplorerRefreshEnd);
  11417. explorer.addEventListener('changeCurrentPage', onVideoExplorerChangePage); //
  11418.  
  11419.  
  11420. $('body').dblclick(function(e){
  11421. var tagName = e.target.tagName, cls = e.target.className || '';
  11422. if (tagName === 'SELECT' || tagName === 'INPUT' || tagName === 'BUTTON' || cls.match(/mylistPopupPanel/)) {
  11423. return;
  11424. }
  11425. if (!WatchController.isFullScreen()) {
  11426. AnchorHoverPopup.hidePopup();
  11427. if (conf.doubleClickScroll) {
  11428. e.preventDefault();
  11429. EventDispatcher.dispatch('onScrollReset');
  11430. WatchController.scrollToVideoPlayer(true);
  11431. }
  11432. }
  11433. });
  11434.  
  11435. var bottomContentTabView = WatchApp.ns.view.BottomContentTabView.getInstance();
  11436. bottomContentTabView.addEventListener('changeContent', function(name) {
  11437. EventDispatcher.dispatch('onBottomContentTabViewReset', name);
  11438. });
  11439.  
  11440.  
  11441. Mylist.onDefMylistUpdate(function() {
  11442. //WatchController.clearDeflistCache();
  11443. });
  11444. Mylist.onMylistUpdate(function(info) {
  11445. WatchController.clearMylistCache(info.groupId);
  11446. });
  11447.  
  11448. $(window).on('beforeunload.watchItLater', function(e) {
  11449. conf.setValue('lastCommentVisibility', WatchController.commentVisibility() ? 'visible' : 'hidden');
  11450. }).on('resize', window._.debounce(function() {
  11451. AnchorHoverPopup.hidePopup();
  11452. EventDispatcher.dispatch('onWindowResizeEnd');
  11453. }, 1000));
  11454.  
  11455. //$(document).on('scroll', WatchApp.ns.event.EventDispatcher.throttle(function() {
  11456. $(document).on('scroll', function() {
  11457. if (document.body.style.pointerEvents !== 'none') {
  11458. document.body.style.pointerEvents = 'none';
  11459. }
  11460. });
  11461. $(document).on('scroll', window._.debounce(function() {
  11462. document.body.style.pointerEvents = '';
  11463. EventDispatcher.dispatch('onScrollEnd');
  11464. }, 500));
  11465.  
  11466. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11467. pac.addEventListener('onVideoChangeStatusUpdated', onVideoChangeStatusUpdated);
  11468. });
  11469.  
  11470. window.addEventListener('message', function(event) {
  11471. if (event.origin.indexOf('nicovideo.jp') < 0) return;
  11472. try {
  11473. var data = JSON.parse(event.data);
  11474. if (data.id !== 'WatchItLater') { return; }
  11475.  
  11476. EventDispatcher.dispatch('onMessage', data.body, data.type);
  11477.  
  11478. } catch (e) {
  11479. console.log(
  11480. '%cError: window.onMessage - ',
  11481. 'color: red; background: yellow',
  11482. e, event.origin, event.data);
  11483. }
  11484. });
  11485. } //
  11486.  
  11487. function initAdditionalButtons() {
  11488.  
  11489. var createPlaylistToggle = function() {
  11490. var $playlistToggle = $('<button title="プレイリスト表示/非表示" class="playlistToggle">プレイリスト</button>');
  11491.  
  11492. $playlistToggle.on('click', function() {
  11493. AnchorHoverPopup.hidePopup();
  11494. conf.setValue('hidePlaylist', !!!conf.hidePlaylist);
  11495. });
  11496.  
  11497. var togglePlaylistDisplay = function(v) {
  11498. $playlistToggle.toggleClass('w_show', !v);
  11499. };
  11500.  
  11501. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  11502. togglePlaylistDisplay(conf.hidePlaylist);
  11503.  
  11504. return $playlistToggle;
  11505. };
  11506. var createOpenExplorer = function() {
  11507. return $('<button class="openVideoExplorer">検索▼</button>').on('click', function() {
  11508. WatchController.openSearch();
  11509. if (!$('body').hasClass('content-fix')) {
  11510. WatchController.scrollToVideoPlayer(true);
  11511. }
  11512. });
  11513. };
  11514. var $div = $('<div class="bottomAccessContainer"/>').append(createPlaylistToggle()).append(createOpenExplorer());
  11515.  
  11516.  
  11517. var $headerMenu = $('<li class="watchItLaterSettingMenu"><a href="javascript:;" title="WatchItLaterの設定">WatchItLater設定</a></li>');
  11518. $('#siteHeaderRightMenuFix').after($headerMenu);
  11519.  
  11520. $('#outline .outer').before($div);
  11521. var $container = $('<div class="bottomConfButtonContainer" />'), $conf = $('<button title="WatchItLaterの設定">設定</button>');
  11522. var $explorerConf = $('<button><span class="open">`・ω・´</span><span class="close">´・ω・`</span></button>');
  11523. var toggleConf = function(e) {
  11524. e.stopPropagation();
  11525. AnchorHoverPopup.hidePopup();
  11526. ConfigPanel.toggle();
  11527. };
  11528. $container.append($conf);
  11529. $conf.addClass('openConfButton');
  11530. $conf.on('click', toggleConf);//.attr('accesskey', 'p');
  11531. $('#outline .outer').before($container);
  11532. $headerMenu.find('a').on('click', toggleConf);//.attr('accesskey', 'p');
  11533.  
  11534.  
  11535. $('.videoExplorerBody').append($explorerConf);
  11536. $explorerConf
  11537. .on('click',
  11538. function() { WatchItLater.config.set('videoExplorerHack', !WatchItLater.config.get('videoExplorerHack')); })
  11539. .addClass('videoExplorerConfig');
  11540.  
  11541. var $body = $('body'), $window = $(window);
  11542. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  11543. if (WatchController.isSearchMode() || WatchController.isFullScreen()) { return; }
  11544. var w = $div.outerWidth(), threshold = ($(window).innerWidth() - 960) / 2;
  11545. $('#outline').toggleClass('under960', w > threshold && !$('#footer').hasClass('noBottom'));
  11546. });
  11547. } // end initAdditionalButtons
  11548.  
  11549.  
  11550. function initSearchContent($, conf, w) {
  11551. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11552. var searchStatus = WatchApp.ns.components.nicosearchstatus ?
  11553. WatchApp.ns.components.nicosearchstatus : WatchApp.ns.components.videoexplorer;
  11554. var SearchSortOrder = searchStatus.model.SearchSortOrder;
  11555. var SearchType = searchStatus.model.SearchType;
  11556. var View = WatchApp.ns.components.videoexplorer.view.content.SearchContentView;
  11557. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11558. var explorer = vec.getVideoExplorer();
  11559. var content = explorer.getContentList().getContent(ContentType.SEARCH);
  11560. var relatedTag = new NicoSearchRelatedTag({});
  11561. var newSearch = new NewNicoSearch({});
  11562. var newSearchWrapper = new NewNicoSearchWrapper({search: newSearch});
  11563. var pager = content._pager;
  11564. var __css__ = Util.here(function() {/*
  11565. .newSearchOption {
  11566. text-align: center; margin-bottom: 16px; padding: 8px;
  11567. background: #eee;
  11568. display: none;
  11569. }
  11570. .newSearchOption select, .newSearchOption label{
  11571. margin-right: 32px;
  11572. }
  11573. .newSearchOption .reset{
  11574. cursor: pointer; background: #eee;
  11575. }
  11576. .newSearchOption p{
  11577. margin: 8px;
  11578. }
  11579. .newSearchOption .ownerName {
  11580. }
  11581. .w_sugoiSearch .newSearchOption {
  11582. display: block;
  11583. }
  11584.  
  11585. .relatedTagList {
  11586. }
  11587. .relatedTagList p{
  11588. display: inline-block; margin: 4px;
  11589. }
  11590. .relatedTagList li, .relatedTagList ul {
  11591. display: inline;
  11592. margin: 0 8px 0 0;
  11593. list-style: none;
  11594. word-break: break-all;
  11595. }
  11596. .relatedTagList li {
  11597. background: #f4f4f4; padding: 2px 4px;
  11598. border: solid 1px #999;
  11599. border-radius: 4px;
  11600. line-height: 180%;
  11601. }
  11602. .relatedTagList li:hover {
  11603. }
  11604. .relatedTagList li:hover a{
  11605. text-decoration: none;
  11606. }
  11607.  
  11608. .sugoiOption {
  11609. display: none;
  11610. }
  11611. .w_sugoiSearch .sugoiOption {
  11612. display: block; background: #eef;
  11613. }
  11614. .w_sugoiSearch optgroup.sugoiOption {
  11615. font-weight: bolder;
  11616. }
  11617.  
  11618. */});
  11619. addStyle(__css__, 'searchContent');
  11620.  
  11621. // 動画表示のテンプレート拡張
  11622. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html());
  11623. $template.find('.searchSortOrder')
  11624. .append([
  11625. '<optgroup label="新検索専用" class="sugoiOption">',
  11626. '<option value="sort=_hot&amp;order=d"" class="sugoiOption">人気が高い順</option>',
  11627. '<option value="sort=_popular&amp;order=d" class="sugoiOption">並び順指定なし</option>',
  11628. '<option value="sort=_explore&amp;order=d" class="sugoiOption">新着優先</option>',
  11629. '</optgroup>'
  11630. ].join(''));
  11631. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html($template.html());
  11632. $template = null;
  11633.  
  11634.  
  11635.  
  11636. var RelatedTagView = function() { this.initialize.apply(this, arguments); };
  11637. RelatedTagView.prototype = {
  11638. _$view: null,
  11639. _relatedTag: null,
  11640. initialize: function(params) {
  11641. this._relatedTag = params.relatedTag;
  11642. this._$view = params.$view;
  11643. this._$list = this._$view.find('ul');
  11644. },
  11645. getView: function() {
  11646. return this._$view;
  11647. },
  11648. detach: function() {
  11649. this._$view.detach();
  11650. },
  11651. update: function(candidates) {
  11652. if (!candidates || candidates.length < 1) {
  11653. this.detach();
  11654. return;
  11655. }
  11656. if (candidates.length > 10) {
  11657. candidates = candidates
  11658. .map(function(a){return {weight:Math.random(), value:a};})
  11659. .sort(function(a, b){return a.weight - b.weight;})
  11660. .map(function(a){return a.value;});
  11661. }
  11662. var $ul = this._$list.empty();
  11663. for (var i = 0, len = Math.min(10, candidates.length); i < len; i++) {
  11664. $ul.append(this._create$tag(candidates[i].tag));
  11665. }
  11666. },
  11667. clear: function() {
  11668. this._$list.empty();
  11669. },
  11670. _create$tag: function(text) {
  11671. var
  11672. $a = $('<a/>')
  11673. .html(text)
  11674. .attr('href', 'http://search.nicovideo.jp/video/tag/' + encodeURIComponent(text))
  11675. .on('click', Util.Closure.openNicoSearch(text)),
  11676. $tag = $('<li/>').append($a);
  11677. return $tag;
  11678. }
  11679. };
  11680.  
  11681. var NewSearchOptionView = function() { this.initialize.apply(this, arguments); };
  11682. NewSearchOptionView.prototype = {
  11683. _content: null,
  11684. _$view: null,
  11685. _$startTimeRange: null,
  11686. _$lengthSecondsRange: null,
  11687. initialize: function(params) {
  11688. this._content = params.content;
  11689. this._$view = params.$view;
  11690. this._$startTimeRange = this._$view.find('.startTimeRange');
  11691. this._$lengthSecondsRange = this._$view.find('.lengthSecondsRange');
  11692. this._$musicDlFilter = this._$view.find('.musicDlFilter');
  11693. this._$ownerFilter = this._$view.find('.ownerFilter');
  11694. this._$ownerName = this._$view.find('.ownerName');
  11695. this._$resetButton = this._$view.find('.reset');
  11696.  
  11697. this._$startTimeRange .val(params.startTimeRange || '');
  11698. this._$lengthSecondsRange.val(params.lengthSecondsRange || '');
  11699. this._$musicDlFilter .attr('checked', !!params.musicDlFilter);
  11700.  
  11701. this._$startTimeRange .on('change', $.proxy(this._onStartTimeRangeSelect , this));
  11702. this._$lengthSecondsRange.on('change', $.proxy(this._onLengthSecondsRangeSelect, this));
  11703. this._$musicDlFilter .on('click', $.proxy(this._onMusicDlFilterChange , this));
  11704. this._$ownerFilter .on('click', $.proxy(this._onOwnerFilterChange , this));
  11705. this._$resetButton .on('click', $.proxy(this.reset , this));
  11706.  
  11707. EventDispatcher.addEventListener('onVideoOwnerChanged', $.proxy(this.onVideoOwnerChange, this));
  11708. this._$ownerName.text(WatchController.getOwnerName());
  11709. },
  11710. getView: function() {
  11711. return this._$view;
  11712. },
  11713. detach: function() {
  11714. this._$view.detach();
  11715. },
  11716. update: function() {
  11717. },
  11718. onVideoOwnerChange: function(ownerInfo) {
  11719. this._content.setOwnerFilter(false);
  11720. this._$ownerFilter.prop('checked', false);
  11721. this._$ownerName.text(ownerInfo.name);
  11722. },
  11723. _onStartTimeRangeSelect: function() {
  11724. this._content.setStartTimeRange(this._$startTimeRange.val());
  11725. this.contentRefresh();
  11726. },
  11727. _onLengthSecondsRangeSelect: function() {
  11728. this._content.setLengthSecondsRange(this._$lengthSecondsRange.val());
  11729. this.contentRefresh();
  11730. },
  11731. _onMusicDlFilterChange: function() {
  11732. this._content.setMusicDlFilter(!!this._$musicDlFilter.prop('checked'));
  11733. this.contentRefresh();
  11734. },
  11735. _onOwnerFilterChange: function() {
  11736. this._content.setOwnerFilter(!!this._$ownerFilter.prop('checked'));
  11737. this.contentRefresh();
  11738. },
  11739. contentRefresh: function() {
  11740. var params = this._content.getParams();
  11741. params.page = 1;
  11742. this._content.changeState(params);
  11743. this._content.refresh({page: 1});
  11744. },
  11745. refresh: function() {
  11746. //console.log('refresh!', this._content.getOwnerFilter(), this._content.getMusicDlFilter(false));
  11747. this._$startTimeRange .val(this._content.getStartTimeRange('') || '');
  11748. this._$lengthSecondsRange.val(this._content.getLengthSecondsRange('') || '');
  11749. this._$musicDlFilter .prop('checked', !!this._content.getMusicDlFilter(false));
  11750. this._$ownerFilter .prop('checked', !!this._content.getOwnerFilter());
  11751. },
  11752. reset: function() {
  11753. var v = this._$startTimeRange.val() + this._$lengthSecondsRange.val();
  11754. if (v !== '') {
  11755. this._content.setStartTimeRange('');
  11756. this._content.setLengthSecondsRange('');
  11757. this._content.setMusicDlFilter(false);
  11758. this._$startTimeRange.val('');
  11759. this._$lengthSecondsRange.val('');
  11760. this._$musicDlFilter.prop('checked', false);
  11761. this._content.changeState({ page: 1 });
  11762. //this._content.refresh({ page: 1 });
  11763. }
  11764. }
  11765. };
  11766.  
  11767. var relatedTagView = new RelatedTagView({
  11768. relatedTag: relatedTag,
  11769. $view: $('<div class="relatedTagList"><p>関連タグ: </p><ul></ul></div>')
  11770. });
  11771. var newSearchOptionView = new NewSearchOptionView({
  11772. content: content,
  11773. startTimeRange: conf.searchStartTimeRange,
  11774. lengthSecondsRange: conf.searchLengthSecondsRange,
  11775. musicDlFilter: conf.searchMusicDlFilter,
  11776. $view: $([
  11777. '<div class="newSearchOption">',
  11778. '<span>投稿日時: </span>',
  11779. '<select class="startTimeRange" name="u">',
  11780. '<option selected="selected" value="" >指定なし</option>',
  11781. '<option value="24h">24時間以内</option>',
  11782. '<option value="1w" >1週間以内</option>',
  11783. '<option value="1m" >1ヶ月(30日)以内</option>',
  11784. '<option value="3m" >3ヶ月(90日)以内</option>',
  11785. '<option value="6m" >6ヶ月(180日)以内</option>',
  11786. '</select>',
  11787. '<span>再生時間: </span>',
  11788. '<select class="lengthSecondsRange" name="l">',
  11789. '<option selected="selected" value="" >指定なし</option>',
  11790. '<option value="short">5分以内</option>',
  11791. '<option value="long" >20分以上</option>',
  11792. '</select>',
  11793. '<p>',
  11794. '<label>',
  11795. '<input type="checkbox" name="m" class="musicDlFilter">音楽DL対応のみ</input>',
  11796. '</label>',
  11797. '<label>',
  11798. '<input type="checkbox" name="owner" class="ownerFilter"><span class="ownerName">この投稿者</span>&nbsp;の動画のみ</input>',
  11799. '</label>',
  11800. '</p>',
  11801. '</div>',
  11802. ''].join(''))
  11803. });
  11804.  
  11805.  
  11806.  
  11807. content._originalWord = '';
  11808. content.changeState_org = content.changeState;
  11809. content.changeState = $.proxy(function(params, callback) {
  11810. var word = WatchApp.get(params, 'searchWord', 'string', '');
  11811. var type = WatchApp.get(params, 'searchType', 'string', this.getSearchType());
  11812. if (typeof word === 'string' && word.length > 0) {
  11813. this._originalWord = word;
  11814.  
  11815. if (conf.defaultSearchOption && conf.defaultSearchOption !== '') {
  11816. if (word.indexOf(conf.defaultSearchOption) < 0 && !word.match(/(sm|nm|so)\d+/)) {
  11817. params.searchWord += " " + conf.defaultSearchOption;
  11818. }
  11819. }
  11820. }
  11821. AnchorHoverPopup.hidePopup();
  11822.  
  11823. EventDispatcher.dispatch('onSearchStart', this._originalWord, type);
  11824. this.changeState_org(params, callback);
  11825. }, content);
  11826.  
  11827. // ニコニコ新検索エンジンを使うための布石
  11828. content._searchEngineType = 'sugoi';//conf.searchEngine;
  11829. content._lastSearchEngineType = 'sugoi';//conf.searchEngine;
  11830. content.setSearchEngineType = $.proxy(function(type) {
  11831. this._searchEngineType = type;
  11832. this.updateSearchPageItemCount();
  11833. }, content);
  11834. content.updateSearchPageItemCount = $.proxy(function() {
  11835. this._pager._pageItemCount = this._searchEngineType === 'sugoi' ? conf.searchPageItemCount : 32;
  11836. }, content);
  11837. content.getSearchEngineType = $.proxy(function() {
  11838. return this._searchEngineType === 'sugoi' ? 'sugoi' : 'normal';
  11839. }, content);
  11840. content.setLastSearchEngineType = $.proxy(function(type) { this._lastSearchEngineType = type; }, content);
  11841. content.getLastSearchEngineType = $.proxy(function() { return this._lastSearchEngineType; }, content);
  11842. content._newSearchWrapper = newSearchWrapper;
  11843.  
  11844. content._startTimeRange = conf.searchStartTimeRange;
  11845. content._lengthSecondsRange = conf.searchLengthSecondsRange;
  11846. content._musicDlFilter = conf.searchMusicDlFilter;
  11847. content._ownerFilter = false;
  11848.  
  11849. content.getStartTimeRange = $.proxy(function() { return this._startTimeRange; }, content);
  11850. content.getLengthSecondsRange = $.proxy(function() { return this._lengthSecondsRange; }, content);
  11851. content.getMusicDlFilter = $.proxy(function() { return this._musicDlFilter; }, content);
  11852. content.getOwnerFilter = $.proxy(function() { return this._ownerFilter; }, content);
  11853. content.setStartTimeRange = $.proxy(function(value) {
  11854. this._startTimeRange = value;
  11855. conf.setValue('searchStartTimeRange', value);
  11856. }, content);
  11857. content.setLengthSecondsRange = $.proxy(function(value) {
  11858. this._lengthSecondsRange = value;
  11859. conf.setValue('searchLengthSecondsRange',value);
  11860. }, content);
  11861. content.setMusicDlFilter = $.proxy(function(value) {
  11862. this._musicDlFilter = !!value;
  11863. conf.setValue('searchMusicDlFilter', !!value);
  11864. }, content);
  11865. content.setOwnerFilter = $.proxy(function(value) {
  11866. this._ownerFilter = !!value;
  11867. }, content);
  11868.  
  11869. // 新検索独自のソート順への対応
  11870. var _searchSortOrder =
  11871. content._nicoSearchStatus ?
  11872. content._nicoSearchStatus._searchSortOrder : content._searchSortOrder;
  11873. _searchSortOrder._flush_org = _searchSortOrder._flush;
  11874. _searchSortOrder._flush = $.proxy(function() {
  11875. var sort = this._sort[SearchType.KEYWORD];
  11876. if (sort === '_hot' || sort === '_popular' || sort === '_explore') { // 新検索にしかないパラメータは保存しない
  11877. return;
  11878. }
  11879. this._flush_org();
  11880. }, _searchSortOrder);
  11881.  
  11882.  
  11883. EventDispatcher.addEventListener('on.config.searchPageItemCount', function() {
  11884. content.updateSearchPageItemCount();
  11885. });
  11886.  
  11887. content.getParams_org = content.getParams;
  11888. content.getParams = $.proxy(function() {
  11889. var params = this.getParams_org();
  11890. params = $.extend(true, {
  11891. l: this.getLengthSecondsRange(),
  11892. u: this.getStartTimeRange(),
  11893. m: this.getMusicDlFilter(),
  11894. size: this._pager._pageItemCount
  11895. }, params);
  11896. if (this.getOwnerFilter()) {
  11897. if (WatchController.isChannelVideo()) {
  11898. params.channelId = WatchController.getOwnerId();
  11899. } else {
  11900. params.userId = WatchController.getOwnerId();
  11901. }
  11902. }
  11903. return params;
  11904. }, content);
  11905.  
  11906. // タグ検索だけ毎回ソート順がデフォルトにリセットされるようになったので、
  11907. // デフォルト値を書き換えるという力技で対抗
  11908. SearchSortOrder.TAG_DEFAULT_SORT = conf.searchSortType;
  11909. SearchSortOrder.TAG_DEFAULT_ORDER = conf.searchSortOrder;
  11910. _searchSortOrder.getSortFromCookie = function() { return conf.searchSortType; };
  11911. _searchSortOrder.getOrderFromCookie = function() { return conf.searchSortOrder; };
  11912.  
  11913. content.load_org = content.load;
  11914. content.load = $.proxy(function(params, callback) {
  11915. var word = this.getSearchWord();
  11916. if (this.getSearchEngineType() !== 'sugoi' || word.length <= 0 || word.match(/(sm|nm|so)\d+/)) {
  11917. // 新検索ではもしかして~が取得できないため、検索ワードに動画IDっぽい文字列が含まれてる場合は旧タグ検索を使う。
  11918. this.setLastSearchEngineType('normal');
  11919. params.sort = 'n';
  11920. params.order = 'd';
  11921. this.load_org(params, callback);
  11922. } else {
  11923. this.setLastSearchEngineType('sugoi');
  11924. params = this.getParams();
  11925.  
  11926.  
  11927. this._newSearchWrapper.load(params, function(err, result) {
  11928. console.log('%cNewNicoSearchWrapper result', 'color: green;', result);
  11929. callback(err, result);
  11930. });
  11931. }
  11932. }, content);
  11933. content.setSearchEngineType('sugoi');//conf.searchEngine);
  11934.  
  11935. // EventDispatcher.addEventListener('on.config.searchEngine', function(type) {
  11936. // content.setSearchEngineType(type);
  11937. // });
  11938.  
  11939.  
  11940. var
  11941. overrideSearchSortOrder = function(proto) { // ソート順を記憶するためのフック
  11942. proto.getSort_org = proto.getSort;
  11943. proto.getSort = function() {
  11944. var sort = conf.searchSortType;
  11945. if ((sort === '_hot' || sort === '_popular' || sort === '_explore') && content.getLastSearchEngineType() !== 'sugoi') {
  11946. // 通常検索で新検索にしかないソート順だったらデフォルトのnを返す
  11947. return 'n';
  11948. }
  11949. return conf.searchSortType;
  11950. };
  11951.  
  11952. proto.setSort_org = proto.setSort;
  11953. proto.setSort = function(type, sort) {
  11954. conf.setValue('searchSortType', sort);
  11955. SearchSortOrder.TAG_DEFAULT_SORT = sort;
  11956. this.setSort_org(type, sort);
  11957. };
  11958.  
  11959. proto.getOrder_org = proto.getOrder;
  11960. proto.getOrder = function() {
  11961. return conf.searchSortOrder;
  11962. };
  11963.  
  11964. proto.setOrder_org = proto.setOrder;
  11965. proto.setOrder = function(type, order) {
  11966. if (content.getLastSearchEngineType() === 'sugoi') { // 新検索の時だけソート順を記憶
  11967. SearchSortOrder.TAG_DEFAULT_ORDER = order;
  11968. conf.setValue('searchSortOrder', order);
  11969. }
  11970. this.setOrder_org(type, order);
  11971. };
  11972. },
  11973. overrideSearchContentView = function(proto, relatedTag) {
  11974. proto._updateRelatedTag = function() {
  11975. if (!conf.enableRelatedTag) { return; }
  11976. var word = this._content._originalWord;
  11977. relatedTagView.clear();
  11978.  
  11979. if (typeof word === 'string' && word.length > 0) {
  11980. this._$header.append(relatedTagView.getView());
  11981. relatedTag.load(word, function(err, result) {
  11982. console.log('SearchContentView._updateRelatedTag', err, result);
  11983. if (err) {
  11984. console.log('load suggest fail', err, result);
  11985. } else {
  11986. relatedTagView.update(result.values);
  11987. }
  11988. });
  11989. }
  11990. };
  11991.  
  11992. proto.detach_org = proto.detach;
  11993. proto.detach = function() {
  11994. this.detach_org();
  11995. newSearchOptionView.detach();
  11996. relatedTagView.detach();
  11997. };
  11998.  
  11999. proto.onUpdate_org = proto.onUpdate;
  12000. proto.onUpdate = function() {
  12001. this.onUpdate_org();
  12002. this._$content.find('.searchBox').after(newSearchOptionView.getView());
  12003. this._updateRelatedTag();
  12004. var engine = this._content.getLastSearchEngineType();
  12005. newSearchOptionView.refresh();
  12006. $('.videoExplorerBody')
  12007. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  12008. .toggleClass('w_normalSearch', engine !== 'sugoi');
  12009. };
  12010.  
  12011. proto.onError_org = proto.onError;
  12012. proto.onError = function() {
  12013. this.onError_org();
  12014. this._$header.append(newSearchOptionView.getView());
  12015. this._updateRelatedTag();
  12016. var engine = this._content.getLastSearchEngineType();
  12017. $('.videoExplorerBody')
  12018. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  12019. .toggleClass('w_normalSearch', engine !== 'sugoi');
  12020. };
  12021.  
  12022. };
  12023.  
  12024. overrideSearchSortOrder(SearchSortOrder.prototype);
  12025. overrideSearchContentView(View.prototype, relatedTag);
  12026.  
  12027. } // end initSearchContent
  12028.  
  12029. function initUserVideoContent($, conf, w) {
  12030. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  12031. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  12032. var explorer = vec.getVideoExplorer();
  12033. var content = explorer.getContentList().getContent(ContentType.USER_VIDEO);
  12034. var pager = content._pager;
  12035.  
  12036. pager._pageItemCount = conf.searchPageItemCount;
  12037. pager._displayPageCount = 5;
  12038. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  12039. pager._pageItemCount = v;
  12040. });
  12041.  
  12042. }
  12043.  
  12044. function initUploadedVideoContent($, conf, w) {
  12045. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  12046. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  12047. var explorer = vec.getVideoExplorer();
  12048. var content = explorer.getContentList().getContent(ContentType.UPLOADED_VIDEO);
  12049. var pager = content._pager;
  12050.  
  12051.  
  12052. // // 本家のマイナーバグ修正
  12053. // content.setUserId_org = content.setUserId;
  12054. // content.setUserId = $.proxy(function(id) {
  12055. // var currentId = this.getUserId();
  12056. // if (currentId !== id) {
  12057. // this.setPage(1);
  12058. // }
  12059. // this.setUserId_org(id);
  12060. // }, content);
  12061.  
  12062. pager._pageItemCount = conf.searchPageItemCount;
  12063. pager._displayPageCount = 5;
  12064. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  12065. pager._pageItemCount = v;
  12066. });
  12067. }
  12068.  
  12069.  
  12070. var isSquareCssInitialized = false;
  12071. function initSquareThumbnail() {
  12072. var isSquare = true;// !!conf.squareThumbnail;
  12073. if (isSquare && !isSquareCssInitialized) {
  12074. var __css__ = Util.here(function() {/*
  12075. {* 元のCSSを打ち消すためにやや冗長 *}
  12076. #videoExplorer .noImage,
  12077. #videoExplorer.w_adjusted .item .thumbnail {
  12078. display: none !important;
  12079. }
  12080. #videoExplorer .thumbnailContainer {
  12081. background-size: contain;
  12082. background-repeat: no-repeat;
  12083. background-position: center center;
  12084. }
  12085.  
  12086. #videoExplorer.w_adjusted .column4 .thumbnailContainer,
  12087. #videoExplorer.w_adjusted .smallThumbnail .thumbnailContainer {
  12088. width: 130px; height: 100px;
  12089. margin-right: 7px;
  12090. border: 1px solid #888;
  12091. }
  12092.  
  12093. #videoExplorer.w_adjusted .column4 .uadFrame,
  12094. #videoExplorer.w_adjusted .smallThumbnail .uadFrame,
  12095. #videoExplorer.w_adjusted .uadTagRelated .uadFrame {
  12096. width: 130px; height: 100px;
  12097. background-size: 100% 100%;
  12098. }
  12099. #videoExplorer.w_adjusted .uadTagRelated .default .itemList .item .imageContainer {
  12100. width: 130px; height: 100px;
  12101. }
  12102.  
  12103. #videoExplorer.w_adjusted .column1 .item .thumbnailContainer {
  12104. border: 1px solid #888;
  12105. margin-right: 8px;
  12106. }
  12107. #videoExplorer.w_adjusted .column1 .item.smallThumbnail .thumbnailContainer {
  12108. border-width: 0px 15px 0px 15px;
  12109. border-color: #888;
  12110. }
  12111. */});
  12112.  
  12113. addStyle(__css__, 'squareThumbnailCss');
  12114. isSquareCssInitialized = true;
  12115. }
  12116. //$('#videoExplorer').toggleClass('squareThumbnail', isSquare);
  12117. } //
  12118.  
  12119. function initPageBottom($, conf, w) {
  12120. function updateHideVideoExplorerExpand(v) {
  12121. $('#content, #outline').toggleClass('w_hideSearchExpand', v === true);
  12122. }
  12123. function updateIchibaVisibility(v) {
  12124. $('#outline').toggleClass('noIchiba', v === 'hidden');
  12125. }
  12126. function updateReviewVisibility(v) {
  12127. $('#outline').toggleClass('noReview', v === 'hidden');
  12128. }
  12129. function updateBottomContentsVisibility(v) {
  12130. $('#bottomContentTabContainer, #footer').toggleClass('noBottom', v === 'hidden');
  12131. }
  12132.  
  12133. EventDispatcher.addEventListener('on.config.hideVideoExplorerExpand', updateHideVideoExplorerExpand);
  12134. EventDispatcher.addEventListener('on.config.ichibaVisibility', updateIchibaVisibility);
  12135. EventDispatcher.addEventListener('on.config.reviewVisibility', updateReviewVisibility);
  12136. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', updateBottomContentsVisibility);
  12137. if (conf.hideVideoExplorerExpand === true) { updateHideVideoExplorerExpand(true); }
  12138. if (conf.ichibaVisibility !== 'visible') { updateIchibaVisibility(conf.ichibaVisibility); }
  12139. if (conf.reviewVisibility !== 'visible') { updateReviewVisibility(conf.reviewVisibility); }
  12140. if (conf.bottomContentsVisibility !== 'visible') { updateBottomContentsVisibility(conf.bottomContentsVisibility); }
  12141.  
  12142. var $bottomToggle = $('<div class="toggleBottom"><div class="openBottom">▽</div><div class="closeBottom">△</div></div>');
  12143. $bottomToggle.on('click', function() {
  12144. var v = conf.bottomContentsVisibility;
  12145. conf.setValue('bottomContentsVisibility', v === 'hidden' ? 'visible' : 'hidden');
  12146. //ConfigPanel.refresh();
  12147. }).attr('title', '市場・レビューの開閉');
  12148. $('#footer').append($bottomToggle);
  12149. } //
  12150.  
  12151.  
  12152.  
  12153. function initShortcutKey() {
  12154. var list = [
  12155. {name: 'shortcutTogglePlay', exec: function(e) {
  12156. WatchController.togglePlay();
  12157. }},
  12158. {name: 'shortcutDefMylist', exec: function(e) {
  12159. WatchController.addDefMylist();
  12160. }},
  12161. {name: 'shortcutMylist', exec: function(e) {
  12162. $('#mylist_add_frame').find('.mylistAdd').click();
  12163. }},
  12164. {name: 'shortcutOpenDefMylist', exec: function(e) {
  12165. WatchController.showDeflist();
  12166. WatchController.scrollToVideoPlayer(true);
  12167. }},
  12168. {name: 'shortcutOpenSearch', exec: function(e) {
  12169. WatchController.openSearch();
  12170. if (!$('body').hasClass('content-fix')) {
  12171. WatchController.scrollToVideoPlayer(true);
  12172. }
  12173. }},
  12174. {name: 'shortcutOpenRecommend', exec: function(e) {
  12175. WatchController.openRecommend();
  12176. if (!$('body').hasClass('content-fix')) {
  12177. WatchController.scrollToVideoPlayer(true);
  12178. }
  12179. }},
  12180. {name: 'shortcutScrollToNicoPlayer', exec: function(e) {
  12181. WatchController.scrollToVideoPlayer(true);
  12182. }},
  12183. {name: 'shortcutCommentVisibility', exec: function(e) {
  12184. WatchController.commentVisibility('toggle');
  12185. }},
  12186. {name: 'shortcutShowOtherVideo', exec: function(e) {
  12187. WatchController.openVideoOwnersVideo();
  12188. }},
  12189. {name: 'shortcutMute', exec: function(e) {
  12190. WatchController.mute('toggle');
  12191. }},
  12192. {name: 'shortcutDeepenedComment', exec: function(e) {
  12193. WatchController.deepenedComment('toggle');
  12194. }},
  12195. {name: 'shortcutToggleStageVideo', exec: function(e) {
  12196. WatchController.toggleStageVideo();
  12197. }},
  12198. {name: 'shortcutInvisibleInput', exec: function(e) {
  12199. $('.invisibleCommentInput').focus();
  12200. }}
  12201. ];
  12202. for (var v in list) {
  12203. var n = list[v].name;
  12204. list[v].keyMatch = KeyMatch.create(conf[n]);
  12205. }
  12206.  
  12207. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12208. for (var v in list) {
  12209. var n = list[v].name;
  12210. if (n === name) {
  12211. list[v].keyMatch = KeyMatch.create(newValue);
  12212. }
  12213. }
  12214. });
  12215.  
  12216. $('body').on('keydown.watchItLater', function(e) {
  12217. // 一部のキーボードについているMusic Key(正式名称不明)に対応 Chromeしか拾えない?
  12218. if (e.keyCode === 178) { // 停止
  12219. WatchController.togglePlay();
  12220. } else
  12221. if (e.keyCode === 179) { // 一時停止
  12222. WatchController.togglePlay();
  12223. } else
  12224. if (e.keyCode === 177) { // 前の曲
  12225. if (WatchController.vpos() > 2000) {
  12226. WatchController.vpos(0);
  12227. } else {
  12228. WatchController.prevVideo();
  12229. }
  12230. } else
  12231. if (e.keyCode === 176) { // 次の曲
  12232. WatchController.nextVideo();
  12233. }
  12234. if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
  12235. return;
  12236. }
  12237. // 全画面時はFlashにフォーカスがなくてもショートカットキーが効くようにする
  12238.  
  12239. for (var v in list) {
  12240. var n = list[v].name;
  12241. if (list[v].keyMatch.test(e)) {
  12242. list[v].exec(e);
  12243. }
  12244. }
  12245. });
  12246. } //
  12247.  
  12248. function initNicoS($, conf, w) {
  12249. WatchJsApi.nicos.addEventListener('nicoSJump', function(e) {
  12250. if (conf.ignoreJumpCommand) {
  12251. e.cancel();
  12252. Popup.show('「@ジャンプ」コマンドをキャンセルしました');
  12253. }
  12254. });
  12255. var seekCount = 0;
  12256. WatchApp.ns.model.player.NicoSClientConnector.addEventListener('nicoSSeek', function(e) {
  12257. seekCount++;
  12258. if (conf.nicoSSeekCount < 0) return;
  12259. if (seekCount > conf.nicoSSeekCount) {
  12260. e.cancel();
  12261. }
  12262. });
  12263. // 動画が切り替わったか、最後まで視聴したらカウンターリセット
  12264. EventDispatcher.addEventListener('onVideoInitialized', function() {
  12265. seekCount = 0;
  12266. });
  12267. EventDispatcher.addEventListener('onVideoEnded', function() {
  12268. seekCount = 0;
  12269. });
  12270. } //
  12271.  
  12272. function initMouse() {
  12273. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12274. if (name === 'mouseClickWheelVolume') {
  12275. if (oldValue === 0) {
  12276. initWheelWatch();
  12277. } else
  12278. if (newValue === 0) {
  12279. $(document)
  12280. .unbind('mousewheel.watchItLaterWheelWatch')
  12281. .unbind('mousedown.watchItLaterWheelWatch')
  12282. .unbind('mouseup.watchItLaterWheelWatch');
  12283. }
  12284. }
  12285. });
  12286.  
  12287. function initWheelWatch() {
  12288. var leftDown = false, rightDown = false, isVolumeChanged = false;
  12289. var event = {
  12290. cancel: false,
  12291. reset: function() { this.cancel = false; return this; },
  12292. preventDefault: function() { this.cancel = true;}
  12293. };
  12294. $(document).on('mousewheel.watchItLaterWheelWatch', function(e, delta) {
  12295. var button = -1;
  12296. // TODO: マジックナンバーを
  12297. if (typeof e.buttons === 'number') { // firefox
  12298. button = e.buttons;
  12299. } else { // chrome
  12300. if (leftDown) { button = 1; }
  12301. else
  12302. if (rightDown) { button = 2; }
  12303. }
  12304. if (button < 1) {
  12305. EventDispatcher._dispatch('onWheelNoButton', e, delta);
  12306. return;
  12307. }
  12308. EventDispatcher.dispatch('onWheelAndButton', event.reset(), delta, button);
  12309. if (event.cancel) {
  12310. e.preventDefault();
  12311. return;
  12312. }
  12313. if (conf.mouseClickWheelVolume !== button) {
  12314. return;
  12315. }
  12316.  
  12317. var v = WatchController.volume(), r;
  12318. isVolumeChanged = true;
  12319. // 音量を下げる時は「うわ音でけぇ!」
  12320. // 音量を上げる時は「ちょっと聞こえにくいな」…というパターンが多いので、変化の比率が異なる
  12321. if (delta > 0) {
  12322. v = Math.max(v, 1);
  12323. r = (v < 5) ? 1.3 : 1.1;
  12324. v = WatchController.volume(v * r);
  12325. } else {
  12326. v = WatchController.volume(Math.floor(v / 1.2));
  12327. }
  12328. e.preventDefault();
  12329. }).on('mousedown.watchItLaterWheelWatch', function(e) { // chromeはホイールイベントでe.buttonsが取れないため
  12330. if (e.which === 1) leftDown = true;
  12331. if (e.which === 3) rightDown = true;
  12332. }).on('mouseup.watchItLaterWheelWatch', function(e) {
  12333. if (e.which === 1) leftDown = false;
  12334. if (e.which === 3) rightDown = false;
  12335. }).on('contextmenu.watchItLaterWheelWatch', function(e) {
  12336. if (isVolumeChanged) {
  12337. e.preventDefault();
  12338. }
  12339. isVolumeChanged = false;
  12340. });
  12341. }
  12342. window.setTimeout(function() { initWheelWatch(); }, 5000);
  12343. } // end initMouse
  12344.  
  12345. function initTouch() {
  12346. var touchInitialized = false;
  12347. TouchEventDispatcher.onflick(function(e) {
  12348. var se = e.startEvent;
  12349. if (!conf.enableQTouch) {return; }
  12350. if (e.direction === 'right') {
  12351. if (se.target.id === 'playerTabWrapper') {
  12352. $(se.target).addClass('w_active');
  12353. }
  12354. if (!touchInitialized) {
  12355. $('#mylist_add_frame, #leftPanelTabContainer, .videoExplorerMenu, #playerTabWrapper').addClass('w_touch');
  12356. $('.userProfile, .resultPagination, #searchResultContainer select, .playlistMenuPopup').addClass('w_touch');
  12357. isTouchActive = true;
  12358. touchInitialized = true;
  12359. }
  12360. } else
  12361. if (e.direction === 'left') {
  12362. if (se.target.tagName === 'DIV' &&
  12363. $.contains('#playerTabWrapper', se.target)) {
  12364. $('#playerTabWrapper').removeClass('w_active');
  12365. }
  12366. }
  12367. });
  12368. } //
  12369.  
  12370. function initOtherCss() {
  12371. var __dynamic_css_template__ = Util.here(function() {/*
  12372. .full_with_browser.w_fullScreenMenu #nicoHeatMap {
  12373. transform: scaleX($scale); -webkit-transform: scaleX($scale); display: block;
  12374. }
  12375. */});
  12376. var exStyle = null;
  12377. var updateDynamicCss = function() {
  12378. var css = __dynamic_css_template__;
  12379. var innerWidth = $('body').innerWidth();
  12380. css = css.split('$scale').join($('body').innerWidth() / 100);
  12381. if (exStyle) {
  12382. exStyle.innerHTML = css;
  12383. return exStyle;
  12384. } else {
  12385. return addStyle(css, 'expression');
  12386. }
  12387. };
  12388. exStyle = updateDynamicCss();
  12389.  
  12390. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12391. updateDynamicCss();
  12392. });
  12393.  
  12394. var __gpuLayer__ = (function() {/*
  12395. body.videoExplorer.content-fix #playerTabWrapper,
  12396. body.videoExplorer.content-fix .videoExplorerMenu,
  12397. body.videoExplorer.content-fix #playlist,
  12398. {*#playerTabWrapper .playerCommentPanel*}
  12399. body:not(.full_with_browser) .mylistPopupPanel.fixed
  12400. {
  12401. -moz-transform: translateZ(0);
  12402. -webkit-transform: translateZ(0);
  12403. transform: translateZ(0);
  12404. }
  12405. {* Firefoxだと問題がある要素はこちら *}
  12406. body.videoExplorer.content-fix #leftPanel,
  12407. body.videoExplorer.content-fix #content,
  12408. #popupMarquee
  12409. {
  12410. -webkit-transform: translateZ(0);
  12411. }
  12412. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  12413. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  12414. if (conf.enableGpuLayer) {
  12415. addStyle(__gpuLayer__, 'watchItLaterGpuLayer');
  12416. }
  12417.  
  12418. var __debug_css__ = Util.here(function() {/*
  12419. .videoExplorer #playerContainerWrapper, .videoExplorer #external_nicoplayer,
  12420. .videoExplorerOpening #playerContainerWrapper, .videoExplorerOpening #external_nicoplayer
  12421. {
  12422. {*transition: width 0.4s ease, height 0.4s ease;*}
  12423. }
  12424. #playerAlignmentArea .toggleCommentPanel {
  12425. {*transition: 0.4s ease-in-out;*}
  12426. }
  12427. {*
  12428. body:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer,
  12429. body:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea{
  12430. transition: width 0.4s ease;
  12431. }
  12432. body:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer{
  12433. transition: height 0.4s ease 0.4s;
  12434. }
  12435. *}
  12436.  
  12437. */});
  12438. if (conf.debugMode) addStyle(__debug_css__, 'watchItLater_debug_css');
  12439. } // end initOtherCss
  12440.  
  12441. function initCustomPlayerSize($, conf, w) {
  12442. var tpl = Util.here(function() {/*
  12443. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea
  12444. { width: {$alignmentAreaWidth}px; }
  12445. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea
  12446. { width: {$alignmentAreaWideWidth}px; }
  12447.  
  12448. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer {
  12449. height: {$playerHeight}px !important;
  12450. }
  12451. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer
  12452. { width: {$playerWidth}px !important;}
  12453.  
  12454. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #external_nicoplayer
  12455. { width: {$playerWidth}px !important; height: {$playerHeight}px !important; }
  12456.  
  12457. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMapContainer {
  12458. width: {$playerWidth}px !important;
  12459. }
  12460. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMap {
  12461. transform: scaleX({$heatMapScale}); -webkit-transform: scaleX({$heatMapScale});
  12462. }
  12463. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #smart_music_kiosk {
  12464. -webkit-transform: scaleX({$songriumScale}); -webkit-transform-origin: 0 0;
  12465. }
  12466.  
  12467. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #inspire_category {
  12468. left: {$songriumCategoryLeft}px !important;
  12469. }
  12470.  
  12471. */});
  12472. var PROFILE_SET = {
  12473. 'WQHD': [2560, 1440],
  12474. '1080p': [1920, 1080],
  12475. 'HD+': [1600, 900],
  12476. 'WXGA+': [1400, 810],
  12477. 'WXGA': [1366, 768],
  12478. '720p': [1280, 720],
  12479. 'WSVGA+': [1152, 648],
  12480. 'WSVGA': [1024, 576],
  12481. 'QHD': [ 960, 540]//, // 元より小さいパターンもサポートする?
  12482. // 'WVGA': [ 854, 480],
  12483. // 'NORMAL': [ 640, 360],
  12484. // 'SMALL': [ 512, 288],
  12485. // 'ECO': [ 352, 200],
  12486. };
  12487. var CONTROL_HEIGHT = 46, INPUT_HEIGHT = 30, PLAYER_TAB_WIDTH = 326 + 10, PLAYER_TAB_WIDTH_WIDE = 420 + 10, TAB_MARGIN = 0;
  12488. var SONGRIUM_WIDTH = 898;
  12489. var HORIZONTAL_MARGIN = 1.05; // 両端に 2.5% x 2 のマージンがある
  12490. var $videoHeader = $('#videoHeader');
  12491.  
  12492. var getTargetSize = function(targetWidth, targetHeight) {
  12493. var plWidth = Math.round(targetWidth * HORIZONTAL_MARGIN);
  12494. var plHeight = targetHeight + CONTROL_HEIGHT + INPUT_HEIGHT;
  12495. var alWidth = plWidth + PLAYER_TAB_WIDTH;
  12496. var alWidthW = plWidth + PLAYER_TAB_WIDTH_WIDE;
  12497. return {
  12498. playerWidth: plWidth,
  12499. playerHeight: plHeight,
  12500. alignmentAreaWidth: alWidth,
  12501. alignmentAreaWideWidth: alWidthW,
  12502. heatMapScale: plWidth / 100,
  12503. songriumScale: plWidth / SONGRIUM_WIDTH,
  12504. songriumCategoryLeft: plWidth + 32
  12505. };
  12506. };
  12507. var suggestProfile = function() {
  12508. var iw = $(window).innerWidth(), ih = $(window).innerHeight();
  12509. var hh = (WatchController.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  12510. iw -= (conf.wideCommentPanel ? PLAYER_TAB_WIDTH_WIDE : PLAYER_TAB_WIDTH);
  12511. iw -= TAB_MARGIN;
  12512.  
  12513. ih -= hh;
  12514. for (var v in PROFILE_SET) {
  12515. var w = PROFILE_SET[v][0], h = PROFILE_SET[v][1];
  12516. if (w * HORIZONTAL_MARGIN <= iw && h <= ih) {
  12517. return {w: w, h: h, name: v};
  12518. }
  12519. }
  12520. return null;
  12521. };
  12522. var generateCss = function() {
  12523. var profile = '';
  12524. if (PROFILE_SET[conf.customPlayerSize]) {
  12525. var s = PROFILE_SET[conf.customPlayerSize];
  12526. profile = {w: s[0], h: s[1], name: conf.customPlayerSize};
  12527. } else {
  12528. profile = suggestProfile();
  12529. }
  12530. if (!profile) return {css: '', profile: ''};
  12531. var ts = getTargetSize(profile.w, profile.h);
  12532. var css = tpl;
  12533. for (var v in ts) {
  12534. css = css.split('{$' + v + '}').join(ts[v]);
  12535. }
  12536. return {css: css, profile: profile};
  12537. };
  12538. var customStyleElement = null, lastCssName = '';
  12539. var updateStyle = function() {
  12540. if (WatchController.isFullScreen() || WatchController.isSearchMode()) {
  12541. return;
  12542. }
  12543.  
  12544. var result = generateCss();
  12545. var css = result.css, profile = result.profile, name = profile.name;
  12546.  
  12547. if (lastCssName === name) {
  12548. return;
  12549. }
  12550. lastCssName = name;
  12551. if (customStyleElement) {
  12552. customStyleElement.innerHTML = css;
  12553. } else {
  12554. customStyleElement = addStyle(css, 'customPlayerSize');
  12555. }
  12556. EventDispatcher.dispatch('onPlayerAlignmentAreaResize');
  12557. };
  12558. var toggleCustomSize = function(v) {
  12559. if (typeof v === 'boolean') {
  12560. $('body').toggleClass('w_size_custom', v);
  12561. } else {
  12562. $('body').toggleClass('w_size_custom');
  12563. }
  12564. };
  12565. var clearStyle = function() {
  12566. if (customStyleElement) {
  12567. customStyleElement.innerHTML = '';
  12568. toggleCustomSize(false);
  12569. }
  12570. lastCssName = '';
  12571. };
  12572. if (conf.customPlayerSize !== '') {
  12573. updateStyle();
  12574. toggleCustomSize();
  12575. }
  12576. EventDispatcher.addEventListener('on.config.customPlayerSize', function(v) {
  12577. if (v === '') {
  12578. clearStyle();
  12579. } else {
  12580. updateStyle();
  12581. toggleCustomSize(true);
  12582. }
  12583. });
  12584. EventDispatcher.addEventListener('on.config.wideCommentPanel', function(v) {
  12585. if (conf.customPlayerSize !== '') {
  12586. updateStyle();
  12587. }
  12588. });
  12589. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12590. if (conf.customPlayerSize !== '') {
  12591. updateStyle();
  12592. }
  12593. });
  12594.  
  12595. if (conf.debugMode) {
  12596. WatchItLater.debug.customSize = {
  12597. suggestProfile: suggestProfile,
  12598. getTargetSize: getTargetSize,
  12599. generateCss: generateCss,
  12600. updateStyle: updateStyle,
  12601. toggleCustomSize: toggleCustomSize
  12602. };
  12603. }
  12604.  
  12605. } //
  12606.  
  12607. function initStageVideo($, conf, w) {
  12608. var onStageVideoAvailabilityUpdated = function(v) {
  12609. $('#nicoplayerContainerInner').toggleClass('stageVideo', v);
  12610. };
  12611.  
  12612. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  12613. onStageVideoAvailabilityUpdated(WatchController.isStageVideoAvailable());
  12614. if (conf.forceEnableStageVideo) {
  12615. try {$('#external_nicoplayer')[0].setIsForceUsingStageVideo(true); } catch (e) { console.log(e);}
  12616. }
  12617. });
  12618.  
  12619. var pac = watch.PlayerInitializer.playerAreaConnector;
  12620.  
  12621. pac.addEventListener('onStageVideoAvailabilityUpdated', onStageVideoAvailabilityUpdated);
  12622.  
  12623. // console.log('StageVideo', $('#external_nicoplayer')[0].isStageVideoSupported() ? 'supported' : 'not supported');
  12624. // console.log('ColorSpaces', $('#external_nicoplayer')[0].getStageVideoSupportedColorSpaces());
  12625.  
  12626. } //
  12627.  
  12628. /**
  12629. * Chromeなら ALT+C
  12630. * Firefoxなら ALT+SHIFT+C で召喚
  12631. *
  12632. * どこでもコメント入力開始する隠し機能
  12633. *
  12634. * :m[0-9a-p] xxxxx でマイリストする機能もある
  12635. *
  12636. **/
  12637. function initInvisibleCommentInput($, conf, w) {
  12638. var $view = $(Util.here(function() {/*
  12639. <div class="invisibleInput">
  12640. <form action="javascript: void(0);">
  12641. <input type="text" value="" autocomplete="on" name="chat" accesskey="c"
  12642. list="myMylist" placeholder="コメント入力" class="invisibleCommentInput"
  12643. maxlength="75"
  12644. ></form>
  12645. <label><input type="checkbox" class="autoPause" checked="checked">動画を自動で一時停止する</label>
  12646. </div>
  12647. */}));
  12648. var css = Util.here(function() {/*
  12649. .invisibleInput {
  12650. position: fixed; z-index: 10000;
  12651. left: 100px; bottom: -100px; width: 300px;
  12652. padding: 16px;
  12653. border: 1px solid black;
  12654. background: #eee;
  12655.  
  12656. transition: bottom 0.4s ease 0.1s;
  12657. }
  12658. .invisibleInput.active {
  12659. bottom: 50px;
  12660. transition: bottom 0.4s ease;
  12661. box-shadow: 2px 2px 2px #333;
  12662. }
  12663.  
  12664. .invisibleInput .invisibleCommentInput {
  12665. width: 100%; font-size: 140%;
  12666. }
  12667. */});
  12668. var $dataList = $('<datalist id="myMylist"></datalist>');
  12669. var $form = $view.find('form');
  12670. var $input = $view.find('input');
  12671. var $autoPause = $view.find('.autoPause').prop('checked', conf.autoPauseInvisibleInput);
  12672.  
  12673. var prevent = function(e) { e.stopPropagation(); e.preventDefault(); };
  12674. var preventEsc = function(e) { if (e.keyCode === 27) { prevent(e); } };
  12675. var isAutoPause = function() { return !!$autoPause.prop('checked'); };
  12676.  
  12677. var mylistList = [];
  12678. var
  12679. onMylistUpdate = function(status, result) {
  12680. if (status === "ok") {
  12681. Popup.show('マイリストに追加しました');
  12682. } else {
  12683. Popup.alert('マイリスト追加に失敗: ' + result.error.description);
  12684. }
  12685. }, addMylist = function(n, d) {
  12686. var num = parseInt(n, 36);
  12687. var description = d || '';
  12688. if (num === 0) {
  12689. Mylist.addDefListItem(WatchController.getWatchId(), onMylistUpdate, description);
  12690. } else
  12691. if (mylistList[num]) {
  12692. Mylist.addMylistItem (WatchController.getWatchId(), mylistList[num].id, onMylistUpdate, description);
  12693. }
  12694. }, showMylist = function(n) {
  12695. var num = parseInt(n, 36);
  12696. if (num === 0) {
  12697. WatchController.showDeflist();
  12698. } else
  12699. if (mylistList[num]) {
  12700. WatchController.showMylist(mylistList[num].id);
  12701. }
  12702. }, seekVideo = function(v) {
  12703. var vpos = WatchController.vpos(), currentVpos = vpos;
  12704. if (v.match(/^([\-+]\d+)/)) {
  12705. vpos += parseInt(RegExp.$1, 10) * 1000;
  12706. } else
  12707. if (v.match(/^\d+$/)) {
  12708. vpos = parseInt(v, 10) * 1000;
  12709. } else
  12710. if (v.match(/^(\d+):(\d+)$/)) {
  12711. vpos = parseInt(RegExp.$1, 10) * 60 * 1000 + parseInt(RegExp.$2, 10) * 1000;
  12712. }
  12713.  
  12714. vpos = Math.max(vpos, 0);
  12715.  
  12716. if (vpos != currentVpos) {
  12717. console.log('seek video', vpos / 1000);
  12718. WatchController.vpos(vpos);
  12719. }
  12720. };
  12721.  
  12722. var isPlaying = false;
  12723. $input
  12724. .on('focus', function(e) {
  12725. isPlaying = WatchController.isPlaying();
  12726. if (isAutoPause()) {
  12727. WatchController.pause();
  12728. }
  12729. $view.addClass('active');
  12730. }).on('blur', function(e) {
  12731. if (isAutoPause() && isPlaying) {
  12732. WatchController.play();
  12733. }
  12734. $view.removeClass('active');
  12735. }).on('keyup', function(e) {
  12736. if (e.keyCode === 27) { // ESC
  12737. prevent(e);
  12738. $input.blur();
  12739. }
  12740. }).on('keydown', preventEsc).on('keyup', preventEsc);
  12741.  
  12742. $autoPause.on('click', function() {
  12743. conf.setValue('autoPauseInvisibleInput', !!$autoPause.prop('checked'));
  12744. });
  12745.  
  12746. $form
  12747. .on('submit', function(e) {
  12748. //prevent(e);
  12749. var val = $.trim($input.val());
  12750.  
  12751. if (val.match(/^:m([0-9a-p])(.*)/)) {
  12752. addMylist(RegExp.$1, RegExp.$2);
  12753. } else
  12754. if (val.match(/^:o([0-9a-p])/)) {
  12755. showMylist(RegExp.$1);
  12756. } else
  12757. if (val.match(/^:s[  \s](.+)/)) {
  12758. WatchController.nicoSearch(RegExp.$1, 'keyword');
  12759. } else
  12760. if (val.match(/^:t[  \s](.+)/)) {
  12761. WatchController.nicoSearch(RegExp.$1, 'tag');
  12762. } else
  12763. if (val.match(/^:v[  \s]([0-9:+\-]+)$/)) {
  12764. seekVideo(RegExp.$1);
  12765. } else {
  12766. WatchController.postComment(val);
  12767. }
  12768.  
  12769. setTimeout(function() { $input.val(''); } , 100);
  12770. $input.blur();
  12771. });
  12772.  
  12773. Mylist.loadMylistList(function(list) {
  12774. mylistList = list.concat();
  12775. mylistList.unshift({description: '', id: '', name: 'とりあえずマイリスト'});
  12776. var isFx = Util.Browser.isFx();
  12777.  
  12778. var tmp = [];
  12779. for (var i = 0, len = mylistList.length; i < len; i++) {
  12780. var c = i.toString(36);
  12781. // それぞれのブラウザで補完しやすい形式に
  12782. if (isFx) { // Fx
  12783. tmp.push('<option value=":m' + c + '">:m'+c+'\t 「' + mylistList[i].name + '」に追加</option>');
  12784. tmp.push('<option value=":o' + c + '">:o'+c+'\t 「' + mylistList[i].name + '」を開く</option>');
  12785. } else { // Chrome
  12786. tmp.push('<option value=":m' + c + '">「' + mylistList[i].name + '」に追加</option>');
  12787. tmp.push('<option value=":o' + c + '">「' + mylistList[i].name + '」を開く</option>');
  12788. }
  12789. }
  12790. tmp.sort();
  12791. if (isFx) {
  12792. tmp.push('<option value=":s ">:s [キーワード検索]</option>');
  12793. tmp.push('<option value=":t ">:t [タグ検索]</option>');
  12794. tmp.push('<option value=":v ">:v [シーク(秒)]</option>');
  12795. } else {
  12796. tmp.push('<option value=":s ">キーワード検索</option>');
  12797. tmp.push('<option value=":t ">タグ検索</option>');
  12798. tmp.push('<option value=":v ">シーク(秒)</option>');
  12799. }
  12800. $dataList.html(tmp.join('\n'));
  12801. });
  12802.  
  12803.  
  12804. addStyle(css, 'invisibleInput');
  12805. $('body').append($view).append($dataList);
  12806. } //
  12807.  
  12808. function initHeatMap($, conf, w) {
  12809. if (!conf.enableHeatMap) return;
  12810. //if (!w.Worker) return;
  12811. //
  12812. // TODO: Web Workers
  12813. var canvasWidth = 100, canvasHeight = 12;
  12814. var comments = [], duration = 0, canvas = null, context = null;
  12815. var commentReady = false, videoReady = false, updated = false, palette = [];
  12816. var __css__ = Util.here(function(){/*
  12817. #nicoHeatMapContainer {
  12818. position: absolute; z-index: 200;
  12819. bottom: 0px; left: 0;
  12820. width: 672px;
  12821. background: #000; height: 6px;
  12822. overflow: hidden;
  12823. }
  12824. .setting_panel #nicoHeatMapContainer { display: none; opacity: 0; }
  12825. .size_normal #nicoHeatMapContainer {
  12826. width: 898px;
  12827. }
  12828. .oldTypeCommentInput #nicoHeatMapContainer {
  12829. bottom: 29px;
  12830. display: none;
  12831. }
  12832. #nicoHeatMap {
  12833. position: absolute; top: 0; left: 0;
  12834. transform-origin: 0 0 0;-webkit-transform-origin: 0 0 0;
  12835. transform: scaleX(6.72);-webkit-transform: scaleX(6.72);
  12836. }
  12837. {* パズルみたいになってきた *}
  12838. body.size_normal:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12839. body.size_medium:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12840. body.videoExplorer #content.w_adjusted:hover #nicoHeatMapContainer,
  12841. body:not(.full_with_browser) #nicoHeatMapContainer.displayAlways {
  12842. display: block;
  12843. }
  12844. #nicoHeatMapContainer.displayAlways {
  12845. cursor: pointer;
  12846. }
  12847. .size_normal #nicoHeatMap {
  12848. transform: scaleX(8.98); -webkit-transform: scaleX(8.98);
  12849. }
  12850. .setting_panel #nicoHeatMapContainer, .full_with_browser #nicoHeatMapContainer, .size_small #content:not(.w_adjusted) #nicoHeatMapContainer {
  12851. display: none;
  12852. }
  12853.  
  12854. .full_with_browser.w_fullScreenMenu #nicoHeatMapContainer {
  12855. display: block;
  12856. width: 100%;
  12857. }
  12858. .full_with_browser.w_fullScreenMenu .oldTypeCommentInput #nicoHeatMapContainer {
  12859. bottom: 29px;
  12860. height: 6px;
  12861. }
  12862.  
  12863. .full_with_browser.w_fullScreenMenu.hideCommentInput #playerContainer #nicoHeatMapContainer {
  12864. position: fixed !important;
  12865. bottom: 0;
  12866. }
  12867. .full_with_browser.w_fullScreenMenu.hideCommentInput.fullWithPlaylist #playerContainer #nicoHeatMapContainer {
  12868. bottom: 167px;
  12869. }
  12870.  
  12871.  
  12872.  
  12873. */});
  12874. addStyle(__css__, 'NicoHeatMapCss');
  12875.  
  12876. watch.PlayerInitializer.playerAreaConnector.addEventListener('onCommentListInitialized', function() {
  12877. w.setTimeout(function() {
  12878. commentReady = true;
  12879. update();
  12880. }, 1000);
  12881. });
  12882. EventDispatcher.addEventListener('onVideoInitialized', function() {
  12883. videoReady = true;
  12884. update();
  12885. });
  12886. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function() {
  12887. commentReady = videoReady = updated = false;
  12888. clearCanvas();
  12889. });
  12890.  
  12891. var update = function() {
  12892. if (!commentReady || !videoReady || updated) return;
  12893. updated = true;
  12894. initCanvas();
  12895. getComments();
  12896. getDuration();
  12897. if (comments.length < 1 || duration < 1) {
  12898. return;
  12899. }
  12900. getHeatMap(function(map) {
  12901. var scale = duration >= canvasWidth ? 1 : (canvasWidth / duration);
  12902. var blockWidth = (canvasWidth / map.length) * scale;
  12903. for (i = map.length - 1; i >= 0; i--) {
  12904. context.fillStyle = palette[map[i]] || palette[0];
  12905. context.beginPath();
  12906. context.fillRect(i * scale, 0, blockWidth, canvasHeight);
  12907. }
  12908. });
  12909. };
  12910.  
  12911. var getComments = function() {
  12912. comments = [];
  12913.  
  12914. var list = watch.PlayerInitializer.commentPanelViewController.commentLists;
  12915. for (var i = 0; i < list.length; i++) {
  12916. if (list[i].listName === 'commentlist:main' && list[i].comments.length > 0) {
  12917. comments = list[i].comments;
  12918. break;
  12919. }
  12920. var ct = list[i].comments;
  12921. comments = (comments.length < ct.length) ? ct : comments;
  12922. }
  12923. list = null;
  12924. };
  12925. var getDuration = function() {
  12926. var exp = document.getElementById('external_nicoplayer');//$('#external_nicoplayer')[0];
  12927. duration = exp.ext_getTotalTime(); //
  12928. };
  12929. var initCanvas = function() {
  12930. if (!canvas) {
  12931. var $container = $('<div id="nicoHeatMapContainer" />');
  12932. $container.on('dblclick', function(e) {
  12933. e.preventDefault();
  12934. e.stopPropagation();
  12935. var $this = $(this).toggleClass('displayAlways');
  12936. conf.setValue('heatMapDisplayMode', $this.hasClass('displayAlways') ? 'always' : 'hover');
  12937. });
  12938. canvas = document.createElement('canvas');
  12939. canvas.id = 'nicoHeatMap';
  12940. canvas.width = canvasWidth;
  12941. canvas.height = canvasHeight;
  12942. $container.append(canvas);
  12943. $('#nicoplayerContainerInner').append($container);
  12944. context = canvas.getContext('2d');
  12945. if (conf.heatMapDisplayMode === 'always') {
  12946. $container.addClass('displayAlways');
  12947. }
  12948.  
  12949. initPalette();
  12950. }
  12951. clearCanvas();
  12952. };
  12953. var initPalette = function() {
  12954. for (var c = 0; c < 256; c++) {
  12955. var
  12956. r = Math.floor((c > 127) ? (c / 2 + 128) : 0),
  12957. g = Math.floor((c > 127) ? (255 - (c - 128) * 2) : (c * 2)),
  12958. b = Math.floor((c > 127) ? 0 : (255 - c * 2));
  12959. palette.push('rgb(' + r + ', ' + g + ', ' + b + ')');
  12960. }
  12961. };
  12962. var clearCanvas = function() {
  12963. if (!context) return;
  12964. context.fillStyle = palette[0];
  12965. context.beginPath();
  12966. context.fillRect(0, 0, canvasWidth, canvasHeight);
  12967. };
  12968.  
  12969. var getHeatMap = function(callback) {
  12970. var map = new Array(100), i = map.length; while(i > 0) map[--i] = 0;
  12971. var exp = $('#external_nicoplayer')[0];
  12972. var ratio = duration > map.length ? (map.length / duration) : 1;
  12973.  
  12974. for (i = comments.length - 1; i >= 0; i--) {
  12975. var pos = comments[i].vpos , mpos = Math.min(Math.floor(pos * ratio / 1000), map.length -1);
  12976. map[mpos]++;
  12977. }
  12978.  
  12979. var max = 0;
  12980. for (i = map.length - 4; i >= 0; i--) max = Math.max(map[i], max); // 末尾は固まってる事があるので参考にしない
  12981. if (max > 0) {
  12982. var rate = 255 / max;
  12983. for (i = map.length - 1; i >= 0; i--) {
  12984. map[i] = Math.min(255, Math.floor(map[i] * rate));
  12985. }
  12986. }
  12987. if (typeof callback === 'function') {
  12988. callback(map);
  12989. }
  12990. };
  12991. } // end of initHeatMap
  12992.  
  12993. /**
  12994. * 既存のポップアップの難点
  12995. *
  12996. * ・閉じる機能がなく、邪魔でも消えるまで待つしかない
  12997. * ・消えるまでの時間が毎回違う?
  12998. * ・クリックしたら消えるのかなと思ったらマイページに飛ばされる
  12999. * ・Chrome以外では動画プレイヤーの上に表示できない (半透明の部分が欠ける)
  13000. * ・↑によってプレイヤー上でフェードイン・アウトが出来ないため、まったく見えない状態から突然出現したようになる
  13001. * ・タイマー処理がバグっていて、一個目の表示中に2個目を連続表示すると2個目がすぐ消える
  13002. *
  13003. * … という所があんまりなので、パッチをあてて直す。
  13004. * ・Chrome以外は半透明をやめて画面外からのスライドにする
  13005. * ・CSS3アニメーションを使う(jQueryより軽い)
  13006. * ・クリックでマイページに飛ぶのをやめて、クリックで消えるようにする
  13007. * ・マウスオーバーしてる間は引っ込まない
  13008. * ・消えるまでの時間を4秒に固定
  13009. *
  13010. *
  13011. * このパッチでも直らない問題
  13012. * ・自分が動画投稿やレビューをしたという情報がなぜか自分にも通知される
  13013. *
  13014. */
  13015. function initPopupMarquee() {
  13016. if (!conf.replacePopupMarquee) { return; }
  13017. var
  13018. marquee = watch.PopupMarqueeInitializer.popupMarqueeViewController,
  13019. itemList = marquee.itemList,
  13020. $popup = $('#popupMarquee'),
  13021. $inner = $popup.find('.popupMarqueeContent'),
  13022. closeTimer = null,
  13023. popupDuration = 6000;
  13024.  
  13025. var
  13026. resetCloseTimer = function() {
  13027. if (closeTimer) {
  13028. clearTimeout(closeTimer);
  13029. closeTimer = null;
  13030. }
  13031. },
  13032. setCloseTimer = function() {
  13033. resetCloseTimer();
  13034. closeTimer = setTimeout(function() {
  13035. disappear();
  13036. closeTimer = null;
  13037. }, popupDuration);
  13038. },
  13039. onData = function(data) {
  13040. $inner.html(data);
  13041.  
  13042. $popup.removeClass('hide').removeClass('show');
  13043. setTimeout(function() {
  13044. $popup.removeClass('hide').addClass('show');
  13045. }, 100);
  13046. setCloseTimer();
  13047. },
  13048. disappear = function() {
  13049. $popup.removeClass('show');
  13050. resetCloseTimer();
  13051. setTimeout(function() {
  13052. if (!$popup.hasClass('show')) $popup.addClass('hide');
  13053.  
  13054. setTimeout(function() {
  13055. itemList.next();
  13056. }, Math.random() * 5000 + 5000);
  13057.  
  13058. }, 500);
  13059. },
  13060. __css__ = Util.here(function() {/*
  13061. #popupMarquee {
  13062. -webkit-filter: opacity( 0%); {* chrome以外はflashの上に半透明要素を置けない *}
  13063. background: #000 !important;
  13064. transition: -webkit-filter 0.25s ease-in, top 0.5s ease-in, bottom 0.5s ease-in; display: block;
  13065. }
  13066. #popupMarquee.show {
  13067. -webkit-filter: opacity(100%);
  13068. transition: -webkit-filter 1.00s ease-out, top 0.5s ease-out, bottom 0.5s ease-out; display: block;
  13069. }
  13070.  
  13071. #popupMarquee.hide {
  13072. opacity: 0; z-index: -1;
  13073. }
  13074.  
  13075. #popupMarquee.popupMarqueeTopRight:not(.show), #popupMarquee.popupMarqueeTopLeft:not(.show) { top: -600px; }
  13076. #popupMarquee.popupMarqueeBottomRight:not(.show), #popupMarquee.popupMarqueeBottomLeft:not(.show) { bottom: -600px; }
  13077. */});
  13078.  
  13079. addStyle(__css__, 'popupMarqueeFix');
  13080.  
  13081. itemList.eventTypeListenerMap.popup = []; //itemList.removeEventListener('popup', marquee.onData);
  13082. $popup
  13083. .css({opacity: ''})
  13084. .off('click').off('mouseover').off('mouseleave').off('mousemove')
  13085. .on('mouseover', resetCloseTimer)
  13086. .on('mouseout', setCloseTimer)
  13087. .on('click', disappear);
  13088.  
  13089. marquee.onData = $.proxy(onData, marquee);
  13090. marquee.disappear = $.proxy(disappear, marquee);
  13091. itemList.addEventListener('popup', $.proxy(onData, marquee));
  13092. } //
  13093.  
  13094.  
  13095. function initScroll($, conf, w) {
  13096. // 動画切り換え時にページの一番上までスクロールするようになったのを強引に阻止する
  13097. window.WatchApp.ns.model.state.WatchPageRouter.getInstance()._scroll = function() {};
  13098.  
  13099. var beforePlayerOffsetTop = 0, $playerAlignmentArea = $('#playerAlignmentArea');
  13100. var $window = $(window);
  13101. var beforeReset = function() {
  13102. beforePlayerOffsetTop = $playerAlignmentArea.offset().top;
  13103. };
  13104. var afterReset = function() {
  13105. var diff = $playerAlignmentArea.offset().top - beforePlayerOffsetTop;
  13106. var scrollTop = $window.scrollTop();
  13107. $window.scrollTop(scrollTop + diff);
  13108. };
  13109. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  13110. watchInfoModel.addEventListener('beforeReset', beforeReset);
  13111. watchInfoModel.addEventListener('afterReset', afterReset);
  13112.  
  13113.  
  13114. // 動画選択画面閉じた時にページの一番上までスクロールするようになったのを強引に阻止する
  13115. window.WatchApp.ns.util.WindowUtil.scroll_org = window.WatchApp.ns.util.WindowUtil.scroll;
  13116. var no_thanks = function() {
  13117. window.WatchApp.ns.util.WindowUtil.scroll = function() {};
  13118. };
  13119. var restore = function() {
  13120. window.WatchApp.ns.util.WindowUtil.scroll = window.WatchApp.ns.util.WindowUtil.scroll_org;
  13121. };
  13122.  
  13123. var vv = window.WatchApp.ns.init.BottomContentInitializer.videoExplorerModeViewController;
  13124. vv.onVideoExplorerClose_org = vv.onVideoExplorerClose;
  13125. vv.onVideoExplorerClose = $.proxy(function() {
  13126. no_thanks();
  13127. this.onVideoExplorerClose_org();
  13128. restore();
  13129. window.WatchApp.ns.util.WindowUtil.scrollFit('#playerContainerWrapper');
  13130. }, vv);
  13131.  
  13132. $ = conf = w = null;
  13133. } //
  13134.  
  13135. function initOther() {
  13136. if (conf.headerViewCounter) $('#siteHeaderInner').width($('#siteHeaderInner').width() + 200);
  13137.  
  13138. initAdditionalButtons();
  13139. initSquareThumbnail();
  13140.  
  13141. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  13142. if (name === 'squareThumbnail') {
  13143. initSquareThumbnail();
  13144. } else
  13145. if (name === 'enableAutoTagContainerHeight') {
  13146. if (newValue) { watch.TagInitializer.tagViewController.tagViewPinStatus.changeStatus(true); }
  13147. } else
  13148. if (name === 'enableMylistDeleteButton') {
  13149. $('.videoExplorerBody').toggleClass('enableMylistDeleteButton', newValue);
  13150. } else
  13151. if (name === 'enableYukkuriPlayButton') {
  13152. newValue ? Yukkuri.show() : Yukkuri.hide();
  13153. } else
  13154. if (name === 'noNicoru') {
  13155. $('body').toggleClass('w_noNicoru', newValue);
  13156. } else
  13157. if (name === 'playerBgStyle') {
  13158. $('#content')
  13159. .toggleClass('w_flat_gray', newValue === 'gray')
  13160. .toggleClass('w_flat_white', newValue === 'white');
  13161. } else
  13162. if (name === 'compactVideoInfo') {
  13163. $('#content, #outline').toggleClass('w_compact', newValue);
  13164. } else
  13165. if (name === 'disableHorizontalScroll') {
  13166. $('body').toggleClass('w_disableHorizontalScroll', newValue);
  13167. } else
  13168. if (name === 'hideCommentPanelSocialButtons') {
  13169. $('#playerTabContainer').toggleClass('w_noSocial', newValue);
  13170. }
  13171. });
  13172.  
  13173. if (conf.enableMylistDeleteButton) $('.videoExplorerBody').addClass('enableMylistDeleteButton');
  13174.  
  13175. if (conf.noNicoru) $('body').addClass('w_noNicoru');
  13176.  
  13177. if (conf.playerBgStyle !== '') $('#content').addClass('w_flat_' + conf.playerBgStyle);
  13178.  
  13179. if (conf.compactVideoInfo) $('#content, #outline').addClass('w_compact');
  13180. onWatchInfoReset(watchInfoModel);
  13181.  
  13182. if (conf.enableYukkuriPlayButton) { Yukkuri.show(); }
  13183.  
  13184. if (conf.disableHorizontalScroll) $('body').addClass('w_disableHorizontalScroll');
  13185.  
  13186. if (conf.hideCommentPanelSocialButtons) $('#playerTabContainer').addClass('w_noSocial');
  13187.  
  13188. $('#videoHeaderMenu .searchText input').attr({'accesskey': '@'}).on('focus', function() {
  13189. WatchController.scrollTop(0, 400);
  13190. });
  13191.  
  13192. watch.PlayerInitializer.commentPanelViewController.commentPanelContentModel.addEventListener('change', function(name) {
  13193. if (name === 'log_comment') {
  13194. $('.logDateSelect .logTime input')[0].setAttribute('type', 'time');
  13195. }
  13196. });
  13197.  
  13198. if (!w.Ads) {
  13199. // hostsに 0.0.0.0 ads.nicovideo.jp してるとスクリプトエラーがうるさいのでダミーを入れる
  13200. w.Ads = {
  13201. Advertisement: function() { return {set: function() {}}; }
  13202. };
  13203. }
  13204.  
  13205. var overrideGenerateURL = function() {
  13206. var wpc = WatchApp.ns.init.WatchPageInitializer.watchPageController;
  13207. wpc.generateWatchURL_org = wpc.generateWatchURL;
  13208. wpc.generateWatchURL = $.proxy(function(s) {
  13209. var ret = this.generateWatchURL_org(s);
  13210. // これのせいで既読リンクの色が変わらないので除去
  13211. ret = ret.replace(/\/(videoExplorer|ichiba)/, '');
  13212. return ret;
  13213. }, wpc);
  13214. };
  13215. overrideGenerateURL();
  13216.  
  13217. // 再現性不明のエラーをとりあえず握りつぶしつつ自動再生を3/2までの仕様に戻す
  13218. var overrrideWindowUtil = function() {
  13219. var wu = WatchApp.ns.util.WindowUtil;
  13220. wu.checkInview_org = wu.checkInview;
  13221. wu.checkInview = function() { return true; };
  13222. //wu.checkInview = $.proxy(function(a) {
  13223. // if (a.length < 0) { return true; }
  13224. // try {
  13225. // this.checkInview_org(a);
  13226. // } catch (e) {
  13227. // console.log('%cerror in WindowUtil.checkInview', 'color: red; ', e, a);
  13228. // console.trace();
  13229. // }
  13230. //}, wu);
  13231. };
  13232. overrrideWindowUtil();
  13233.  
  13234. // ニコる数を取得するためにコメントパネルがめちゃくちゃ重くなってるのを改善
  13235. WatchApp.ns.model.player.NicoPlayerConnector.getCommentNicoruCount = function(name, num) {
  13236. if (conf.noNicoru) {
  13237. return 0;
  13238. }
  13239. return window.PlayerApp.ns.player.Nicoplayer.getInstance().getCommentNicoruCount(name, num);
  13240. };
  13241.  
  13242. var playerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig;
  13243. if (conf.autoPlayIfWindowActive === 'yes') {
  13244. playerConfig.set({autoPlay: false});
  13245. }
  13246. if (conf.autoPlay2ndVideo) {
  13247. playerConfig.set({autoPlay: false});
  13248. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  13249. WatchController.pause();
  13250. WatchController.vpos(0);
  13251. setTimeout(function() {
  13252. playerConfig.set({autoPlay: true});
  13253. }, 3000);
  13254. });
  13255. $(window).on('beforeunload.watchItLater.autoPlay2ndVideo', function(e) {
  13256. playerConfig.set({autoPlay: false});
  13257. });
  13258. }
  13259.  
  13260. if (conf.debugMode) {
  13261. watch.PopupMarqueeInitializer.popupMarqueeViewController.itemList.addEventListener('popup', function(body) {
  13262. console.log('%c popup: ' + body, 'background: #0ff');
  13263. });
  13264. console.log(JSON.parse($('#watchAPIDataContainer').text()));
  13265.  
  13266. //WatchApp.ns.util.WindowUtil.shake = function() { console.log('%cshake', 'background: lightgreen;');};
  13267. //NicoPlayerConnector.getCommentNicoruCount_org = NicoPlayerConnector.getCommentNicoruCount;
  13268. }
  13269. }
  13270.  
  13271. // ?ref=等がついてるせいで未読既読のリンクの色が変わらなくなる問題の対策
  13272. // ShinjukuWatchと違いこっちはプレイリスト消えないモードがあるので、マイリスト等からの遷移でも遠慮無く全部消す
  13273. if (location.href.indexOf('?') >= 0) {
  13274. window.history.replaceState('', '', location.href.split('?')[0]);
  13275. }
  13276.  
  13277.  
  13278. function initTest(test) {
  13279. var console = window.console;
  13280. var expect = test.expect;
  13281. WatchApp.mixin(WatchItLater.test.spec, {
  13282. testChannelVideo: function(def) {
  13283. ChannelVideoList.load(function(result) {
  13284. console.log('ChannelVideoList.load', result);
  13285. expect(result.name).toEqual('ニコニコアプリちゃんねるの動画', 'チャンネル名');
  13286. expect(result.list.length >= 30).toBeTrue('2013/08/28時点で33件');
  13287. def.resolve();
  13288. }, {id: '55', ownerName: 'ニコニコアプリちゃんねる'});
  13289. },
  13290. testNewNicoSearch: function(def) {
  13291. var size = 15;
  13292. var search = new NewNicoSearch({});
  13293. search.load({query: 'vocaloid', size: size}, function(err, result) {
  13294. console.log('testNewNicoSearch.load', err, result);
  13295. expect(err).toBeNull('err === null');
  13296. expect(result[0].dqnid) .toBeTruthy('先頭にdqnidが含まれる(なんの略?)');
  13297. expect(typeof result[0].values[0].total).toEqual('number', 'ヒット件数');
  13298. expect(result[0].values[0].service) .toEqual('video', '検索の種類');
  13299.  
  13300. expect(result[1].type).toEqual('stats', 'type === stats'); // データの開始?
  13301.  
  13302. expect(result[2].type ).toEqual ('hits', 'type === hits');
  13303. expect(result[2].values ).toBeTruthy('ヒットした内容');
  13304. expect(result[2].values.length ).toEqual (size, 'sizeで指定した件数が返る');
  13305. expect(result[2].values[0].cmsid).toBeTruthy('ヒットした内容にデータが含まれる');
  13306.  
  13307. expect(result[3].type).toEqual('hits', 'type === stats'); // データの終了?
  13308. def.resolve();
  13309. });
  13310. },
  13311. testNewNicoSearchWrapperQuery: function(def) {
  13312. var wrapper = new NewNicoSearchWrapper({search: {}});
  13313. var params = {
  13314. searchWord: 'VOCALOID',
  13315. searchType: 'tag',
  13316. u: '1m',
  13317. l: 'short',
  13318. sort: 'l',
  13319. order: 'a',
  13320. page: 3
  13321. };
  13322. var query = wrapper._buildSearchQuery(params);
  13323.  
  13324. console.log(params, query);
  13325. expect(query.query).toEqual(params.searchWord, '検索ワードのセット');
  13326. expect(query.from).toEqual(params.page * 32 - 32, 'ページ番号 -> fromの変換');
  13327. expect(query.sort_by).toEqual('length_seconds', 'l -> length_seconds');
  13328. expect(query.order).toEqual('asc', 'a -> asc');
  13329.  
  13330. // TODO:
  13331. expect(JSON.stringify(query.search).indexOf('["tags"]') >= 0).toBeTrue('タグ検索');
  13332. var filters = JSON.stringify(query.filters);
  13333. //console.log(filters);
  13334. expect(query.filters.length >= 2).toBeTrue('filters.lengthが2以上');
  13335. expect(filters.indexOf('"field":"start_time"') >= 0).toBeTrue('filtersにstart_timeが含まれる');
  13336. expect(filters.indexOf('"field":"length_seconds"') >= 0).toBeTrue('filtersにlength_secondsが含まれる');
  13337. def.resolve();
  13338. },
  13339. testNewNicoSearchWrapper: function(def) {
  13340. console.log('testNewNicoSearchWrapper');
  13341. var search = new NewNicoSearch({});
  13342. var wrapper = new NewNicoSearchWrapper({search: search});
  13343. wrapper.load({searchWord: 'ぬこぬこ動画', size: 100}, function(err, result) {
  13344. console.log('testNewNicoSearchWrapper.load', err, result);
  13345. expect(err).toBeNull('err === null');
  13346. expect(typeof result.count).toEqual('number', '件数がnumber');
  13347. expect(result.count > 0).toBeTrue('件数が入っている');
  13348. expect(result.list.length).toBeTruthy('データが入っている');
  13349. expect(result.list.length).toEqual(100, 'sizeで指定した件数が入っている');
  13350. expect(result.list[0].type).toEqual(0, 'type === 0');
  13351. expect(/^\d+:\d+/.test(result.list[0].length)).toBeTrue('動画長がmm:dd形式で入ってる');
  13352. def.resolve();
  13353.  
  13354. });
  13355. },
  13356. testNicoSearchRelatedTag: function(def) {
  13357. var related = new NicoSearchRelatedTag({});
  13358. related.load('voiceroid', function(err, result) {
  13359. console.log('testNicoSearchRelatedTag.load', err, result);
  13360. console.log(expect(err));
  13361. expect(err).toBeNull('err === null');
  13362. expect(result.type).toEqual('tags', 'type === "tags"');
  13363. expect(result.values).toBeTruthy('データが入っている');
  13364. expect(typeof result.values[0]._rowid).toEqual('number', 'データに_rowidが入っている');
  13365. expect(typeof result.values[0].tag) .toEqual('string', 'データにtagが入っている');
  13366. def.resolve();
  13367. });
  13368. },
  13369. testSearchSuggest: function(def) {
  13370. var suggest = new NicoSearchSuggest({});
  13371. suggest.load('MMD', function(err, result) {
  13372. console.log('testSearchSuggest.load', err, result);
  13373. console.log(expect(err));
  13374. expect(err).toBeNull('err === null');
  13375. expect(result.candidates).toBeTruthy('suggestの中身がある');
  13376. expect(result.candidates.length).toBeTruthy('suggestのlengthがある');
  13377. def.resolve();
  13378. });
  13379. },
  13380. testUpdateMylistComment: function(def) {
  13381. // 一個以上マイリストがあって先頭のマイリストになにか登録されている必要がある
  13382. var Mylist = WatchItLater.mylist;
  13383. var randomMessage = 'RND: ' + Math.random();
  13384.  
  13385. var d = new $.Deferred();
  13386. d.promise()
  13387. .then(function() {
  13388. var d = new $.Deferred();
  13389. Mylist.loadMylistList(function(mylistList) {
  13390. expect(mylistList.length > 0).toBeTruthy('マイリスト一覧が1件以上');
  13391. console.log('先頭のマイリスト', mylistList[0].id, mylistList[0].name);
  13392. var groupId = mylistList[0].id;
  13393. if (mylistList.length <= 0) {
  13394. d.reject();
  13395. return;
  13396. }
  13397. d.resolve(groupId);
  13398. });
  13399. return d.promise();
  13400. })
  13401. .then(function(groupId) {
  13402. var d = new $.Deferred();
  13403. Mylist.reloadMylist(groupId, function(mylist) {
  13404. expect(mylist.length > 0).toBeTruthy('マイリストアイテムが一個以上');
  13405. var item = mylist[0];
  13406. var watchId = item.item_data.watch_id;
  13407. console.log('マイリスト先頭のアイテム', watchId, item.item_data.title);
  13408. d.resolve(watchId, groupId);
  13409. });
  13410. return d.promise();
  13411. })
  13412. .then(function(watchId, groupId) {
  13413. var d = new $.Deferred();
  13414. Mylist.updateMylistItem(watchId, groupId, function(result) {
  13415. expect(result).toEqual('ok', 'updateMylistItem() result=ok');
  13416. d.resolve(watchId, groupId);
  13417. }, randomMessage);
  13418. return d.promise();
  13419. })
  13420. .then(Util.Deferred.wait(500))
  13421. .then(function(watchId, groupId) {
  13422. var d = new $.Deferred();
  13423. Mylist.reloadMylist(groupId, function(newlist) {
  13424. console.log('reloadMylist', groupId, newlist);
  13425. expect(newlist[0].description)
  13426. .toEqual(randomMessage, 'マイリストコメントが更新できている => ' + newlist[0].description);
  13427. d.resolve();
  13428. });
  13429. return d.promise();
  13430. }).then(function() {
  13431. def.resolve();
  13432. });
  13433. d.resolve();
  13434. },
  13435. testVideoRanking: function(def) {
  13436. VideoRanking.load(null, {id: -4000})
  13437. .then(function(result) {
  13438. console.log('VideoRanking.load result:', result);
  13439. expect(result.name).toEqual('カテゴリ合算', 'ダミーマイリストの名前が一致');
  13440. expect(result.list.length).toEqual(300, 'カテゴリ合算ランキングは300件');
  13441. expect(result.list[ 0].title.indexOf('第001位')).toEqual(0,'ランキング1位のタイトル');
  13442. expect(result.list[299].title.indexOf('第300位')).toEqual(0,'ランキング300位のタイトル');
  13443. def.resolve();
  13444. },
  13445. function() {
  13446. def.reject();
  13447. });
  13448. },
  13449. testNicorepoVideo: function(def) {
  13450. NicorepoVideo.loadAll(null, null)
  13451. .then(function(result) {
  13452. console.log('NicorepoVideo.loadAll result:', result);
  13453. expect(result.name).toEqual('【ニコレポ】すべての動画', 'ダミーマイリストの名前が一致');
  13454. expect(result.list).toBeTruthy('ニコレポがある');
  13455. def.resolve();
  13456. },
  13457. function() {
  13458. def.reject();
  13459. });
  13460. },
  13461. testVideoInfoLoader: function(def) {
  13462. var loader = new VideoInfoLoader({});
  13463. $.when(
  13464. loader.load('sm9').then(function(result) {
  13465. expect(result.id).toEqual('sm9', '存在する動画ID');
  13466. expect(result.length).toEqual('5:19', 'length');
  13467. return this.done();
  13468. }, function(err) {
  13469. return this.fail();
  13470. }),
  13471.  
  13472. loader.load('sm1').then(function(result) {
  13473. return new $.Deferred().reject().promise();
  13474. }, function(resp) {
  13475. expect(resp.status).toEqual('fail', '存在しない動画ID');
  13476. return new $.Deferred().resolve().promise();
  13477. })
  13478.  
  13479. ).then(function() { def.resolve(); }, function() { def.reject(); });
  13480. },
  13481. testRelatedVideo: function(def) {
  13482. var loader = new RelatedVideo({});
  13483. loader.load('sm9').then(function(result) {
  13484. console.log('RelatedVideo', result);
  13485. expect(result.list).toBeTruthy('関連動画がある');
  13486. expect(result.list[0].title).toBeTruthy('タイトルがある');
  13487. expect(result.list[0].title.length >= 0).toBeTrue('タイトル長がある');
  13488. expect(typeof result.list[0].type === 'number').toBeTrue('type属性がある');
  13489. def.resolve();
  13490. });
  13491. },
  13492. testVideoArray: function(def) {
  13493. window.WatchItLater.loader.videoArrayAPILoader.load(['sm9', 'sm13']).then(function(result) {
  13494. console.log('VideoArrayAPILoader', result);
  13495. expect(result['sm9']).toBeTruthy('動画情報');
  13496. expect(result['sm13'].title).toBeTruthy('タイトルがある');
  13497. expect(result['sm13'].title.length >= 0).toBeTrue('タイトル長がある');
  13498. window.WatchItLater.loader.videoArrayAPILoader.load(['sm13', '1394785382']).then(function(result) {
  13499. console.log('VideoArrayAPILoader', result);
  13500. expect(result['1394785382']).toBeTruthy('スレッドIDでも引ける');
  13501. expect(result['1394785382'].title).toEqual('鬼灯の冷徹 第10話「十王の晩餐」「ダイエットは地獄みたいなもの」', '動画タイトル一致');
  13502. def.resolve();
  13503. });
  13504. });
  13505. },
  13506. testCeAPIVideoArray: function(def) {
  13507. window.WatchItLater.loader.ceAPIClient.videoArray(['sm9', 'sm13']).then(function(result) {
  13508. console.log('ceAPIAPIClient.videoArray', result);
  13509. expect(result.status).toEqual('ok', 'status');
  13510. expect(result.video_info).toBeTruthy('動画情報がある');
  13511. expect(result.video_info[0].video.id).toEqual('sm9', '動画id');
  13512. def.resolve();
  13513. });
  13514. }
  13515.  
  13516.  
  13517.  
  13518. }); // end WatchApp.mixin
  13519.  
  13520. } // end initTest
  13521.  
  13522. window.console.time('init WatchItLater');
  13523. // window.console.profile('init WatchItLater');
  13524. LocationHashParser.initialize();
  13525. initNews();
  13526. initShortcutKey();
  13527. initMouse();
  13528. initTouch();
  13529. initEvents();
  13530.  
  13531. initSearchContent($, conf, w);
  13532. initUserVideoContent($, conf, w);
  13533. initMylistContent($, conf, w);
  13534. initUploadedVideoContent($, conf, w);
  13535. initDeflistContent($, conf, w);
  13536. initVideoExplorer($, conf, w);
  13537.  
  13538. initRightPanel($, conf, w);
  13539. initLeftPanel($, conf, w);
  13540. initVideoReview($, conf, w);
  13541.  
  13542. initHidariue();
  13543. initVideoCounter();
  13544. initScreenMode();
  13545. initPlaylist($, conf, w);
  13546.  
  13547. initPageBottom($, conf, w);
  13548. initPageHeader($, conf, w);
  13549. initVideoTagContainer($, conf, w);
  13550.  
  13551. initNicoS($, conf, w);
  13552. initInvisibleCommentInput($, conf, w);
  13553. initOtherCss();
  13554. initCustomPlayerSize($, conf, w);
  13555.  
  13556. initStageVideo($, conf, w);
  13557. initHeatMap($, conf, w);
  13558. initPopupMarquee();
  13559. initMylistPanel($, conf, w);
  13560. initScroll($, conf, w);
  13561. initOther();
  13562. // window.console.profileEnd('init WatchItLater');
  13563. window.console.timeEnd('init WatchItLater');
  13564.  
  13565. onWindowResizeEnd();
  13566.  
  13567. if (conf.debugMode) {
  13568. initTest(WatchItLater.test);
  13569. }
  13570. };
  13571.  
  13572. if (window.PlayerApp) {
  13573. (function() {
  13574. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  13575. if (watchInfoModel.initialized) {
  13576. window.WatchItLater.WatchController =
  13577. window.WatchController =
  13578. _watchController(window);
  13579. ZeroFunc(window);
  13580. } else {
  13581. var onReset = function() {
  13582. watchInfoModel.removeEventListener('reset', onReset);
  13583. window.setTimeout(function() {
  13584. window.WatchItLater.WatchController =
  13585. window.WatchController =
  13586. _watchController(window);
  13587.  
  13588. ZeroFunc(window);
  13589. }, 100);
  13590. };
  13591. watchInfoModel.addEventListener('reset', onReset);
  13592. if (conf.initializeImmediately) {
  13593. console.log('%cinitialize Immediately', 'background: lightgreen;');
  13594. WatchApp.ns.EmbeddedWatchData.run_ = WatchApp.ns.EmbeddedWatchData.run;
  13595. WatchApp.ns.EmbeddedWatchData.run = function() {};
  13596. window.setTimeout(function() {
  13597. console.time('initialize Immediately');
  13598. WatchApp.ns.EmbeddedWatchData.run_(JSON.parse($('#configDataContainer').html()));
  13599. console.timeEnd('initialize Immediately');
  13600. }, 0);
  13601. }
  13602. }
  13603. })();
  13604. } else
  13605. if (location.host === 'www.nicovideo.jp' && location.pathname ==='/stamp') {
  13606. niconicodoRedirect();
  13607. }
  13608.  
  13609.  
  13610. /**
  13611. * 原宿プレイヤーでのあれこれ
  13612. *
  13613. * マイリストパネルだけ追加
  13614. *
  13615. */
  13616. (function() {
  13617. if (!w.Video) return;
  13618. if (!location.href.match(/\/watch\/(sm\d+|nm\d+|so\d+|\d+)/)) return;
  13619. var watchId = void 0, videoId = void 0;
  13620. if (w.Video === null) {
  13621. watchId = RegExp.$1;
  13622. w.Video = {id: watchId};
  13623. } else {
  13624. Video = w.Video;
  13625. watchId = Video.v;
  13626. videoId = Video.id;
  13627. }
  13628. watchId = RegExp.$1;
  13629. var iframe = Mylist.getPanel('');
  13630. iframe.id = "mylist_add_frame";
  13631. iframe.setAttribute('style', 'position: fixed; right: 0; bottom: 0;');
  13632.  
  13633. document.body.appendChild(iframe);
  13634. iframe.watchId(watchId, videoId);
  13635. })();
  13636.  
  13637.  
  13638. /**
  13639. * キーボードイベント他
  13640. *
  13641. */
  13642. (function() {
  13643. w.document.body.addEventListener('keydown', function(e) {
  13644. if (e.keyCode === 27 || e.keyCode === 88) { // ESC or x
  13645. AnchorHoverPopup.hidePopup();
  13646. Popup.hide();
  13647. }
  13648. }, false);
  13649. w.document.body.addEventListener('click', function(e) {
  13650. var tagName = e.target.tagName, className = e.target.className;
  13651. //console.log(tagName, className);
  13652. if (tagName !== 'BUTTON' && tagName !== 'SELECT' && tagName !== 'OPTION' && className !== 'popupTagItem' && className.indexOf('mylistPopupPanel') < 0) {
  13653. AnchorHoverPopup.hidePopup();
  13654. }
  13655.  
  13656. }, false);
  13657. var touchInitialized = false;
  13658. TouchEventDispatcher.onflick(function(e) {
  13659. if (e.direction === 'right') {
  13660. if (!touchInitialized) {
  13661. document.getElementById('videoTagPopupContainer').className += ' w_touch';
  13662. touchInitialized = true;
  13663. }
  13664. }
  13665. }, false);
  13666. // w.document.body.addEventListener('dblclick', function(e) {var tagName = e.target.tagName, className = e.target.className;console.log(tagName, className);});
  13667.  
  13668. })(w);
  13669.  
  13670. //===================================================
  13671. //===================================================
  13672. //===================================================
  13673.  
  13674. }); // end of monkey();
  13675.  
  13676. /**
  13677. * スマートフォン用APIを利用して動画情報を取得する Firefox + Greasemonkey用
  13678. */
  13679. var spapi = function() {
  13680. if (window.name.indexOf('watchItLaterAPILoader') < 0 ) { return; }
  13681. var resp = document.getElementsByTagName('nicovideo_video_response');
  13682. var session = location.hash.length > 1 ? location.hash.substr(1) : location.search;
  13683. var origin = 'http://' + location.host.replace(/^.*?\./, 'www.');
  13684. var xml = '';
  13685. if (resp.length > 0) {
  13686. xml = resp[0].outerHTML;
  13687. }
  13688.  
  13689. try {
  13690. parent.postMessage(JSON.stringify({
  13691. id: 'WatchItLater',
  13692. type: 'VideoArrayAPILoader',
  13693. body: {
  13694. session: session,
  13695. xml: xml
  13696. }
  13697. }),
  13698. origin);
  13699. } catch (e) {
  13700. console.log('err', e);
  13701. }
  13702. };
  13703.  
  13704. /**
  13705. * Vita/3DS用APIを利用して情報を取得する Chrome + Tampermonkey用
  13706. * 参考: http://www59.atwiki.jp/nicoapi/pages/24.html
  13707. */
  13708. var ceapi = function() {
  13709. if (window.name.indexOf('ceAPILoader') < 0 ) { return; }
  13710. var origin = 'http://www.nicovideo.jp'; //'http://' + location.host.replace(/^.*?\./, 'www.');
  13711.  
  13712. var xmlHttpRequest = function(options) {
  13713. try {
  13714. var req = new XMLHttpRequest();
  13715. var method = options.method || 'GET';
  13716. req.onreadystatechange = function() {
  13717. if (req.readyState === 4) {
  13718. if (typeof options.onload === 'function') options.onload(req);
  13719. }
  13720. };
  13721. req.open(method, options.url, true);
  13722. if (options.headers) {
  13723. for (var h in options.headers) {
  13724. req.setRequestHeader(h, options.headers[h]);
  13725. }
  13726. }
  13727.  
  13728. req.send(options.data || null);
  13729. } catch (e) {
  13730. console.error(e);
  13731. }
  13732. };
  13733.  
  13734. window.addEventListener('message', function(event) {
  13735. var data = JSON.parse(event.data), timeoutTimer = null, isTimeout = false;
  13736. if (!data.url) { return; }
  13737. var sessionId = data.sessionId;
  13738. xmlHttpRequest({
  13739. url: data.url,
  13740. onload: function(resp) {
  13741.  
  13742. if (isTimeout) { return; }
  13743. else { window.clearTimeout(timeoutTimer); }
  13744.  
  13745. try {
  13746. parent.postMessage(JSON.stringify({
  13747. id: 'WatchItLater',
  13748. type: 'ceAPILoader',
  13749. body: {
  13750. sessionId: sessionId,
  13751. status: 'ok',
  13752. url: data.url,
  13753. body: resp.responseText
  13754. }
  13755. }),
  13756. origin);
  13757. } catch (e) {
  13758. console.log(
  13759. '%cError: parent.postMessage - ',
  13760. 'color: red; background: yellow',
  13761. e, event.origin, event.data);
  13762. }
  13763. }
  13764. });
  13765.  
  13766. timeoutTimer = window.setTimeout(function() {
  13767. isTimeout = true;
  13768. parent.postMessage(JSON.stringify({
  13769. id: 'WatchItLater',
  13770. type: 'ceAPILoader',
  13771. body: {
  13772. sessionId: sessionId,
  13773. status: 'timeout',
  13774. url: data.url
  13775. }
  13776. }),
  13777. origin);
  13778. }, 30000);
  13779.  
  13780. });
  13781.  
  13782. try {
  13783. parent.postMessage(JSON.stringify({
  13784. id: 'WatchItLater',
  13785. type: 'ceAPILoader',
  13786. body: {
  13787. status: 'initialized'
  13788. }
  13789. }),
  13790. origin);
  13791. } catch (e) {
  13792. console.log('err', e);
  13793. }
  13794. };
  13795.  
  13796.  
  13797. try {
  13798. if (location.host === 'flapi.nicovideo.jp') {
  13799. return;
  13800. } else
  13801. if (location.host === 'i.nicovideo.jp') {
  13802. spapi();
  13803. } else
  13804. if (location.host === 'api.ce.nicovideo.jp') {
  13805. ceapi();
  13806. } else
  13807. if (location.host.indexOf('smile-') >= 0) {
  13808. return;
  13809. } else
  13810. if (location.host.indexOf('localhost.') === 0 || location.host.indexOf('www.') === 0 || !this.GM_getValue || this.GM_getValue.toString().indexOf("not supported")>-1) {
  13811. isNativeGM = false;
  13812. var inject = document.createElement("script");
  13813. inject.id = "monkey";
  13814. inject.setAttribute("type", "text/javascript");
  13815. inject.setAttribute("charset", "UTF-8");
  13816.  
  13817. inject.appendChild(document.createTextNode("(" + monkey + ")(false)"));
  13818. // inject.appendChild(document.createTextNode("try {(" + monkey + ")(false) } catch(e) { console.log(e); }"));
  13819.  
  13820. if (document.body) {
  13821. document.body.appendChild(inject);
  13822. } else {
  13823. document.documentElement.appendChild(inject);
  13824. }
  13825. } else {
  13826. // やや古いFirefoxはここらしい
  13827. monkey(true);
  13828. }
  13829.  
  13830. } catch(e) {
  13831. // 最近のFirefoxはここに飛んでくる
  13832. monkey(true);
  13833. }
  13834. })();
  13835.  
  13836.