WatchItLater

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

目前為 2014-07-23 提交的版本,檢視 最新版本

  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.140723
  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. watchCounter: 0, // お前は今までに見た動画の数を覚えているのか?をカウントする
  179. forceEnableStageVideo: false,
  180. forceExpandStageVideo: false,
  181. enableAutoPlaybackContinue: false, // 一定時間操作しなかくても自動再生を続行
  182. lastLeftTab: 'videoInfo',
  183. lastRightTab: 'w_videoInfo',
  184. lastRightTabInExplorer: 'comment',
  185. lastControlPanelPosition: '',
  186. enableSortTypeMemory: true, // 検索のソート順を記憶する
  187. searchSortType: 'n', //
  188. searchSortOrder: 'd', // 'd'=desc 'a' = asc
  189. fxInterval: 40, // アニメーションのフレームレート 40 = 25fps
  190. enableGpuLayer: false, // 一部の要素でGPU描画を有効にしてみる?
  191. debugMode: false
  192. };
  193.  
  194.  
  195. //===================================================
  196. //===================================================
  197. //===================================================
  198.  
  199. function addStyle(styles, id) {
  200. var elm = document.createElement('style');
  201. window.setTimeout(function() {
  202. elm.type = 'text/css';
  203. if (id) { elm.id = id; }
  204.  
  205. var text = styles.toString();
  206. text = document.createTextNode(text);
  207. elm.appendChild(text);
  208. var head = document.getElementsByTagName('head');
  209. head = head[0];
  210. head.appendChild(elm);
  211. }, 0);
  212. return elm;
  213. }
  214.  
  215. if (!isNativeGM) {
  216. this.GM_xmlhttpRequest = function(options) {
  217. try {
  218. var req = new XMLHttpRequest();
  219. var method = options.method || 'GET';
  220. req.onreadystatechange = function() {
  221. if (req.readyState === 4) {
  222. if (typeof options.onload === "function") options.onload(req);
  223. }
  224. };
  225. req.open(method, options.url, true);
  226. if (options.headers) {
  227. for (var h in options.headers) {
  228. req.setRequestHeader(h, options.headers[h]);
  229. }
  230. }
  231.  
  232. req.send(options.data || null);
  233. } catch (e) {
  234. if (conf.debugMode) console.log(e);
  235. }
  236. };
  237. }
  238.  
  239. (function() { // 各ページ共通
  240. var __css__ = (function() {/*
  241. .tagItemsPopup {
  242. background: #eef;
  243. }
  244. .popupMenu {
  245. position: absolute;
  246. min-width: 200px;
  247. font-Size: 12pt;
  248. z-index: 2000000;
  249. box-shadow: 2px 2px 2px #888;
  250. }
  251. .popupMenu ul, .popupMenu ul li {
  252. position: relative;
  253. list-style-type: none;
  254. margin: 0; padding: 0;
  255. white-space: nowrap;
  256. }
  257. .tagItemsPopup .icon{
  258. width: 17px;
  259. height: 15px;
  260. }
  261. .tagItemsPopup .nicodic, .tagItemsPopup .newsearch {
  262. margin: 1px 4px 1px 1px;
  263. }
  264. .tagItemsPopup .nicodic:hover, .tagItemsPopup .newsearch:hover {
  265. margin: 0px 3px 0px 0px;
  266. border: 1px outset;
  267. }
  268.  
  269. .mylistListPopup {
  270. position:absolute;
  271. background: #fff;
  272. overflow: visible;
  273. padding: 8px;
  274. border: 1px outset #333;
  275. transition: opacity 0.3s ease;
  276. }
  277. .mylistListPopup.popupMenu ul li {
  278. position: relative;
  279. margin: 2px 8px;
  280. overflow-y: visible;
  281. }
  282. .mylistListPopup:not(.show) {
  283. left: -9999px;
  284. top: -9999px;
  285. opacity: 0;
  286. }
  287. .mylistListPopup.show {
  288. opacity: 1;
  289. }
  290. .mylistListPopup .listInner {
  291. -webkit-column-width: auto; -moz-column-width: auto;
  292. -webkit-column-count: 1 !important; {* Chromeだけバグるので *}
  293. }
  294. .mylistListPopup .icon {
  295. display: inline-block;
  296. width: 18px;
  297. height: 14px;
  298. margin: -4px 4px 0 0;
  299. vertical-align: middle;
  300. margin-right: 15px;
  301. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  302. transform: scale(1.5); -webkit-transform: scale(1.5);
  303. transform-origin: 0 0 0; -webkit-transform-origin: 0 0 0;
  304. transition: transform 0.1s ease, box-shadow 0.1s ease;
  305. -webkit-transition: -webkit-transform 0.1s ease, box-shadow 0.1s ease;
  306. cursor: pointer;
  307. }
  308. .mylistListPopup .icon:hover {
  309. background-color: #ff9;
  310. transform: scale(2); -webkit-transform: scale(2);
  311. }
  312. .mylistListPopup .icon:after {
  313. content: '開く';
  314. position: absolute;
  315. bottom: 0px;
  316. right: 12px;
  317. padding: 2px;
  318. opacity: 0;
  319. transform: scale(0.5); -webkit-transform: scale(0.5);
  320. z-index: -1;
  321. }
  322. .mylistListPopup .icon:hover:after {
  323. {*box-shadow: 2px 2px 2px #888;*}
  324. background: #fff;
  325. z-index: 100;
  326. opacity: 1;
  327. }
  328. .mylistListPopup .deflist .icon { background-position: 0 -253px; }
  329. .mylistListPopup .folder1 .icon { background-position: 0 -23px;}
  330. .mylistListPopup .folder2 .icon { background-position: 0 -46px;}
  331. .mylistListPopup .folder3 .icon { background-position: 0 -69px;}
  332. .mylistListPopup .folder4 .icon { background-position: 0 -92px;}
  333. .mylistListPopup .folder5 .icon { background-position: 0 -115px;}
  334. .mylistListPopup .folder6 .icon { background-position: 0 -138px;}
  335. .mylistListPopup .folder7 .icon { background-position: 0 -161px;}
  336. .mylistListPopup .folder8 .icon { background-position: 0 -184px;}
  337. .mylistListPopup .folder9 .icon { background-position: 0 -207px;}
  338.  
  339.  
  340. .mylistListPopup .name {
  341. display: inline-block;
  342. vertical-align: middle;
  343. font-size: 110%;
  344. color: #666;
  345. text-derocation: none !important;
  346. }
  347. .mylistListPopup .name:hover {
  348. color: #000;
  349. background-color: #ff9;
  350. }
  351. .mylistListPopup .name:after {
  352. content: ' に登録';
  353. font-size: 75%;
  354. color: #fff;
  355. }
  356. .mylistListPopup .name.exist:after {
  357. content: ' に登録済';
  358. color: #933;
  359. }
  360. .mylistListPopup .name:hover:after {
  361. color: #666;
  362. }
  363.  
  364. {* マイリスト登録パネル *}
  365. .mylistPopupPanel {
  366. height: 24px;
  367. z-index: 10000;
  368. {*border: 1px solid silver;
  369. border-radius: 3px; *}
  370. padding: 0;
  371. margin: 0;
  372. overflow: hidden;
  373. display: inline-block;
  374. background: #eee;
  375. }
  376. .mylistPopupPanel.fixed {
  377. position: fixed; right: 0; bottom: 0;
  378. transition: right 0.1s ease-out;
  379. }
  380. .mylistPopupPanel.fixed.left {
  381. right: auto;
  382. left: 0;
  383. }
  384. .mylistPopupPanel.fixed.top {
  385. bottom: auto;
  386. top: 0;
  387. }
  388.  
  389. .mylistPopupPanel.fixed>* {
  390. transition: opacity 0.1s ease-out;
  391. }
  392. .mylistPopupPanel>*>* {
  393. transition: background 0.5s ease 0.5s, color 0.5s ease 0.5s;
  394. }
  395. .full_with_browser .mylistPopupPanel, .mylistPopupPanel.black {
  396. background: #000; border: 0;
  397. }
  398. body.full_with_browser .mylistPopupPanel *,body .mylistPopupPanel.black.fixed * {
  399. background: #000; color: #888; border-color: #333;
  400. }
  401. .full_with_browser .mylistPopupPanel.hideAllInFull{
  402. display: none;
  403. }
  404. .full_with_browser.fullWithPlaylist .mylistPopupPanel.hideInFull,
  405. .full_with_browser.fullWithPlaylist .mylistPopupPanel.hideAllInFull {
  406. display: block;
  407. }
  408. .full_with_browser .mylistPopupPanel.hideInFull.fixed:not(:hover) {
  409. right: -100px;
  410. transition: right 0.8s ease-in-out 0.5s;
  411. }
  412. .full_with_browser .mylistPopupPanel.hideInFull.fixed.left:not(:hover) {
  413. left: -100px; right: auto;
  414. transition: left 0.8s ease-in-out 0.5s;
  415. }
  416. .full_with_browser .mylistPopupPanel.hideInFull:not(:hover)>*{
  417. opacity: 0;
  418. transition: opacity 0.3s ease-out 1s;
  419. }
  420. .mylistPopupPanel.w_touch {
  421. height: 40px;
  422. }
  423. iframe.popup {
  424. position: absolute;
  425. }
  426. {* マウスホバーで出るほうのマイリスト登録パネル *}
  427. .mylistPopupPanel.popup {
  428. position: absolute;
  429. z-index: 1000000;
  430. box-shadow: 2px 2px 2px #888;
  431. }
  432. {* マイリスト登録パネルの中の各要素 *}
  433. .mylistPopupPanel .mylistSelect {
  434. width: 64px;
  435. margin: 0;
  436. padding: 0;
  437. font-size: 80%;
  438. white-space: nowrap;
  439. {*background: #eee;*}
  440. border: 1px solid silver;
  441. }
  442. .mylistSelect:focus {
  443. border-style: outset;
  444. }
  445. {* 誤操作を減らすため、とりマイの時だけスタイルを変える用 *}
  446. .mylistPopupPanel.w_touch button {
  447. padding: 8px 18px;
  448. }
  449. .mylistPopupPanel.w_touch .mylistSelect {
  450. font-size: 170%; width: 130px; border: none;
  451. }
  452. .mylistPopupPanel.w_touch .mylistSelect:focus {
  453. border: 1px dotted;
  454. }
  455. .mylistPopupPanel.deflistSelected button {
  456. }
  457. .mylistPopupPanel.mylistSelected button {
  458. color: #ccf;
  459. }
  460. .mylistPopupPanel button {
  461. margin: 0;
  462. font-weight: bolder;
  463. cursor: pointer;
  464. }
  465. .mylistPopupPanel button:active, #outline .playlistToggle:active, #outline .openVideoExplorer:active, #content .openConfButton:active {
  466. border:1px inset !important
  467. }
  468. .mylistPopupPanel button:hover, #outline .playlistToggle:hover, #outline .openVideoExplorer:hover, #outline .openConfButton:hover, #yukkuriPanel .yukkuriButton:hover {
  469. border:1px outset
  470. }
  471. .mylistPopupPanel .mylistAdd, .mylistPopupPanel .tagGet, #yukkuriPanel .yukkuriButton {
  472. 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;
  473. }
  474. .mylistPopupPanel .mylistAdd:focus, .mylistPopupPanel .tagGet:focus, #yukkuriPanel .yukkuriButton:focus {
  475. border-style: outset; color: orange;
  476. }
  477. .mylistPopupPanel.deflistSelected {
  478. color: #ff9;
  479. }
  480. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  481. 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;
  482. }
  483. .mylistPopupPanel.deflistSelected {
  484. color: #ff9;
  485. }
  486. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  487. 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;
  488. }
  489. .mylistPopupPanel.mylistSelected .deflistRemove {
  490. display: none;
  491. }
  492. .mylistPopupPanel .closeButton{
  493. color: #339;
  494. padding: 0;
  495. margin: 0;
  496. font-size: 80%;
  497. text-decoration: none;
  498. }
  499. .mylistPopupPanel .newTabLink{
  500. padding: 0 2px; text-decoration: underline; text-shadow: -1px -1px 0px #442B2B;
  501. }
  502. .mylistPopupPanel.fixed .newTabLink, .mylistPopupPanel.fixed .closeButton {
  503. display: none;
  504. }
  505. .w_fullScreenMenu .mylistPopupPanel.fixed { bottom: 2px; }
  506.  
  507. {* ポイントが無いときは表示しない *}
  508. .item:not(.silver):not(.gold) .uadContainer {
  509. display: none !important;
  510. }
  511.  
  512. .xDomainLoaderFrame {
  513. width: 1px;
  514. height: 1px;
  515. position: fixed;
  516. top: -100px;
  517. left: -100px;
  518. border: 0;
  519. overflow: hidden;
  520. }
  521.  
  522. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  523. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  524. addStyle(__css__, 'watchItLaterCommonStyle');
  525. })(); // end of commoncss
  526.  
  527.  
  528.  
  529.  
  530. (function() { // watchページだけのstyle
  531. if (!w.WatchApp) { return; }
  532. var __css__ = (function() { /*
  533. {* 動画タグとプレイリストのポップアップ *}
  534. #videoTagPopupContainer {
  535. }
  536. #videoTagPopupContainer.w_touch {
  537. line-height: 200%; font-size: 130%;
  538. }
  539. #videoTagPopupContainer.w_touch .nicodic{
  540. margin: 4px 14px;
  541. }
  542. .playlistMenuPopup {
  543. background: #666; color: white; padding: 4px 8px;
  544. }
  545. .playlistMenuPopup.w_touch {
  546. line-height: 250%;
  547. }
  548. #playlistSaveDialog {
  549. display: none;
  550. }
  551. #playlistSaveDialog.show {
  552. display: block;
  553. }
  554. #playlistSaveDialog.show .shadow{
  555. position: fixed;
  556. top: 0; left: 0; width: 100%; height: 100%;
  557. background: #000; opacity: 0.5;
  558. z-index: 30000;
  559. }
  560. #playlistSaveDialog.show .formWindow{
  561. position: fixed;
  562. margin: 0 auto;
  563. text-align: center;
  564. width: 100%;
  565. top: 45%;
  566. z-index: 30001;
  567. }
  568. #playlistSaveDialog .formWindow .formWindowInner{
  569. -webkit-transition: opacity 1s ease-out;
  570. transition: opacity 1s ease-out;
  571. opacity: 0;
  572. }
  573. #playlistSaveDialog.show .formWindow .formWindowInner{
  574. text-align: left;
  575. opacity: 1;
  576. margin: 0 auto;
  577. background: #f4f4f4;
  578. width: 500px;
  579. padding: 8px;
  580. border: 1px solid;
  581. }
  582. #playlistSaveDialog.show .formWindow .formWindowInner a{
  583. font-weight: bolder;
  584. }
  585. #playlistSaveDialog.show .formWindow .formWindowInner a:hover{
  586. text-decoration: underline; background: white;
  587. }
  588. #playlistSaveDialog.show .formWindow .formWindowInner label{
  589. margin: 8px;
  590. }
  591. #playlistSaveDialog.show .formWindow .formWindowInner input{
  592.  
  593. }
  594. #playlistSaveDialog.show .formWindow .formWindowInner .desc{
  595. font-size: 80%;
  596. }
  597. .playlistMenuPopup ul li {
  598. cursor: pointer;
  599. }
  600. .playlistMenuPopup ul li.savelist {
  601. color: #aaa;
  602. }
  603. .playlistMenuPopup ul li:hover {
  604. text-decoration: underline; background: #888;
  605. }
  606. #yukkuriPanel .yukkuriButton.active {
  607. border:1px inset
  608. }
  609.  
  610. #content .openConfButton {
  611. 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;
  612. }
  613. #outline .playlistToggle, #outline .openVideoExplorer, #outline .openConfButton {
  614. 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;
  615. height: 24px; border-radius: 0 0 8px 8px;
  616. }
  617. #outline .openConfButton { padding: 0 8px; letter-spacing: 4px; width: 60px; }
  618.  
  619. {* 全画面時にプレイリスト表示 *}
  620. body.full_with_browser.fullWithPlaylist #playlist{
  621. z-index: 100 !important;
  622. display: block !important;
  623. position: absolute;
  624. bottom: 0;
  625. }
  626. body.full_with_browser.fullWithPlaylist .browserFullOption{
  627. display: none !important;
  628. }
  629. body.full_with_browser.fullWithPlaylist #content.playlist #playerContainerWrapper {
  630. margin-bottom: 167px !important;
  631. }
  632.  
  633.  
  634. {* 少しでも縦スクロールを減らすため、動画情報を近づける。人によっては窮屈に感じるかも *}
  635. #outline {
  636. margin-top: -16px;
  637. }
  638. #outline #feedbackLink{
  639.  
  640. }
  641. #outline .videoEditMenuExpand{
  642. position: absolute;right: 0;top: 26px; z-index: 1;
  643. }
  644. {* ヘッダに表示する再生数 *}
  645. #videoCounter {
  646. color: #ff9; font-size: 70%;
  647. }
  648. {* 右に表示する動画情報 *}
  649. .sidePanel .sideVideoInfo, .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  650. padding: 0px 0px 0 0px; width: 196px; height: 100%; z-index: 10019;
  651. position:absolute; top:0; right:0; border: 1px solid #000;
  652. overflow-x: visible; overflow-y: auto;
  653. }
  654. {* 右に表示する動画情報 *}
  655. #playerTabWrapper.sidePanel .sideVideoInfo, #playerTabWrapper.sidePanel .sideIchibaPanel, #playerTabWrapper.sidePanel .sideReviewPanel {
  656. padding: 0px 0px 0 0px; width: 324px; height: 100%;
  657. position: absolute; top: 0; right:0;
  658. }
  659.  
  660. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideVideoInfo,
  661. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideIchibaPanel,
  662. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideReviewPanel,
  663. .videoExplorer #playerTabWrapper .sideVideoInfo,
  664. .videoExplorer #playerTabWrapper .sideIchibaPanel,
  665. .videoExplorer #playerTabWrapper .sideReviewPanel {
  666. width: 418px;
  667. }
  668. #playerTabWrapper.w_videoInfo #appliPanel, #playerTabWrapper.w_ichiba #appliPanel, #playerTabWrapper.w_review #appliPanel {
  669. top: -9999px;
  670. }
  671. .sidePanel .sideVideoInfo {
  672. background: #f4f4f4; text-Align: left; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%; border: 1px solid black;
  673. }
  674. .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  675. background: #f4f4f4; text-Align: center; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%;
  676. }
  677. .sidePanel .sideVideoInfo .sideVideoInfoInner {
  678. padding: 0 4px; position: relative;
  679. }
  680. .sidePanel .sideVideoInfo .videoTitleContainer {
  681. background: #ddd; text-align: center; color: #000; margin: 6px 6px 0;
  682. border: solid; border-width: 2px 2px 0; border-color: #888;
  683. }
  684. .sidePanel .sideVideoInfo .videoOwnerInfoContainer {
  685. margin: 0 6px; border: solid; border-width: 0 2px 0; border-color: #888;
  686. }
  687. .sidePanel .sideVideoInfo .videoThumbnailContainer {
  688. background: #ddd; text-align: center; color: #000; margin: 0;
  689. }
  690. .sidePanel .sideVideoInfo .videoThumbnailContainer img {
  691. cursor: pointer;
  692. }
  693. .sidePanel .sideVideoInfo .videoTitle {
  694. padding: 8px;
  695. }
  696. .sidePanel .sideVideoInfo .videoPostedAt {
  697. color: #333;
  698. }
  699. .sidePanel .sideVideoInfo .videoStats{
  700. font-size:90%;
  701. }
  702. .sidePanel .sideVideoInfo .videoStats li{
  703. display: inline-block; margin: 0 2px;
  704. }
  705. .sidePanel .sideVideoInfo .videoStats li span{
  706. font-weight: bolder;
  707. }
  708. .sidePanel .sideVideoInfo .videoStats .ranking{
  709. display: none !important;
  710. }
  711. .sidePanel .sideVideoInfo .videoInfo{
  712. background: #ddd; text-align: center; padding: 4px; margin: 0 6px 6px;
  713. border: solid; border-width: 0 2px 2px; border-color: #888;
  714. }
  715. .sideVideoInfo .sideVideoInfoInner{
  716. -webkit-transition: opacity 1s ease-out, color 3s ease-out;
  717. transition: opacity 1s ease-out, color 3s ease-out;
  718. opacity: 0;
  719. }
  720. .sideVideoInfo.show .sideVideoInfoInner{
  721. opacity: 1;
  722. }
  723. .videoCount.blink {
  724. color: #ccc;
  725. }
  726. .sidePanel .videoCountDiff {
  727. position: absolute; color: white; right: 0; opacity: 0; z-index: 100; text-shadow: 1px 1px 0 orange;
  728. }
  729. .sidePanel .videoCountDiff.blink {
  730. opacity: 1; color: white;
  731. }
  732. #siteHeader .videoCount, #siteHeader .videoCountDiff {
  733. min-width: 32px; text-align: right; display: inline-block;
  734. }
  735. #siteHeader .videoCountDiff, #fullScreenToggleContainer .videoCountDiff {
  736. position: absolute; color: yellow; opacity: 0; font-weight: bolder; text-shadow: 1px 1px 0 red;
  737. }
  738. #siteHeader .videoCountDiff.blink, #fullScreenToggleContainer .videoCountDiff.blink {
  739. opacity: 1; color: yellow;
  740. }
  741. .videoExplorer #siteHeaderNicopoPurchase,
  742. .videoExplorer #siteHeaderUserNickNameContainer,
  743. .size_medium #siteHeaderNicopoPurchase,
  744. .size_medium #siteHeaderUserNickNameContainer,
  745. #siteHeaderNotificationPremium {
  746. display: none !important;
  747. }
  748. #fullScreenToggleContainer .blink, #videoCounter .blink {
  749. color: #000;
  750. }
  751. .videoCountDiff:before {content: '+';}
  752. .videoCountDiff.down:before {content: ''; }
  753. #popupMarquee .videoCountDiff {display: none;}
  754. .sidePanel .sideVideoInfo .videoDescription{
  755. overflow-x: hidden; text-align: left;
  756. }
  757. .sidePanel .sideVideoInfo .videoDescriptionInner{
  758. margin: 0 8px;
  759. }
  760. .sidePanel .sideVideoInfo .videoDetails{
  761. min-width: 150px;
  762. }
  763. .sidePanel .sideVideoInfo .videoDetails a{
  764. margin: auto 4px;
  765. }
  766. .sidePanel .sideVideoInfo .videoDetails a:visited{
  767. color: #04618c;
  768. }
  769. .sidePanel .sideVideoInfo .videoDetails a.watch{
  770. margin: auto 30px auto 4px;
  771. display:inline-block;
  772. }
  773. .sideVideoInfo .userName, .sideVideoInfo .channelName{
  774. display: block;
  775. font-size: 120%;
  776. cursor: pointer;
  777. }
  778. .sideVideoInfo .userIconContainer, .sideVideoInfo .channelIconContainer {
  779. background: #ddd; width: 100%; text-align: center; float: none;
  780. }
  781. .sidePanel .userIcon, .sidePanel .channelIcon{
  782. min-width: 128px; max-width: 150px; width: auto; height: auto; border: 0;
  783. box-shadow: 0 0 4px #666; cursor: pointer;
  784. }
  785. .sideVideoInfo .descriptionThumbnail {
  786. text-align: left; font-size: 90%; padding: 4px; background: #ddd;
  787. min-height: 50px; margin: 4px 8px; font-weight: normal; color: black;
  788. }
  789. .sideVideoInfo .descriptionThumbnail p {
  790. margin: 0 8px;
  791. font-weight: bolder;
  792. }
  793. .sideVideoInfo .descriptionThumbnail .uploadAt {
  794. font-size: 90%;
  795. margin: 4px;
  796. color: #333;
  797. }
  798. .sideVideoInfo .descriptionThumbnail .counterContainer {
  799. text-align: center;
  800. }
  801. .sideVideoInfo .descriptionThumbnail .view,
  802. .sideVideoInfo .descriptionThumbnail .comment,
  803. .sideVideoInfo .descriptionThumbnail .mylist
  804. {
  805. font-size: 90%;
  806. white-space: nowrap;
  807. margin-right: 4px;
  808. color: #333;
  809. }
  810. .sideVideoInfo .descriptionThumbnail .count {
  811. font-weight: bolder;
  812. }
  813.  
  814. .sideVideoInfo .descriptionThumbnail.video img{
  815. height: 50px; cursor: pointer; float: left;
  816. }
  817. .sideVideoInfo .descriptionThumbnail.mylist img{
  818. height: 40px; cursor: pointer;
  819. }
  820. .sideVideoInfo .descriptionThumbnail.illust img{
  821. height: 60px; cursor: pointer;
  822. }
  823. .sideVideoInfo a.otherSite {
  824. font-weight: bolder; text-decoration: underline;
  825. }
  826. body:not(.videoExplorer) #leftPanel.removed {
  827. display: none; left: 0px;
  828. }
  829. body:not(.videoExplorer) #leftPanel.removed .sideVideoInfo {
  830. display: none; width: 0px !important; border: none; margin: 0; padding: 0; right: auto;
  831. }
  832. .sideVideoInfo .userIconContainer.isUserVideoPublic .notPublic { display: none; }
  833. .sideVideoInfo .userIconContainer .isPublic { display: none; }
  834. .sideVideoInfo .userIconContainer.isUserVideoPublic .isPublic { display: inline; }
  835. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem,
  836. .sidePanel .sideIchibaPanel .ichiba_mainitem {
  837. width: 180px; display:inline-block; vertical-align: top;
  838. margin: 4px 3px; border 1px solid silver;
  839. }
  840.  
  841. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  842. font-size: 60px;
  843. }
  844. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem>div>dt {
  845. height: 50px;position: relative;
  846. }
  847. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  848. position: absolute;width: 100%;
  849. }
  850. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  851. position: absolute;
  852. }
  853. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonShita {
  854. position: absolute;
  855. }
  856.  
  857. .sidePanel.videoInfo, .sidePanel.ichiba{
  858. background: none;
  859. }
  860.  
  861. .sideVideoInfo.isFavorite .userName:after, .sideVideoInfo.isFavorite.isChannel .videoOwnerInfoContainer .channelName:after{
  862. content: ' ★ '; color: gold; text-shadow: 1px 1px 1px black;
  863. }
  864.  
  865. .sidePanel.videoInfo #leftPanelContent, .sidePanel.ichiba #leftPanelContent {
  866. display: none;
  867. }
  868. .sidePanel.w_comment #playerTabContainer,
  869. .sidePanel.videoInfo .sideVideoInfo,
  870. .sidePanel.ichiba .sideIchibaPanel,
  871. .sidePanel.w_videoInfo .sideVideoInfo,
  872. .sidePanel.w_ichiba .sideIchibaPanel,
  873. .sidePanel.w_review .sideReviewPanel {
  874. display: block; z-index: 10060;
  875. }
  876. .sidePanel:not(.w_comment) .watchWatchContainer {
  877. display: none;
  878. }
  879.  
  880. #leftPanelTabContainer {
  881. display:none; background: #666; position: absolute; right: 4px; top: -27px; list-style-type: none; padding: 4px 6px 3px 60px; height: 20px;
  882. }
  883. #sidePanelTabContainer {
  884. display: none;
  885. position: absolute; list-style-type: none;
  886. padding: 5px 10px 0; right: -408px; top: 0; width: 350px; height: 34px;
  887. transform: rotate(90deg); transform-origin: 0 0 0;
  888. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  889. z-index: 1000;
  890. }
  891. .full_with_browser #sidePanelTabContainer {
  892. background: #000 !important;
  893. }
  894. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left {
  895. background: #000; right: auto; left: -375px; padding: 0; height: 27px;
  896. transform: rotate(-90deg); transform-origin: 100% 0 0;
  897. -webkit-transform: rotate(-90deg); -webkit-transform-origin: 100% 0 0;
  898. }
  899.  
  900. #leftPanelTabContainer.w_touch {
  901. top: -40px; height: 33px;
  902. }
  903. .sidePanel:hover #sidePanelTabContainer, .sidePanel:hover #leftPanelTabContainer {
  904. display: block;
  905. }
  906. #leftPanelTabContainer .tab{
  907. display: inline-block; cursor: pointer; background: #999; padding: 2px 4px 0px; border-width: 2px 2px 0px;
  908. }
  909. #leftPanelTabContainer.w_touch .tab, #sidePanelTabContainer.w_touch .tab {
  910. padding: 8px 12px 8px;
  911. }
  912. #sidePanelTabContainer .tab {
  913. background: none repeat scroll 0 0 #999999; border-width: 2px 2px 0; cursor: pointer;
  914. display: inline-block; font-size: 13px; padding: 5px 10px 8px;
  915. border-radius: 8px 8px 0px 0px;
  916. }
  917. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left .tab {
  918. display: inline-block; font-size: 13px; padding: 5px 10px 0px;
  919. }
  920. #leftPanel.videoInfo .tab.videoInfo, #leftPanel.ichiba .tab.ichiba {
  921. background: #f4f4f4; border-style: outset;
  922. }
  923. #playerTabWrapper.w_comment .tab.comment,
  924. #playerTabWrapper.w_videoInfo .tab.videoInfo,
  925. #playerTabWrapper.w_ichiba .tab.ichiba,
  926. #playerTabWrapper.w_review .tab.review
  927. {
  928. background: #dfdfdf; border-style: outset;
  929. }
  930.  
  931. #playerTabWrapper.w_videoInfo #playerCommentPanel,
  932. #playerTabWrapper.w_ichiba #playerCommentPanel,
  933. #playerTabWrapper.w_review #playerCommentPanel {
  934. {*display: none;*} top: -9999px;
  935. }
  936. .sidePanel.ichibaEmpty .tab.ichiba, .sidePanel.reviewEmpty .tab.review {
  937. color: #ccc;
  938. }
  939.  
  940. .sideIchibaPanel .ichibaPanelInner {
  941. margin:0; color: #666;
  942. }
  943. .sideIchibaPanel .ichibaPanelHeader .logo{
  944. text-shadow: 1px 1px 1px #666; cursor: pointer; padding: 4px 0px 4px; font-size: 125%;
  945. }
  946. .sideIchibaPanel .ichibaPanelFooter{
  947. text-align: center;
  948. }
  949. .sideIchibaPanel .ichiba_mainitem {
  950. margin: 0 0 8px 0; background: white; border-bottom : 1px dotted #ccc;
  951. }
  952. .sideIchibaPanel .ichiba_mainitem a:hover{
  953. background: #eef;
  954. }
  955. .sideIchibaPanel .ichiba_mainitem>div {
  956. max-width: 266px; margin: auto; text-align: center;
  957. }
  958. .sideIchibaPanel .ichiba_mainitem .blomagaArticleNP {
  959. background: url("http://ichiba.nicovideo.jp/embed/zero/img/bgMainBlomagaArticleNP.png") no-repeat scroll 0 0 transparent;
  960. height: 170px;
  961. margin: 0 auto;
  962. width: 180px;
  963. }
  964. .sideIchibaPanel .ichiba_mainitem .blomagaLogo {
  965. color: #FFFFFF;font-size: 9px;font-weight: bold;padding-left: 10px;padding-top: 8px;
  966. }
  967. .sideIchibaPanel .ichiba_mainitem .blomagaLogo span{
  968. background: none repeat scroll 0 0 #AAAAAA;padding: 0 3px;
  969. }
  970. .sideIchibaPanel .ichiba_mainitem .blomagaText {
  971. color: #666666;font-family: 'HGS明朝E','MS 明朝';font-size: 16px;height: 100px;
  972. padding: 7px 25px 0 15px;text-align: center;white-space: normal;word-break: break-all;word-wrap: break-word;
  973. }
  974. .sideIchibaPanel .ichiba_mainitem .blomagaAuthor {
  975. color: #666666; font-size: 11px;padding: 0 20px 0 10px;text-align: right;
  976. }
  977. .sideIchibaPanel .ichiba_mainitem .balloonUe{
  978. bottom: 12px; display: block; max-width: 266px;
  979. }
  980. .sideIchibaPanel .ichiba_mainitem .balloonUe a{
  981. background: url("/img/watch_zero/ichiba/imgMainBalloonUe.png") no-repeat scroll center top transparent;
  982. color: #666666 !important;
  983. display: block;
  984. font-size: 108%;
  985. line-height: 1.2em;
  986. margin: 0 auto;
  987. padding: 8px 15px 3px;
  988. text-align: center;
  989. text-decoration: none;
  990. word-wrap: break-word;
  991. }
  992. .sideIchibaPanel .ichiba_mainitem .balloonShita{
  993. height: 12px; bottom: 0; left: 0;
  994. }
  995. .sideIchibaPanel .ichiba_mainitem .balloonShita img{
  996. vertical-align: top !important;
  997. }
  998. .sideIchibaPanel .ichiba_mainitem .ichibaMarquee {
  999. display: none;
  1000. }
  1001. .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  1002. font-size: 22px; color: #0066CC;
  1003. font-family: 'ヒラギノ明朝 Pro W3','Hiragino Mincho Pro','MS P明朝','MS PMincho',serif;
  1004. }
  1005. .sideIchibaPanel .ichiba_mainitem .action {
  1006. font-size: 85%;
  1007. }
  1008. .sideIchibaPanel .ichiba_mainitem .action .buy {
  1009. font-weight: bolder; color: #f60;
  1010. }
  1011. .sideIchibaPanel .ichiba_mainitem .itemname {
  1012. font-weight: bolder;
  1013. }
  1014. .sideIchibaPanel .ichiba_mainitem .maker {
  1015. font-size: 77%; margin-bottom: 2px;
  1016. }
  1017. .sideIchibaPanel .ichiba_mainitem .price {
  1018. }
  1019. .sideIchibaPanel .ichiba_mainitem .action .click {
  1020. font-weight: bolder;
  1021. }
  1022. .sideIchibaPanel .ichiba_mainitem .goIchiba {
  1023. font-size: 77%; margin: 5px 0;
  1024. }
  1025. .sideIchibaPanel .addIchiba, .sideIchibaPanel .reloadIchiba {
  1026. cursor: pointer;
  1027. }
  1028. .sideIchibaPanel .noitem {
  1029. cursor: pointer;
  1030. }
  1031.  
  1032. #outline .bottomAccessContainer {
  1033. position: absolute; top: 12px;
  1034. }
  1035. #outline .bottomConfButtonContainer {
  1036. position: absolute; top: 12px; right: 0px;
  1037. }
  1038. body.videoExplorer .bottomAccessContainer{
  1039. display: none;
  1040. }
  1041. #outline.under960 .bottomAccessContainer{
  1042. right: 60px;
  1043. }
  1044. .watchItLaterSettingMenu {
  1045. font-weight: bolder;
  1046. white-space: nowrap;
  1047. }
  1048. #outline .sidebar {
  1049. -webkit-transition: margin-top 0.3s ease-out;
  1050. transition: margin-top 0.3s ease-out;
  1051. }
  1052. #outline.under960 .sidebar {
  1053. margin-top: 24px;
  1054. }
  1055. #videoHeader.menuClosed .watchItLaterMenu, #videoHeader.menuClosed .hidariue { display: none; }
  1056. #videoHeader .watchItLaterMenu {
  1057. position: absolute; width: 100px; left: -55px; top: 32px;
  1058. }
  1059. {* タイトルクリックでヘッダが開閉できるのをわかりやすく *}
  1060. .videoDetailToggleButton:hover {
  1061. text-decoration: underline;
  1062. }
  1063. .videoDetailToggleButton:hover:after {
  1064. content: '▼';
  1065. position: absolute;
  1066. width: 32px;
  1067. height: 20px;
  1068. top: 0;
  1069. bottom: 0;
  1070. right: -32px;
  1071. margin: auto;
  1072. color: #888;
  1073. font-size: 80%;
  1074. }
  1075. .infoActive .videoDetailToggleButton:hover:after {
  1076. content: '▲';
  1077. }
  1078.  
  1079. {* プレイリスト出したり隠したり *}
  1080. #playlist>* {
  1081. -webkit-transition: opacity 0.6s; transition: opacity 0.6s;
  1082. }
  1083. body:not(.full_with_browser):not(.videoExplorer) #playlist.w_closing>* {
  1084. opacity: 0;
  1085. }
  1086. body:not(.full_with_browser):not(.videoExplorer) #playlist:not(.w_show){
  1087. position: absolute; top: -9999px;
  1088. }
  1089. #playlist.w_show{
  1090. {*max-height: 180px;*}
  1091. }
  1092. .playlistToggle:after {
  1093. content: "▼";
  1094. }
  1095. .playlistToggle.w_show:after {
  1096. content: "▲";
  1097. }
  1098.  
  1099. body.videoExplorer #content.w_adjusted #playlist .playlistInformation {
  1100. white-space: nowrap;
  1101. }
  1102. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .playbackOption {
  1103. position: absolute;
  1104. }
  1105. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .generationMessage{
  1106. margin-left: 90px; max-width: 350px; overflow: hidden; text-overflow: ellipsis;
  1107. }
  1108. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption {
  1109. position: absolute; right: 0; top: 0;
  1110. }
  1111. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption a {
  1112. background: #444;
  1113. }
  1114. #playlistContainerInner .thumbContainer, #playlistContainerInner .balloon{
  1115. cursor: move;
  1116. }
  1117.  
  1118.  
  1119. {* ページャーの字が小さくてクリックしにくいよね *}
  1120. #resultPagination {
  1121. padding: 5px; font-weight: bolder; border: 1px dotted silter; font-size: 130%;
  1122. }
  1123.  
  1124. #playlistContainer #playlistContainerInner .playlistItem .balloon {
  1125. bottom: auto; top: -2px; padding: auto;
  1126. }
  1127.  
  1128. body.w_channel #leftPanel .userIconContainer{
  1129. display: none;
  1130. }
  1131. {* WatchItLater設定パネル *}
  1132. #watchItLaterConfigPanel {
  1133. position: fixed; bottom: 0px; right: 16px; z-index: 10001;
  1134. width: 460px; padding: 0;
  1135. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1136. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1137. transform: scaleY(0); -webkit-transform: scaleY(0);
  1138. }
  1139. #watchItLaterConfigPanel.open {
  1140. transform: scaleY(1); -webkit-transform: scaleY(1);
  1141. }
  1142. #watchItLaterConfigPanelShadow {
  1143. position: fixed; bottom: 16px; right: 16px; z-index: 10000;
  1144. width: 460px; height: 559px; padding: 0;
  1145. background: #000; {*box-shadow: 0 0 2px black; border-radius: 8px;*} -webkit-filter: opacity(70%);
  1146. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1147. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1148. transform: scaleY(0); -webkit-transform: scaleY(0);
  1149. }
  1150. #watchItLaterConfigPanelShadow.open {
  1151. transform: scaleY(1); -webkit-transform: scaleY(1);
  1152. }
  1153. #watchItLaterConfigPanelShadowTop {
  1154. position: fixed; bottom: 563px; right:0px; z-index: 10000; background: #333;
  1155. width: 492px; height: 20px; padding: 0; border-radius: 32px; -webkit-filter: opacity(90%); display: none;
  1156. }
  1157. #watchItLaterConfigPanelOverShadow {
  1158. position: fixed; bottom: 575px; right: 0px; width: 488px; height: 8px;
  1159. box-shadow: 0 4px 16px #333;z-index: 10002; display: none;
  1160. }
  1161. #watchItLaterConfigPanel .head {
  1162. background-color: #CCCCCC;border-radius: 0;color: black;height: 50px;
  1163. overflow: hidden;padding: 5px 0 0 16px;position: relative;
  1164. }
  1165. #watchItLaterConfigPanel .head h2 {
  1166. font-size: 135%;
  1167. }
  1168. #watchItLaterConfigPanel .inner{
  1169. height: 500px; overflow-y: auto;border-width: 4px 16px 16px 16px; border-radius: 0 0 16px 16px;
  1170. border-style: solid;border-color: #ccc;
  1171. }
  1172. #watchItLaterConfigPanel ul{
  1173. border-style: inset; border-color: #ccc; border-width: 0 1px 0;
  1174. }
  1175. #watchItLaterConfigPanel ul.shortcutContainer{
  1176. border-width: 0 1px 1px;
  1177. }
  1178. #watchItLaterConfigPanel ul.videoStart{
  1179. border-width: 1px 1px 0;
  1180. }
  1181. #watchItLaterConfigPanel li{
  1182. }
  1183. #watchItLaterConfigPanel li:hover{
  1184. {*background: #ddd;*}
  1185. }
  1186. #watchItLaterConfigPanel li.buggy{
  1187. color: #888;
  1188. }
  1189. #watchItLaterConfigPanel label{
  1190. margin: 0 5px;
  1191. }
  1192. #watchItLaterConfigPanel label:hover{
  1193. }
  1194. #watchItLaterConfigPanel .foot {
  1195. text-align: right; padding: 0 12px;
  1196. }
  1197. #watchItLaterConfigPanel .closeButton{
  1198. border: 0 none;border-radius: 0 0 4px 4px;box-shadow: 0 1px 2px white;color: #666; border: 1px solid #999;
  1199. cursor: pointer;float: right;margin-top: 8px;position: absolute;right: 16px;
  1200. text-shadow: 0 1px 0 white;top: -10px; width: 60px;
  1201. }
  1202. #watchItLaterConfigPanel.autoBrowserFull_false .disableAutoBrowserFullIfNicowari,
  1203. #watchItLaterConfigPanel.autoBrowserFull_true .autoScrollToPlayer,
  1204. #watchItLaterConfigPanel.autoBrowserFull_true .autoOpenSearch,
  1205. #watchItLaterConfigPanel.removeLeftPanel_true .leftPanelJack {
  1206. color: #ccc; text-shadow: -1px -1px 0 #888;
  1207. }
  1208. #watchItLaterConfigPanel .reload .title:after {
  1209. content: ' (※)'; font-size: 80%; color: #900;
  1210. }
  1211. #watchItLaterConfigPanel .debugOnly {
  1212. display: none;
  1213. }
  1214. #watchItLaterConfigPanel.debugMode .debugOnly {
  1215. display: block; background: #888;
  1216. }
  1217. #watchItLaterConfigPanel .section {
  1218. border-style: solid;border-width: 10px 12px 10px 12px;color: white; font-size: 135%; position: relative;
  1219. font-weight: bolder; cursor: pointer; {*text-shadow: 2px 2px 1px #000000;*}
  1220. 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;
  1221. }
  1222. #watchItLaterConfigPanel .open .section {
  1223. border-width: 20px 12px 12px 12px;
  1224. transition: border-width 0.2s ease-in-out ; -webkit-transition: border-width 0.2s ease-in-out ;
  1225. }
  1226. #watchItLaterConfigPanel .section:hover:after {
  1227. content: '▼';
  1228. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1229. transition: transform 0.2s ease-in-out 0.4s; -webkit-transition: -webkit-transform 0.2s ease-in-out 0.4s;
  1230. }
  1231. #watchItLaterConfigPanel .open .section:after {
  1232. content: '▼';
  1233. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1234. transform: rotate(180deg); -webkit-transform: rotate(180deg);
  1235. transition: transform 0.2s ease-in-out ; -webkit-transition: -webkit-transform 0.2s ease-in-out;
  1236. }
  1237. #watchItLaterConfigPanel .section > div {
  1238. padding: 8px 0 8px 12px; box-shadow: 0 0 4px black;
  1239. }
  1240. #watchItLaterConfigPanel .section > div > span {
  1241. {*background: #333;*}
  1242. }
  1243. #watchItLaterConfigPanel li:not(.section) {
  1244. background: #fff; border-width: 0px 0px 0px 24px; border-style: solid; border-color: #fff;
  1245. max-height: 0px; overflow: hidden;
  1246. transition: max-height 0.4s ease-in-out , border-width 0.4s ease-in-out;
  1247. }
  1248. #watchItLaterConfigPanel .open li:not(.section) {
  1249. max-height: 100px; border-width: 4px 0px 4px 24px;
  1250. transition: max-height 0.4s ease-in-out 0.2s, border-width 0.4s ease-in-out 0.2s;
  1251. }
  1252. #watchItLaterConfigPanel .section .description{
  1253. display: block; font-size: 80%;;
  1254. }
  1255. #watchItLaterConfigPanel .shortcutSetting:not(.enable) span :not(.enable){
  1256. color: silver;
  1257. }
  1258. #watchItLaterConfigPanel .shortcutSetting .enable {
  1259. cursor: pointer; margin: auto 10px;
  1260. }
  1261. #watchItLaterConfigPanel .shortcutSetting .enable:before {
  1262. content: '○ ';
  1263. }
  1264. #watchItLaterConfigPanel .shortcutSetting.enable .enable:before {
  1265. content: '㋹ '; color: blue;
  1266. }
  1267. #watchItLaterConfigPanel .shortcutSetting .ctrl, #watchItLaterConfigPanel .shortcutSetting .alt, #watchItLaterConfigPanel .shortcutSetting .shift {
  1268. cursor: pointer; border: 2px outset; margin: 4px 4px; padding: 2px 4px; width: 180px; border-radius: 4px;background: #eee;
  1269. }
  1270. #watchItLaterConfigPanel .shortcutSetting.ctrl .ctrl, #watchItLaterConfigPanel .shortcutSetting.alt .alt, #watchItLaterConfigPanel .shortcutSetting.shift .shift {
  1271. border: 2px inset; color: blue;
  1272. }
  1273. #watchItLaterConfigPanel .hoverMenuDelay input {
  1274. width: 50px; ime-mode: disabled; text-align: center;
  1275. }
  1276.  
  1277.  
  1278. {* 動画検索画面に出るお気に入りタグ・お気に入りマイリスト *}
  1279. .videoExplorerMenu .watchItLaterMenu.open,
  1280. .videoExplorerMenu .watchItLaterMenu.opening {
  1281. background: -moz-linear-gradient(center top , #D1D1D1, #FDFDFD) repeat scroll 0 0 transparent !important;
  1282. background: -webkit-gradient(linear, left top, left bottom, from(#D1D1D1), to(#FDFDFD)) !important;
  1283. border-bottom: 0 !important;
  1284. }
  1285. .videoExplorerMenu .watchItLaterMenu {
  1286. position: relative;
  1287. {*background: -moz-linear-gradient(center top , whitesmoke 0%, #E1E1E1 100%) repeat scroll 0 0 transparent;*}
  1288. {*box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.1) inset;*}
  1289. {*background: #f5f5f5;*}
  1290. border-bottom: 1px solid #CCCCCC;
  1291. }
  1292. .videoExplorerMenu .watchItLaterMenu:hover{
  1293. background: #dbdbdb;
  1294. }
  1295. .videoExplorerMenu .watchItLaterMenu {
  1296. padding: 0 12px; display: block; color: black;
  1297. }
  1298. .videoExplorerMenu .slideMenu{
  1299. width: 100%; height: auto !important;
  1300. overflow-x: hidden;
  1301. overflow-y: auto;
  1302. padding: 0;
  1303. background: #fdfdfd;
  1304. border-top: 0 !important;
  1305. display: block;
  1306. max-height: 0;
  1307. transition: max-height 0.5s ease-in-out;
  1308. }
  1309. .videoExplorerMenu .slideMenu.open {
  1310. max-height: 2000px;
  1311. transition: max-height 1s ease-in-out;
  1312. }
  1313. .videoExplorerMenu .toggleVideoExplorerMenu a {
  1314. color: black; display: block;
  1315. }
  1316. .videoExplorerMenu .toggleVideoExplorerMenu a:after {
  1317. content: "▼"; position: absolute; background: none; top: 0px; right: 10px; color: #ccc;
  1318. }
  1319. .videoExplorerMenu .toggleVideoExplorerMenu.open a:after {
  1320. content: "▲";
  1321. }
  1322.  
  1323. .videoRankingList .isCategory {
  1324. position: relative;
  1325. }
  1326.  
  1327. .rankingCategoryToggle {
  1328. position: absolute;
  1329. display: none;
  1330. height: 20px;
  1331. padding: 0px 8px;
  1332. right: 14px;
  1333. top: 0;
  1334. cursor: pointer;
  1335. border: 1px solid;
  1336. color: #666;
  1337. outline: none;
  1338. }
  1339. .rankingCategoryToggle::-moz-focus-inner {
  1340. border: 0px;
  1341. }
  1342. .slideMenu.open .isCategory:hover .rankingCategoryToggle {
  1343. display: block;
  1344. }
  1345. .categoryClose .rankingCategoryToggle .close, .rankingCategoryToggle .open{
  1346. display: none;
  1347. }
  1348. .categoryClose .rankingCategoryToggle .open{
  1349. display: inline;
  1350. }
  1351. .videoRankingList li:not(.isCategory) {
  1352. transition: max-height 0.5s;
  1353. max-height: 50px; overflow:hidden;
  1354. margin-left: 8px;
  1355. }
  1356. .videoRankingList .categoryClose:not(.isCategory) {
  1357. max-height: 0px;
  1358. }
  1359.  
  1360.  
  1361. .videoExplorerMenu .slideMenu ul{
  1362. }
  1363. .videoExplorerMenu .slideMenu ul li{
  1364. background: #fdfdfd; padding: 0; border: 0;font-size: 90%; height: auto !important;
  1365. }
  1366. .videoExplorerMenu .slideMenu ul li a{
  1367. line-height: 165%; background: none; display: block;
  1368. }
  1369. .videoExplorerMenu.w_touch .slideMenu ul li a{
  1370. line-height: 300%; font-size: 120%; color: black;
  1371. }
  1372. .videoExplorerMenu .slideMenu ul li a:before{
  1373. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  1374. display: inline-block;
  1375. height: 14px;
  1376. margin: -4px 4px 0 0;
  1377. vertical-align: middle;
  1378. width: 18px;
  1379. content: ""
  1380. }
  1381. .videoExplorerMenu .slideMenu ul li a.defMylist:before{ background-position: 0 -253px;}
  1382. .videoExplorerMenu .slideMenu ul li.folder0 a:before{ background-position: 0 0;}
  1383. .videoExplorerMenu .slideMenu ul li.folder1 a:before{ background-position: 0 -23px;}
  1384. .videoExplorerMenu .slideMenu ul li.folder2 a:before{ background-position: 0 -46px;}
  1385. .videoExplorerMenu .slideMenu ul li.folder3 a:before{ background-position: 0 -69px;}
  1386. .videoExplorerMenu .slideMenu ul li.folder4 a:before{ background-position: 0 -92px;}
  1387. .videoExplorerMenu .slideMenu ul li.folder5 a:before{ background-position: 0 -115px;}
  1388. .videoExplorerMenu .slideMenu ul li.folder6 a:before{ background-position: 0 -138px;}
  1389. .videoExplorerMenu .slideMenu ul li.folder7 a:before{ background-position: 0 -161px;}
  1390. .videoExplorerMenu .slideMenu ul li.folder8 a:before{ background-position: 0 -184px;}
  1391. .videoExplorerMenu .slideMenu ul li.folder9 a:before{ background-position: 0 -207px;}
  1392.  
  1393. .videoExplorerMenu .slideMenu ul li.g_ent2 a:before { background-position: 0 -23px;}
  1394. .videoExplorerMenu .slideMenu ul li.g_life2 a:before { background-position: 0 -46px;}
  1395. .videoExplorerMenu .slideMenu ul li.g_politics a:before { background-position: 0 -69px;}
  1396. .videoExplorerMenu .slideMenu ul li.g_tech a:before { background-position: 0 -92px;}
  1397. .videoExplorerMenu .slideMenu ul li.g_culture2 a:before { background-position: 0 -115px;}
  1398. .videoExplorerMenu .slideMenu ul li.g_other a:before { background-position: 0 -138px;}
  1399. .videoExplorerMenu .slideMenu ul li.r18 a:before { background-position: 0 -207px;}
  1400. .videoExplorerMenu .slideMenu ul li.all a.all,
  1401. .videoExplorerMenu .slideMenu ul li.g_ent2 a.g_ent2,
  1402. .videoExplorerMenu .slideMenu ul li.g_life2 a.g_life2,
  1403. .videoExplorerMenu .slideMenu ul li.g_politics a.g_politics,
  1404. .videoExplorerMenu .slideMenu ul li.g_tech a.g_tech,
  1405. .videoExplorerMenu .slideMenu ul li.g_culture2 a.g_culture2,
  1406. .videoExplorerMenu .slideMenu ul li.g_other a.g_other,
  1407. .videoExplorerMenu .slideMenu ul li.r18 a.r18
  1408. { font-weight: bolder; border-top: 1px dotted #ccc; }
  1409.  
  1410.  
  1411. .videoExplorerMenu .slideMenu ul li a:after{
  1412. background: none !important;
  1413. }
  1414. .videoExplorerMenu .slideMenu ul li a:hover{
  1415. background: #f0f0ff;
  1416. }
  1417. .videoExplorerMenu .slideMenu ul .reload{
  1418. cursor: pointer; border: 1px solid; padding: 0;
  1419. }
  1420.  
  1421. .videoExplorerMenu .tagSearchHistory {
  1422. border-radius: 0px; margin-top: 2px; padding: 4px; background: #ccc;
  1423. }
  1424. .videoExplorerMenu .itemList > li, #videoExplorerExpand {
  1425. background: #f5f5f5;
  1426. }
  1427. .videoExplorerMenu .itemList ul > li:hover {
  1428. background: #e7e7e7;
  1429. }
  1430. .videoExplorerMenu .itemList ul > li.active {
  1431. background: #343434;
  1432. }
  1433.  
  1434.  
  1435. {* 動画タグが1行以下の時 *}
  1436. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1437. height: 12px; padding: 6px 4px 2px;
  1438. }
  1439. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit .toggleText{
  1440. display: none;
  1441. }
  1442. {* 動画タグが2行以下の時 *}
  1443. body:not(.full_with_browser) .tag2Lines #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1444. height: 36px;
  1445. }
  1446. {* タグ領域とプレイヤーの隙間をなくす *}
  1447. body:not(.full_with_browser) #videoTagContainer, body:not(.full_with_browser) #videoHeader .videoMenuToggle {
  1448. margin-bottom: -10px;
  1449. }
  1450. #videoHeaderMenu .searchContainer .searchText {
  1451. margin-top: -8px;
  1452. }
  1453. #videoHeaderMenu .clear-button {
  1454. position: absolute;
  1455. top: -8px;
  1456. right: 0px;
  1457. }
  1458.  
  1459. body.size_small #playerContainerWrapper {
  1460. padding: 0;
  1461. }
  1462.  
  1463. {* ニュース履歴 *}
  1464. body.videoExplorer #textMarquee .openNewsHistory, body.videoExplorer #textMarquee .newsHistory {
  1465. display: none;
  1466. }
  1467. #textMarquee .openNewsHistory {
  1468. position: absolute; width: 30px;
  1469. font-size: 13px; padding: 0; margin: 0; height: 28px;
  1470. cursor: pointer;
  1471. bottom: 0;
  1472. background: none repeat scroll 0 0 transparent;
  1473. border: 1px none;
  1474. border-radius: 2px 2px 2px 2px;
  1475. cursor: pointer;
  1476. right: 18px;
  1477. z-index: 200;
  1478. }
  1479. #textMarquee .newsHistory {
  1480. position: absolute;
  1481. bottom: 0px; right: 0px; width: 100%;
  1482. max-height: 132px;
  1483. min-height: 40px;
  1484. overflow-y: auto;
  1485. overflow-x: hidden;
  1486. z-index: 1;
  1487. padding: 4px;
  1488. display: none;
  1489. background: #333;
  1490. text-align: left;
  1491. font-size: 14px;
  1492. padding: 0;
  1493. }
  1494. #textMarquee .newsHistory li{
  1495. padding: 0 2px;
  1496. }
  1497. #textMarquee .newsHistory li:nth-child(odd){
  1498. background: #444;
  1499. }
  1500. #textMarquee .newsHistory li:nth-child(even){
  1501. background: #333;
  1502. }
  1503.  
  1504. body #popupMarquee {
  1505. width: 360px;
  1506. }
  1507. {* 半透明だとflashの上に来ると描画されないので強制的に黒にする(Chromeは平気) *}
  1508. body.full_with_browser #popupMarquee.popupMarqueeBottomLeft {
  1509. background: #000 !important; width: 400px; opacity: 1;
  1510. }
  1511. body.full_with_browser #playerContainer {
  1512. margin-left: 0 !important;
  1513. }
  1514. body:not(.full_with_browser) #playerContainerWrapper {
  1515. padding: 0px;
  1516. }
  1517. body.full_with_browser #playerContainer, body.size_small #playerContainer {
  1518. top: auto;
  1519. }
  1520. body.full_with_browser.no_setting_panel .videoExplorerMenu {
  1521. display:none;
  1522. }
  1523.  
  1524.  
  1525. body:not(.videoExplorer) {*#playlist:not(.nico-bucket-videoExplorer-b)*} #videoExplorerExpand {
  1526. display: none;
  1527. }
  1528. #outline .openVideoExplorer {
  1529. display: none;
  1530. }
  1531. #outline.w_hideSearchExpand .openVideoExplorer {
  1532. display: inline-block;
  1533. }
  1534.  
  1535. .videoExplorerMenu .quickSearchInput {
  1536. background: none repeat scroll 0 0 #F4F4F4;
  1537. border: 1px inset silver;
  1538. left: 60px;
  1539. padding-left: 4px;
  1540. position: absolute;
  1541. top: 2px;
  1542. width: 180px;
  1543. }
  1544. .videoExplorerMenu.w_touch .quickSearchInput {
  1545. top: 4px; font-size: 20px;
  1546. }
  1547. .videoExplorerMenu .clear-button {
  1548. position: absolute;
  1549. width: 20px;
  1550. height: 20px;
  1551. top: 4px;
  1552. left: 250px;
  1553. line-height: 18px;
  1554. font-size: 16px;
  1555. padding: 0;
  1556. background: #e5e5e5;
  1557. text-align: center;
  1558. color: #999;
  1559. cursor: pointer;
  1560. display: none;
  1561. box-sizing: border-box;
  1562. -webkit-box-sizing: border-box;
  1563. -moz-box-sizing: border-box;
  1564. }
  1565.  
  1566. .videoExplorerContent .contentItemList .column4 {
  1567. text-align: center;
  1568. }
  1569. .videoExplorerContent .contentItemList .column4 .balloon {
  1570. bottom: auto; top: 10px;
  1571. }
  1572. .videoExplorerContent .contentItemList .column4 .videoInformation>.info {
  1573. font-size: 85%;
  1574. }
  1575. .videoExplorerContent .contentItemList .column4 .videoInformation>.info .info{
  1576. color: #000;
  1577. }
  1578. .videoExplorerContent .contentItemList .column4 .videoInformationOuter {
  1579. width: 100px; height: 48px; margin: auto; color: #666; text-align: left;
  1580. }
  1581. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  1582. height: 220px;
  1583. }
  1584. .column1 .itemMylistComment {
  1585. font-size: 85%; color: #666; display: none;
  1586. color: #400; border: 1px solid #ccc; padding: 0 4px 0px; line-height: 130%; border-radius: 4px;
  1587. }
  1588. .column1 .itemMylistComment:before {
  1589. content: 'マイリストコメント ';
  1590. background: #ccc; border-radius: 0 0 8px 0; display: inline-block; margin: 0 4px 4px -4px; padding: 2px;
  1591. min-width: 100px;
  1592. }
  1593. .log-user-video-review .column1 .itemMylistComment {
  1594. color: #004;
  1595. }
  1596. .log-user-video-review .column1 .itemMylistComment:before {
  1597. content: 'レビュー ';
  1598. }
  1599. .column1 .itemMylistComment:after {
  1600. content: '';
  1601. }
  1602. .column1 .itemMylistComment pre {
  1603. font-family: inherit;
  1604. display: inline;
  1605. white-space: pre-wrap;
  1606. }
  1607.  
  1608. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer {
  1609. display: none;
  1610. }
  1611. .videoExplorerContent .contentItemList .nicorepoResult .column1 .nicorepoOwnerIconContainer {
  1612. float: right; display: block;
  1613. padding: 24px 14px 0 4px;
  1614. }
  1615. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer img {
  1616. height: 48px;
  1617. }
  1618.  
  1619. .videoExplorerBody.dummyMylist #searchResultContainer .favMylistEditContainer,
  1620. .videoExplorerBody.dummyMylist:not(.ranking) #searchResultMylistSortOptions,
  1621. .videoExplorerBody.dummyMylist .favMylistEditContainer,
  1622. .videoExplorerBody.dummyMylist:not(.ownerNicorepo) #searchResultHeader {
  1623. display: none !important;
  1624. }
  1625.  
  1626. .videoExplorerContent .contentItemList .thumbnailHoverMenu {
  1627. position: absolute; padding: 0; z-index: 100;
  1628. display: none;
  1629. bottom: -1px; left: 0px;
  1630. }
  1631. .videoExplorerContent .contentItemList .deleteFromMyMylist {
  1632. cursor: pointer; font-size: 70%; border: 1px solid #ccc; padding: 0;
  1633. display: none;
  1634. }
  1635. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1636. cursor: pointer; font-size: 70%; border: 1px solid #ccc;;
  1637. }
  1638. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1639. padding: 0 4px;
  1640. }
  1641. .videoExplorerContent .contentItemList .item:hover .thumbnailHoverMenu {
  1642. display: block;
  1643. }
  1644. .videoExplorerContent .contentItemList .log-user-video-upload {
  1645. background: #ffe; border-radius: 4px;
  1646. }
  1647. .videoExplorerContent .contentItemList .nicorepoResult .itemVideoDescription, .videoExplorerContent .contentItemList .nicorepoResult .videoTitle{
  1648. }
  1649. .videoExplorerContent .contentItemList.channelGuideVideo {
  1650. background: #eff; {* 検索結果にチャンネル動画が紛れ込むようになったのでわかりやすく *}
  1651. }
  1652.  
  1653. #videoExplorer.w_deflist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist,
  1654. #videoExplorer.w_mylist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist
  1655. {
  1656. display: inline-block;
  1657. }
  1658.  
  1659. #playlist .generationMessage {
  1660. cursor: pointer;
  1661. }
  1662. #playlist .generationMessage:hover {
  1663. text-decoration: underline;
  1664. }
  1665. #playlist .generationMessage:after {
  1666. content: "▼";
  1667. }
  1668.  
  1669. #yukkuriPanel {
  1670. position: fixed; z-index: 1500; bottom: 0; left: 0; display: inline-block;
  1671. transition: bottom 0.2s ease;
  1672. }
  1673. #yukkuriPanel.mylistPanelLeft {
  1674. bottom: 24px;
  1675. }
  1676. body.w_noNicoru .nicoru-button{
  1677. left: -9999; display: none !important;
  1678. }
  1679. body.w_noNicoru .menuOpened #videoMenuTopList li.videoMenuListNicoru .nicoru-button{
  1680. display: block !important;
  1681. }
  1682. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li {
  1683. margin: 0 18px 4px 0;
  1684. }
  1685. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlContainer, body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlEditContainer {
  1686. padding: 1px 0;
  1687. }
  1688.  
  1689. .userProfile.w_touch {
  1690. font-size: 150%; line-height: 120%;
  1691. }
  1692. .resultPagination.w_touch {
  1693. font-size: 200%;
  1694. }
  1695. .resultPagination.w_touch li{
  1696. padding: 4px 16px;
  1697. }
  1698. select.w_touch {
  1699. font-size: 200%;
  1700. }
  1701. {* 真・browserFullモード *}
  1702. body.full_with_browser.hideCommentInput #nicoplayerContainerInner {
  1703. {* コメント入力欄は動画上表示にするのではなく、画面外に押し出す事によって見えなくする *}
  1704. margin-top: -10px; margin-bottom: -30px !important;
  1705. }
  1706.  
  1707. {*body.full_with_browser:not(.w_fullScreenMenu) .mylistPopupPanel.fixed,*}
  1708. body.full_with_browser .yukkuriButton
  1709. { display:none; }
  1710.  
  1711. #fullScreenMenuContainer {
  1712. -webkit-transition: opacity 0.2s ease-out;
  1713. position:absolute;
  1714. display: none;
  1715. }
  1716. body.full_with_browser #fullScreenToggleContainer {
  1717. background: black;
  1718. display: block;
  1719. bottom: 100px;
  1720. right: 50px;
  1721. z-index: 10000;
  1722. min-width: 400px;
  1723. cursor: nw-resize;
  1724. opacity: 0;
  1725. color: white;
  1726. box-shadow: 2px 2px 2px silver;
  1727. border-radius: 4px;
  1728. }
  1729. body.full_with_browser #fullScreenToggleContainer .title {
  1730. color: #ffc; font-size: 120%;
  1731. }
  1732. body.full_with_browser #fullScreenToggleContainer .ownerIcon {
  1733. float: left; height: 55px; padding: 8px;
  1734. }
  1735. body.full_with_browser #fullScreenToggleContainer:hover, body.full_with_browser #fullScreenToggleContainer.active, body.w_fullScreenMenu #fullScreenToggleContainer {
  1736. opacity: 1;
  1737. }
  1738. body:not(.full_with_browser) #fullScreenToggleContainer { display: none; }
  1739.  
  1740. #sharedNgSettingContainer {
  1741. display: inline-block; font-size: 80%; position: absolute; top: -18px; left: 5px;
  1742. }
  1743. #sharedNgSetting {
  1744. background: #ddd; border: 1px solid silver;
  1745. }
  1746. {* ニュース消す *}
  1747. #content.noNews #textMarquee {
  1748. display: none !important;
  1749. }
  1750. body:not(.videoExplorer):not(.full_with_browser) #content.noNews #playerContainer {
  1751. min-height: 461px;
  1752. }
  1753. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper {
  1754. height: auto !important; position: absolute; bottom: 18px;
  1755. }
  1756. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabContainer {
  1757. bottom: -17px;
  1758. }
  1759. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #playerTabContainer {
  1760. bottom: 20px;
  1761. }
  1762. #playerTabWrapper.w_videoInfo #playerTabContainer, #playerTabWrapper.w_ichiba #playerTabContainer, #playerTabWrapper.w_review #playerTabContainer {
  1763. bottom: 0px !important;
  1764. }
  1765. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_videoInfo,
  1766. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_ichiba,
  1767. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_review
  1768. {
  1769. height: auto !important; position: absolute; bottom: 2px;
  1770. }
  1771. {* body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #leftPanel {
  1772. height: auto !important; position: absolute; bottom: 2px;
  1773. }*}
  1774. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerCommentPanel {
  1775. height: 100% !important;
  1776. }
  1777. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #appliPanel {
  1778. bottom: -18px !important;
  1779. }
  1780. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer {
  1781. height: auto;
  1782. }
  1783. #outline.noIchiba #nicoIchiba, #outline.noReview #videoReview{
  1784. display: none;
  1785. }
  1786. #bottomContentTabContainer.noBottom .outer, #bottomContentTabContainer.noBottom #pageFooter {
  1787. display: none;
  1788. }
  1789. #bottomContentTabContainer.noBottom #outline {
  1790. background: #141414; padding-top: 0; padding-bottom: 35px;
  1791. }
  1792.  
  1793. #content.w_flat_gray #playerContainerWrapper {
  1794. background: #666;
  1795. }
  1796. #content.w_flat_white #playerContainerWrapper {
  1797. background: #f4f4f4;
  1798. }
  1799. #content.w_flat_gray #wallImageContainer, #content.w_flat_white #wallImageContainer,
  1800. #content.w_flat_gray #chipWallList, #content.w_flat_white #chipWallList {
  1801. display: none !important;
  1802. }
  1803. #content #chipWallList {
  1804. right: auto; left: -42px;
  1805. }
  1806. #content #playlist .playlistInformation {
  1807. background: #444;
  1808. }
  1809. #content #videoExplorerExpand a {
  1810. text-shadow: none;
  1811. }
  1812.  
  1813. .videoMenuToggle {
  1814. -webkit-transform-origin: 100% 100%; -webkit-transition: -webkit-transform 0.4s;
  1815. transform-origin: 100% 100%; transition: transform 0.4s;
  1816. z-index: 1000;
  1817. }
  1818. #content.w_compact .videoHeaderTitle {
  1819. letter-spacing: -1px;
  1820. }
  1821. #content.w_compact .videoDetailExpand .arrow {
  1822. position: absolute; top: 8px; right: -24px;
  1823. }
  1824. #content.w_compact .tag1Line .videoMenuToggle {
  1825. transform: scale(0.8, 0.41); -webkit-transform: scale(0.8, 0.41);
  1826. }
  1827. #content.w_compact .tag2Lines .videoMenuToggle {
  1828. transform: scale(0.8); -webkit-transform: scale(0.8);
  1829. }
  1830. #content.w_compact #topVideoInfo .parentVideoInfo {
  1831. margin-top: -9px; margin-bottom: 9x;
  1832. }
  1833. #content.w_compact #topVideoInfo .parentVideoInfo .cct{
  1834. margin-bottom: 0;
  1835. }
  1836. #content.w_compact #topVideoInfo .parentVideoInfo .videoThumb{
  1837. margin-top: 4px;
  1838. }
  1839. #content.w_compact #topVideoInfo .ch_prof, #content.w_compact #topVideoInfo .userProfile {
  1840. min-width: 297px; margin-top: -1px; border: 1px solid #e7e7e7;
  1841. }
  1842. #content.w_compact #videoHeaderDetail .videoDetailExpand{
  1843. height: auto; padding: 0;
  1844. }
  1845. #content.w_compact #topVideoInfo .videoDescription.description {
  1846. background: #fff; margin: 10px 0 0;padding: 4px ;width: 1000px;{* base - 8 *} {*font-size: 90%;*}
  1847. }
  1848.  
  1849. {* 本家の幅が変わったら変える必要がある。 変数化した方が楽かも base = 1008 *}
  1850. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  1851. width: 1318px; {* base + 310 *}
  1852. }
  1853. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact #topVideoInfo .videoDescription.description {
  1854. width: 1226px; {* base + 218 *}
  1855. }
  1856. body:not(.full_with_browser) #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  1857. width: 1092px; {* base + 84 *}
  1858. }
  1859. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoTagContainer {
  1860. width: 1263px; {* base + 255 *}
  1861. }
  1862. body:not(.full_with_browser) #content.w_compact.w_wide #videoTagContainer {
  1863. width: 1040px; {* base + 32 *}
  1864. }
  1865. body:not(.full_with_browser) #content.w_compact #videoTagContainer {
  1866. width: 948px; {* base - 60 *}
  1867. }
  1868. body:not(.full_with_browser) #content.w_compact #videoHeader, #foot_inner {
  1869. width: 1008px; {* base + 48 *}
  1870. }
  1871. body:not(.full_with_browser).size_normal #content.w_compact #videoHeader, .size_normal #foot_inner {
  1872. width: 1234px; {* base + 226 *}
  1873. }
  1874. body:not(.full_with_browser) #content.w_compact.w_wide #videoHeader {
  1875. width: 1100px;
  1876. }
  1877. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoHeader {
  1878. width: 1326px;
  1879. }
  1880.  
  1881. #content.w_compact #topVideoInfo .videoMainInfoContainer{
  1882. padding: 0;
  1883. }
  1884. #content.w_compact #videoDetailInformation{
  1885. border-top: 0;
  1886. }
  1887. #content.w_compact #videoHeaderMenu .searchContainer {
  1888. top: -16px;
  1889. }
  1890. #content.w_compact .videoInformation{
  1891. margin: -4px 0 ;
  1892. }
  1893. #content.w_compact #topVideoInfo .videoStats {
  1894. margin-bottom: 2px;
  1895. }
  1896. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1897. width: 72px;
  1898. }
  1899. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList {
  1900. padding-left: 85px;
  1901. }
  1902. body.full_with_browser #videoHeaderTagList { background: #fafafa; }
  1903. #content.w_compact #topVideoInfo {
  1904. margin: 4px 0 4px;
  1905. }
  1906. #content.w_compact #topVideoInfo .videoShareLinks .socialLinks {
  1907. margin-top: -6px;
  1908. }
  1909. #outline.w_compact #videoInfoHead{
  1910. margin: 0 ;
  1911. }
  1912. #outline.w_compact .videoInformation #videoTitle {
  1913. margin: -4px 0 0;
  1914. }
  1915. #outline.w_compact .videoInformation #videoStats {
  1916. margin-top: -4px;
  1917. }
  1918. #outline.w_compact .videoInformation #videoStats .ranking {
  1919. margin: 0 0 4px;
  1920. }
  1921. #outline.w_compact #videoShareLinks {
  1922. margin: 0;
  1923. }
  1924. #outline.w_compact #bottomVideoDetailInformation {
  1925. margin: -18px 0 0;
  1926. }
  1927. #outline.w_compact .infoHeadOuter .videoEditMenuExpand {
  1928. position: absolute; top: 0;
  1929. }
  1930. #outline.w_compact .videoEditMenu {
  1931. margin: 0;
  1932. }
  1933. #outline.w_compact .videoDescription {
  1934. font-size: 90%; margin-top: -8px; padding: 0 0 4px 4px;
  1935. }
  1936. #outline.w_compact #videoComment {
  1937. margin: 0px; border: 1px solid silver; border-radius: 4px 4px 4px 4px; padding: 0 4px;
  1938. }
  1939. #outline.w_compact #videoComment h4{
  1940. padding-left: 4px;
  1941. }
  1942. #outline.w_compact .videoMainInfoContainer {
  1943. border-bottom: 0; margin-bottom: 0;
  1944. }
  1945. #outline.w_compact {
  1946. border-bottom: 0; margin-bottom: 0;
  1947. }
  1948.  
  1949. #outline.w_compact .sidebar { width: 300px; }
  1950.  
  1951. #outline.w_compact #ichibaMain dl.ichiba_mainitem {
  1952. margin: 0 22px 30px 0;
  1953. }
  1954. #footer { z-index: 1; }
  1955.  
  1956. body.en-us #playerAlignmentArea, body.zh-tw #playerAlignmentArea {
  1957. {*padding-right: 0;*}
  1958. }
  1959. #footer .toggleBottom {
  1960. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  1961. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  1962. }
  1963. #footer:hover .toggleBottom {
  1964. border: 1px outset; background: #ccc;
  1965. }
  1966. #footer .toggleBottom:hover {
  1967. box-shadow: 0px 0px 8px #fff;
  1968. }
  1969. #footer.noBottom .toggleBottom {
  1970. border-radius: 0 0 16px 16px;
  1971. }
  1972. #footer .toggleBottom .openBottom, #footer.noBottom .toggleBottom .closeBottom {
  1973. display: none;
  1974. }
  1975. #footer.noBottom .toggleBottom .openBottom {
  1976. display: block;
  1977. }
  1978. #footer .toggleBottom>div {
  1979. -webkit-transform: scaleX(3); transform: scaleX(3);
  1980. }
  1981. #footer .toggleBottom {
  1982. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  1983. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  1984. }
  1985. #footer:hover .toggleBottom {
  1986. border: 1px outset; background: #ccc;
  1987. }
  1988. #footer .toggleBottom:hover {
  1989. box-shadow: 0px 0px 8px #fff;
  1990. }
  1991.  
  1992. #footer.noBottom #foot_inner { padding: 0; }
  1993. #footer.noBottom a:nth-of-type(3):after, #footer.noBottom a:nth-of-type(6):after {
  1994. content: ' | '; color: white;
  1995. }
  1996. #footer.noBottom br { display: none; }
  1997. html { background: #141414; }
  1998. .videoExplorer #videoExplorer,
  1999. .videoExplorer #videoExplorer .videoExplorerBody,
  2000. .videoExplorerContentWrapper
  2001. {
  2002. background: none;
  2003. }
  2004.  
  2005. .animateBlink {
  2006. -webkit-transition: 1s ease-in; transition: 1s ease-in;
  2007. }
  2008.  
  2009. .w_compact .toggleDetailExpand, .w_compact .shortVideoInfo {
  2010. display: none;
  2011. }
  2012. .videoDetailToggleButton {
  2013. cursor: pointer;
  2014. }
  2015. #leftPanel {
  2016. {*border-radius: 4px 4px 4px 4px;*}
  2017. display: none; padding: 0; position: absolute; text-align: left; top: 0; z-index: 101;
  2018. }
  2019. body.ja-jp #leftPanel { display: none; }
  2020. body:not(.videoExplorer) #leftPanel { display: none; }
  2021.  
  2022.  
  2023. body.full_with_browser #playerTabWrapper, body.full_with_browser:not(.videoExplorer) .w_wide #playerTabWrapper {
  2024. top: auto !important; bottom: 3000px !important; right: 50px !important;
  2025. transition: bottom 0.2s ease-out; max-height: 500px;
  2026. }
  2027.  
  2028. body.full_with_browser.w_fullScreenMenu:not(.videoExplorer) #playerTabWrapper {
  2029. top: auto !important; bottom: 200px !important; right: 50px !important;
  2030. }
  2031.  
  2032. #fullScreenMenuContainer { display: none; }
  2033. body.full_with_browser #fullScreenMenuContainer {
  2034. display: block; position: absolute; bottom: 3000px; left: 50px; z-index: 10000;
  2035. background: #fff; cursor: pointer; transition: bottom 0.2s ease-out;
  2036. }
  2037. body.full_with_browser.w_fullScreenMenu #fullScreenMenuContainer {
  2038. bottom: 100px;
  2039. }
  2040.  
  2041. #fullScreenMenuContainer .button {
  2042. cursor: pointer; transition: color 0.4s ease-out;
  2043. }
  2044. #fullScreenMenuContainer .modeStatus { display: none; font-weight: bolder; }
  2045.  
  2046. body:not(.fullWithPlaylist) #fullScreenMenuContainer .fullScreenModeSwitch .playlistClosing,
  2047. body.fullWithPlaylist #fullScreenMenuContainer .fullScreenModeSwitch .playlistOpening
  2048. { display: inline; }
  2049.  
  2050. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch { color: blue; }
  2051. #nicoplayerContainerInner:not(.stageVideo) #fullScreenMenuContainer .stageVideoSwitch .mode_off,
  2052. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch .mode_on { display: inline; }
  2053.  
  2054.  
  2055. body.full_with_browser.w_fullScreenMenu .videoHeaderOuter {
  2056. position: absolute; z-index: 1000; width: 100%;
  2057. }
  2058. body.full_with_browser.w_fullScreenMenu #videoTagContainer {
  2059. display: block;
  2060. width: 100%;
  2061. margin-top: 0;
  2062. }
  2063. body.full_with_browser.w_fullScreenMenu #videoTagContainer #videoHeaderTagList {
  2064. padding-left: 0;
  2065. }
  2066. body.full_with_browser #videoTagContainer .toggleTagEdit {
  2067. display: none !important;
  2068. }
  2069.  
  2070. .popupMarqueeContent {
  2071. background: black;
  2072. }
  2073.  
  2074. #videoExplorer, #playlist {
  2075. transition: margin-left 0.2s ease-in-out;
  2076. }
  2077.  
  2078. .dummyMylist .editFavorite {
  2079. display: none;
  2080. }
  2081.  
  2082. {* 不要な時まで横スクロールバーが出てしまうので *}
  2083. #songrium_inline { overflow: hidden; }
  2084.  
  2085. .sideVideoInfo .videoLinkContainer {
  2086. display: inline-block;
  2087. white-space: nowrap;
  2088. }
  2089. .sideVideoInfo .nextPlayButton {
  2090. position: absolute;
  2091. margin-top: -6px;
  2092. margin-left: -30px;
  2093. width: 30px;
  2094. height: 30px;
  2095. background: url(http://res.nimg.jp/img/watch_q9/icon_nextplay.png);
  2096. {*background: url("http://res.nimg.jp/img/watch_zero/videoexplorer-s90d011f9a7.png") no-repeat scroll -37px 0 rgba(0, 0, 0, 0);*}
  2097. z-index: 100;
  2098. cursor: pointer;
  2099. text-indent: -999em;
  2100. overflow: hidden;
  2101. display: inline-block;
  2102. -webkit-transform: scale(1.0); transform: scale(1.0);
  2103. }
  2104.  
  2105. .nextPlayButton {
  2106. -webkit-transform: scale(1.5); transform: scale(1.5);
  2107. transition: transform 0.1s ease; -webkit-transition: -webkit-transform 0.1s ease;
  2108. }
  2109. .sideVideoInfo .nextPlayButton:hover {
  2110. -webkit-transform: scale(1.5); transform: scale(1.5);
  2111. }
  2112. .nextPlayButton:active, .sideVideoInfo .nextPlayButton:active {
  2113. -webkit-transform: scale(1.2); transform: scale(1.2);
  2114. }
  2115.  
  2116. .sideVideoInfo .nextPlayButton:active {
  2117. background-position-y: 30px;
  2118. }
  2119.  
  2120. body.w_disableHorizontalScroll {
  2121. overflow-x: hidden !important;
  2122. }
  2123.  
  2124. #videoTagContainerPin { display: none !important; } {* タグを固定しているか4行以上の時に現われるピン *}
  2125.  
  2126. .w_adjusted #selectionSideAdAds >* {
  2127. width: 100%; height: auto; max-width: 300px; max-height: 250px;
  2128. }
  2129.  
  2130. {* *}
  2131. .w_noHover {
  2132. pointer-events: none !important;
  2133. }
  2134. .w_noHover #playlist {
  2135. pointer-events: auto !important;
  2136. }
  2137. {* ソーシャルボタン *}
  2138. .area-JP .panel_ads_shown #playerTabContainer.w_noSocial.has_panel_ads .playerTabContent {
  2139. bottom: 80px;
  2140. }
  2141. .area-JP #playerTabContainer.w_noSocial .playerTabContent {
  2142. bottom: 4px;
  2143. }
  2144. #playerTabContainer.w_noSocial .playerTabAds {
  2145. bottom: 0;
  2146. }
  2147. #playerTabContainer.w_noSocial .socialButtons{
  2148. display: none;
  2149. }
  2150. .w_noSocial .nicoSpotAds {
  2151. bottom: 8px;
  2152. }
  2153.  
  2154.  
  2155. {* テレビちゃんメニュー スライドをやめる *}
  2156. body #videoHeader #videoMenuWrapper{
  2157. position: absolute; width: 324px; height: auto !important;
  2158. opacity: 0;
  2159. transition: opacity 0.4s ease;
  2160. right: 0px;
  2161. }
  2162. body #videoHeader.menuOpened #videoMenuWrapper{
  2163. z-index: 1000 !important;
  2164. border: 1px solid #000;
  2165. background: white;
  2166. box-shadow: 0px 0px 4px #000;
  2167. top: 110px;
  2168. bottom: auto;
  2169. opacity: 1;
  2170. }
  2171. body .tag1Line #videoHeader.menuOpened #videoMenuWrapper{
  2172. top: 62px;
  2173. }
  2174. body .tag2Lines #videoHeader.menuOpened #videoMenuWrapper{
  2175. top: 86px;
  2176. }
  2177. body #videoHeader.infoActive.menuOpened #videoMenuWrapper{
  2178. top: auto;
  2179. bottom: 48px;
  2180. }
  2181. {* body #videoHeader #videoMenuWrapper .defmylistButton, body #videoHeader #videoMenuWrapper .mylistButton {
  2182. display: none !important;
  2183. } *}
  2184. body #videoHeader #videoMenuTopList{
  2185. position: relative;
  2186. width: auto;
  2187. }
  2188. body #videoHeader.menuOpened #videoMenuWrapper .videoMenuList{
  2189. display: inline-block;
  2190. width: 60px;
  2191. min-height: 72px;
  2192. }
  2193. body #videoMenuTopList li.videoMenuListNicoru {
  2194. float: right;
  2195. min-height: 72px;
  2196. }
  2197. body #videoHeader.isAdult .videoMenuToggle, body #videoHeader.noAudioDownload .downloadButton {
  2198. display: inline-block;
  2199. opacity: 0.5;
  2200. pointer-events: none !important;
  2201. }
  2202. {* テレビちゃんメニューのスライド殺す *}
  2203. body #videoHeader.menuOpened #videoMenuWrapper {
  2204. margin-bottom: 0;
  2205. }
  2206. body #videoHeader.menuOpened #videoHeaderDetail {
  2207. margin-top: 8px;
  2208. }
  2209.  
  2210.  
  2211. .largeThumbnailPopup, .largeThumbnailPopup div{
  2212. background-color: #000;
  2213. background-size: contain;
  2214. background-repeat: no-repeat;
  2215. background-position: center center;
  2216. background-size: contain;
  2217. -moz-background-size: contain;
  2218. -webkit-background-size: contain;
  2219. -o-background-size: contain;
  2220. -ms-background-size: contain;
  2221. }
  2222.  
  2223. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  2224. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  2225. addStyle(__css__, 'watchItLaterStyle');
  2226. })(); // end of watchItLaterStyle
  2227.  
  2228.  
  2229.  
  2230.  
  2231.  
  2232. conf.load = function() {
  2233. try {
  2234. function loadStorage(key, def) {
  2235. if (window.localStorage[key] === void 0) { return def; }
  2236. return JSON.parse(window.localStorage.getItem(key));
  2237. }
  2238.  
  2239. for (var v in conf) {
  2240. if (typeof conf[v] === 'function') { continue; }
  2241. conf[v] = loadStorage('watchItLater_' + v, conf[v]);
  2242. }
  2243. } catch (e) {
  2244. }
  2245. };
  2246.  
  2247. conf.getValue = function(varName) {
  2248. return conf[varName];
  2249. };
  2250. conf.setValue = function(k, v) {
  2251. var lastValue = conf[k];
  2252. if (lastValue !== v) {
  2253. conf[k] = v;
  2254. window.localStorage.setItem('watchItLater_' + k, JSON.stringify(v));
  2255. EventDispatcher.dispatch('on.config.' + k, v, lastValue);
  2256. }
  2257. };
  2258. conf.load();
  2259.  
  2260. var console = (function(conf) {
  2261. if (conf.debugMode) { return window.console; }
  2262. var noop = function() {};
  2263. return {
  2264. log: noop,
  2265. error: noop,
  2266. trace: noop,
  2267. warn: noop,
  2268. table: noop
  2269. };
  2270. })(conf);
  2271.  
  2272. var ConfigPanel = (function($, conf, w) {
  2273. var pt = function(){};
  2274. var $panel = null, $shadow = null;
  2275. var menus = [
  2276. {title: '再生開始・終了時の設定', className: 'videoStart'},
  2277. {title: '自動で全画面モードにする', varName: 'autoBrowserFull',
  2278. values: {'する': true, 'しない': false}, addClass: true},
  2279. {title: '自動全画面化オンでも、ユーザーニコ割のある動画は', varName: 'disableAutoBrowserFullIfNicowari',
  2280. values: {'全画面化しない': true, '全画面化する': false}},
  2281. {title: '自動で検索モードにする(自動全画面化オフ時)', varName: 'autoOpenSearch',
  2282. values: {'する': true, 'しない': false}},
  2283. {title: '動画の位置に自動スクロール(自動全画面化オフ時)', varName: 'autoScrollToPlayer',
  2284. values: {'する': true, 'しない': false}},
  2285. // {title: '終了時に全画面モードを解除(原宿と同じにする)', varName: 'autoNotFull',
  2286. // values: {'する': true, 'しない': false},
  2287. // description: '連続再生中は解除しません'},
  2288. {title: 'ウィンドウがアクティブの時だけ自動再生する', varName: 'autoPlayIfWindowActive',
  2289. description: 'QWatch側の設定パネルの自動再生はオフにしてください。\n■こんな人におすすめ\n・自動再生ONにしたいけど別タブで開く時は自動再生したくない\n・複数タブ開いたままブラウザ再起動したら全部のタブで再生が始まって「うるせー!」という経験のある人',
  2290. values: {'する': 'yes', 'しない': 'no'}},
  2291. {title: '動画が切り替わる時、ポップアップでタイトルと再生数を表示', varName: 'popupViewCounter',
  2292. description: '全画面状態で連続再生している時などに便利です',
  2293. values: {'する': 'always', '全画面時のみ': 'full', 'しない': 'none'}},
  2294.  
  2295. {title: 'プレイヤーの設定', className: 'playerSetting'},
  2296. {title: 'コメントパネルを広くする', varName: 'wideCommentPanel',
  2297. values: {'する': true, 'しない': false}},
  2298. {title: 'コメントパネルにNG共有設定を表示', varName: 'enableSharedNgSetting',
  2299. values: {'する': true, 'しない': false}, addClass: true},
  2300. {title: 'コメントの表示', varName: 'commentVisibility',
  2301. values: {'オフ': 'hidden', '最後の状態を記憶': 'lastState', 'オン': 'visible'}},
  2302. {title: '右のパネルに動画情報・市場・レビューを表示', varName: 'rightPanelJack', reload: true,
  2303. values: {'する': true, 'しない': false}},
  2304. {title: 'ページのヘッダに再生数表示', varName: 'headerViewCounter', reload: true,
  2305. values: {'する': true, 'しない': false}},
  2306. {title: 'ニコニコニュースの履歴を保持する', varName: 'enableNewsHistory', reload: true,
  2307. values: {'する': true, 'しない': false}},
  2308. {title: 'ニコニコニュースを消す', varName: 'hideNicoNews',
  2309. values: {'消す': true, '消さない': false}},
  2310. {title: 'プレイヤーの背景', varName: 'playerBgStyle',
  2311. description: 'ウォール機能より優先されます',
  2312. values: {'白': 'white', 'グレー': 'gray', 'ウォール': ''}},
  2313. {title: 'コメントの盛り上がりをグラフ表示', varName: 'enableHeatMap', reload: true,
  2314. description: '動画のどのあたりが盛り上がっているのか、わかりやすくなります',
  2315. values: {'する': true, 'しない': false}},
  2316. {title: '大画面をもっと大画面にする', varName: 'customPlayerSize',
  2317. description: '※有効にするとニコニコニュースが表示できなくなります。',
  2318. values: {'フルHD': '1080p', '720p': '720p', '自動調整(推奨)': 'auto', 'しない': ''}},
  2319. {title: 'プレイリスト消えないモード(実験中)', varName: 'storagePlaylistMode', reload: true,
  2320. description: '有効にすると、リロードしてもプレイリストが消えなくなります。',
  2321. values:
  2322. (conf.debugMode ?
  2323. {'ウィンドウを閉じるまで': 'sessionStorage', 'ずっと保持': 'localStorage', 'しない': ''} :
  2324. {'有効(ウィンドウを閉じるまで)': 'sessionStorage', '無効': ''})
  2325. },
  2326. {title: '説明文中の動画IDにサムネイル表示(実験中)', varName: 'enableDescriptionThumbnail', reload: true,
  2327. // description: 'Chrome+Tampermonkeyでは動きません',
  2328. values: {'有効': true, '無効': false}},
  2329.  
  2330.  
  2331. {title: '検索モードの設定', className: 'videoExplorer'},
  2332. {title: '検索モードを無効化', varName: 'disableVideoExplorer',
  2333. description: '無効にするとタグ検索などが原宿と同じになります。\nただし、自分で検索モードにしている時は検索モードで開きます',
  2334. values: {'する': true, 'しない': false}},
  2335. {title: 'プレイヤーをできるだけ大きくする (コメントやシークも可能にする)', varName: 'videoExplorerHack',
  2336. description: '便利ですがちょっと重いです。\n大きめのモニターだと快適ですが、小さいといまいちかも',
  2337. values: {'する': true, 'しない': false}},
  2338. {title: 'お気に入りタグを表示', varName: 'enableFavTags',
  2339. values: {'する': true, 'しない': false}},
  2340. {title: 'お気に入りマイリストを表示', varName: 'enableFavMylists',
  2341. description: '更新のあったリストが上に来るので、新着動画のチェックに便利です。',
  2342. values: {'する': true, 'しない': false}},
  2343. {title: '「マイリストから外す」ボタンを表示', varName: 'enableMylistDeleteButton',
  2344. description: 'マイリストの整理に便利。\n ※ 消す時に確認ダイアログは出ないので注意',
  2345. values: {'する': true, 'しない': false}},
  2346. {title: '検索時に関連タグを表示する', varName: 'enableRelatedTag',
  2347. values: {'する': true, 'しない': false}},
  2348. // {title: 'niconico新検索βを使う', varName: 'searchEngine',
  2349. // description: '投稿期間や動画長による絞り込みができるようになります',
  2350. // values: {'使う': 'sugoi', '使わない': 'normal'}},
  2351. {title: '1ページの表示件数', varName: 'searchPageItemCount',
  2352. values: {'100件': 100, '50件': 50, '32件': 32}},
  2353.  
  2354. {title: '全画面モードの設定', className: 'fullScreen'},
  2355. {title: '操作パネルとコメント入力欄を隠す', varName: 'controllerVisibilityInFull',
  2356. description: '全画面の時は少しでも動画を大きくしたい場合に便利',
  2357. values: {'隠す': 'hidden', '隠さない': ''}},
  2358. {title: '右下のマイリストメニュー', varName: 'hideMenuInFull',
  2359. values: {'完全に消す': 'hideAll', '色だけ変える': '', '目立たなくする': 'hide'}},
  2360. {title: 'ホイールを回したら動画情報を出す', varName: 'enableFullScreenMenu',
  2361. description: 'ホイールを大きく下に回すとメニューが出ます。タッチパネルも対応',
  2362. values: {'する': true, 'しない': false}},
  2363.  
  2364. {title: 'ページ下半身の設定', className: 'playerBottom'},
  2365. {title: 'ニコニコ市場の表示', varName: 'ichibaVisibility',
  2366. values: {'非表示': 'hidden', '表示': 'visible'}},
  2367. {title: 'レビューの表示', varName: 'reviewVisibility',
  2368. values: {'非表示': 'hidden', '表示': 'visible'}},
  2369.  
  2370. {title: '省スペース/軽量化設定', className: 'compact'},
  2371. {title: 'タグが2行以内の時に高さを詰める(ピン留め時のみ)', varName: 'enableAutoTagContainerHeight', reload: true,
  2372. values: {'詰める': true, '詰めない': false}},
  2373. {title: '動画情報の空きスペースを詰める', varName: 'compactVideoInfo',
  2374. description: '原宿ぐらいの密度になります。ちょっと窮屈かも',
  2375. values: {'詰める': true, '詰めない': false}},
  2376. // {title: '背景のグラデーションをなくす', varName: 'flatDesignMode',
  2377. // description: '軽い表示になります',
  2378. // values: {'なくす': 'on', 'なくさない': ''}},
  2379. {title: '「ニコる」をなくす', varName: 'noNicoru',
  2380. description: '画面上から見えなくなります。\nまた、コメントパネルの処理が軽くなります',
  2381. values: {'なくす': true, 'なくさない': false}},
  2382. {title: 'コメントパネルのマウスオーバー処理をなくす', varName: 'removeCommentPanelHoverEvent', reload: true,
  2383. description: 'マウスオーバー時のちらちらした物がなくなり、表示が軽くなります',
  2384. values: {'なくす': true, 'なくさない': false}},
  2385. {title: 'タグの自動更新を無効化', varName: 'disableTagReload',
  2386. values: {'する': true, 'しない': false}},
  2387. {title: '横スクロールバーを出なくする', varName: 'disableHorizontalScroll',
  2388. values: {'する': true, 'しない': false}},
  2389. {title: 'コメントパネル下のソーシャルボタン', varName: 'hideCommentPanelSocialButtons',
  2390. values: {'隠す': true, '隠さない': false}},
  2391. {title: 'GPUレイヤーを使用してみる(上級者用)', varName: 'enableGpuLayer', reload: true, debugOnly: true,
  2392. description: '環境によっては軽くなる かも しれません',
  2393. values: {'する': true, 'しない': false}},
  2394.  
  2395. {title: 'その他の設定', className: 'otherSetting'},
  2396. {title: '動画リンクにカーソルを重ねたらマイリストメニューを表示', varName: 'enableHoverPopup', reload: true,
  2397. description: 'マウスカーソルを重ねた時に出るのが邪魔な人はオフにしてください',
  2398. values: {'する': true, 'しない': false}},
  2399. {title: '動画リンクにカーソルを重ねてからメニューが出るまでの時間(秒)', varName: 'hoverMenuDelay',
  2400. type: 'text', description: '単位は秒。 標準は0.4です'},
  2401. {title: 'ニコレポのポップアップを置き換える', varName: 'replacePopupMarquee', reload: true,
  2402. description: '画面隅に出るポップアップの不可解な挙動を調整します',
  2403. values: {'する': true, 'しない': false}},
  2404. {title: '検索時のデフォルトパラメータ', varName: 'defaultSearchOption', type: 'text',
  2405. description: '常に指定したいパラメータ指定するのに便利です\n例: 「-グロ -例のアレ」とすると、その言葉が含まれる動画が除外されます'},
  2406. {title: '「@ジャンプ」を無効化', varName: 'ignoreJumpCommand', reload: true,
  2407. description: '勝手に他の動画に飛ばされる機能を無効化します。',
  2408. values: {'する': true, 'しない': false}},
  2409. {title: '「@ジャンプ」によるシーク無効化(無限ループなど)', varName: 'nicoSSeekCount', reload: true,
  2410. description: '完全に無効にする以外に、一動画あたりの回数を指定できます',
  2411. values: {'2回まで有効': 2, '1回まで有効': 1, '完全無効化': 0, 'しない': -1}},
  2412. {title: 'タッチパネル向けモード(画面を右フリックで開始)', varName: 'enableQTouch',
  2413. description: '指で操作しやすいように、一部のボタンやメニューが大きくなります',
  2414. values: {'使う': true, '使わない': false}},
  2415. {title: 'マイリストメニューの位置', varName: 'mylistPanelPosition',
  2416. values: {'左下': 'left', '右下': ''}},
  2417. {title: '2本目以降の動画だけ自動再生 (※プレミアム用)', varName: 'autoPlay2ndVideo', reload: true,
  2418. values: {'する': true, 'しない': false}},
  2419. {title: 'マイリストのローカルキャッシュ', varName: 'enableLocalMylistCache', reload: true,
  2420. description: '動画がどのマイリストに登録されてるかの情報をキャッシュします。\n「my」ボタンの右クリックを活用する人はおすすめ。',
  2421. values: {'有効': true, '無効': false}},
  2422.  
  2423.  
  2424. {title: 'マウスとキーボードの設定', description: '※Chromeはコメント入力中も反応してしまいます', className: 'shortcut'},
  2425. {title: '背景ダブルクリックで動画の位置にスクロール', varName: 'doubleClickScroll',
  2426. description: 'なにもない場所をダブルクリックすると、動画の位置にスクロールします。\n 市場を見てからプレイヤーに戻りたい時などに便利',
  2427. values: {'する': true, 'しない': false}},
  2428. {title: 'マウスのボタン+ホイールでどこでも音量調整', varName: 'mouseClickWheelVolume',
  2429. description: 'とっさに音量を変えたい時に便利',
  2430. values: {'左ボタン+ホイール': 1, '右ボタン+ホイール': 2, '使わない': 0}},
  2431. {title: '停止/再生', varName: 'shortcutTogglePlay', type: 'keyInput'},
  2432. {title: 'とりあえずマイリスト登録', varName: 'shortcutDefMylist', type: 'keyInput'},
  2433. {title: 'マイリスト登録', varName: 'shortcutMylist', type: 'keyInput',
  2434. description: '右下で選択中のマイリストに登録'},
  2435. {title: 'とりあえずマイリストを開く', varName: 'shortcutOpenDefMylist', type: 'keyInput'},
  2436. {title: '動画投稿者の関連動画を開く', varName: 'shortcutShowOtherVideo', type: 'keyInput'},
  2437. {title: '検索画面を開く', varName: 'shortcutOpenSearch', type: 'keyInput'},
  2438. {title: '関連動画(オススメ)を開く', varName: 'shortcutOpenRecommend', type: 'keyInput'},
  2439. {title: 'コメント表示ON/OFF', varName: 'shortcutCommentVisibility', type: 'keyInput'},
  2440. {title: 'プレイヤーの位置までスクロール', varName: 'shortcutScrollToNicoPlayer', type: 'keyInput'},
  2441. {title: 'ミュート', varName: 'shortcutMute', type: 'keyInput'},
  2442. {title: 'コメントの背面表示ON/FF', varName: 'shortcutDeepenedComment', type: 'keyInput'},
  2443. {title: 'ハードウェアアクセラレーションON/FF', varName: 'shortcutToggleStageVideo', type: 'keyInput'},
  2444.  
  2445. {title: 'その他2(一発ネタ系)', description: 'いつのまにか消えるかもしれません', className: 'shortcut'},
  2446. {title: 'テレビちゃんメニュー内にランダム画像(左上)表示', varName: 'hidariue',
  2447. values: {'する': true, 'しない': false}},
  2448. {title: 'ゆっくり再生(スロー再生)ボタンを表示', varName: 'enableYukkuriPlayButton',
  2449. values: {'する': true, 'しない': false}},
  2450.  
  2451. {title: '実験中の設定', debugOnly: true, className: 'forDebug'},
  2452. // {title: 'プレイリスト消えないモード(※実験中)', varName: 'hashPlaylistMode', debugOnly: true, reload: true,
  2453. // values: {'有効(連続再生中のみ)': 1, '有効(常時)': 2, '無効': 0}},
  2454.  
  2455.  
  2456.  
  2457. ];
  2458.  
  2459. var listener = [];
  2460. function dispatchEvent(name, value, lastValue) {
  2461. for (var i = 0; i < listener.length; i++) {
  2462. (listener[i])(name, value, lastValue);
  2463. }
  2464. }
  2465. pt.createPanelDom = function() {
  2466. if ($panel === null) {
  2467. $panel = w.jQuery([
  2468. '<div id="watchItLaterConfigPanel">',
  2469. '<div class="head"><button class="closeButton" title="閉じる">▲</button><h2>WatchItLaterの設定</h2>(※)のつく項目は、リロード後に反映されます</div>',
  2470. '<div class="inner"></div></div>'
  2471. ].join(''));
  2472. $panel.on('click', function(e) { e.stopPropagation(); });
  2473.  
  2474. var scrollTo = function() {
  2475. var $target = this;
  2476. var isOpen = $target.parent().toggleClass('open').hasClass('open');
  2477. if (isOpen) {
  2478. setTimeout(function() {
  2479. var $inner = $('#watchItLaterConfigPanel .inner');
  2480. $inner.animate({
  2481. scrollTop: $inner.scrollTop() + $target.parent().position().top - 50
  2482. }, 400);
  2483. }, 200);
  2484. }
  2485. };
  2486.  
  2487. var $ul = null, $inner = $panel.find('.inner'), $item; //$panel.find('ul'), $item;
  2488. for (var i = 0, len = menus.length; i < len; i++) {
  2489. if (menus[i].varName) {
  2490. $item = this.createMenuItem(menus[i]);
  2491. } else {
  2492. if (menus[i].description) {
  2493. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span><span class="description">'+ menus[i].description + '</span></div></li>');
  2494. } else {
  2495. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span></div></li>');
  2496. }
  2497. if ($ul) $inner.append($ul);
  2498. $ul =$('<ul class="sectionContainer"/>').addClass(menus[i].className + 'Container');
  2499. $item.click($.proxy(scrollTo, $item));
  2500. }
  2501. $item.toggleClass('debugOnly', menus[i].debugOnly === true).toggleClass('reload', menus[i].reload === true);
  2502. if ($ul) $ul.append($item);
  2503. }
  2504. if ($ul) $inner.append($ul);
  2505. $panel.toggleClass('debugMode', conf.debugMode);
  2506. var $bottom = w.jQuery('<div class="foot"></div>'), self = this;
  2507. $panel.append($bottom);
  2508. $panel.find('.closeButton').click(function() {
  2509. self.close();
  2510. });
  2511. if ($shadow === null) {
  2512. $shadow = $('<div id="watchItLaterConfigPanelShadow" /><div id="watchItLaterConfigPanelShadowTop"/><div id="watchItLaterConfigPanelOverShadow"/>');
  2513. }
  2514. }
  2515. };
  2516.  
  2517. pt.refresh = function() {
  2518. var isVisible = $panel.hasClass('open');
  2519. $panel.remove().empty();
  2520. $panel = null;
  2521. this.createPanelDom();
  2522. if (isVisible) { $panel.show(); }
  2523. };
  2524.  
  2525. pt.createMenuItem = function(menu) {
  2526. if (menu.type === 'text') {
  2527. return this.createTextMenuItem(menu);
  2528. } else
  2529. if (menu.type === 'keyInput') {
  2530. return this.createKeyInputMenuItem(menu);
  2531. } else {
  2532. return this.createRadioMenuItem(menu);
  2533. }
  2534. };
  2535. pt.createRadioMenuItem = function(menu) {
  2536. var title = menu.title, varName = menu.varName, values = menu.values;
  2537. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2538. if (menu.className) { $menu.addClass(menu.className);}
  2539. if (menu.description) { $menu.attr('title', menu.description); }
  2540. var currentValue = conf.getValue(varName);
  2541. $menu.addClass(menu.varName);
  2542. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2543. for (var k in values) {
  2544. var v = values[k];
  2545. var $label = w.jQuery('<label></label>');
  2546. var $chk = w.jQuery('<input>');
  2547. $chk.attr({type: 'radio', name: varName, value: JSON.stringify(v)});
  2548.  
  2549. if (currentValue === v) {
  2550. $chk.prop('checked', 'checked');
  2551. }
  2552. $chk.click(function() {
  2553. var newValue = JSON.parse(this.value), oldValue = conf.getValue(varName);
  2554. if (oldValue !== newValue) {
  2555. if (menu.addClass) {
  2556. $panel.removeClass(menu.varName + '_' + oldValue).addClass(menu.varName + '_' + newValue);
  2557. }
  2558. conf.setValue(menu.varName, newValue);
  2559. if (typeof menu.onchange === 'function') {
  2560. menu.onchange(newValue, oldValue);
  2561. }
  2562. dispatchEvent(menu.varName, newValue, oldValue);
  2563. }
  2564. });
  2565. $label.append($chk).append(w.jQuery('<span>' + k + '</span>'));
  2566. $menu.append($label);
  2567. }
  2568. return $menu;
  2569. };
  2570. pt.createTextMenuItem = function(menu) {
  2571. var title = menu.title, varName = menu.varName;
  2572. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2573. if (menu.className) { $menu.addClass(menu.className);}
  2574. if (menu.description) { $menu.attr('title', menu.description); }
  2575. var currentValue = conf.getValue(varName);
  2576. var $input = w.jQuery('<input type="text" />');
  2577. $menu.addClass(menu.varName);
  2578. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2579. $input.val(currentValue);
  2580. $input.change(function() {
  2581. var newValue = $input.val(), oldValue = conf.getValue(varName);
  2582. if (oldValue !== newValue) {
  2583. conf.setValue(varName, newValue);
  2584. if (typeof menu.onchange === 'function') {
  2585. menu.onchange(newValue, oldValue);
  2586. }
  2587. dispatchEvent(menu.varName, newValue, oldValue);
  2588. }
  2589. });
  2590. $menu.append($input);
  2591. return $menu;
  2592. };
  2593.  
  2594. pt.createKeyInputMenuItem = function(menu) {
  2595. var title = menu.title, varName = menu.varName;
  2596. var currentValue = conf.getValue(varName), currentKey = currentValue.char;
  2597.  
  2598. function update() {
  2599. var newValue = {char: $sel.val(), ctrl: $menu.hasClass('ctrl'), alt: $menu.hasClass('alt'), shift: $menu.hasClass('shift'), enable: $menu.hasClass('enable')};
  2600. conf.setValue(varName, newValue);
  2601. if (typeof menu.onchange === 'function') {
  2602. menu.onchange(newValue);
  2603. }
  2604. dispatchEvent(menu.varName, newValue, conf.getValue(varName));
  2605. }
  2606.  
  2607. var $menu = w.jQuery('<li class="shortcutSetting"><p class="title">' + title + '</p></li>');
  2608. var sel = ['<select>'], $sel;
  2609. for (var v = 48; v <= 90; v++) {
  2610. if (v >= 0x3c && v <= 0x3f) continue;
  2611. var c = String.fromCharCode(v);
  2612. var op = ['<option value="', c, '">', c, '</option>' ].join('');
  2613. sel.push(op);
  2614. }
  2615. sel.push('</select>');
  2616. $sel = w.jQuery(sel.join(''));
  2617. 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) {
  2618. var meta = w.jQuery(e.target).attr('data-meta');
  2619. $menu.toggleClass(meta);
  2620. update();
  2621. });
  2622. $sel.change(update);
  2623.  
  2624. $menu.toggleClass('enable', currentValue.enable).toggleClass('ctrl', currentValue.ctrl).toggleClass('alt', currentValue.alt).toggleClass('shift', currentValue.shift);
  2625. $sel.val(currentKey);
  2626.  
  2627. if (menu.className) { $menu.addClass(menu.className);}
  2628. if (menu.description) { $menu.attr('title', menu.description); }
  2629.  
  2630. $menu.append(w.jQuery('<span/>').append($meta).append($sel));
  2631.  
  2632. return $menu;
  2633. };
  2634.  
  2635. pt.toggleOpenSection = function(sectionName, toggle) {
  2636. $('#watchItLaterConfigPanel .'+ sectionName + 'Container').toggleClass('open', toggle);
  2637. $('#watchItLaterConfigPanel .inner').scrollTop($('#watchItLaterConfigPanel .' + sectionName).position().top - 50);
  2638. };
  2639.  
  2640. pt.addChangeEventListener = function(callback) {
  2641. listener.push(callback);
  2642. };
  2643. pt.open = function() {
  2644. $('body').append($shadow).append($panel);
  2645. setTimeout(function() {
  2646. $shadow.addClass('open'); $panel.addClass('open');
  2647. }, 50);
  2648. setTimeout(function() {
  2649. if (WatchController.isFullScreen()) {
  2650. pt.toggleOpenSection('fullScreen', true);
  2651. } else
  2652. if (WatchController.isSearchMode()) {
  2653. pt.toggleOpenSection('videoExplorer', true);
  2654. }
  2655. }, 1000);
  2656. };
  2657. pt.close = function() {
  2658. $shadow.removeClass('open'); $panel.removeClass('open');
  2659. setTimeout(function() {
  2660. $shadow.detach(); $panel.detach();
  2661. }, 800);
  2662. };
  2663. pt.toggle = function() {
  2664. this.createPanelDom();
  2665. if ($panel.hasClass('open')) {
  2666. this.close();
  2667. } else {
  2668. this.open();
  2669. }
  2670. };
  2671.  
  2672. return pt;
  2673. })(w.jQuery, conf, w);
  2674.  
  2675.  
  2676. /**
  2677. * 通信用
  2678. */
  2679. window.WatchItLater = {
  2680. config: {
  2681. get: function(varName) {
  2682. return conf.getValue(varName);
  2683. },
  2684. set: function(varName, value) {
  2685. conf.setValue(varName, value);
  2686. },
  2687. open: function() {
  2688. ConfigPanel.open();
  2689. }
  2690. },
  2691. loader: {},
  2692. debug: {},
  2693. init: {},
  2694. test: {
  2695. assert: function(v, m) {
  2696. if (v === true) {
  2697. window.console.log('%c OK: ', 'color: black; background: lime;', m);
  2698. } else {
  2699. window.console.log('%cFail: ', 'color: white; background: red;', m);
  2700. throw {message: 'Fail'};
  2701. }
  2702. },
  2703. expect: function(a) {
  2704. try {
  2705. var assert = window.WatchItLater.test.assert, exp = {
  2706. toBeTrue: function( desc) { assert(a === true , desc); },
  2707. toBeFalse: function( desc) { assert(a === false , desc); },
  2708. toEqual: function(b, desc) { assert(a === b , desc); },
  2709. toBeNull: function( desc) { assert(a === null , desc); },
  2710. toBeNotNull: function( desc) { assert(a !== null , desc); },
  2711. toBeDefined: function( desc) { assert(a !== void 0 , desc); },
  2712. toBeTruthy: function( desc) { assert(a ? true : false, desc); }
  2713. };
  2714. return exp;
  2715. } catch(e) {
  2716. window.console.log('%c', a);
  2717. }
  2718. },
  2719. spec: {},
  2720. run: function(name) {
  2721. var def = (new $.Deferred()), promise = def.promise();
  2722. var con = function(name) {
  2723. return function() {
  2724. var d = new $.Deferred();
  2725. setTimeout(function() {
  2726. window.console.log('%c RUN: ' + name, 'background: #8ff;');
  2727. d.resolve();
  2728. }, 100);
  2729. return d.promise();
  2730. };
  2731. };
  2732. var wrap = function(self, name) {
  2733. return function() {
  2734. var d = new $.Deferred();
  2735. setTimeout(function() {
  2736. try {
  2737. $.proxy(self.spec[name], self)(d);
  2738. } catch (e) {
  2739. window.console.log(e);
  2740. d.reject();
  2741. }
  2742. }, 0);
  2743. return d.promise();
  2744. };
  2745. };
  2746. var onFail = function(e) {
  2747. window.console.log('%c fail : ','background: red;', e);
  2748. };
  2749.  
  2750. if (name) {
  2751. promise = promise.then(con(name)).then(wrap(this, name), onFail);
  2752. } else {
  2753. for(var v in this.spec) {
  2754. if (!v.match(/^test/)) continue;
  2755. promise = promise.then(con(v)) .then(wrap(this, v), onFail);
  2756. }
  2757. }
  2758. promise.then(
  2759. function() { window.console.log('%cテスト完了', 'background: #8ff'); },
  2760. function() { window.console.log('%cテスト失敗', 'background: #f00'); }
  2761. );
  2762. def.resolve();
  2763. }
  2764. }
  2765. };
  2766. // w.WatchItLater = window.WatchItLater;
  2767.  
  2768.  
  2769. var EventDispatcher = (function(conf) {
  2770. var events = {};
  2771.  
  2772. function addEventListener(name, callback) {
  2773. name = name.toLowerCase();
  2774. if (!events[name]) {
  2775. events[name] = [];
  2776. }
  2777. events[name].push(callback);
  2778. }
  2779.  
  2780. function _dispatch(name) {
  2781. name = name.toLowerCase();
  2782. if (!events[name]) { return; }
  2783. var e = events[name];
  2784. for (var i =0, len = e.length; i < len; i++) {
  2785. try {
  2786. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  2787. } catch (ex) {
  2788. console.log('%c' + name, 'background:red; color: white;', i, e[i], ex);
  2789. }
  2790. }
  2791. }
  2792. function dispatch(name) {
  2793. console.log('%cevent:', 'background: blue; color: white;', name);//, arguments);
  2794. _dispatch.apply(null, arguments);
  2795. }
  2796. return {
  2797. addEventListener: addEventListener,
  2798. dispatch: dispatch,
  2799. _dispatch: _dispatch // コンソール汚したくない用
  2800. };
  2801. })(conf);
  2802. window.WatchItLater.event = EventDispatcher;
  2803.  
  2804. /*
  2805. * 通算視聴回数をカウント。 カウントしても意味はないけど、どれだけ無駄な時間を費やしたかを知りたくて実装。
  2806. */
  2807. var WatchCounter = (function(conf, w) {
  2808. var key = 'watchItLater_watchCounter';
  2809. function get() {
  2810. return JSON.parse(w.localStorage.getItem(key));
  2811. }
  2812. function add() {
  2813. var v = get() + 1;
  2814. w.localStorage.setItem(key, JSON.stringify(v));
  2815. console.log('%cwatchCounter: %c%d', 'color: orange;', 'font-weight: bolder;', v);
  2816. return v;
  2817. }
  2818. var self = {
  2819. get: get,
  2820. add: add
  2821. };
  2822. return self;
  2823. })(conf, w);
  2824. window.WatchItLater.counter = WatchCounter;
  2825.  
  2826. /**
  2827. * 動画タグ取得とポップアップ
  2828. *
  2829. */
  2830. var VideoTags = (function(conf, w){
  2831.  
  2832. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  2833. var pt = function(){};
  2834. var lastPopup = null;
  2835.  
  2836. pt.get = function(watchId, callback) {
  2837. var _get = function(watchId, callback) {
  2838. var url = 'http://' + host + '/tag_edit/' + watchId + '/?res_type=json&cmd=tags';
  2839. //http://www.nicovideo.jp/tag_edit/sm9/?res_type=json&cmd=tags
  2840. var req = {
  2841. method: 'GET',
  2842. url: url,
  2843. onload: function(resp) {
  2844. var result = JSON.parse(resp.responseText);
  2845. if (typeof callback === 'function') callback(result.status, result);
  2846. }
  2847. };
  2848. GM_xmlhttpRequest(req);
  2849. };
  2850.  
  2851. WatchController.getTid2Vid(watchId, function(videoId) {
  2852. _get(videoId, callback);
  2853. });
  2854. };
  2855.  
  2856. pt.hidePopup = function() {
  2857. if (lastPopup) {
  2858. lastPopup.style.display = 'none';
  2859. }
  2860. };
  2861.  
  2862. var uniq = null, $history = null, popupContainer = null;
  2863. pt.popupItems = function(watchId, baseX, baseY) {
  2864. var self = this;
  2865. popupContainer.innerHTML = '';
  2866. this.get(watchId, function(status, resp) {
  2867. if (status === 'ok') {
  2868. var tags = resp.tags;
  2869. self.hidePopup();
  2870. if (tags.length > 0) {
  2871. lastPopup = createPopup(tags, baseX, baseY);
  2872. } else {
  2873. Popup.show('この動画のタグはありません');
  2874. }
  2875. } else {
  2876. Popup.alert(resp.error_message);
  2877. }
  2878. });
  2879.  
  2880. function createPopup(tags, baseX, baseY) {
  2881. var popup = createDOM(tags, baseX, baseY);
  2882. popupContainer.appendChild(popup);
  2883. popup.style.right = null;
  2884. popup.style.left = baseX + 'px';
  2885. popup.style.top = Math.max(baseY - popup.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  2886. if (popup.offsetLeft + popup.offsetWidth > document.body.clientWidth) {
  2887. popup.style.left = null;
  2888. popup.style.right = 0;
  2889. }
  2890.  
  2891. return popup;
  2892. }
  2893.  
  2894. function createDOM(tags) {
  2895. var items = document.createElement('ul');
  2896. for (var i = 0, len = tags.length; i < len; i++) {
  2897. items.appendChild(createItemDOM(tags[i]));
  2898. }
  2899. var popup = createPopupDOM();
  2900.  
  2901. popup.appendChild(items);
  2902. return popup;
  2903. }
  2904.  
  2905. function createPopupDOM() {
  2906. var popup = document.createElement('div');
  2907. popup.className = 'tagItemsPopup popupMenu';
  2908. popup.addEventListener('click', createPopupOnClick(), false);
  2909. return popup;
  2910. }
  2911.  
  2912. function createPopupOnClick() {
  2913. return function(e) {
  2914. if (e.button !== 0 || e.shiftKey || e.ctrlKey || e.altKey || e.target.className === 'icon' || e.target.tagName === 'A') {
  2915. return;
  2916. }
  2917. this.style.display = 'none';
  2918. e.preventDefault();
  2919. e.stopPropagation();
  2920. };
  2921. }
  2922.  
  2923. function appendTagHistory(dom, text, dic) {
  2924. var $ = w.$;
  2925. if (uniq === null) {
  2926. uniq = {};
  2927. $history = $('<div class="tagSearchHistory"><h3 class="title">タグ検索履歴</h3></div>');
  2928. $history.css({width: $('.videoExplorerMenu').width() - 8, maxHeight: '300px', overflowY: 'auto'});
  2929. $('.videoExplorerMenu').append($history);
  2930. }
  2931. if (!uniq[text]) {
  2932. var a = $(dom).clone().css({marginRight: '8px', fontSize: '80%'}).click(Util.Closure.openNicoSearch(text));
  2933. dic.style.marginRight = '0';
  2934. $history.find('.title').after(a).after(dic);
  2935. }
  2936. uniq[text] = 1;
  2937. }
  2938.  
  2939. function createItemDOM(tag) {
  2940. var text = tag.tag;
  2941. var li = document.createElement('li');
  2942. li.className = 'popupTagItem';
  2943.  
  2944. // 大百科アイコン
  2945. var dic = createDicIconDOM(tag, text);
  2946. li.appendChild(dic);
  2947.  
  2948. // 新検索(search.nicovideo.jp)へのリンク
  2949. var newSearchIcon = createNewSearchIconDOM(tag, text);
  2950. li.appendChild(newSearchIcon);
  2951.  
  2952. // 本文リンク
  2953. var a = document.createElement('a');
  2954. a.appendChild(document.createTextNode(text));
  2955.  
  2956. var href = text;
  2957. if (conf.defaultSearchOption && conf.defaultSearchOption !== '' && !text.match(/(sm|nm|so)\d+/)) {
  2958. href += ' ' + conf.defaultSearchOption;
  2959. }
  2960. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  2961. a.href = 'http://' + host + '/tag/' + encodeURIComponent(href) + sortOrder;
  2962. a.addEventListener('click', createItemOnClick(text, dic), false);
  2963. li.appendChild(a);
  2964.  
  2965. return li;
  2966. }
  2967.  
  2968. function createItemOnClick(text, dic) {
  2969. return function(e) {
  2970. if (e.button !== 0 || e.metaKey) return;
  2971. if (w.WatchApp) {
  2972. WatchController.nicoSearch(text, 'tag');
  2973. e.preventDefault();
  2974. appendTagHistory(this, text, dic);
  2975. }
  2976. return false;
  2977. };
  2978. }
  2979.  
  2980. function createNewSearchIconDOM(tag, text) {
  2981. var link = document.createElement('a');
  2982. link.className = 'newsearch';
  2983. link.title = 'niconico新検索で開く';
  2984.  
  2985. // TODO: パラメータの対応表作ってあわせる
  2986. var newSortOrder = '';
  2987. link.href = 'http://search.nicovideo.jp/video/search/' + encodeURIComponent(text) + newSortOrder;
  2988. if (location.host !== 'search.nicovdieo.jp') {
  2989. link.target = '_blank';
  2990. }
  2991.  
  2992. var icon = document.createElement('img');
  2993. icon.className = 'icon';
  2994. icon.src = 'http://uni.res.nimg.jp/img/favicon.ico';
  2995. link.appendChild(icon);
  2996.  
  2997. return link;
  2998. }
  2999. function createDicIconDOM(tag, text) {
  3000. var dic = document.createElement('a');
  3001. dic.className = 'nicodic';
  3002. dic.href = 'http://dic.nicovideo.jp/a/' + encodeURIComponent(text);
  3003. dic.target = '_blank';
  3004. var icon = document.createElement('img');
  3005. icon.className = 'icon';
  3006. icon.src = tag.dic ? 'http://live.nicovideo.jp/img/2012/watch/tag_icon002.png' : 'http://live.nicovideo.jp/img/2012/watch/tag_icon003.png';
  3007. dic.appendChild(icon);
  3008. return dic;
  3009. }
  3010. };
  3011. popupContainer = document.createElement('div');
  3012. popupContainer.id = 'videoTagPopupContainer';
  3013. document.body.appendChild(popupContainer);
  3014.  
  3015. return pt;
  3016. })(conf, w);
  3017.  
  3018.  
  3019.  
  3020.  
  3021.  
  3022.  
  3023.  
  3024.  
  3025.  
  3026. /**
  3027. * マイリスト登録API
  3028. *
  3029. * (9)の頃は、iframeを作ってその中にマイリスト登録のポップアップウィンドウを開くという手抜きを行っていたが、
  3030. * ポップアップウィンドウは評判が悪いし、そのうち廃止されるだろうなと思うので、
  3031. * 真面目にAPIを叩くようにした。 (マイリストの新規作成機能は省略)
  3032. *
  3033. * …と思っていたのだが、(9)からQになった今でもポップアップウィンドウは廃止されないようだ。
  3034. */
  3035. var Mylist = window.WatchItLater.mylist = (function(){
  3036. var mylistlist = [];
  3037. var initialized = false;
  3038. var defListItems = [], mylistItems = {};
  3039. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  3040. var token = '';//
  3041.  
  3042. function Mylist() {
  3043. this.initialize();
  3044. }
  3045.  
  3046. function getToken() {
  3047. if (!isNativeGM && host !== location.host) return null; //
  3048.  
  3049. var _token = (w.NicoAPI) ? w.NicoAPI.token : '';
  3050. if (w.NicoAPI) {
  3051. return w.NicoAPI.token;
  3052. } else
  3053. if (w.WatchApp && w.WatchJsApi) {
  3054. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  3055. watchInfoModel.addEventListener('reset', function(watchInfoModel) {
  3056. token = watchInfoModel.csrfToken;
  3057. });
  3058. if (watchInfoModel.initialized) {
  3059. return watchInfoModel.csrfToken;
  3060. } else {
  3061. var dc = JSON.parse($("#watchAPIDataContainer").text());
  3062. return dc.flashvars.csrfToken;
  3063. }
  3064. } else
  3065. if (_token === null && w.FavMylist && w.FavMylist.csrf_token) {
  3066. _token = w.FavMylist.csrf_token;
  3067. }
  3068.  
  3069. if (_token !== '') {
  3070. return _token;
  3071. }
  3072. var url = 'http://' + host + '/mylist_add/video/sm9'; // マイリスト登録ウィンドウから強引にtoken取得
  3073. // var url = 'http://' + host + '/my/mylist'; // マイリスト登録ウィンドウから強引にtoken取得
  3074. GM_xmlhttpRequest({
  3075. url: url,
  3076. onload: function(resp) {
  3077. var result = resp.responseText;
  3078. if (result.match(/NicoAPI\.token = "([a-z0-9\-]+)";/)) {
  3079. token = RegExp.$1;
  3080. }
  3081. }
  3082. });
  3083. return _token;
  3084. }
  3085.  
  3086. var pt = Mylist.prototype, events = {defMylistUpdate: [], mylistUpdate: []};
  3087.  
  3088. function dispatchEvent(name) {
  3089. var e = events[name];
  3090. for (var i =0, len = e.length; i < len; i++) {
  3091. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  3092. }
  3093. }
  3094.  
  3095. pt.onDefMylistUpdate = function(callback) {
  3096. events.defMylistUpdate.push(callback);
  3097. };
  3098.  
  3099. pt.onMylistUpdate = function(callback) {
  3100. events.mylistUpdate.push(callback);
  3101. };
  3102.  
  3103. pt.getUserId = function() {
  3104. if (document.cookie.match(/user_session_(\d+)/)) {
  3105. return RegExp.$1;
  3106. } else {
  3107. return false;
  3108. }
  3109. };
  3110.  
  3111. var onInitialized = [];
  3112. pt.initialize = function() {
  3113. if (initialized) return;
  3114. var uid = this.getUserId();
  3115. if (!uid) {
  3116. return;
  3117. }
  3118. if (!isNativeGM && host !== location.host) {
  3119. initialized = true;
  3120. return;
  3121. }
  3122. token = getToken();
  3123. //var url = 'http://' + host + '/api/watch/uservideo?user_id=' + uid;
  3124. var url = 'http://' + host + '/api/mylistgroup/list';
  3125. GM_xmlhttpRequest({
  3126. url: url,
  3127. onload: function(resp) {
  3128. var result = JSON.parse(resp.responseText);
  3129. if (result.status === "ok" && result.mylistgroup) {
  3130. mylistlist = result.mylistgroup;
  3131. initialized = true;
  3132. for (var i = 0; i < onInitialized.length; i++) {
  3133. onInitialized[i](mylistlist.concat());
  3134. }
  3135. }
  3136. }
  3137. });
  3138. this.reloadDefList();
  3139. };
  3140.  
  3141. pt.loadMylistList = function(callback) {
  3142. if (initialized) {
  3143. setTimeout(function() { callback(mylistlist.concat()); }, 0);
  3144. } else {
  3145. onInitialized.push(callback);
  3146. }
  3147. };
  3148.  
  3149. pt.isMine = function(id) {
  3150. if (!initialized) { return false; }
  3151. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3152. if (mylistlist[i].id == id) { return true; }
  3153. }
  3154. return false;
  3155. };
  3156.  
  3157. pt.reloadDefList = function(callback) {
  3158. var url = 'http://' + host + '/api/deflist/list';
  3159. GM_xmlhttpRequest({
  3160. url: url,
  3161. onload: function(resp) {
  3162. try {
  3163. JSON.parse(resp.responseText);
  3164. } catch (e) {
  3165. window.console.log(e);
  3166. window.console.log(resp.responseText);
  3167. }
  3168. if (!resp.responseText) return;
  3169. var result = JSON.parse(resp.responseText);
  3170. if (result.status === "ok" && result.mylistitem) {
  3171. defListItems = result.mylistitem;
  3172. if (typeof callback === "function") callback(defListItems);
  3173. }
  3174. }
  3175. });
  3176. };
  3177.  
  3178. pt.loadMylist = function(groupId, callback) {
  3179. if (mylistItems[groupId]) {
  3180. setTimeout(function() {callback(mylistItems[groupId]); }, 0);
  3181. return;
  3182. }
  3183. var url = 'http://' + host + '/api/mylist/list?group_id=' + groupId;
  3184. GM_xmlhttpRequest({
  3185. url: url,
  3186. onload: function(resp) {
  3187. var result = JSON.parse(resp.responseText);
  3188. if (result.status === "ok" && result.mylistitem) {
  3189. mylistItems[groupId] = result.mylistitem;
  3190. if (typeof callback === "function") callback(result.mylistitem);
  3191. }
  3192. }
  3193. });
  3194. };
  3195.  
  3196. pt.clearMylistCache = function(groupId) {
  3197. delete mylistItems[groupId];
  3198. };
  3199.  
  3200. pt.reloadMylist = function(groupId, callback) {
  3201. this.clearMylistCache(groupId);
  3202. return this.loadMylist(groupId, callback);
  3203. };
  3204.  
  3205.  
  3206. pt.findDeflistByWatchId = function(watchId) {
  3207. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3208.  
  3209. for (var i = 0, len = defListItems.length; i < len; i++) {
  3210. var item = defListItems[i], wid = item.item_data.watch_id;
  3211. if (wid == watchId) return item;
  3212. }
  3213. return null;
  3214. };
  3215.  
  3216. pt.findMylistByWatchId = function(watchId, groupId) {
  3217. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3218. var items = mylistItems[groupId];
  3219. if (!items) { return null; }
  3220. for (var i = 0, len = items.length; i < len; i++) {
  3221. var item = items[i], wid = item.item_data.watch_id;
  3222. if (wid == watchId) return item;
  3223. }
  3224. return null;
  3225. };
  3226.  
  3227. // おもに参考にしたページ
  3228. // http://uni.res.nimg.jp/js/nicoapi.js
  3229. // http://d.hatena.ne.jp/lolloo-htn/20110115/1295105845
  3230. // http://d.hatena.ne.jp/aTaGo/20100811/1281552243
  3231. pt.deleteDefListItem = function(watchId, callback) {
  3232. var item = this.findDeflistByWatchId(watchId);
  3233. if (!item) return false;
  3234. var item_id = item.item_id;
  3235. var url = 'http://' + host + '/api/deflist/delete';
  3236. var data = 'id_list[0][]=' + item_id + '&token=' + token;
  3237. var req = {
  3238. method: 'POST',
  3239. data: data,
  3240. headers: {'Content-Type': 'application/x-www-form-urlencoded'}, // これを忘れて小一時間はまった
  3241. url: url,
  3242. onload: function(resp) {
  3243. var result = JSON.parse(resp.responseText);
  3244. if (typeof callback === "function") callback(result.status, result);
  3245. if (window.jQuery) {
  3246. defListItems = window.jQuery.grep(defListItems, function(item) {
  3247. return item.item_data.watch_id !== watchId;
  3248. });
  3249. }
  3250. dispatchEvent('defMylistUpdate');
  3251. }
  3252. };
  3253. GM_xmlhttpRequest(req);
  3254. return true;
  3255. };
  3256.  
  3257. pt.addDefListItem = function(watchId, callback, description) {
  3258. var url = 'http://' + host + '/api/deflist/add';
  3259.  
  3260. // 例えば、とりマイの300番目に登録済みだった場合に「登録済みです」と言われても探すのがダルいし、
  3261. // 他の動画を追加していけば、そのうち押し出されて消えてしまう。
  3262. // なので、重複時にエラーを出すのではなく、「消してから追加」することによって先頭に持ってくる。
  3263. // 「重複してたら先頭に持ってきて欲しいな~」って要望掲示板にこっそり書いたりしたけど相手にされないので自分で実装した。
  3264. var data = "item_id=" + watchId + "&token=" + token, replaced = true;
  3265. if (description) {
  3266. data += '&description='+ encodeURIComponent(description);
  3267. }
  3268.  
  3269. var _add = function(status, resp) {
  3270. var req = {
  3271. method: 'POST',
  3272. data: data,
  3273. url: url,
  3274. headers: {'Content-Type': 'application/x-www-form-urlencoded' }, // これを忘れて小一時間はまった
  3275. onload: function(resp) {
  3276. var result = JSON.parse(resp.responseText);
  3277. if (typeof callback === "function") callback(result.status, result, replaced);
  3278. }
  3279. };
  3280. GM_xmlhttpRequest(req);
  3281. };
  3282. // とりあえずマイリストにある場合はdeleteDefListItem()のcallbackで追加、ない場合は即時追加
  3283. if (!this.deleteDefListItem(watchId, _add)) {
  3284. replaced = false;
  3285. _add();
  3286. dispatchEvent('defMylistUpdate');
  3287. }
  3288. };
  3289.  
  3290. pt.addMylistItem = function(watchId, groupId, callback, description) {
  3291. var self = this;
  3292. var url = 'http://' + host + '/api/mylist/add';
  3293. var data = ['item_id=', watchId,
  3294. '&group_id=', groupId,
  3295. '&item_type=', 0, // video=0 seiga=5
  3296. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3297. '&token=', token
  3298. ].join('');
  3299. // 普通のマイリストのほうは重複しても「消してから追加」という処理を行っていない。
  3300. // とりあえずマイリストと違って登録の順番に意味があるのと、
  3301. // 古いのが押し出される心配がないため。
  3302. var _add = function() {
  3303. var req = {
  3304. method: 'POST',
  3305. data: data,
  3306. url: url,
  3307. headers: {'Content-Type': 'application/x-www-form-urlencoded' },
  3308. onload: function(resp) {
  3309. var result = JSON.parse(resp.responseText);
  3310. if (typeof callback === "function") callback(result.status, result);
  3311. if (result.status === 'ok') {
  3312. dispatchEvent('mylistUpdate', {action: 'add', groupId: groupId, watchId: watchId});
  3313. EventDispatcher.dispatch('onMylistItemAdded', groupId, watchId);
  3314. self.clearMylistCache(groupId);
  3315. }
  3316. },
  3317. error: function() {
  3318. Popup.alert('ネットワークエラー');
  3319. }
  3320. };
  3321. GM_xmlhttpRequest(req);
  3322. };
  3323. // 普通のマイリストに入れたら、とりあえずマイリストからは削除(≒移動)
  3324. if (!this.deleteDefListItem(watchId, _add)) _add();
  3325. };
  3326.  
  3327. pt.updateMylistItem = function(watchId, groupId, callback, description) {
  3328. var self = this;
  3329. this.loadMylist(groupId, function() {
  3330. var item = self.findMylistByWatchId(watchId, groupId);
  3331. if (!item) {
  3332. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3333. return;
  3334. }
  3335. var
  3336. itemId = item.item_id,
  3337. url = 'http://' + host + '/api/mylist/update',
  3338. data = ['item_id=', itemId,
  3339. '&group_id=', groupId,
  3340. '&item_type=', 0, // video=0 seiga=5
  3341. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3342. '&token=', token
  3343. ].join(''),
  3344. req = {
  3345. method: 'POST',
  3346. data: data,
  3347. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3348. url: url,
  3349. onload: function(resp) {
  3350. var result = JSON.parse(resp.responseText);
  3351. if (result.status === 'ok') {
  3352. if (typeof callback === "function") callback(result.status, result);
  3353. dispatchEvent('mylistUpdate', {action: 'update', groupId: groupId, watchId: watchId});
  3354. EventDispatcher.dispatch('onMylistItemUpdated', groupId, watchId);
  3355. }
  3356. },
  3357. error: function() {
  3358. Popup.alert('ネットワークエラー');
  3359. }
  3360. };
  3361.  
  3362. GM_xmlhttpRequest(req);
  3363. });
  3364. };
  3365.  
  3366.  
  3367. pt.deleteMylistItem = function(watchId, groupId, callback) {
  3368. var self = this;
  3369. this.loadMylist(groupId, function() {
  3370. var item = self.findMylistByWatchId(watchId, groupId);
  3371. if (!item) {
  3372. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3373. return;
  3374. }
  3375. var
  3376. item_id = item.item_id,
  3377. url = 'http://' + host + '/api/mylist/delete',
  3378. data = [
  3379. 'id_list[0][]=', item_id,
  3380. '&group_id=', groupId,
  3381. '&token=', token
  3382. ].join(''),
  3383. req = {
  3384. method: 'POST',
  3385. data: data,
  3386. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3387. url: url,
  3388. onload: function(resp) {
  3389. var result = JSON.parse(resp.responseText);
  3390. if (result.status === 'ok') {
  3391. if (typeof callback === "function") callback(result.status, result);
  3392. dispatchEvent('mylistUpdate', {action: 'delete', groupId: groupId, watchId: watchId});
  3393. EventDispatcher.dispatch('onMylistItemDeleted', groupId, watchId);
  3394. }
  3395. },
  3396. error: function() {
  3397. Popup.alert('ネットワークエラー');
  3398. }
  3399. };
  3400.  
  3401. GM_xmlhttpRequest(req);
  3402. });
  3403. };
  3404.  
  3405.  
  3406. /**
  3407. * マイリスト登録パネルを返す
  3408. */
  3409. pt.getPanel = function(watchId, videoId) {
  3410. if (isNativeGM || host === location.host) {
  3411. return this.getNativePanel(watchId, videoId);
  3412. } else {
  3413. return this.getIframePanel(watchId, videoId);
  3414. }
  3415. };
  3416.  
  3417. pt.getNativePanel = function(watchId, videoId) {
  3418. var self = this;
  3419. var _watchId = watchId, _videoId = videoId || watchId;
  3420. var body = document.createElement('div');
  3421. var mylistListPopup = null;
  3422. body.className = 'mylistPopupPanel deflistSelected';
  3423. var nobr = document.createElement('nobr');
  3424. body.appendChild(nobr);
  3425.  
  3426. var extArea = document.createElement('span');
  3427.  
  3428.  
  3429. var isWatchPage = (window.PlayerApp) ? true : false;
  3430.  
  3431. var addDeflist = function(watchId, description) {
  3432. self.addDefListItem(watchId, function(status, result, replaced) {
  3433. self.reloadDefList();
  3434. if (status !== 'ok') {
  3435. Popup.alert('とりあえずマイリストへの登録に失敗: ' + result.error.description);
  3436. } else {
  3437. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  3438. Popup.show(
  3439. torimai +
  3440. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  3441. );
  3442. }
  3443. }, description);
  3444. };
  3445. var addMylist = function(watchId, mylistId, mylistName, description) {
  3446. self.addMylistItem(watchId, mylistId, function(status, result) {
  3447. self.reloadDefList();
  3448. if (status === 'ok') {
  3449. Popup.show( '<a href="/my/mylist/#/' + mylistId + '">' + mylistName + '</a>に登録しました');
  3450. } else {
  3451. Popup.alert(mylistName + 'への登録に失敗: ' + result.error.description);
  3452. }
  3453. }, description);
  3454. };
  3455. var setButtonStyleUpdating = function(btn) {
  3456. btn.style.opacity = 0.5;
  3457. btn.style.cursor = 'pointer';
  3458. btn.disabled = true;
  3459.  
  3460. window.setTimeout(function() {
  3461. btn.disabled = false;
  3462. btn.style.opacity = 1;
  3463. btn.style.cursor = 'pointer';
  3464. btn = null;
  3465. }, 1000);
  3466. };
  3467. var onMylistListClick = function(mylistId, mylistName, type) {
  3468. if (type === 'icon') {
  3469. if (window.WatchApp) {
  3470. if (mylistId === 'default') {
  3471. WatchController.showDeflist();
  3472. } else {
  3473. WatchController.showMylist(mylistId);
  3474. }
  3475. } else {
  3476. location.href = 'http://' + host + '/my/mylist/#/' + mylistId.replace('default','home');
  3477. }
  3478. return;
  3479. }
  3480. if (mylistId === 'default') {
  3481. addDeflist(_watchId);
  3482. } else {
  3483. addMylist(_watchId, mylistId, mylistName);
  3484. }
  3485. };
  3486.  
  3487. body.watchId = function(w, v) {
  3488. if (w) {
  3489. _watchId = w;
  3490. _videoId = v || w;
  3491. var isThreadId = (/^[0-9]+$/.test(w));
  3492.  
  3493. deleteDef.disabled = false;
  3494. if (self.findDeflistByWatchId(w)) {
  3495. deleteDef.style.display = '';
  3496. } else {
  3497. deleteDef.style.display = 'none';
  3498. }
  3499. if (!isWatchPage && isThreadId) {
  3500. tagBtn.style.display = 'none'; // スレッドIDから動画IDを取る手段がないためタグ取得が難しい
  3501. } else {
  3502. tagBtn.style.display = '';
  3503. }
  3504. if (newTabLink) {
  3505. newTabLink.href = 'http://nico.ms/' + _watchId; // QWatchに乗っ取られないようにnico.msをかます(せこい)
  3506. }
  3507. if (mylistListPopup) {
  3508. mylistListPopup.hide();
  3509. }
  3510. return body;
  3511. }
  3512. return _watchId;
  3513. };
  3514.  
  3515. body.show = function() {
  3516. body.style.display = '';
  3517. if (mylistListPopup) {
  3518. mylistListPopup.hide();
  3519. }
  3520. };
  3521. body.hide = function() {
  3522. body.style.display = 'none';
  3523. if (mylistListPopup) {
  3524. mylistListPopup.hide();
  3525. }
  3526. };
  3527.  
  3528. function createSelector() {
  3529. var sel = document.createElement('select');
  3530. var lastSelect = 0;
  3531.  
  3532. sel.className = 'mylistSelect';
  3533. var appendO = function(sel, text, value) {
  3534. var opt = document.createElement('option');
  3535. opt.appendChild(document.createTextNode(text));
  3536. opt.value = value;
  3537. sel.appendChild(opt);
  3538. return opt;
  3539. },
  3540. createOptions = function() {
  3541. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3542. var mylist = mylistlist[i];
  3543. appendO(sel, (i + 1).toString(36) + ':' + mylist.name, mylist.id);
  3544. }
  3545. },
  3546. onSelect = function() {
  3547. // jQueryは全てのページにあるわけではないので気をつける。忘れると原宿が死ぬ
  3548. if (sel.selectedIndex === 0) {
  3549. body.className = body.className.replace('mylistSelected', 'deflistSelected');
  3550. } else {
  3551. lastSelect = sel.selectedIndex;
  3552. body.className = body.className.replace('deflistSelected', 'mylistSelected');
  3553. }
  3554. },
  3555. selectDeflist = function() {
  3556. sel.selectedIndex = 0;
  3557. onSelect();
  3558. },
  3559. onContextMenu = function(e) {
  3560. e.preventDefault();
  3561. e.stopPropagation();
  3562.  
  3563. if (lastSelect === 0) return;
  3564. if (sel.selectedIndex === 0) {
  3565. sel.selectedIndex = lastSelect;
  3566. } else {
  3567. sel.selectedIndex = 0;
  3568. }
  3569. onSelect();
  3570. };
  3571.  
  3572. appendO(sel, '0:とりマイ', 'default');
  3573. sel.selectedIndex = 0;
  3574. window.setTimeout(createOptions, initialized ? 0 : 3000);
  3575.  
  3576. sel.addEventListener('change', onSelect, false);
  3577. sel.addEventListener('contextmenu', onContextMenu, false);
  3578.  
  3579.  
  3580. body.addEventListener('dblclick', selectDeflist, false);
  3581. return sel;
  3582. }
  3583.  
  3584. function createSubmitButton() {
  3585. var btn = document.createElement('button');
  3586. btn.appendChild(document.createTextNode('my'));
  3587. btn.className = 'mylistAdd';
  3588. btn.title = 'マイリストに追加\n(ボタンを右クリックで詳細メニュー)';
  3589.  
  3590. var callMylistListPopup = function() {
  3591. if (!mylistListPopup) {
  3592. mylistListPopup = new MylistListPopup(mylistlist, onMylistListClick);
  3593. }
  3594. mylistListPopup.toggle(btn, _watchId);
  3595. };
  3596.  
  3597. btn.addEventListener('contextmenu', function(e) {
  3598. if (window.jQuery) {
  3599. e.preventDefault();
  3600. e.stopPropagation();
  3601. callMylistListPopup();
  3602. }
  3603. });
  3604.  
  3605. btn.addEventListener('click', function(e) {
  3606. var description = null;
  3607. if (e.shiftKey) {
  3608. description = prompt('マイリストコメントの入力');
  3609. if (!description) return;
  3610. }
  3611. setButtonStyleUpdating(btn);
  3612.  
  3613. var mylistId = sel.value, name = sel.options[sel.selectedIndex].textContent;
  3614. if (mylistId === 'default') {
  3615. addDeflist(_watchId, description);
  3616. } else {
  3617. addMylist(_watchId, mylistId, name, description);
  3618. }
  3619. } ,false);
  3620. return btn;
  3621. }
  3622.  
  3623. function createDeleteDeflistItemButton() {
  3624. var btn = document.createElement('button');
  3625. btn.appendChild(document.createTextNode('×'));
  3626. btn.className = 'deflistRemove';
  3627. btn.title = 'とりあえずマイリストから外す';
  3628.  
  3629. btn.addEventListener('click', function() {
  3630.  
  3631. setButtonStyleUpdating(btn);
  3632.  
  3633. self.deleteDefListItem(_watchId, function(status, result) {
  3634. self.reloadDefList();
  3635. btn.style.display = 'none';
  3636. if (status !== "ok") {
  3637. Popup.alert('とりあえずマイリストから削除に失敗: ' + result.error.description);
  3638. } else {
  3639. Popup.show('とりあえずマイリストから外しました');
  3640. }
  3641. });
  3642. } ,false);
  3643. return btn;
  3644. }
  3645.  
  3646. function createTagListButton() {
  3647. var btn = document.createElement('button');
  3648. btn.appendChild(document.createTextNode('tag'));
  3649. btn.className = 'tagGet';
  3650. btn.title = 'タグ取得';
  3651. btn.addEventListener('click', function(e) {
  3652. btn.disabled = true;
  3653.  
  3654. setButtonStyleUpdating(btn);
  3655.  
  3656. if (w.jQuery) {
  3657. var $btn = w.jQuery(btn), o = $btn.offset();
  3658. VideoTags.popupItems(_videoId, o.left, o.top + $btn.outerHeight());
  3659. } else {
  3660. VideoTags.popupItems(_videoId, e.pageX, e.pageY);
  3661. }
  3662. } ,false);
  3663. return btn;
  3664. }
  3665.  
  3666. function createNewTabLink() {
  3667. var a = document.createElement('a');
  3668. a.className = 'newTabLink';
  3669. a.target = '_blank';
  3670. a.title = 'この動画を新しいウィンドウで開く';
  3671. a.innerHTML = '▲';
  3672. return a;
  3673. }
  3674.  
  3675. var newTabLink = createNewTabLink();
  3676. if (w.WatchApp) {
  3677. nobr.appendChild(newTabLink);
  3678. }
  3679.  
  3680.  
  3681. var sel = createSelector();
  3682. var submit = createSubmitButton(sel);
  3683. nobr.appendChild(sel);
  3684. nobr.appendChild(submit);
  3685. if (w.jQuery) {
  3686. w.jQuery(sel).keydown(function(e) {
  3687. e.stopPropagation();
  3688. if (e.keyCode === 13) { // ENTER
  3689. submit.click();
  3690. }
  3691. });
  3692. }
  3693.  
  3694. var tagBtn = createTagListButton();
  3695. nobr.appendChild(tagBtn);
  3696.  
  3697. var deleteDef = createDeleteDeflistItemButton();
  3698. nobr.appendChild(deleteDef);
  3699.  
  3700.  
  3701.  
  3702. nobr.appendChild(extArea);
  3703.  
  3704. body.watchId(_watchId, _videoId);
  3705. return body;
  3706. };
  3707.  
  3708. // XHRでクロスドメインを超えられない場合はこちら
  3709. // 将来マイリストのポップアップウィンドウが廃止されたら使えない
  3710. // (マイページから強引に生成するか?)
  3711. pt.getIframePanel = function(watchId) {
  3712. var _watchId = watchId;
  3713. var body = document.createElement('iframe');
  3714. body.name = 'nicomylistaddDummy';
  3715. body.className = 'mylistPopupPanel';
  3716. body.setAttribute('style', 'width: 130px; height: 24px; z-index: 10000; border: 1px solid silver; padding: 0; margin: 0; overflow: hidden');
  3717.  
  3718. body.watchId = function(w) {
  3719. if (w) {
  3720. _watchId = w;
  3721. body.contentWindow.location.replace("http:/" + "/www.nicovideo.jp/mylist_add/video/" + w);
  3722. return body;
  3723. }
  3724. return _watchId;
  3725. };
  3726. if (watchId !== '') {
  3727. body.src = "http:/" + "/www.nicovideo.jp/mylist_add/video/" + _watchId;
  3728. }
  3729.  
  3730. // ダミーメソッド
  3731. body.show = function() {
  3732. body.style.display = '';
  3733. };
  3734. body.hide = function() {
  3735. body.style.display = 'none';
  3736. };
  3737.  
  3738.  
  3739. return body;
  3740. };
  3741.  
  3742. return new Mylist();
  3743. })();
  3744.  
  3745. var MylistListPopup = function() { this.initialize.apply(this, arguments); };
  3746. MylistListPopup.prototype = {
  3747. initialize: function(mylistList, onItemClick) {
  3748. this._mylistList = mylistList.concat();
  3749. this.initializeView(mylistList);
  3750. this._onItemClick = onItemClick;
  3751. },
  3752. initializeView: function() {
  3753. var $ = window.jQuery;
  3754. this._$view = $([
  3755. '<div class="mylistListPopup popupMenu">',
  3756. '<div class="listInner">',
  3757. '<ul></ul>',
  3758. '</div>',
  3759. '</div>',
  3760. ''].join(''));
  3761. this._$list = this._$view.find('ul');
  3762. this._$inner = this._$view.find('.listInner');
  3763.  
  3764.  
  3765. $('body').append(this._$view);
  3766.  
  3767. this.refresh();
  3768.  
  3769. this.initializeEvent(this._$view);
  3770. },
  3771. initializeEvent: function($view) {
  3772. $view.on('click', window.jQuery.proxy(function(e) {
  3773. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  3774. var target = e.target, $target = window.jQuery(target);
  3775. this.hide();
  3776. e.preventDefault();
  3777.  
  3778. var mylistId = $target.attr('data-mylist-id');
  3779. var mylistName = $target.attr('data-mylist-name');
  3780. if (!mylistId) { return; }
  3781. var type = target.className;
  3782.  
  3783. if (typeof this._onItemClick === 'function') {
  3784. this._onItemClick(mylistId, mylistName, type);
  3785. }
  3786. }, this));
  3787. var self = this, closeTimer = null;
  3788. $view.hover(
  3789. function() {
  3790. if (closeTimer) { window.clearTimeout(closeTimer); }
  3791. },
  3792. function() {
  3793. closeTimer = window.setTimeout(function() { self.hide(); }, 1000);
  3794. });
  3795.  
  3796. $view = null;
  3797. },
  3798. adjustColumnCount: function() {
  3799. this._$inner.css({
  3800. 'column-count': '',
  3801. 'max-height': ''
  3802. });
  3803. var height = this._$view.outerHeight(),
  3804. clientHeight = window.jQuery(window).innerHeight(),
  3805. threshold = clientHeight * 0.4;
  3806. if (threshold < height) {
  3807. var columns = parseInt( height / threshold, 10) + 1;
  3808. this._$inner.css({
  3809. 'column-count': columns,
  3810. 'max-height': clientHeight * 0.8
  3811. });
  3812. }
  3813. },
  3814. updateList: function(mylistList) {
  3815. this._mylistList = mylistList.concat();
  3816. this.refresh();
  3817. },
  3818. refresh: function() {
  3819. var mylistList = this._mylistList;
  3820. this._$list.empty();
  3821. for (var i = 0, len = mylistList.length; i < len; i++) {
  3822. var mylist = mylistList[i];
  3823. this.appendItem(mylist.id, mylist.name, mylist.icon_id);
  3824. }
  3825. this.appendItem('default', 'とりあえずマイリスト');
  3826.  
  3827. this.adjustColumnCount();
  3828. },
  3829. appendItem: function(id, name, icon_id) {
  3830. var $mylist = window.jQuery([
  3831. '<li class="folder', icon_id, '">',
  3832. '<span class="icon"></span>',
  3833. '<a href="my/mylist/#/', id.replace('default', 'home'), '" class="name">',
  3834. name,
  3835. '</a>',
  3836. '</li>',
  3837. ''].join(''));
  3838. $mylist.find('.icon, .name').attr({
  3839. 'data-mylist-id': id,
  3840. 'data-mylist-name': name
  3841. });
  3842.  
  3843. if (id === 'default') {
  3844. $mylist.addClass('deflist');
  3845. } else
  3846. if (id.indexOf('ext') === 0) {
  3847. $mylist.addClass('ext');
  3848. }
  3849. this._$list.append($mylist);
  3850. },
  3851. updateExist: function(watchId) {
  3852. if (!watchId) {
  3853. return;
  3854. }
  3855. this._$view.find('.exist').removeClass('exist');
  3856. this._$view.find('.name').each(function() {
  3857. var $this = window.jQuery(this), mylistId = $this.attr('data-mylist-id');
  3858. if (mylistId === 'default') { return; }
  3859. $this
  3860. .toggleClass('exist', window.WatchItLater.mylist.cache.hasItem(mylistId, watchId));
  3861. });
  3862.  
  3863. this._$view.find('.deflist .name')
  3864. .toggleClass('exist', window.WatchItLater.mylist.findDeflistByWatchId(watchId) !== null);
  3865. },
  3866. show: function(elm, watchId) {
  3867. this.adjustColumnCount();
  3868. this._$view.addClass('show active');
  3869.  
  3870. if (!elm) { return; }
  3871. var
  3872. $ = window.jQuery,
  3873. $elm = $(elm),
  3874. o = $elm.offset(),
  3875. $view = this._$view,
  3876. $window = $(window),
  3877. scrollLeft = $window.scrollLeft(),
  3878. scrollTop = $window.scrollTop();
  3879.  
  3880. $view.css({
  3881. top: Math.max(o.top - $view.outerHeight(), 0, scrollTop),
  3882. left: o.left
  3883. });
  3884. if ($view.offset().left + $view.outerWidth() > $window.innerWidth() + scrollLeft) {
  3885. $view.css({
  3886. left: Math.max(0, $window.innerWidth() + scrollLeft - $view.outerWidth())
  3887. });
  3888. }
  3889.  
  3890. this.updateExist(watchId);
  3891. window.jQuery('body').on('click', $.proxy(this._onBodyClick, this));
  3892. },
  3893. hide: function() {
  3894. var $view = this._$view
  3895. .removeClass('show');
  3896. window.setTimeout(function() {
  3897. $view.css({top: '', left: '', right: ''}).removeClass('active');
  3898. }, 500);
  3899. window.jQuery('body').off('click', this._onBodyClick);
  3900. },
  3901. toggle: function(elm, watchId) {
  3902. if (this._$view.hasClass('avtive')) {
  3903. this.hide();
  3904. } else {
  3905. this.show(elm, watchId);
  3906. }
  3907. },
  3908. _onBodyClick: function() {
  3909. this.hide();
  3910. }
  3911. };
  3912.  
  3913. window.WatchItLater.mylist.cache = (function() {
  3914. var CacheList = function() { this.initialize.apply(this, arguments); };
  3915. CacheList.prototype = {
  3916. initialize: function() {
  3917. this.reset();
  3918. },
  3919. reset: function() {
  3920. this._cacheList = {};
  3921. },
  3922. setCache: function(mylistId, items) {
  3923. if (!this.hasCache(mylistId)) {
  3924. this._cacheList[mylistId] = new MylistCache();
  3925. }
  3926. this._cacheList[mylistId].update(items);
  3927. },
  3928. hasCache: function(mylistId) {
  3929. if (this._cacheList[mylistId]) {
  3930. return true;
  3931. }
  3932. return false;
  3933. },
  3934. hasItem: function(mylistId, watchId) {
  3935. if (!this.hasCache(mylistId)) {
  3936. return false;
  3937. }
  3938. return this._cacheList[mylistId].hasItem(watchId);
  3939. },
  3940. addItem: function(mylistId, watchId) {
  3941. if (!this.hasCache(mylistId)) {
  3942. return false;
  3943. }
  3944. this._cacheList[mylistId].addItem(watchId);
  3945. },
  3946. removeItem: function(mylistId, watchId) {
  3947. if (!this.hasCache(mylistId)) {
  3948. return false;
  3949. }
  3950. this._cacheList[mylistId].removeItem(watchId);
  3951. },
  3952. count: function(mylistId) {
  3953. if (!this.hasCache(mylistId)) {
  3954. return NaN;
  3955. }
  3956. this._cacheList[mylistId].count();
  3957. },
  3958. toJSON: function() {
  3959. var cacheList = this._cacheList;
  3960. return this._cacheList;
  3961. },
  3962. parse: function(jsonString) {
  3963. var data;
  3964. try {
  3965. data = JSON.parse(jsonString);
  3966. } catch (e) {
  3967. data = {};
  3968. }
  3969. this.reset();
  3970. for (var mylistId in data) {
  3971. var mylistCache = data[mylistId];
  3972. this._cacheList[mylistId] = new MylistCache(mylistCache);
  3973. }
  3974. }
  3975. };
  3976.  
  3977. var MylistCache = function() { this.initialize.apply(this, arguments); };
  3978. MylistCache.prototype = {
  3979. initialize: function(mylistData) {
  3980. this._name = '';
  3981. if (mylistData) {
  3982. this.update(mylistData);
  3983. }
  3984. },
  3985. update: function(mylistData) {
  3986. this._cache = [];
  3987. this._hash = {};
  3988. var items = mylistData.items ? mylistData.items : mylistData;
  3989. for (var i = 0, len = items.length; i < len; i++) {
  3990. var
  3991. item = items[i],
  3992. watchId = typeof item.getId === 'function' ? item.getId() : item.id;
  3993. this._cache.push({id: watchId});
  3994. this._hash[watchId] = true;
  3995. }
  3996. if (mylistData.name) {
  3997. this._name = mylistData.name;
  3998. }
  3999. },
  4000. hasItem: function(watchId) {
  4001. return this._hash[watchId] === true;
  4002. },
  4003. addItem: function(watchId) {
  4004. if (this.hasItem(watchId)) {
  4005. return;
  4006. }
  4007. this._hash[watchId] = true;
  4008. this._cache.push({id: watchId});
  4009. },
  4010. removeItem: function(watchId) {
  4011. if (!this.hasItem(watchId)) {
  4012. return;
  4013. }
  4014. delete this._hash[watchId];
  4015. this._cache = $.grep(this._cache, function(item) {
  4016. return item.id !== watchId;
  4017. });
  4018. },
  4019. count: function() {
  4020. return this._cache.length;
  4021. },
  4022. toJSON: function() {
  4023. return {
  4024. name: this._name,
  4025. items: this._cache
  4026. };
  4027. },
  4028. parse: function(jsonString) {
  4029. var items;
  4030. try {
  4031. items = JSON.parse(jsonString);
  4032. } catch (e) {
  4033. items = [];
  4034. }
  4035. this.update(items);
  4036. }
  4037. };
  4038.  
  4039. var cacheList, noop = function() {};
  4040. var initialize = function() {
  4041. initialize = noop;
  4042. console.log('%cinitialize mylistCache', 'background: lightgreen;');
  4043. cacheList = new CacheList();
  4044.  
  4045. if (conf.enableLocalMylistCache) {
  4046. if (window.PlayerApp) {
  4047. $(window).on('beforeunload.watchItLater', function(e) {
  4048. window.localStorage.setItem('watchItLater_mylistCache', serialize());
  4049. });
  4050. }
  4051. var cacheData = window.localStorage.getItem('watchItLater_mylistCache');
  4052. if (cacheData) {
  4053. cacheList.parse(cacheData);
  4054. }
  4055. }
  4056. };
  4057. var hasCache = function(mylistId, watchId) {
  4058. initialize();
  4059. return cacheList.hasItem(mylistId, watchId);
  4060. };
  4061. var hasItem = function(mylistId, watchId) {
  4062. initialize();
  4063. return cacheList.hasItem(mylistId, watchId);
  4064. };
  4065. var addItem = function(mylistId, watchId) {
  4066. initialize();
  4067. cacheList.addItem(mylistId, watchId);
  4068. };
  4069. var removeItem = function(mylistId, watchId) {
  4070. initialize();
  4071. cacheList.removeItem(mylistId, watchId);
  4072. };
  4073. var setCache = function(mylistId, items) {
  4074. initialize();
  4075. cacheList.setCache(mylistId, items);
  4076. };
  4077. var serialize = function() {
  4078. initialize();
  4079. return JSON.stringify(cacheList);
  4080. };
  4081. var unserialize = function(json) {
  4082. initialize();
  4083. cacheList.parse(json);
  4084. };
  4085. var clearCache = function() {
  4086. window.localStorage.removeItem('watchItLater_mylistCache');
  4087. };
  4088.  
  4089.  
  4090. EventDispatcher.addEventListener('onMyMylistLoad', function(mylistId, list) {
  4091. setCache(mylistId, list || []);
  4092. });
  4093. EventDispatcher.addEventListener('onMylistItemAdded', function(mylistId, watchId) {
  4094. initialize();
  4095. cacheList.addItem(mylistId, watchId);
  4096. });
  4097. EventDispatcher.addEventListener('onMylistItemDeleted', function(mylistId, watchId) {
  4098. initialize();
  4099. cacheList.removeItem(mylistId, watchId);
  4100. });
  4101.  
  4102.  
  4103. return {
  4104. initialize: initialize,
  4105. hasItem: hasItem,
  4106. addItem: addItem,
  4107. setCache: setCache,
  4108. serialize: serialize,
  4109. unserialize: unserialize,
  4110. clearCache: clearCache
  4111. };
  4112. })();
  4113.  
  4114.  
  4115. var LocationHashParser = (function(conf, w) {
  4116. var self, dat = {};
  4117.  
  4118. function initialize() {
  4119. var hash = w.location.hash.toString();
  4120. var redirectedHash = window.sessionStorage.getItem('watchItLater_redirectedHash');
  4121. if (redirectedHash) {
  4122. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  4123. hash = redirectedHash;
  4124. window.sessionStorage.removeItem('watchItLater_redirectedHash');
  4125. }
  4126. try {
  4127. if (hash.indexOf('#json={') === 0) {
  4128. dat = JSON.parse(hash.substr(6));
  4129. }
  4130. } catch (e) {
  4131. try {
  4132. dat = JSON.parse(decodeURIComponent(hash.substr(6)));
  4133. } catch(ex) {
  4134. console.log(ex);
  4135. }
  4136. console.log(e);
  4137. }
  4138. }
  4139. function setValue(key, value) {
  4140. dat[key] = value;
  4141. }
  4142. function getValue(key) {
  4143. return dat[key];
  4144. }
  4145. function deleteValue(key) {
  4146. delete dat[key];
  4147. }
  4148. function updateHash() {
  4149. var loc = window.location.href.split('#')[0];
  4150. location.replace(loc + getHash());
  4151. }
  4152. function removeHash() {
  4153. if (location.hash.length <= 1) { return; }
  4154. var scrollTop = $(window).scrollTop();
  4155. var loc = window.location.href.split('#')[0];
  4156. location.replace(loc + '#');
  4157. $(window).scrollTop(scrollTop);
  4158. }
  4159. function getHash() {
  4160. var json = JSON.stringify(dat);
  4161. if (json === '{}') { return ''; }
  4162. return '#json=' + json;
  4163. }
  4164. function getUrl() {
  4165. var loc = window.location.href.split('#')[0];
  4166. return loc + getHash();
  4167. }
  4168. function clear() {
  4169. dat = {};
  4170. removeHash();
  4171. }
  4172.  
  4173. self = {
  4174. initialize: initialize,
  4175. setValue: setValue,
  4176. getValue: getValue,
  4177. deleteValue: deleteValue,
  4178. updateHash: updateHash,
  4179. removeHash: removeHash,
  4180. getHash: getHash,
  4181. getUrl: getUrl,
  4182. clear: clear
  4183. };
  4184. return self;
  4185. })(conf, w);
  4186.  
  4187. window.WatchItLater.loader.favMylists = (function() {
  4188. var lastUpdate = 0;
  4189. var favMylistList = [];
  4190. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4191. var $ = w.$;
  4192. /**
  4193. * お気に入りマイリストの取得。 jQueryのあるページでしか使えない
  4194. * マイページを無理矢理パースしてるので突然使えなくなるかも
  4195. */
  4196. var self = {
  4197. load: function(callback) {
  4198. if (!w.jQuery) return; //
  4199.  
  4200. function request(page) {
  4201. url = baseUrl + '?page=' + page;
  4202. GM_xmlhttpRequest({
  4203. url: url,
  4204. onload: function(resp) {
  4205. var $result = $(resp.responseText).find('#favMylist');
  4206.  
  4207. if ($result.length >= 1) {
  4208. updateMaxPage($result);
  4209.  
  4210. if (page === 1) { favMylistList = []; }
  4211.  
  4212. $result.find('.outer').each(function() {
  4213. favMylistList.push(readBlock(this));
  4214. });
  4215. }
  4216.  
  4217. if (page < maxPage) {
  4218. setTimeout(function() {
  4219. page++;
  4220. request(page);
  4221. }, 500);
  4222. } else {
  4223. sort();
  4224. do_callback();
  4225. }
  4226. }
  4227. });
  4228. }
  4229. function readBlock(elm) {
  4230. var
  4231. $elm = $(elm),
  4232. $a = $elm.find('h5 a'), $desc = $elm.find('.mylistDescription'),
  4233. iconType = $elm.find('.folderIcon').attr('class').split(' ')[1],
  4234. id = ($a.attr('href').split('/').reverse())[0],
  4235. $postTime = $elm.find('.postTime span'),
  4236. postTime = $.trim($postTime.text()),
  4237. postTimeData = $postTime.data(),
  4238. $videoLink = $elm.find('.videoTitle a'),
  4239. videoTitle = $videoLink.text(),
  4240. videoHref = $videoLink.attr('href'),
  4241. videoId = videoHref ? (videoHref.split('/').reverse()[0]) : '';
  4242. return {
  4243. id: id,
  4244. name: $a.text(),
  4245. description: $desc.text(),
  4246. iconType: iconType,
  4247. lastVideo: {
  4248. title: videoTitle,
  4249. videoId: videoId,
  4250. postedAt: postTime,
  4251. postTimeData: postTimeData
  4252. }
  4253. };
  4254. }
  4255.  
  4256. function updateMaxPage($result) {
  4257. var $paging = $result.find('.pagerWrap:first .pager:first a');
  4258. maxPage = Math.min(Math.max($paging.length, 1), 3);
  4259. }
  4260. function sort() {
  4261. favMylistList.sort(function(a, b) {
  4262. return (a.lastVideo.postedAt < b.lastVideo.postedAt) ? 1 : -1;
  4263. });
  4264. }
  4265. function do_callback() {
  4266. if (typeof callback === 'function') { callback(favMylistList); }
  4267. }
  4268.  
  4269. var now = Date.now();
  4270. if (now - lastUpdate < 3 * 60 * 1000) {
  4271. do_callback();
  4272. return;
  4273. }
  4274. lastUpdate = now;
  4275.  
  4276. var
  4277. baseUrl = 'http://' + host + '/my/fav/mylist',
  4278. url = baseUrl,
  4279. maxPage = 1;
  4280.  
  4281. request(1);
  4282.  
  4283. }
  4284. };
  4285. return self;
  4286. })();
  4287.  
  4288.  
  4289. window.WatchItLater.loader.favTags = (function(w) {
  4290. var lastUpdate = 0;
  4291. var favTagList = [], favTagTextList = [];
  4292. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4293. var $ = w.$;
  4294. var pt = function(){};
  4295.  
  4296. var load = function(callback) {
  4297. if (!w.jQuery) return; //
  4298. var now = Date.now();
  4299. if (now - lastUpdate < 60 * 1000) {
  4300. if (typeof callback === 'function') { callback(favTagList); }
  4301. return;
  4302. }
  4303. lastUpdate = now;
  4304. var api = 'http://' + host + '/api/favtag/list?t=' + now;
  4305. $.ajax({
  4306. url: api,
  4307. timeout: 30000,
  4308. complete: function(result) {
  4309. if (result.status !== 200) {
  4310. return;
  4311. }
  4312. try {
  4313. var json = JSON.parse(result.responseText), items = json.favtag_items;
  4314. for (var i = 0, len = items.length; i < len; i++) {
  4315. var text = items[i]['tag'];
  4316. favTagList.push({href: '/tag/' + encodeURIComponent(text), name: items[i]['tag']});
  4317. favTagTextList.push(text);
  4318. }
  4319. EventDispatcher.dispatch('onFavTagsLoad', favTagTextList.concat());
  4320. if (typeof callback === 'function') { callback(favTagList); }
  4321. } catch (e) {
  4322. console.log('tag parse error!', e);
  4323. }
  4324. }
  4325. });
  4326. };
  4327.  
  4328. pt.load = load;
  4329. return pt;
  4330. })(w);
  4331.  
  4332.  
  4333. /**
  4334. * 左下に出るポップアップメッセージ
  4335. *
  4336. */
  4337. var Popup = (function(){
  4338. function Popup() {}
  4339.  
  4340. Popup.show = function(text) {
  4341. console.log('%c' + text, 'background: cyan;');
  4342. if (w.WatchApp) {
  4343. text = text.replace(/[\n]/, '<br />');
  4344. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4345. // Firefoxではflashの上に半透明要素を重ねられないのでとりあえず黒で塗りつぶす
  4346. '<span style="background: black;">' + text + '</span>'
  4347. );
  4348. }
  4349. };
  4350.  
  4351. Popup.alert = function(text) {
  4352. console.log('%c' + text, 'background: yellow;');
  4353. if (w.WatchApp) {
  4354. text = text.replace(/[\n]/, '<br />');
  4355. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4356. '<span style="background: black; color: red;">' + text + '</span>'
  4357. );
  4358. } else {
  4359. w.alert(text);
  4360. }
  4361. };
  4362.  
  4363. Popup.hide = function() {
  4364. if (w.WatchApp) {
  4365. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.stop();
  4366. }
  4367. };
  4368. return Popup;
  4369. })();
  4370.  
  4371.  
  4372. var KeyMatch = (function() {
  4373. var self;
  4374.  
  4375. function create(def) {
  4376. var ch = def.char[0].toUpperCase();
  4377. return {
  4378. prop: {
  4379. char: ch,
  4380. code: typeof def.code === 'number' ? def.code : ch.charCodeAt(0),
  4381. shift: !!def.shift,
  4382. ctrl: !!def.ctrl,
  4383. alt: !!def.alt,
  4384. enable: !!def.enable
  4385. },
  4386. test: function(event) {
  4387. if (
  4388. this.prop.enable === true &&
  4389. this.prop.shift === event.shiftKey &&
  4390. this.prop.ctrl === event.ctrlKey &&
  4391. this.prop.alt === event.altKey &&
  4392. this.prop.code === event.which
  4393. ) {
  4394. event.preventDefault();
  4395. return true;
  4396. }
  4397. return false;
  4398. },
  4399. json: function() {
  4400. return JSON.stringify(this.prop);
  4401. }
  4402. };
  4403. }
  4404.  
  4405. self = {
  4406. create: create
  4407. };
  4408. return self;
  4409. })();
  4410.  
  4411. var TouchEventDispatcher = (function(target) {
  4412. var
  4413. self,
  4414. touchStartEvent = null,
  4415. touchEndEvent = null,
  4416. events = {
  4417. onflick: []
  4418. };
  4419. function dispatchEvent(name) {
  4420. var e = events[name];
  4421. for (var i =0, len = e.length; i < len; i++) {
  4422. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  4423. }
  4424. }
  4425.  
  4426. target.addEventListener('touchstart', function(e) {
  4427. touchStartEvent = e;
  4428. }, false);
  4429. target.addEventListener('touchcancel', function(e) {
  4430. touchStartEvent = null;
  4431. }, false);
  4432. target.addEventListener('touchend', function(e) {
  4433. touchEndEvent = e;
  4434. if (touchStartEvent !== null) {
  4435. var
  4436. sx = touchStartEvent.changedTouches[0].pageX, sy = touchStartEvent.changedTouches[0].pageY,
  4437. ex = touchEndEvent.changedTouches[0].pageX, ey = touchEndEvent.changedTouches[0].pageY,
  4438. dx = (sx - ex), dy = (sy - ey), len = Math.sqrt(dx * dx + dy * dy), s;
  4439. if (len > 150) {
  4440. s = dy / len;
  4441. var a = Math.abs(s), ss = Math.round(s);
  4442. if (a <= 0.3 || a >= 0.7) {
  4443. var d;
  4444. if (ss < 0) { d = 'down'; } else if (ss > 0) { d = 'up'; }
  4445. else if (dx < 0) { d = 'right';} else { d = 'left'; }
  4446. dispatchEvent('onflick', {
  4447. direction: d,
  4448. distance: len,
  4449. x: dx, y: dy,
  4450. startEvent: touchStartEvent,
  4451. endEvent: touchEndEvent
  4452. });
  4453. }
  4454. }
  4455. }
  4456. touchStartEvent = touchEndEvent = null;
  4457. }, false);
  4458.  
  4459. function onflick(callback) {
  4460. events.onflick.push(callback);
  4461. }
  4462.  
  4463. self = {
  4464. onflick: onflick
  4465. };
  4466. return self;
  4467. })(w.document);
  4468.  
  4469.  
  4470.  
  4471. /**
  4472. * リンクのマウスオーバーに仕込む処理
  4473. * ここの表示は再考の余地あり
  4474. */
  4475. var AnchorHoverPopup = (function(w, conf,EventDispatcher) {
  4476. var mylistPanel = Mylist.getPanel(''), hoverMenuDelay = conf.hoverMenuDelay * 1000;
  4477. mylistPanel.className += ' popup';
  4478. mylistPanel.style.display = 'none';
  4479. document.body.appendChild(mylistPanel);
  4480.  
  4481. EventDispatcher.addEventListener('on.config.hoverMenuDelay', function(delay) {
  4482. delay = parseFloat(delay, 10);
  4483. if (isNaN(delay)) { return; }
  4484. hoverMenuDelay = Math.abs(delay * 1000);
  4485. });
  4486.  
  4487. function showPanel(watchId, baseX, baseY, w_touch) {
  4488.  
  4489. var cn = mylistPanel.className.toString();
  4490. if (w_touch === true) {
  4491. cn = cn.replace(' w_touch', '') + ' w_touch';
  4492. } else {
  4493. if (cn.indexOf('w_touch') >= 0 && mylistPanel.style.display !== 'none') {
  4494. // フリック操作で表示したパネルが出ている間はそちらを優先し、なにもしない
  4495. return;
  4496. }
  4497. cn = cn.replace(' w_touch', '');
  4498.  
  4499. }
  4500. VideoTags.hidePopup();
  4501. if (mylistPanel.className !== cn) mylistPanel.className = cn;
  4502.  
  4503. mylistPanel.style.display = '';
  4504. mylistPanel.watchId(watchId);
  4505. mylistPanel.style.right = null;
  4506. mylistPanel.style.left = (baseX) + 'px';
  4507. mylistPanel.style.top = Math.max(baseY - mylistPanel.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  4508.  
  4509. if (mylistPanel.offsetLeft + mylistPanel.offsetWidth > document.body.clientWidth) {
  4510. mylistPanel.style.left = null;
  4511. mylistPanel.style.right = 0;
  4512. }
  4513.  
  4514. }
  4515.  
  4516.  
  4517. var videoReg = /(\?cc_video_id=|\?cc_id=|watch\/)([a-z0-9]+)/;
  4518. var excludeReg = /(news|live|seiga)\..*?nicovideo\.jp/;
  4519.  
  4520. function each(w, watchId) {
  4521.  
  4522. this.w_eventInit = false;
  4523.  
  4524. this.addEventListener('mouseover', function() {
  4525. var mx = 0, my = 0, self = this;
  4526. if (this.href) this.setAttribute('href', this.href.split('?')[0]);
  4527.  
  4528. self.w_mouse_in = true;
  4529. self.w_mouse_timer = null;
  4530. self.w_mouse_timer = setTimeout(function() {
  4531. self.w_mouse_timer = null;
  4532. if (!self.w_mouse_in) {
  4533. return;
  4534. }
  4535. var o;
  4536. if (w.jQuery) {
  4537. var $e = w.jQuery(self);
  4538. var t = $e.text();
  4539. o = t !== "" ? $e.offset() : $e.find('*').offset();
  4540. showPanel(watchId, o.left, o.top);
  4541. } else
  4542. if (self.getBoundingClientRect) {
  4543. o = (self.firstChild && self.firstChild.tagName === 'IMG') ? self.firstChild.getBoundingClientRect() : self.getBoundingClientRect();
  4544. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4545. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4546. showPanel(watchId, left + o.left, top + o.top);
  4547. } else {
  4548. showPanel(watchId, mx + 8, my + 8);
  4549. }
  4550. EventDispatcher.dispatch('mylistPanelOpen', mylistPanel, self, watchId);
  4551. }, hoverMenuDelay);
  4552.  
  4553. if (!this.w_eventInit) {
  4554. this.addEventListener('mouseout', function() {
  4555. self.w_mouse_in = false;
  4556. if (self.w_mouse_timer) {
  4557. clearTimeout(self.w_mouse_timer);
  4558. self.w_mouse_timer = null;
  4559. }
  4560. }, false);
  4561. if (!w.jQuery) {
  4562. this.addEventListener('mousemove', function(ev) {
  4563. mx = ev.pageX;
  4564. my = ev.pageY;
  4565. }, false);
  4566. }
  4567. this.w_eventInit = true;
  4568. }
  4569. }, false);
  4570. this.added = 1;
  4571. }
  4572.  
  4573. function bind(force, target) {
  4574. if (!conf.enableHoverPopup) { return; }
  4575.  
  4576. var a = Array.prototype.slice.apply(document.links), vreg = videoReg, ereg = excludeReg;
  4577. for (var i = 0, len = a.length; i < len; i++) {
  4578. var e = a[i];
  4579. var m, href= e.href;
  4580. if (
  4581. href &&
  4582. !e.added &&
  4583. (m = vreg.exec(href)) !== null &&
  4584. !ereg.test(href) &&
  4585. // e.className !== "itemEcoLink" &&
  4586. e.className !== "playlistSaveLink"
  4587. ) {
  4588. each.apply(e, [w, m[2]]);
  4589. }
  4590. }
  4591. }
  4592. function bindTouch() {
  4593. TouchEventDispatcher.onflick(function(e) {
  4594. var se = e.startEvent;
  4595. if (e.direction === 'right' && (se.target.tagName === 'A' || se.target.parentElement.tagName === 'A')) {
  4596. var
  4597. a = (se.target.tagName === 'A') ? e.startEvent.target : e.startEvent.target.parentElement,
  4598. href = a.href, vreg = videoReg, ereg = excludeReg, m, watchId;
  4599. if (
  4600. href &&
  4601. (m = vreg.exec(href)) !== null &&
  4602. !ereg.test(href) &&
  4603. // e.className !== "itemEcoLink" &&
  4604. true
  4605. ) {
  4606. watchId = m[2];
  4607. var o;
  4608. if (w.jQuery) {
  4609. var $a = w.jQuery(a);
  4610. var t = $a.text();
  4611. o = t !== "" ? $a.offset() : $a.find('*').offset();
  4612. showPanel(watchId, o.left, o.top, true);
  4613. } else {
  4614. o = (a.firstChild && a.firstChild.tagName === 'IMG') ? a.firstChild.getBoundingClientRect() : a.getBoundingClientRect();
  4615. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4616. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4617. showPanel(watchId, left + o.left, top + o.top, true);
  4618. }
  4619. }
  4620. }
  4621. });
  4622. }
  4623.  
  4624. var lastUpdate = 0, linksCount = document.links.length,
  4625. bindLoop = function(nextTime) {
  4626. var now = Date.now();
  4627. var updateInterval = w.document.hasFocus() ? 3000 : 15000;
  4628. if (now - lastUpdate < updateInterval) {
  4629. var len = document.links.length;
  4630. if (linksCount === len) {
  4631. return;
  4632. }
  4633. linksCount = len;
  4634. }
  4635. bind();
  4636. lastUpdate = now;
  4637. };
  4638.  
  4639. var self = {
  4640. hidePopup: function() {
  4641. VideoTags.hidePopup();
  4642. mylistPanel.hide();
  4643. return this;
  4644. },
  4645. updateNow: function() {
  4646. bind();
  4647. lastUpdate -= 1500;
  4648. return this;
  4649. }
  4650. };
  4651.  
  4652.  
  4653. if (location.host === "ext.nicovideo.jp") {
  4654. bind();
  4655. } else {
  4656. var thumbnailReg = /\.smilevideo\.jp\/smile\?i=(\d+)/;
  4657. if (location.host === 'ch.nicovideo.jp' && w.jQuery) {
  4658. w.jQuery('.lazyimage, .thumb_video.thumb_114.wide img, .itemset li .image a .item').each(function() {
  4659. var $e = w.jQuery(this).text(' ');
  4660. var src = $e.attr('data-original') || $e.attr('src');
  4661. if (typeof src === 'string' && thumbnailReg.test(src)) {
  4662. each.apply(this, [w, 'so' + RegExp.$1]);
  4663. }
  4664. });
  4665. }
  4666. bindTouch();
  4667. bind();
  4668. setInterval(bindLoop, 500);
  4669. }
  4670. return self;
  4671. })(w, conf, EventDispatcher);
  4672. window.WatchItLater.popup = AnchorHoverPopup;
  4673.  
  4674.  
  4675. //===================================================
  4676. //===================================================
  4677. //===================================================
  4678.  
  4679.  
  4680. /**
  4681. * マイリスト登録のポップアップウィンドウを乗っ取る処理
  4682. *
  4683. * iframeの子ウィンドウ内に開かれた時に実行される
  4684. * クロスドメインを越えられない環境ではこっちを使うしかない
  4685. */
  4686. (function(){ // mylist window
  4687. if (w.location.href.indexOf('/mylist_add/') < 0 || w.name === 'nicomylistadd') return;
  4688.  
  4689. var $ = w.jQuery;
  4690. $('body,table,img,td').css({border:0, margin:0, padding:0, background: "transparent", overflow: 'hidden'});
  4691. $('#main_frm').css({background: '#fff', paddig: 0, borderRadius: 0}).addClass('mylistPopupPanel');
  4692.  
  4693. if ($('#edit_description').length < 1) {
  4694. $('#main_frm .font12:first').css({position: 'absolute', margin: 0, top: 0, left: 0, padding: 0, color: 'red', fontSize: '8pt'});
  4695. // ログインしてないぽい
  4696. return;
  4697. }
  4698.  
  4699. $('#box_success').css({position: 'absolute', top: 0, left: 0});
  4700. $('#box_success h1').css({color: 'black', fontSize: '8pt', padding: 0});
  4701. $('td').css({padding: 0});
  4702.  
  4703. // 「マイリストに登録しました」
  4704. // $('.mb8p4:last').show();
  4705. // $('.mb8p4:last h1').css({fontSize : "8pt"});
  4706.  
  4707. $('table:first').css({width: '200px'});
  4708. $('table:first td.main_frm_bg').css({height: '20px'});
  4709. $('table:first table:first').hide();
  4710.  
  4711. $('select')
  4712. .css({width: '64px',position: 'absolute', top:0, left:0, margin: 0})
  4713. .addClass('mylistSelect');
  4714. $('select')[0].selectedIndex = $('select')[0].options.length - 1;
  4715. $('#select_group option:last')[0].innerHTML = 'とりマイ';
  4716.  
  4717. // var submit = document.createElement("input");
  4718. // submit.className = 'mylistAdd';
  4719. // submit.type = "submit";
  4720. // submit.value = "マ";
  4721. // $(submit).css({position: 'absolute', top: 0, left: '64px'});
  4722. // $('select')[0].parentNode.appendChild(submit);
  4723.  
  4724.  
  4725. $('#edit_description').hide();
  4726.  
  4727. w.document.documentElement.scrollTop = 0;
  4728. w.document.documentElement.scrollLeft = 0;
  4729.  
  4730.  
  4731. $($.browser.safari ? 'body' : 'html').scrollTop(0);
  4732.  
  4733. w.window.close = function()
  4734. {
  4735. return;
  4736. };
  4737. w.window.alert = function()
  4738. {
  4739. w.document.write('<span style="position:absolute;top:0;left:0;font-size:8pt;color:red;">' +
  4740. arguments[0] + '</span>');
  4741. };
  4742. })();
  4743.  
  4744.  
  4745. //===================================================
  4746. //===================================================
  4747. //===================================================
  4748.  
  4749. window.WatchItLater.loader.videoArrayAPILoader = (function() {
  4750. var sessionId = 0;
  4751. var deferredList = {};
  4752. var cacheData = {};
  4753. var currentRequestIds = '', currentPromise;
  4754. var loaderFrame, loaderWindow;
  4755. var BASE_URL = 'http://i.nicovideo.jp/v3/video.array?v=';
  4756. var isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') >= 0;
  4757.  
  4758. //WatchItLater.loader.videoArrayAPILoader.load('sm9').then(function(info) { console.log(info); });
  4759.  
  4760. var load = function(watchId) {
  4761. var ids = [], result = {}, def = new $.Deferred(), timeoutTimer = null;
  4762.  
  4763. initialize();
  4764.  
  4765. $(typeof watchId !== 'string' ? watchId : [watchId]).each(function(i, id) {
  4766. if (cacheData[id]) {
  4767. result[id] = cacheData[id];
  4768. } else {
  4769. ids.push(id);
  4770. }
  4771. });
  4772. ids = window._.unique(ids);
  4773. if (ids.length < 1) {
  4774. window.setTimeout(function() { def.resolve(result); }, 0);
  4775. return def;
  4776. }
  4777. var _ids = JSON.stringify(ids);
  4778. var onSuccess = function(infoList) {
  4779. $(ids).each(function(i, id) {
  4780. result[id] = result[id] || infoList[id] || cacheData[id];
  4781. });
  4782.  
  4783. window.clearTimeout(timeoutTimer);
  4784. currentRequestIds = ''; currentPromise = null;
  4785. def.resolve(result);
  4786. def = null;
  4787. };
  4788. var onFail = function() {
  4789. window.clearTimeout(timeoutTimer);
  4790. console.log('%cVideoArrayAPILoader.onFail', 'color: red;');
  4791. currentRequestIds = ''; currentPromise = null;
  4792. if (def) {
  4793. def.reject();
  4794. }
  4795. def = null;
  4796. };
  4797.  
  4798. sessionId++;
  4799.  
  4800. timeoutTimer = window.setTimeout(onFail, 30 * 1000);
  4801.  
  4802. if (_ids === currentRequestIds) {
  4803. currentPromise.then(onSuccess, onFail);
  4804. return def;
  4805. }
  4806.  
  4807. currentRequestIds = _ids;
  4808. sendRequest(ids, sessionId).then(onSuccess, onFail);
  4809.  
  4810. return def.promise();
  4811. };
  4812.  
  4813. var sendRequest = function(ids, sessionId) {
  4814. var def = new $.Deferred();
  4815. currentPromise = def;
  4816. deferredList[sessionId] = def;
  4817. if (isChrome && conf.debugMode) {
  4818. // 基本的に i.nicovideo.jpのほうが高機能だが、Chrome + Tampermonkeyからは使えないため回避策
  4819. window.WatchItLater.loader.ceAPIClient.videoArray(ids, 'xml').then(
  4820. function(xml) { onXmlLoad(sessionId, xml); },
  4821. function() { onXmlFail(sessionId); }
  4822. );
  4823. } else {
  4824. loaderWindow.location.replace(BASE_URL + ids.join(',') + '#' + sessionId);
  4825. }
  4826. return def.promise();
  4827. };
  4828.  
  4829. var parseVideoArray = function(xml) {
  4830. var $xml = $(xml), infoList = {};
  4831. $xml.find('video_info').each(function() {
  4832. var info = new parseVideoInfo($(this));
  4833. infoList[info.id] = cacheData[info.id] = cacheData[info.default_thread] = info;
  4834. if (info.threadIds && info.threadIds.length > 1) {
  4835. $(info.threadIds).each(function(i, threadId) {
  4836. infoList[threadId] = cacheData[threadId] = info;
  4837. });
  4838. }
  4839. });
  4840. return infoList;
  4841. };
  4842.  
  4843. var parseVideoInfo = function($xml) {
  4844. var info = {threadIds: []};
  4845. var elements = [
  4846. 'id', 'user_id', 'title', 'description', 'length_in_seconds',
  4847. 'thumbnail_url', 'first_retrieve', 'default_thread',
  4848. 'view_counter', 'mylist_counter'];
  4849.  
  4850. $(elements).each(function(i, elm) {
  4851. info[elm] = $xml.find(elm + ':first').text();
  4852. });
  4853.  
  4854. info['num_res'] = $xml.find('thread:first num_res').text();
  4855.  
  4856. var duration = parseInt(info['length_in_seconds'], 10);
  4857. info['length'] = parseInt(duration / 60, 10) + ':' + (100 + (duration % 60)).toString().substr(1);
  4858.  
  4859. info['first_retrieve_t'] = info['first_retrieve'];
  4860. info['first_retrieve'] =
  4861. window.WatchApp.ns.util.DateFormat.strftime(
  4862. '%Y-%m-%d %H:%M:%S',
  4863. new Date(info['first_retrieve'])
  4864. );
  4865.  
  4866. $xml.find('thread id, channel_thread id').each(function() {
  4867. info.threadIds.push(this.innerHTML);
  4868. });
  4869. return info;
  4870. };
  4871.  
  4872. var onXmlLoad = function(sessionId, xml) {
  4873. if (deferredList[sessionId]) {
  4874. deferredList[sessionId].resolve(parseVideoArray(xml));
  4875. delete deferredList[sessionId];
  4876. currentPromise = null;
  4877. }
  4878. };
  4879.  
  4880. var onXmlFail = function(sessionId) {
  4881. if (deferredList[sessionId]) {
  4882. deferredList[sessionId].reject();
  4883. delete deferredList[sessionId];
  4884. currentPromise = null;
  4885. }
  4886. };
  4887.  
  4888. var initialize = function() {
  4889. initialize = window._.noop;
  4890.  
  4891. loaderFrame = document.createElement('iframe');
  4892. loaderFrame.name = 'watchItLaterAPILoader';
  4893. loaderFrame.className = 'watchItLaterAPILoaderFrame xDomainLoaderFrame';
  4894. document.body.appendChild(loaderFrame);
  4895.  
  4896. loaderWindow = loaderFrame.contentWindow;
  4897.  
  4898. EventDispatcher.addEventListener('onMessage', function(data, type) {
  4899. if (type !== 'VideoArrayAPILoader') { return; }
  4900. var sessionId = data.session, xml = data.xml;
  4901. try {
  4902. //console.log('VideoArrayAPILoader.onMessage', data.session, data.xml);
  4903. onXmlLoad(sessionId, xml);
  4904. } catch (e) {
  4905. console.log('message parse error', e);
  4906. onXmlFail(sessionId);
  4907. }
  4908. });
  4909.  
  4910. };
  4911.  
  4912. // sample URL
  4913. // http://i.nicovideo.jp/v3/video.array?v=sm9,sm3393520,sm5909863,so23023492,1394173596
  4914. //initialize();
  4915. return {
  4916. load: load
  4917. };
  4918. })();
  4919.  
  4920. // 参考: http://www59.atwiki.jp/nicoapi/pages/24.html
  4921. // TampermonkeyはContent-Type: text/xmlのページで動かないため、
  4922. // 同じドメインにある適当なテキストapiを踏み台にして通信する
  4923. window.WatchItLater.loader.ceAPIClient = (function() {
  4924. var BASE_URL = 'http://api.ce.nicovideo.jp/api/v1/system.unixtime?';
  4925. var MESSAGE_ORIGIN = 'http://api.ce.nicovideo.jp/';
  4926.  
  4927. function CeAPIClient() {}
  4928.  
  4929. CeAPIClient.prototype = {
  4930. initialize: function() {
  4931. this.initialize = this.initialize_;
  4932. console.log('%cinitialize CeAPIClient', 'background: lightgreen;');
  4933. this._initialDef = new $.Deferred();
  4934.  
  4935. var sessions = this._sessions = {};
  4936. var cacheData = this._cacheData = {};
  4937.  
  4938. var loaderFrame = document.createElement('iframe');
  4939. loaderFrame.name = 'ceAPILoader';
  4940. loaderFrame.className = 'ceAPILoaderFrame xDomainLoaderFrame';
  4941. document.body.appendChild(loaderFrame);
  4942.  
  4943. this._loaderWindow = loaderFrame.contentWindow;
  4944.  
  4945. EventDispatcher.addEventListener('onMessage', $.proxy(function(data, type) {
  4946. if (type !== 'ceAPILoader') { return; }
  4947. if (data.status === 'initialized') {
  4948. return this._onInitialized();
  4949. }
  4950.  
  4951. if (data.sessionId) {
  4952. var def = sessions[data.sessionId];
  4953. delete sessions[data.sessionId];
  4954.  
  4955. if (data.status === 'ok') {
  4956. cacheData[data.url] = data.body;
  4957. return def.resolve(data.body);
  4958. } else {
  4959. cacheData[data.url] = data.body;
  4960. window.setTimeout(function() { delete cacheData[data.url]; }, 60000);
  4961. return def.reject(data.status);
  4962. }
  4963. }
  4964. }, this));
  4965.  
  4966. this._loaderWindow.location.replace(BASE_URL);
  4967.  
  4968. return this._initialDef.promise();
  4969. },
  4970. initialize_: function() {
  4971. var def = new $.Deferred();
  4972. window.setTimeout(function() { def.resolve(); }, 0);
  4973. return def.promise();
  4974. },
  4975. _onInitialized: function() {
  4976. return this._initialDef.resolve();
  4977. },
  4978. _load: function(url) {
  4979. var def = new $.Deferred(), cacheData = this._cacheData;
  4980. if (cacheData[url]) {
  4981. window.setTimeout(function() { def.resolve(cacheData[url]); }, 0);
  4982. return def.promise();
  4983. }
  4984.  
  4985. var sessionId = 'session_' + Math.random();
  4986. this._sessions[sessionId] = def;
  4987. try {
  4988. this._loaderWindow.postMessage(JSON.stringify({
  4989. sessionId: sessionId,
  4990. url: url
  4991. }),
  4992. MESSAGE_ORIGIN);
  4993. } catch (e) {
  4994. console.log('%cException!', 'background: red;', e);
  4995. delete this._sessions[sessionId];
  4996. return def.reject();
  4997. }
  4998. return def.promise();
  4999. },
  5000. videoArray: function(watchId, format) {
  5001. return this.initialize().then($.proxy(function() {
  5002. var url = '/nicoapi/v1/video.array?v=';
  5003. var ids = [], def = new $.Deferred();
  5004.  
  5005. $(typeof watchId !== 'string' ? watchId : [watchId]).each(function(i, id) {
  5006. ids.push(id);
  5007. });
  5008. ids = window._.unique(ids);
  5009. if (ids.length < 1) {
  5010. window.setTimeout(function() { def.resolve({}); }, 0);
  5011. return;
  5012. }
  5013.  
  5014. url = url + ids.join(',');
  5015. if (!format || format !== 'xml') {
  5016. url += '&__format=json';
  5017. }
  5018.  
  5019. this._load(url).then(function(result) {
  5020. try {
  5021. if (!format && format !== 'xml') {
  5022. result = JSON.parse(result).nicovideo_video_response;
  5023. result.status = result['@status'];
  5024. delete result['@status'];
  5025. }
  5026. } catch (e) {
  5027. console.log('%cJSON parse Error!', 'background: red;', e);
  5028. def.reject({});
  5029. }
  5030. return def.resolve(result);
  5031. }, function() {
  5032. return def.reject();
  5033. });
  5034.  
  5035. return def.promise();
  5036. }, this));
  5037. }
  5038. };
  5039.  
  5040. return new CeAPIClient();
  5041. })();
  5042.  
  5043.  
  5044. //===================================================
  5045. //===================================================
  5046. //===================================================
  5047.  
  5048. var _watchController = function(w) {
  5049. var WatchApp = w.WatchApp, _false = function() { return false; };
  5050. var
  5051. watch = (WatchApp && WatchApp.ns.init) || {},
  5052. watchInfoModel = (watch.CommonModelInitializer && WatchApp.ns.model.WatchInfoModel.getInstance()) || {},
  5053. nicoPlayer = (watch.PlayerInitializer && watch.PlayerInitializer.nicoPlayerConnector) || {},
  5054. videoExplorerController = watch.VideoExplorerInitializer.videoExplorerController,
  5055. videoExplorer = videoExplorerController.getVideoExplorer(),
  5056. videoExplorerContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  5057. $ = w.$, WatchJsApi = w.WatchJsApi;
  5058. return {
  5059. isZeroWatch: function() {
  5060. return (window.PlayerApp) ? true : false;
  5061. },
  5062. getWatchInfoModel: function() {
  5063. return watchInfoModel;
  5064. },
  5065. nicoSearch: function(word, search_type) {
  5066. if (!search_type) {
  5067. try {
  5068. var type = videoExplorerContentType.SEARCH;
  5069. search_type = videoExplorer.getContentList().getContent(type).getSearchType();
  5070. } catch(e) {
  5071. search_type = search_type || 'tag';
  5072. }
  5073. }
  5074. videoExplorerController.searchVideo(word, search_type);
  5075. AnchorHoverPopup.hidePopup();
  5076. },
  5077. showMylist: function(mylistId) {
  5078. videoExplorerController.showMylist(mylistId.toString());
  5079. },
  5080. clearDeflistCache: function() {
  5081. watch.VideoExplorerInitializer.deflistVideoAPILoader._cache.clear();
  5082. },
  5083. clearMylistCache: function(id) {
  5084. if (id) {
  5085. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.deleteElement(
  5086. 'http://riapi.nicovideo.jp/api/watch/mylistvideo?id=' + id.toString()
  5087. );
  5088. } else {
  5089. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.clear();
  5090. }
  5091. },
  5092. showDeflist: function() {
  5093. videoExplorerController.showDeflist();
  5094. },
  5095. changeScreenMode: function(mode) {
  5096. WatchJsApi.player.changePlayerScreenMode(mode);
  5097. setTimeout(function(){$(window).resize();}, 3000);
  5098. },
  5099. isFixedHeader: function() {
  5100. return !$('body').hasClass('nofix');
  5101. },
  5102. // ヘッダー追従かどうかを考慮したscrollTop
  5103. scrollTop: function(top, dur) {
  5104. var header = (this.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  5105.  
  5106. if (top !== void 0) {
  5107. return $(window).scrollTop(top - header, dur);
  5108. } else {
  5109. return $(window).scrollTop() + header;
  5110. }
  5111. },
  5112. scrollToVideoPlayer: function(force) {
  5113. // 縦解像度がタグ+プレイヤーより大きいならタグの開始位置、そうでないならプレイヤーの位置にスクロール
  5114. // ただし、該当部分が画面内に納まっている場合は、勝手にスクロールするとかえってうざいのでなにもしない
  5115. var $body = $('body'), isContentFix = $body.hasClass('content-fix');
  5116. $body.addClass('w_noHover').removeClass('content-fix');
  5117. var h = $('#playerContainer').outerHeight() + $('#videoTagContainer').outerHeight();
  5118. var top = $(window).height() >= h ? '#videoTagContainer, #playerContainer' : '#playerContainer';
  5119.  
  5120.  
  5121. if (force) {
  5122. // 要素が画面内に納まっている場合でも、その要素の位置までスクロール
  5123. WatchApp.ns.util.WindowUtil.scrollFit(top, 600);
  5124. } else {
  5125. // 要素が画面内に収まっている場合はスクロールしない
  5126. WatchApp.ns.util.WindowUtil.scrollFitMinimum(top, 600);
  5127. }
  5128. $(window).scrollLeft(0);
  5129. $body.toggleClass('content-fix', isContentFix);
  5130. setTimeout(function() {
  5131. $body.removeClass('w_noHover');
  5132. }, 800);
  5133. },
  5134. play: function() {
  5135. nicoPlayer.playVideo();
  5136. },
  5137. pause: function() {
  5138. nicoPlayer.stopVideo();
  5139. },
  5140. togglePlay: function() {
  5141. var status = $("#external_nicoplayer")[0].ext_getStatus();
  5142. if (status === 'playing') {
  5143. this.pause();
  5144. } else {
  5145. this.play();
  5146. }
  5147. },
  5148. isPlaying: function() {
  5149. var status = $("#external_nicoplayer")[0].ext_getStatus();
  5150. return status === 'playing';
  5151. },
  5152. nextVideo: function() {
  5153. return nicoPlayer.playNextVideo();
  5154. },
  5155. prevVideo: function() {
  5156. return nicoPlayer.playPreviousVideo();
  5157. },
  5158. vpos: function(v) {
  5159. if (typeof v === 'number') {
  5160. return nicoPlayer.seekVideo(v);
  5161. } else {
  5162. return nicoPlayer.getVpos();
  5163. }
  5164. },
  5165. openSearch: function() {
  5166. WatchApp.ns.init.VideoExplorerInitializer.expandButtonView.open();
  5167. // videoExplorer.openByCurrentCondition();
  5168. // videoExplorer.changeState(true);
  5169. },
  5170. closeSearch: function() {
  5171. videoExplorer.changeState(false);
  5172. videoExplorer.close();
  5173. },
  5174. openVideoOwnersVideo: function() {
  5175. if (this.isChannelVideo()) {
  5176. this.openChannelOwnersVideo();
  5177. } else {
  5178. this.openUpNushiVideo();
  5179. }
  5180. },
  5181. openUpNushiVideo: function() {
  5182. videoExplorerController.showOwnerVideo();
  5183. },
  5184. openChannelOwnersVideo: function() {
  5185. videoExplorerController.showMylist('-3');
  5186. },
  5187. openUserVideo: function(userId, userNick) {
  5188. videoExplorerController.showOtherUserVideos(userId, userNick);
  5189. },
  5190. openRecommend: function() {
  5191. var
  5192. type = videoExplorerContentType.RELATED_VIDEO,
  5193. open = function() {
  5194. var rel = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer._menu.getItemByContentType(type);
  5195. rel.select();
  5196. };
  5197. if (videoExplorer.isOpen()) {
  5198. open();
  5199. } else {
  5200. this.openSearch();
  5201. setTimeout(open, 500);
  5202. }
  5203. },
  5204. getVideoExplorerCurrentItems: function(format) {
  5205. var ac = videoExplorer._contentList.getActiveContent();
  5206. if (!ac || !ac.getItems) return [];
  5207. var items = ac.getItems();
  5208.  
  5209. if (!format) {
  5210. return items;
  5211. } else
  5212. if (format === 'playlist') {
  5213. var result = [];
  5214. for (var i = items.length - 1; i >= 0; i--) {
  5215. result.unshift(
  5216. videoExplorerController._item2playlistItem(items[i])
  5217. );
  5218. }
  5219. return result;
  5220. }
  5221. },
  5222. getWatchId: function() {// スレッドIDだったりsmXXXXだったり
  5223. return watchInfoModel.v;
  5224. },
  5225. getVideoId: function() {// smXXXXXX, soXXXXX など
  5226. return watchInfoModel.id;
  5227. },
  5228. getMyNick: function() {
  5229. return watch.CommonModelInitializer.viewerInfoModel.nickname;
  5230. },
  5231. getMyUserId: function() {
  5232. return watch.CommonModelInitializer.viewerInfoModel.userId;
  5233. },
  5234. getPlaylistItems: function() {
  5235. return watch.PlaylistInitializer.playlist.items || watch.PlaylistInitializer.playlist.currentItems;
  5236. },
  5237. setPlaylistItems: function(items, currentItem) {
  5238. var playlist = watch.PlaylistInitializer.playlist;
  5239. // var isAutoPlay = playlist.isContinuous();//.isAutoPlay();
  5240. playlist.reset(
  5241. items,
  5242. 'WatchItLater',
  5243. playlist.type,
  5244. playlist.option
  5245. );
  5246. if (currentItem) { playlist.playingItem = currentItem; }
  5247. else { playlist.playingItem = items[0]; }
  5248. // if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  5249. // playlist.disableContinuous();
  5250. // }
  5251. },
  5252. shufflePlaylist: function(target) {
  5253. var x = this.getPlaylistItems(), items = [], i, currentIndex = -1, currentItem = null;
  5254. if (target === 'right') {
  5255. for (i = 0; i < x.length;) {
  5256. if (x[0]._isPlaying) {
  5257. currentIndex = i;
  5258. currentItem = x.shift();
  5259. items.push(currentItem);
  5260. break;
  5261. } else {
  5262. items.push(x.shift());
  5263. }
  5264. }
  5265. }
  5266.  
  5267. x = x.map(function(a){return {weight:Math.random(), value:a};})
  5268. .sort(function(a, b){return a.weight - b.weight;})
  5269. .map(function(a){return a.value;});
  5270. for (i = 0; i < x.length; i++) {
  5271. if (x[i]._isPlaying) {
  5272. items.unshift(x[i]);
  5273. } else {
  5274. items.push(x[i]);
  5275. }
  5276. }
  5277. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView;
  5278. var left = pm.getLeftSideIndex();
  5279. this.setPlaylistItems(items, currentItem);
  5280. pv.scroll(left);
  5281. },
  5282. clearPlaylist: function(target) {
  5283. var x = this.getPlaylistItems(), items = [], i, currentItem = null;
  5284. if (target === 'left') {
  5285. for (i = x.length - 1; i >= 0; i--) {
  5286. items.unshift(x[i]);
  5287. if (x[i]._isPlaying) {
  5288. currentItem = x[i];
  5289. break;
  5290. }
  5291. }
  5292. } else
  5293. if (target === 'right') {
  5294. for (i = 0; i < x.length ; i++) {
  5295. items.push(x[i]);
  5296. if (x[i]._isPlaying) {
  5297. currentItem = x[i];
  5298. break;
  5299. }
  5300. }
  5301. }
  5302. else {
  5303. for (i = 0; i < x.length; i++) {
  5304. if (x[i]._isPlaying) {
  5305. currentItem = x[i];
  5306. items.unshift(x[i]);
  5307. }
  5308. }
  5309. }
  5310. this.setPlaylistItems(items, currentItem);
  5311. },
  5312. appendSearchResultToPlaylist: function(mode) {
  5313. var
  5314. items = this.getPlaylistItems(),
  5315. searchItems = this.getVideoExplorerCurrentItems('playlist'),
  5316. uniq = {}, i, playingIndex = 0, c, len, currentItem = null;
  5317. if (!searchItems || searchItems.length < 1) {
  5318. return;
  5319. }
  5320. for (i = 0, len = items.length; i < len; i++) {
  5321. uniq[items[i].id] = true;
  5322. if (items[i]._isPlaying) { playingIndex = i; currentItem = items[i]; }
  5323. }
  5324. if (mode === 'next') {
  5325. for (i = searchItems.length - 1; i >= 0; i--) {
  5326. c = searchItems[i];
  5327. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === void 0 && items.splice(playingIndex + 1, 0, c);
  5328. }
  5329. } else {
  5330. for (i = 0, len = searchItems.length; i < len; i++) {
  5331. c = searchItems[i];
  5332. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === void 0 && items.push(c);
  5333. }
  5334. }
  5335. this.setPlaylistItems(items, currentItem);
  5336. },
  5337. insertVideoToPlaylist: function(id) {
  5338. WatchItLater.VideoInfoLoader.load(id).then(function(info) {
  5339. var item = new WatchApp.ns.model.playlist.PlaylistItem(info);
  5340. watch.PlaylistInitializer.playlist.insertNextPlayingItem(item);
  5341. }, function(err) {
  5342. Popup.alert(err.message);
  5343. });
  5344. },
  5345. addDefMylist: function(description) {
  5346. var watchId = watchInfoModel.id;
  5347. setTimeout(function() {
  5348. Mylist.addDefListItem(watchId, function(status, result, replaced) {
  5349. Mylist.reloadDefList();
  5350. if (status !== "ok") {
  5351. Popup.alert('とりあえずマイリストの登録に失敗: ' + result.error.description);
  5352. } else {
  5353. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  5354. Popup.show(
  5355. torimai +
  5356. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  5357. );
  5358. }
  5359. }, description);
  5360. }, 0);
  5361. },
  5362. commentVisibility: function(v) {
  5363. if (v === 'toggle') {
  5364. return this.commentVisibility(!this.commentVisibility());
  5365. } else
  5366. if (typeof v === 'boolean') {
  5367. nicoPlayer.playerConfig.set({commentVisible: v});
  5368. return this;
  5369. } else {
  5370. var pc = nicoPlayer.playerConfig.get();
  5371. return pc.commentVisible;
  5372. }
  5373. },
  5374. deepenedComment: function(v) {
  5375. if (v === 'toggle') {
  5376. return this.deepenedComment(!this.deepenedComment());
  5377. } else
  5378. if (typeof v === 'boolean') {
  5379. nicoPlayer.playerConfig.set({deepenedComment: v});
  5380. return this;
  5381. } else {
  5382. var pc = nicoPlayer.playerConfig.get();
  5383. return pc.deepenedComment;
  5384. }
  5385. },
  5386. allowStageVideo: function(v) {
  5387. if (v === 'toggle') {
  5388. return this.allowStageVideo(!this.allowStageVideo());
  5389. } else
  5390. if (typeof v === 'boolean') {
  5391. nicoPlayer.playerConfig.set({allowStageVideo: v});
  5392. return this;
  5393. } else {
  5394. var pc = nicoPlayer.playerConfig.get();
  5395. return pc.allowStageVideo;
  5396. }
  5397. },
  5398. isStageVideoSupported: function() {
  5399. try {
  5400. var exp = w.document.getElementById('external_nicoplayer');
  5401. return exp.isStageVideoSupported();
  5402. } catch(e) {
  5403. console.log(e);
  5404. return false;
  5405. }
  5406. },
  5407. isStageVideoAvailable: function() {
  5408. try {
  5409. var exp = w.document.getElementById('external_nicoplayer');
  5410. return exp.isStageVideoAvailable();
  5411. } catch(e) {
  5412. console.log(e);
  5413. return false;
  5414. }
  5415. },
  5416. toggleStageVideo: function() {
  5417. if (!this.isStageVideoSupported()) {
  5418. Popup.alert('ハードウェアアクセラレーションを使用できない状態か、未対応の環境です');
  5419. return;
  5420. }
  5421. var isAllowed = this.allowStageVideo(), exp = $('#external_nicoplayer')[0];
  5422. exp.setIsForceUsingStageVideo(!isAllowed && conf.forceEnableStageVideo);
  5423. this.allowStageVideo(!isAllowed);
  5424. setTimeout($.proxy(function() {
  5425. isAllowed = this.allowStageVideo();
  5426. var isAvailable = this.isStageVideoAvailable();
  5427. Popup.show('ハードウェアアクセラレーション:' +
  5428. (isAllowed ? '設定ON' : '設定OFF') + ' / ' +
  5429. (isAvailable ? '使用可能' : '使用不能')
  5430. );
  5431. }, this), 100);
  5432. },
  5433. mute: function(v) {
  5434. var exp = w.document.getElementById('external_nicoplayer');
  5435.  
  5436. if (v === 'toggle') {
  5437. return this.mute(!this.mute());
  5438. } else
  5439. if (typeof v === 'boolean') {
  5440. exp.ext_setMute(v);
  5441. return this;
  5442. } else {
  5443. return exp.ext_isMute();
  5444. }
  5445. },
  5446. volume: function(v) {
  5447. var exp = w.document.getElementById('external_nicoplayer');
  5448. if (typeof v === 'string' && v.match(/^[+-]\d+$/)) {
  5449. this.volume(this.volume() + v * 1);
  5450. } else
  5451. if (typeof v === 'number' || (typeof v === 'string' && v.match(/^\d+$/))) {
  5452. exp.ext_setVolume(Math.max(0, Math.min(v * 1, 100)));
  5453. }
  5454. return exp.ext_getVolume();
  5455. },
  5456. isWide: function() {
  5457. var exp = w.document.getElementById('external_nicoplayer');
  5458. return exp.ext_isWide();
  5459. },
  5460. isPlaylistActive: function() {
  5461. return watch.PlaylistInitializer.playlist.getPlaybackMode() !== 'normal';
  5462. },
  5463. isPlaylistRandom: function() {
  5464. return watch.PlaylistInitializer.playlist.isShuffle();
  5465. },
  5466. isPlaylistContinuous: function() {
  5467. return watch.PlaylistInitializer.playlist.getPlaybackMode() === 'continuous';
  5468. },
  5469. getOwnerIcon: function() {
  5470. try {
  5471. return this.isChannelVideo() ? watchInfoModel.channelInfo.iconUrl : watchInfoModel.uploaderInfo.iconUrl;
  5472. } catch (e) {
  5473. return 'http://uni.res.nimg.jp/img/user/thumb/blank_s.jpg';
  5474. }
  5475. },
  5476. getOwnerName: function() {
  5477. try {
  5478. return this.isChannelVideo() ? watchInfoModel.channelInfo.name : watchInfoModel.uploaderInfo.nickname;
  5479. } catch (e) {
  5480. return '';
  5481. }
  5482. },
  5483. getOwnerId: function() {
  5484. try {
  5485. return this.isChannelVideo() ? watchInfoModel.channelInfo.id : watchInfoModel.uploaderInfo.id;
  5486. } catch (e) {
  5487. return '0';
  5488. }
  5489. },
  5490. getOwnerType: function() {
  5491. try {
  5492. return this.isChannelVideo() ? 'channel' : 'user';
  5493. } catch (e) {
  5494. return 'channel';
  5495. }
  5496. },
  5497. getOwnerPage: function() {
  5498. try {
  5499. if (this.isChannelVideo()) {
  5500. return $('#ch_prof').find('.symbol').attr('href');
  5501. } else {
  5502. return '/user/' + this.getOwnerId();
  5503. }
  5504. } catch (e) {
  5505. return '';
  5506. }
  5507. },
  5508. isFavoriteOwner: function() {
  5509. try {
  5510. return this.isChannelVideo() ?
  5511. !!(watchInfoModel.channelInfo && watchInfoModel.channelInfo.isFavorited) :
  5512. watchInfoModel.uploaderInfo.isFavorited;
  5513. } catch (e) {
  5514. return false;
  5515. }
  5516. },
  5517. isVideoPublic: function() { // 投稿動画一覧を公開しているか? 公開マイリストがあるかどうかとは別なのでややこしい
  5518. return this.isChannelVideo() ? true : watchInfoModel.uploaderInfo.isUserVideoPublic;
  5519. },
  5520. isChannelVideo: function() {
  5521. return watchInfoModel.isChannelVideo();
  5522. },
  5523. getOwnerInfo: function() {
  5524. return {
  5525. type: this.getOwnerType(),
  5526. name: this.getOwnerName(),
  5527. icon: this.getOwnerIcon(),
  5528. id: this.getOwnerId(),
  5529. page: this.getOwnerPage(),
  5530. isFavorite: this.isFavoriteOwner(),
  5531. isVideoPublic: this.isVideoPublic()
  5532. };
  5533. },
  5534. isSearchMode: function() {
  5535. return videoExplorer.isOpen(); ////return $('body').hasClass('videoExplorer');
  5536. },
  5537. isFullScreen: function() {
  5538. return $('body').hasClass('full_with_browser');
  5539. },
  5540. // フルスクリーンの時にタグとかプレイリストを表示する設定かどうか
  5541. isFullScreenContentAll: function() {
  5542. try {
  5543. var content = localStorage.BROWSER_FULL_OPTIONS;
  5544. if (typeof content !== 'string') return false;
  5545. var isAll = JSON.parse(content).content === 'all';
  5546. return isAll;
  5547. } catch(e) {
  5548. console.log('%cexception', 'background: red; color: white;', e);
  5549. return false;
  5550. }
  5551. },
  5552. isIchibaEmpty: function() {
  5553. return $('#ichibaMain') .find('.ichiba_mainitem').length < 1;
  5554. },
  5555. postComment: function(comment, command) {
  5556. comment = $.trim(comment);
  5557. if (comment.length <= 0) { return; }
  5558. if (!command) { command = ''; }
  5559.  
  5560. setTimeout(function() {
  5561. try {
  5562. var exp = w.document.getElementById('external_nicoplayer');
  5563. console.log('postComment: ', [comment, command]);
  5564. if (!exp.externalPostChat(comment, command)) {
  5565. Popup.alert('コメント投稿に失敗しました');
  5566. }
  5567. } catch(e) {
  5568. Popup.alert('コメント投稿に失敗しました');
  5569. }
  5570. }, 0);
  5571. },
  5572. // スレッドIDから動画IDに変換。出来なかった時はそのまま返す
  5573. getTid2Vid: function(watchId, callback) {
  5574. if (!watchId.match(/^[0-9]+$/)) {
  5575. return callback(watchId);
  5576. }
  5577. WatchItLater.VideoInfoLoader.load(watchId).then(function(info) {
  5578. callback(info.id);
  5579. },
  5580. function() {
  5581. callback(watchId);
  5582. });
  5583. }
  5584. };
  5585. }; // end _watchController
  5586.  
  5587. (function() {
  5588. window.WatchItLater.WatchController =
  5589. window.WatchController = {
  5590. isZeroWatch: function() { return window.PlayerApp ? true : false; },
  5591. isFullScreen: function() { return false; },
  5592. isSearchMode: function() { return false; },
  5593. getTid2Vid: function(threadId, callback) { return callback(threadId);}
  5594. };
  5595. })();
  5596.  
  5597.  
  5598.  
  5599. var Util = (function() {
  5600. var Cache = {
  5601. storage: {},
  5602. get: function(key) {
  5603. if (!this.storage[key]) {
  5604. console.log('no cache');
  5605. return false;
  5606. } else
  5607. if (this.storage[key].cachedUntil <= Date.now()){
  5608. console.log('cache timeout');
  5609. delete this.storage[key];
  5610. return false;
  5611. } else {
  5612. console.log('cache exist');
  5613. return this.storage[key].data;
  5614. }
  5615. },
  5616. set: function(key, data, cacheTimeMs) {
  5617. cacheTimeMs = cacheTimeMs || 1000 * 60 * 10;
  5618. console.log('set cache', key, cacheTimeMs);
  5619. this.storage[key] = {
  5620. data: data,
  5621. cachedUntil: Date.now() + cacheTimeMs
  5622. };
  5623. return data;
  5624. }
  5625. };
  5626. var Browser = {
  5627. isWebkit: function() {
  5628. return navigator.userAgent.toLowerCase().indexOf('webkit') >= 0;
  5629. },
  5630. isFx: function() {
  5631. return navigator.userAgent.toLowerCase().indexOf('firefox') >= 0;
  5632. }
  5633. };
  5634. var
  5635. isMetaKey = function(e) {
  5636. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return true; }
  5637. return false;
  5638. },
  5639. prevent = function(e) {
  5640. e.preventDefault(); e.stopPropagation();
  5641. },
  5642. scrollToVideoExplorer = function() {
  5643. if (!WatchController.isSearchMode()) { return; }
  5644. window.setTimeout(function() {
  5645. window.WatchApp.ns.util.WindowUtil.scrollFit($('#videoExplorer'));
  5646. }, 100);
  5647. };
  5648.  
  5649. var Closure = {
  5650. outScope: function(func) {
  5651. return new Function([
  5652. '(' + func.toString() + ').apply(this, arguments);'
  5653. ].join(''));
  5654. },
  5655. openVideoOwnersVideo: function() {
  5656. return function(e) {
  5657. if (isMetaKey(e)) { return; }
  5658. prevent(e);
  5659. WatchController.openVideoOwnersVideo();
  5660. scrollToVideoExplorer();
  5661. };
  5662. },
  5663. openVideoOwnersNicorepo: function() {
  5664. return function(e) {
  5665. if (isMetaKey(e)) { return; }
  5666. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  5667. return;
  5668. }
  5669. prevent(e);
  5670. //WatchController.showMylist(NicorepoVideo.REPO_OWNER);
  5671. WatchController.showMylist('repo-owner-' + WatchController.getOwnerId());
  5672. scrollToVideoExplorer();
  5673. };
  5674. },
  5675. openDefMylist: function() {
  5676. return function(e) {
  5677. if (isMetaKey(e)) { return; }
  5678. prevent(e);
  5679. WatchController.showDeflist();
  5680. scrollToVideoExplorer();
  5681. };
  5682. },
  5683. openMylist: function(id) {
  5684. return function(e) {
  5685. if (isMetaKey(e)) { return; }
  5686. prevent(e);
  5687. WatchController.showMylist(id);
  5688. scrollToVideoExplorer();
  5689. };
  5690. },
  5691. openNicoSearch: function(word, type) {
  5692. return function(e) {
  5693. if (isMetaKey(e)) { return; }
  5694. if (WatchController.isZeroWatch()) {
  5695. prevent(e);
  5696. WatchController.nicoSearch(word, type);
  5697. scrollToVideoExplorer();
  5698. }
  5699. };
  5700. },
  5701. seekVideo: function(vpos) {
  5702. return function(e) {
  5703. if (isMetaKey(e)) { return; }
  5704. prevent(e);
  5705. WatchController.vpos(vpos);
  5706. };
  5707. },
  5708. showLargeThumbnail: function(url) {
  5709. return function() {
  5710. WatchController.showLargeThumbnail(url);
  5711. };
  5712. },
  5713. commentPanelContextMenu: function() {
  5714. return function(a) {
  5715. a.preventDefault(); a.stopPropagation();
  5716. var c = this.commentListModel.getComment(this.parseResNo(jQuery(a.currentTarget)));
  5717. var WatchApp = null, WatchController = null;
  5718. if (c) {
  5719. var
  5720. $d = this.CommentContextMenu.$contextMenuContainer,
  5721. $e = jQuery("#playerCommentPanel"),
  5722. left = this.$commentTableHeaderOuter.position().left,
  5723. top = a.pageY - $e.offset().top,
  5724. f = Math.min($e.offset().top + $e.outerHeight(), jQuery(window).scrollTop() + jQuery(window).outerHeight());
  5725. if (f < a.pageY + $d.outerHeight()) top -= a.pageY + $d.outerHeight() - f;
  5726. this.CommentContextMenu.show(c, left, top);
  5727. }
  5728. };
  5729. }
  5730. };
  5731. var Deferred = {
  5732. wait: function(msec) {
  5733. return function() {
  5734. var args = Array.prototype.slice.call(arguments, 0);
  5735. var d = new $.Deferred();
  5736. setTimeout(function() {
  5737. d.resolve.apply(d, args);
  5738. }, msec);
  5739. return d.promise();
  5740. };
  5741. }
  5742. };
  5743.  
  5744.  
  5745. var self = {
  5746. Cache: Cache,
  5747. Closure: Closure,
  5748. Deferred: Deferred,
  5749. Browser: Browser,
  5750. here: function(func) { // えせヒアドキュメント
  5751. return func.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  5752. }
  5753. };
  5754. return self;
  5755. })();
  5756. window.WatchItLater.util = Util;
  5757.  
  5758. var NicoNews = (function() {
  5759. var WatchApp = null, watch = null, $ = null, WatchJsApi = null, initialized = false;
  5760. var $button = null, $history = null, $ul = null, deteru = {}, $textMarquee, $textMarqueeInner;
  5761. var isHover = false;
  5762.  
  5763. function onNewsUpdate(news) {
  5764. var type = news.data.type, $current = null,
  5765. newsText = $textMarqueeInner.find('.categoryOuter:last').text() +
  5766. $textMarqueeInner.find('.item .title, .item .header, .item .bannertext, .item .text').text(),
  5767. newsHref = $textMarqueeInner.find('a').attr('href');
  5768. if (deteru[newsHref]) {
  5769. $current = deteru[newsHref].remove();
  5770. } else {
  5771. $current = deteru[newsHref] = makeTopic(newsText, newsHref, type);
  5772. }
  5773. $ul.append($current);
  5774. $current.show(200, scrollToBottom);
  5775. }
  5776. function makeTopic(title, url, type) {
  5777. return $([
  5778. '<li style="display: none;">',
  5779. '<a href="', url , '" target="_blank" class="', type, ' title="', escape(title),'">', title, '</a>',
  5780. '</li>',
  5781. ''].join(''));
  5782. }
  5783. function scrollToBottom() {
  5784. if (!isHover) {
  5785. $history.animate({scrollTop: $('.newsHistory ul').innerHeight()}, 200);
  5786. }
  5787. }
  5788.  
  5789. var self = {
  5790. initialize: function(w) {
  5791. WatchApp = w.WatchApp;
  5792. if (!WatchApp || initialized) { return; }
  5793. watch = WatchApp.ns.init;
  5794. $ = w.$;
  5795. WatchJsApi = w.WatchJsApi;
  5796. $textMarquee = $('#textMarquee');
  5797. $textMarqueeInner = $textMarquee.find('.textMarqueeInner');
  5798.  
  5799. watch.TextMarqueeInitializer.textMarqueeViewController.scheduler.addEventListener(
  5800. 'schedule',
  5801. onNewsUpdate);
  5802.  
  5803. $button = $('<button class="openNewsHistory" title="ニコニコニュースの履歴を開く">▲</button>');
  5804. $history = $('<div class="newsHistory" style="display: none;"><ul></ul></div>');
  5805. $history.hover(
  5806. function() { isHover = true; },
  5807. function() { isHover = false; }
  5808. );
  5809. $ul = $history.find('ul');
  5810. $button.click(function() { self.toggle(); });
  5811.  
  5812. $textMarquee.append($button).append($history);
  5813. initialized = true;
  5814. },
  5815. open: function() {
  5816. $history.show(200, function() {
  5817. scrollToBottom();
  5818. WatchApp.ns.util.WindowUtil.scrollFitMinimum('.newsHistory', 200);
  5819. });
  5820. },
  5821. close: function() {
  5822. $history.hide(200);
  5823. isHover = false;
  5824. },
  5825. toggle: function() {
  5826. if ($history.is(':visible')) {
  5827. $button.text('▲');
  5828. this.close();
  5829. } else {
  5830. $button.text('▼');
  5831. this.open();
  5832. }
  5833. }
  5834. };
  5835. return self;
  5836. })();
  5837.  
  5838.  
  5839.  
  5840. /**
  5841. * マイリストや検索API互換形式のデータを返すやつ
  5842. */
  5843. var DummyMylist = function() { this.initialize.apply(this, arguments); };
  5844. DummyMylist.prototype = {
  5845. banner: '',
  5846. id: '-100',
  5847. sort: '4',
  5848. isDeflist: -1,
  5849. isWatchngCountFull: false,
  5850. isWatchngThisMylist: false,
  5851. itemCount: 0,
  5852. items: [],
  5853. rawData: {},
  5854. page: 1,
  5855. perPage: 32,
  5856. type: 2, // 2: MYLIST_VIDEO
  5857. //
  5858. // ver130726より新規追加 defineGetterのほうがいいかも
  5859.  
  5860. status: 'ok',
  5861. name: '',
  5862. description: '',
  5863. user_id: '',
  5864. user_nickname: 'ニコニコ動画',
  5865. default_sort: '1',
  5866. is_watching_this_mylist: false,
  5867. is_watching_count_full: false,
  5868. list: [],
  5869. // ここまで
  5870. initialize: function(param) {
  5871. this.rawData = {
  5872. status: 'ok',
  5873. list: [],
  5874. name: '総合ランキング',
  5875. description: '',
  5876. is_watching_count_full: false,
  5877. is_watching_this_mylist: false,
  5878. user_nickname: '',
  5879. user_id: '',
  5880. sort: '1'
  5881. };
  5882. this._baseCreateTime = Date.now();//new Date();
  5883. this.rawData.user_nickname = param.user_nickname || WatchController.getMyNick();
  5884. this.rawData.user_id = param.user_id || WatchController.getMyUserId();
  5885. this.rawData.name = param.name || this.rawData.name;
  5886. this.rawData.description = param.description || '';
  5887.  
  5888. this.type = param.type || WatchApp.ns.components.videoexplorer.model.ContentType.MYLIST_VIDEO;
  5889. this.sort = this.rawData.sort = param.sort || this.sort;
  5890. this.id = param.id || '-100';
  5891.  
  5892. this.status = this.rawData.status;
  5893. this.list = this.rawData.list;
  5894. this.name = this.rawData.name;
  5895. this.description = this.rawData.description;
  5896. this.default_sort = this.rawData.sort || this.sort;
  5897. this.user_nickname = this.rawData.user_nickname || this.user_nickname;
  5898. this.user_id = this.rawData.user_id;
  5899.  
  5900.  
  5901. },
  5902. setName: function(name) {
  5903. this.rawData.name = name;
  5904. },
  5905. getName: function() {
  5906. return this.rawData.name || '';
  5907. },
  5908. setPage: function(page) {
  5909. this.page = page;
  5910. this.items = this.rawData.list.slice(page * this.perPage - this.perPage, page * this.perPage);
  5911. },
  5912. push: function(item) {
  5913. if (!item.create_time) {
  5914. var tm = this._baseCreateTime - 60000 * this.itemCount;
  5915. item.create_time = tm;
  5916. }
  5917. this.rawData.list.push(item);
  5918. this.itemCount = this.rawData.list.length;
  5919. this.setPage(this.page);
  5920. },
  5921. unshift: function(item) {
  5922. if (!item.create_time) {
  5923. var tm = this._baseCreateTime + 60000 * this.itemCount;
  5924. item.create_time = tm;
  5925. }
  5926. this.rawData.list.unshift(item);
  5927. this.itemCount = this.rawData.list.length;
  5928. this.setPage(this.page);
  5929. },
  5930. slice: function(b, e) {
  5931. this.rawData.list = this.rawData.list.slice(b, e);
  5932. this.itemCount = this.rawData.list.length;
  5933. this.setPage(this.page);
  5934. },
  5935. sortItem: function(sortId, force) {
  5936. sortId = parseInt(sortId, 10);
  5937. if (!!!force && (sortId < 0 || sortId === parseInt(this.sort, 10)) ) { return; }
  5938. var sortKey = ([
  5939. 'create_time', 'create_time',
  5940. 'mylist_comment', 'mylist_comment',
  5941. 'title', 'title',
  5942. 'first_retrieve', 'first_retrieve',
  5943. 'view_counter', 'view_counter',
  5944. 'thread_update_time', 'thread_update_time',
  5945. 'num_res', 'num_res',
  5946. 'mylist_counter', 'mylist_counter',
  5947. 'length_seconds', 'length_seconds'
  5948. ])[sortId],
  5949. order = (sortId % 2 === 0) ? 'asc' : 'desc';
  5950.  
  5951. if (!sortKey) { return; }
  5952. var compare= {
  5953. asc: function(a, b) { return (a[sortKey] > b[sortKey] ) ? 1 : -1; },
  5954. desc: function(a, b) { return (a[sortKey] < b[sortKey] ) ? 1 : -1; },
  5955. iasc: function(a, b) { return (a[sortKey]*1 > b[sortKey]*1) ? 1 : -1; },
  5956. idesc: function(a, b) { return (a[sortKey]*1 < b[sortKey]*1) ? 1 : -1; }
  5957. };
  5958. // 偶数がascで奇数がdescかと思ったら特に統一されてなかった
  5959. if (
  5960. sortKey === 'first_retrieve' ||
  5961. sortKey === 'thread_update_time'
  5962. ) {
  5963. order = (sortId % 2 === 1) ? 'asc' : 'desc';
  5964. } else
  5965. // 数値系は偶数がdesc
  5966. if (sortKey === 'view_counter' ||
  5967. sortKey === 'num_res' ||
  5968. sortKey === 'mylist_counter' ||
  5969. sortKey === 'length_seconds'
  5970. ) {
  5971. order = (sortId % 2 === 1) ? 'iasc' : 'idesc';
  5972. }
  5973. this.sort = this.rawData.sort = sortId.toString();
  5974. this.rawData.list.sort(compare[order]);
  5975. this.items = this.rawData.list.slice(0, 32);
  5976. this.list = this.rawData.list.slice(0);
  5977. }
  5978. };
  5979.  
  5980. var DummyMylistVideo = function() { this.initialize.apply(this, arguments); };
  5981. DummyMylistVideo.prototype = {
  5982. id: 0,
  5983. title: '',
  5984. length: 0,
  5985. view_counter: 0,
  5986. num_res: 0,
  5987. mylist_counter: 0,
  5988. description_short: '',
  5989. first_retrieve: null,
  5990. thumbnail_url: null,
  5991. mylist_comment: '',
  5992. create_time: null,
  5993. type: 0, //'video',
  5994. _info: {},
  5995.  
  5996.  
  5997. initialize: function(info) {
  5998. this._info = info._info || this;
  5999. this.id = info.id;
  6000. this.length = info.length;
  6001. this.mylist_counter = info.mylist_counter || 0;
  6002. this.view_counter = info.view_counter || 0;
  6003. this.num_res = info.num_res || 0;
  6004. this.first_retrieve = info.first_retrieve || '2000-01-01 00:00:00';
  6005. this.create_time = info.create_time || null;
  6006. this.thumbnail_url = info.thumbnail_url || 'http://res.nimg.jp/img/common/video_deleted_ja-jp.jpg' /* 「視聴できません」 */;
  6007. this.title = info.title || '';
  6008. this.type = info.type || 'video';
  6009. this.description_short = info.description_short;
  6010. this.length = info.length || '00:00';
  6011. this.length_seconds = parseInt(info.length_seconds || 0, 10);
  6012. this.mylist_comment = info.mylist_comment || '';
  6013. this.type = info.type || WatchApp.ns.components.videoexplorer.model.ContentItemType.VIDEO;
  6014.  
  6015. if (this.length_seconds === 0 && this.length && this.length.indexOf(':') >= 0) {
  6016. var sp = this.length.split(':');
  6017. this.length_seconds = sp[0] * 60 + sp[1] * 1;
  6018. } else
  6019. if (this.length === '00:00' && this.length_seconds > 0) {
  6020. this.length = parseInt(this.length_seconds / 60, 10) + ':' + (this.length_seconds % 60);
  6021. }
  6022.  
  6023. if (typeof info.is_middle_thumbnail !== 'boolean') {
  6024. if (this.thumbnail_url.indexOf('.M') >= 0) {
  6025. this.thumbnail_url = this.thumbnail_url.replace(/\.M$/, '');
  6026. this.is_middle_thumbnail = true;
  6027. } else
  6028. if (this.thumbnail_url.indexOf('.M') < 0 &&
  6029. this.id.indexOf('sm') === 0) {
  6030. var threshold = 23608629, // .Mのついた最小ID?
  6031. _id = _.parseInt(this.id.substr(2));
  6032. if (_id >= threshold) {
  6033. this.is_middle_thumbnail = true;
  6034. }
  6035. }
  6036. }
  6037. },
  6038. getType: function() { return this.type; },
  6039. getInfo: function() { return this; }, // 手抜き
  6040. getName: function() { return this.title; },
  6041. getId: function() { return this.id; },
  6042. getDescription: function() { return this.description_short; },
  6043.  
  6044.  
  6045. length_seconds: 0, // TODO:
  6046. thread_update_time: '2000-01-01 00:00:00' // TODO: 「コメントが新しい順でソート」に必要?
  6047. };
  6048.  
  6049. // 参考:
  6050. // http://looooooooop.blog35.fc2.com/blog-entry-1146.html
  6051. // http://toxy.hatenablog.jp/entry/2013/07/25/200645
  6052. // http://ch.nicovideo.jp/pita/blomaga/ar297860
  6053. // http://search.nicovideo.jp/docs/api/ma9.html
  6054. var NewNicoSearch = function() { this.initialize.apply(this, arguments); };
  6055. NewNicoSearch.API_BASE_URL = 'http://api.search.nicovideo.jp/api/';
  6056. NewNicoSearch.PAGE_BASE_URL = 'http://search.nicovideo.jp/video/';
  6057. NewNicoSearch.prototype = {
  6058. _u: '', // 24h, 1w, 1m, ft 期間指定
  6059. _ftfrom: '', // YYYY-MM-DD
  6060. _ftto: '', // YYYY-MM-DD
  6061. _l: '', // short long
  6062. _m: false, // true=音楽ダウンロード
  6063. _sort: '', // last_comment_time, last_comment_time_asc,
  6064. // view_counter, view_counter_asc,
  6065. // comment_counter, comment_counter_asc,
  6066. // mylist_counter, mylist_counter_asc,
  6067. // upload_time, upload_time_asc,
  6068. // length_seconds, length_seconds_asc
  6069. _size: 32, // 一ページの件数 maxは100
  6070. _issuer: 'watch-it-later',
  6071. _base_url: NewNicoSearch.API_BASE_URL,
  6072. initialize: function(params) {
  6073.  
  6074. },
  6075. load: function(params, callback) {
  6076. var url = this._base_url;
  6077. var data = {};
  6078. data.query = params.query || 'Qwatch';
  6079. data.service = params.service || ['video']; // video video_tag
  6080. data.search = params.search || ['title', 'tags', 'description'];
  6081. data.join = params.join || [
  6082. // TODO:投稿者IDを取得する方法がないか?
  6083. 'cmsid', 'title', 'description', 'thumbnail_url', 'start_time',
  6084. 'view_counter', 'comment_counter', 'mylist_counter', 'length_seconds', 'last_res_body'
  6085. // 'user_id', 'channel_id', 'main_community_id', 'ss_adlut'
  6086. ];
  6087. data.filters = params.filters || [{}];
  6088. data.sort_by = params.sort_by || 'start_time';
  6089. data.order = params.order || 'desc';
  6090. data.timeout = params.timeout || 10000;
  6091. data.issuer = params.issuer || 'watch-it-later';
  6092. data.reason = params.reason || 'video-explorer'; // 'watchItLater';
  6093. data.size = params.size || 32;
  6094. data.from = params.from || 0;
  6095.  
  6096. if (params.sort_by === '_hot') { // 人気順ソートのパラメータ
  6097. data.hot_field = params.hot_field;
  6098. data.hot_from = params.hot_from;
  6099. data.hot_to = params.hot_to;
  6100. }
  6101.  
  6102. var cache_key = JSON.stringify({url: url, data: data}), cache = Util.Cache.get(cache_key);
  6103. if (cache) {
  6104. setTimeout(function() { callback(null, cache); }, 0);
  6105. return;
  6106. }
  6107.  
  6108. $.ajax({
  6109. url: url,
  6110. type: 'POST',
  6111. data: JSON.stringify(data),
  6112. timeout: 30000,
  6113. complete: function(result) {
  6114. console.log('result', result);
  6115. if (result.status !== 200) {
  6116. callback('fail', 'HTTP status:' + result.status);
  6117. return;
  6118. }
  6119. var data;
  6120. try {
  6121. var lines = result.responseText.split('\n'), head = JSON.parse(lines[0]);
  6122. if (head.values[0].total > 0) {
  6123. data = [head];
  6124. for (var i = 1, len = lines.length; i < len - 1; i++) {
  6125. data.push(JSON.parse(lines[i]));
  6126. }
  6127. } else {
  6128. data = [head, JSON.parse(lines[1]), {type: 'hits', values: []}, JSON.parse(lines[2])];
  6129. }
  6130. Util.Cache.set(cache_key, data);
  6131. } catch(e) {
  6132. console.log('Exception: ', e, result);
  6133. callback('fail', 'JSON syntax');
  6134. return;
  6135. }
  6136. callback(null, data);
  6137. },
  6138. error: function(req, status, thrown) {
  6139. if (status === 'parsererror') {
  6140. return;
  6141. }
  6142. console.log('%c ajax error: ' + status, 'background: red', arguments);
  6143. callback('fail', status);
  6144. }
  6145. });
  6146. }
  6147. };
  6148.  
  6149.  
  6150.  
  6151. /**
  6152. * niconico新検索の検索結果を既存の検索API互換形式に変換して返すやつ
  6153. */
  6154. var NewNicoSearchWrapper = function() { this.initialize.apply(this, arguments); };
  6155. NewNicoSearchWrapper.prototype = {
  6156. _search: null,
  6157. sortTable: {f: 'start_time', v: 'view_counter', r: 'comment_counter', m: 'mylist_counter', l: 'length_seconds',
  6158. '_hot': '_hot', // 人気が高い順
  6159. '_explore': '_explore', // 新着優先
  6160. '_popular': '_popular' // 並び順指定なし
  6161. },
  6162. initialize: function(params) {
  6163. this._search = params.search;
  6164. },
  6165. _buildSearchQuery: function(params) {
  6166. var query = {filters: []};
  6167. var sortTable = this.sortTable;
  6168. query.query = params.searchWord;
  6169. query.search = params.searchType === 'tag' ? ['tags'] : ['tags', 'title', 'description'];
  6170. query.sort_by = params.sort && sortTable[params.sort] ? sortTable[params.sort] : 'last_comment_time';
  6171. query.order = params.order === 'd' ? 'desc' : 'asc';
  6172. query.size = params.size || 32;
  6173. query.from = params.page ? Math.max(parseInt(params.page, 10) - 1, 0) * query.size : 0;
  6174.  
  6175. var n = new Date();
  6176. var now = n.getTime();
  6177. switch (params.u) {
  6178. case '1h':
  6179. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 1 * 60 * 60 * 1000)));
  6180. break;
  6181. case '24h': case '1d':
  6182. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 24 * 60 * 60 * 1000)));
  6183. break;
  6184. case '1w': case '7d':
  6185. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 7 * 24 * 60 * 60 * 1000)));
  6186. break;
  6187. case '1m':
  6188. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 30 * 24 * 60 * 60 * 1000)));
  6189. break;
  6190. case '3m':
  6191. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 90 * 24 * 60 * 60 * 1000)));
  6192. break;
  6193. case '6m':
  6194. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 180 * 24 * 60 * 60 * 1000)));
  6195. break;
  6196. default:
  6197. break;
  6198. }
  6199.  
  6200. if (query.sort_by === '_hot') {
  6201. // 人気が高い順ソート
  6202. (function() {
  6203. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6204. query.hot_field = 'mylist_counter';
  6205. query.hot_from = format(new Date(now - 1 * 24 * 60 * 60 * 1000));
  6206. query.hot_to = format(n);
  6207.  
  6208. query.order = 'desc';
  6209. })();
  6210. }
  6211.  
  6212. if (typeof params.userId === 'string' && params.userId.match(/^\d+$/)) {
  6213. query.filters.push({type: 'equal', field: 'user_id', value: params.userId});
  6214. }
  6215. if (typeof params.channelId === 'string' && params.channelId.match(/^\d+$/)) {
  6216. query.filters.push({type: 'equal', field: 'channel_id', value: params.channelId});
  6217. }
  6218.  
  6219. if (params.l === 'short') { // 5分以内
  6220. query.filters.push(this._buildLengthSecondsRangeFilter(0, 60 * 5));
  6221. } else
  6222. if (params.l === 'long' ) { // 20分以上
  6223. query.filters.push(this._buildLengthSecondsRangeFilter(60 * 20));
  6224. }
  6225.  
  6226. if (params.m === true) { // 音楽ダウンロード
  6227. query.filters.push({type: 'equal', field: 'music_download', value: true});
  6228. }
  6229.  
  6230. // TODO: これの調査 → {field: 'ss_adult', type: 'equal', value: false}
  6231.  
  6232. return query;
  6233. },
  6234. _buildStartTimeRangeFilter: function(from, to) {
  6235. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6236. var range = {field: 'start_time', type: 'range', include_lower: true, };
  6237. range.from = format(from);
  6238. if (to) range.to = format(to);
  6239. return range;
  6240. },
  6241. _buildLengthSecondsRangeFilter: function(from, to) {
  6242. var range = {field: 'length_seconds', type: 'range'};
  6243. if (to) { // xxx ~ xxx
  6244. range.from = Math.min(from, to);
  6245. range.to = Math.max(from, to);
  6246. range.include_lower = range.include_upper = true;
  6247. } else { // xxx以上
  6248. range.from = from;
  6249. range.include_lower = true;
  6250. }
  6251. return range;
  6252. },
  6253. load: function(params, callback) {
  6254. var query = this._buildSearchQuery(params);
  6255. this._search.load(query, $.proxy(function(err, result) {
  6256. this.onLoad(err, result, params, query, callback);
  6257. }, this));
  6258. },
  6259. onLoad: function(err, result, params, query, callback) {
  6260. if (err) {
  6261. console.log('load fail', err, result);
  6262. callback('fail', {message: '通信に失敗しました1'});
  6263. return;
  6264. }
  6265. var searchResult;
  6266. searchResult = {
  6267. status: 'ok',
  6268. count: result[0].values[0].total,
  6269. page: params.page,
  6270. list: []
  6271. };
  6272. var pushItems = function(items) {
  6273. var len = items.length;
  6274. for (var i = 0; i < len; i++) {
  6275. var item = items[i], description = item.description ? item.description.replace(/<.*?>/g, '') : '';
  6276.  
  6277. item.id = item.cmsid;
  6278. if (item.thumbnail_url.indexOf('.M') >= 0) {
  6279. item.thumbnail_url = item.thumbnail_url.replace(/\.M$/, '');
  6280. item.is_middle_thumbnail = true;
  6281. } else
  6282. if (item.thumbnail_url.indexOf('.M') < 0 &&
  6283. item.id.indexOf('sm') === 0) {
  6284. var threshold = 23608629, // .Mのついた最小ID?
  6285. _id = _.parseInt(item.id.substr(2));
  6286. if (_id >= threshold) {
  6287. item.is_middle_thumbnail = true;
  6288. }
  6289. }
  6290.  
  6291. searchResult.list.push({
  6292. id: item.cmsid,
  6293. type: 0, // 0 = VIDEO,
  6294. length: item.length_seconds ?
  6295. Math.floor(item.length_seconds / 60) + ':' + (item.length_seconds % 60 + 100).toString().substr(1) : '',
  6296. mylist_counter: item.mylist_counter,
  6297. view_counter: item.view_counter,
  6298. num_res: item.comment_counter,
  6299. first_retrieve: item.start_time,
  6300. create_time: item.start_time,
  6301. thumbnail_url: item.thumbnail_url,
  6302. title: item.title,
  6303. description_short: description.substr(0, 150),
  6304. description_full: description,
  6305. length_seconds: item.length_seconds,
  6306. last_res_body: item.last_res_body,
  6307. is_middle_thumbnail: item.is_middle_thumbnail
  6308. // channel_id: item.channel_id,
  6309. // main_community_id: item.main_community_id
  6310. });
  6311. }
  6312. };
  6313. for (var i = 1; i < result.length; i++) {
  6314. if (result[i].type === 'hits' && result[i].endofstream) { break; }
  6315. if (result[i].type === 'hits' && result[i].values) {
  6316. pushItems(result[i].values);
  6317. }
  6318. }
  6319. callback(null, searchResult);
  6320. }
  6321. };
  6322.  
  6323. // sug.search.nicovideo.jpはリアルタイムの入力補完用? で、関連タグはhttp://api.search.nicovideo.jp/api/tag/ っぽい
  6324. var NicoSearchSuggest = function() { this.initialize.apply(this, arguments); };
  6325. NicoSearchSuggest.API_BASE_URL = 'http://sug.search.nicovideo.jp/'; //'/suggestion/complete';
  6326. NicoSearchSuggest.prototype = {
  6327. _base_url: NicoSearchSuggest.API_BASE_URL,
  6328. initialize: function(params) {
  6329. },
  6330. load: function(word, callback) {
  6331. if (typeof word !== 'string' || word.length <= 0) {
  6332. throw new Error('wordが設定されてない!');
  6333. }
  6334. var url = this._base_url + 'suggestion/complete',
  6335. cache_key = JSON.stringify({url: url, word: word}),
  6336. cache_time = 60 * 1000 * 1,
  6337. cache = Util.Cache.get(cache_key);
  6338. if (cache) {
  6339. setTimeout(function() { callback(null, cache); }, 0);
  6340. return;
  6341. }
  6342. $.ajax({
  6343. url: url,
  6344. type: 'POST',
  6345. data: word,
  6346. timeout: 30000,
  6347. complete: function(result) {
  6348. if (result.status !== 200) {
  6349. callback('fail', 'HTTP status:' + result.status);
  6350. return;
  6351. }
  6352. var data;
  6353. try {
  6354. data = JSON.parse(result.responseText);
  6355. } catch(e) {
  6356. console.log('Exception: ', e, result);
  6357. callback('fail', 'JSON syntax');
  6358. }
  6359. Util.Cache.set(cache_key, data, cache_time);
  6360. callback(null, data);
  6361. },
  6362. error: function(req, status, thrown) {
  6363. if (status === 'parsererror') {
  6364. return;
  6365. }
  6366. callback('fail', status);
  6367. }
  6368. });
  6369. }
  6370. };
  6371.  
  6372. var NicoSearchRelatedTag = function() { this.initialize.apply(this, arguments); };
  6373. NicoSearchRelatedTag.API_BASE_URL = 'http://api.search.nicovideo.jp/';
  6374. NicoSearchRelatedTag.prototype = {
  6375. _base_url: NicoSearchRelatedTag.API_BASE_URL,
  6376. initialize: function(params) {
  6377. },
  6378. load: function(word, callback) {
  6379. var url = this._base_url + 'api/tag/',
  6380. cache_key = JSON.stringify({url: url, word: word}),
  6381. cache_time = 60 * 1000 * 10,
  6382. cache = Util.Cache.get(cache_key);
  6383. if (cache) {
  6384. setTimeout(function() { callback(null, cache); }, 0);
  6385. return;
  6386. }
  6387. var query = {query: word, service: ['tag_video'], from: 0, size: 100, timeout: 10000, reason: 'user'};
  6388. $.ajax({
  6389. url: url,
  6390. type: 'POST',
  6391. data: JSON.stringify(query),
  6392. timeout: 30000,
  6393. complete: function(result) {
  6394. if (result.status !== 200) {
  6395. callback('fail', 'HTTP status:' + result.status);
  6396. return;
  6397. }
  6398. var data;
  6399. try {
  6400. var lines = result.responseText.split('\n');
  6401. data = JSON.parse(lines[0]);
  6402. } catch(e) {
  6403. console.log('Exception: ', e, result);
  6404. callback('fail', 'JSON syntax');
  6405. return;
  6406. }
  6407. Util.Cache.set(cache_key, data, cache_time);
  6408. callback(null, data);
  6409. },
  6410. error: function(req, status, thrown) {
  6411. if (status === 'parsererror') {
  6412. return;
  6413. }
  6414. callback('fail', status);
  6415. }
  6416. });
  6417. }
  6418. };
  6419.  
  6420.  
  6421. var VideoInfoLoader = function() { this.initialize.apply(this, arguments); };
  6422. VideoInfoLoader.BASE_URL = "http://riapi.nicovideo.jp/api/search/tag";
  6423. VideoInfoLoader.prototype = {
  6424. initialize: function(params) {
  6425. },
  6426. load: function(id, callback) {
  6427. var def = new $.Deferred();
  6428.  
  6429. var cache_key = JSON.stringify({'VideoInfoLoaderCache': id}), cacheData = Util.Cache.get(cache_key);
  6430. if (cacheData) {
  6431. return def.resolve(cacheData);
  6432. }
  6433.  
  6434. if (id.toString().match(/^\d+$/)) { // watchId
  6435. WatchApp.ns.init.PlaylistInitializer.videoInfoAPILoader.load(
  6436. [id],
  6437. function(err, resp) {
  6438. if (err !== null) {
  6439. return def.reject({message: '通信に失敗しました(1)', status: 'fail'});
  6440. }
  6441. if (resp.items && resp.items[id] && resp.items[id].id) {
  6442. if (typeof callback === 'function') { callback(null, resp.items[id]); }
  6443. return def.resolve(Util.Cache.set(cache_key, resp.items[id]));
  6444. }
  6445. err = {message: '動画が見つかりませんでした(1): ' + id, status: 'fail'};
  6446. if (typeof callback === 'function') { callback(err, null); }
  6447. return def.reject(err);
  6448. }
  6449. );
  6450. return def.promise();
  6451. }
  6452.  
  6453. // タグ検索APIの「もしかして: xxx」を使って動画情報を取得する
  6454. WatchApp.ns.util.HTTPUtil.loadXDomainAPI({ // videoId
  6455. url: VideoInfoLoader.BASE_URL,
  6456. type: 'GET',
  6457. dataType: 'json',
  6458. xhrFields: {
  6459. withCredentials: true
  6460. },
  6461. data: {
  6462. words: 'watch/' + id,
  6463. sort: 'f',
  6464. order: 'd',
  6465. page: '1',
  6466. mode: 'watch'
  6467. },
  6468. success: function(result) {
  6469. if (result.suggest_video && result.suggest_video.id) {
  6470. if (typeof callback === 'function') { callback(null, result.suggest_video); }
  6471. def.resolve(Util.Cache.set(cache_key, result.suggest_video));
  6472. } else {
  6473. var err = {message: '動画が見つかりませんでした(2): ' + id, status: 'fail'};
  6474. if (typeof callback === 'function') { callback(err, null); }
  6475. def.reject(err);
  6476. }
  6477. },
  6478. error: function(resp) {
  6479. var err = {message: '通信に失敗しました(2)', status: 'fail'};
  6480. if (typeof callback === 'function') { callback(err, null); }
  6481. def.reject(err);
  6482. }
  6483. });
  6484. return def.promise();
  6485. }
  6486. };
  6487. WatchItLater.VideoInfoLoader = new VideoInfoLoader({});
  6488.  
  6489. var RelatedVideo = function() { this.initialize.apply(this, arguments); }
  6490. RelatedVideo.prototype = {
  6491. initialize: function(params) {
  6492. },
  6493. load: function(watchId) {
  6494. var def = new $.Deferred;
  6495. window.WatchApp.ns.init.VideoExplorerInitializer.relatedVideoAPILoader.load(
  6496. {'video_id': watchId},
  6497. function(err, result) {
  6498. if (err !== null) {
  6499. return def.reject({message: '通信に失敗しました(1)', status: 'fail', err: err});
  6500. }
  6501. return def.resolve(result);
  6502. }
  6503. );
  6504. return def.promise();
  6505. }
  6506. };
  6507. WatchItLater.RelatedVideo = new RelatedVideo({});
  6508.  
  6509. /**
  6510. * 動画視聴履歴をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  6511. */
  6512. var VideoWatchHistory = (function(w, Util){
  6513. function load(callback) {
  6514. var watch, $, myNick, myId, url;
  6515. try{
  6516. watch = w.WatchApp.ns.init;
  6517. $ = w.$; url = '/my/history';
  6518. myNick = WatchController.getMyNick(); myId = WatchController.getMyUserId();
  6519. } catch (e) {
  6520. console.log(e);
  6521. throw { message: 'エラーが発生しました', status: 'fail'};
  6522. }
  6523.  
  6524. var CACHE_KEY = 'videohistory', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6525. if (cacheData) {
  6526. setTimeout(function() {callback(cacheData);}, 0);
  6527. return;
  6528. }
  6529.  
  6530. var result = new DummyMylist({
  6531. id: '-1',
  6532. sort: '1',
  6533. name: myNick + 'の視聴履歴',
  6534. user_id: myId,
  6535. user_name: 'ニコニコ動画'
  6536. });
  6537. GM_xmlhttpRequest({
  6538. url: url,
  6539. onload: function(resp) {
  6540. var $dom = $(resp.responseText), $list = $dom.find('#historyList');
  6541. $list.find('.outer').each(function() {
  6542. var
  6543. $item = $(this), $meta = $item.find('.metadata'), $title = $item.find('.section h5 a'),
  6544. id = $title.attr('href').split('/').reverse()[0], title = $title.text(),
  6545. duration = $item.find('.videoTime').text(),
  6546. viewCnt = $meta.find('.play') .text().split(':')[1].replace(/,/g, ''),
  6547. resCnt = $meta.find('.comment').text().split(':')[1].replace(/,/g, ''),
  6548. mylistCnt = $meta.find('.mylist') .text().split(':')[1].replace(/,/g, ''),
  6549. postedAt = '20' + $meta.find('.posttime').text().replace(/(年|月)/g, '-').replace(/(日| *投稿)/g, ''),
  6550. thumbnail = $item.find('.thumbContainer a .video').attr('src');
  6551.  
  6552. var item = new DummyMylistVideo({
  6553. id: id,
  6554. length: duration,
  6555. mylist_counter: mylistCnt,
  6556. view_counter: viewCnt,
  6557. num_res: resCnt,
  6558. first_retrieve: postedAt,
  6559. thumbnail_url: thumbnail,
  6560. title: title,
  6561. _info: {first_retrieve: postedAt},
  6562. description_short: $item.find('.section .posttime span').text()
  6563. });
  6564. result.push(item);
  6565. });
  6566. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6567. },
  6568. onerror: function() {
  6569. Popup.alert('視聴履歴の取得に失敗しました');
  6570. }
  6571. });
  6572.  
  6573. }
  6574. var self = {
  6575. load : load
  6576. };
  6577. return self;
  6578. })(w, Util);
  6579.  
  6580.  
  6581.  
  6582. var VideoRecommendations = (function(w, Util){
  6583. var histories = {};
  6584. function request(callback) {
  6585. var watch, $, url, myNick, myId;
  6586. try{
  6587. watch = w.WatchApp.ns.init;
  6588. $ = w.$;
  6589. url = '/recommendations';
  6590. myNick = WatchController.getMyNick();
  6591. myId = WatchController.getMyUserId();
  6592. } catch (e) {
  6593. console.log(e);
  6594. throw { message: 'エラーが発生しました', status: 'fail'};
  6595. }
  6596. var CACHE_KEY = 'recommend', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6597. if (cacheData) {
  6598. setTimeout(function() {callback(cacheData); }, 0);
  6599. return;
  6600. }
  6601.  
  6602. var result = new DummyMylist({
  6603. id: '-2',
  6604. sort: '1',
  6605. name: 'あなたにオススメの動画'
  6606. });
  6607. GM_xmlhttpRequest({
  6608. url: url,
  6609. onload: function(resp) {
  6610. var text = resp.responseText, lines = text.split(/[\r\n]/), found = false, data, i, len;
  6611. for (i = 0, len = lines.length; i < len; i++) {
  6612. var line = lines[i];
  6613. if (line.indexOf('var Nico_RecommendationsParams') >= 0 &&
  6614. lines[i + 5] && lines[i + 5].indexOf('first_data') >= 0) {
  6615. data = JSON.parse(lines[i + 5].replace(/^.*?:/, ''));
  6616. if (data && data.videos) {
  6617. found = true;
  6618. break;
  6619. }
  6620. }
  6621. }
  6622. if (!found) {
  6623. throw { message: '取得に失敗しました', status: 'fail'};
  6624. }
  6625.  
  6626. for (i = 0, len = data.videos.length; i < len; i++) {
  6627. var video = data.videos[i];
  6628. if (histories[video.id]) {
  6629. delete histories[video.id];
  6630. }
  6631. var item = new DummyMylistVideo({
  6632. id: video.id,
  6633. length: video.length,
  6634. mylist_counter: video.mylist_counter,
  6635. view_counter: video.view_counter,
  6636. num_res: video.num_res,
  6637. first_retrieve: video.first_retrieve,
  6638. thumbnail_url: video.thumbnail_url,
  6639. title: video.title_short,
  6640. _info: video,
  6641. description_short: '関連タグ: ' + video.recommend_tag
  6642. });
  6643. histories[video.id] = item;
  6644. }
  6645. for (var v in histories) {
  6646. result.unshift(histories[v]);
  6647. }
  6648. result.slice(0, 128);
  6649. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6650. },
  6651. onerror: function() {
  6652. throw { message: '取得に失敗しました', status: 'fail'};
  6653. }
  6654. });
  6655.  
  6656. }
  6657. function load(callback, param) {
  6658. request(function(result) {
  6659. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6660. result.setPage(viewPage);
  6661. callback(result);
  6662. });
  6663. }
  6664. var self = {
  6665. load : load
  6666. };
  6667. return self;
  6668. })(w, Util);
  6669.  
  6670.  
  6671. var NicorepoVideo = (function(w, Util) {
  6672. if (!window.PlayerApp) return {};
  6673.  
  6674. var CACHE_TIME = 1000 * 60 * 10;
  6675. var WatchApp = w.WatchApp;
  6676.  
  6677. var getNicorepoTitle = function(type, param) {
  6678. var base = '【ニコレポ】';
  6679. if (type === 'all') {
  6680. return base + 'すべての動画';
  6681. } else
  6682. if (type === 'chcom') {
  6683. return base + 'お気に入りチャンネル&コミュニティの動画';
  6684. } else
  6685. if (type === 'mylist') {
  6686. return base + 'お気に入りマイリストの動画';
  6687. } else
  6688. if (type === 'owner') {
  6689. return WatchController.getOwnerName() + 'のニコレポ';
  6690. }
  6691. return base + 'お気に入りユーザーの動画';
  6692. };
  6693.  
  6694. var parseItemList = function($dom) {
  6695. var $list = $dom.find('.timeline');
  6696. return $list.find([
  6697. '.log-user-mylist-add',
  6698. '.log-user-uad-advertise',
  6699. '.log-user-video-upload',
  6700. '.log-user-video-review',
  6701. '.log-mylist-added-video',
  6702. '.log-community-video-upload',
  6703. '.log-user-video-round-number-of-view-counter',
  6704. '.log-user-video-round-number-of-mylist-counter'
  6705. ].join(', '));
  6706. };
  6707.  
  6708. var ownerReg = /\/(community|user|channel)\/((co|ch)?\d+)\??/;
  6709. var parseNicorepoItem = function(src) {
  6710. var
  6711. $item = $(src), $title = $item.find('.log-content .log-target-info a'),
  6712. id = $title.attr('href').split('/').reverse()[0].replace(/\?.*$/, ''), title = $title.text(),
  6713. duration = '--:--',
  6714. viewCnt = '-',
  6715. resCnt = '-',
  6716. mylistCnt = '-',
  6717. postedAt = WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', new Date($item.find('.log-footer-date time').attr('datetime'))),
  6718. thumbnail = $item.find('.log-target-thumbnail .video').attr('data-src'),
  6719. description_short = $.trim($item.find('.log-body').text()).replace(/(しました|されました)。/g, ''),
  6720. $owner = $item.find('.author-user, .author-community'),
  6721. ownerPage = $owner.attr('href'),
  6722. ownerMatch = ownerReg.exec(ownerPage),
  6723. ownerName = $owner.text(),
  6724. ownerId = (ownerMatch !== null && ownerMatch.length >= 3) ? ownerMatch[2] : null,
  6725. ownerIcon = $item.find('.log-author img').attr('data-src'),
  6726. mylistComment = $item.find('.log-content .log-subdetails').text().trim()
  6727. ;
  6728.  
  6729. $item.removeClass('log').removeClass('passive').removeClass('first');
  6730. if (src.className === 'log-mylist-added-video') {
  6731. ownerName = $item.find('.log-body a:first').text();
  6732. ownerPage = $item.find('.log-body a:last').attr('href');
  6733. }
  6734.  
  6735. var item = new DummyMylistVideo({
  6736. id: id,
  6737. length: duration,
  6738. mylist_counter: mylistCnt,
  6739. view_counter: viewCnt,
  6740. num_res: resCnt,
  6741. first_retrieve: postedAt,
  6742. thumbnail_url: thumbnail,
  6743. mylist_comment: mylistComment,
  6744. title: title,
  6745. _info: {
  6746. first_retrieve: postedAt,
  6747. nicorepo_className: src.className,
  6748. nicorepo_log: [window._.escape(description_short)],
  6749. nicorepo_owner: {
  6750. id: ownerId,
  6751. icon: ownerIcon,
  6752. page: ownerPage,
  6753. name: ownerName
  6754. }
  6755. },
  6756. description_short: description_short
  6757. });
  6758. return item;
  6759. };
  6760.  
  6761. var loadPage = function(baseUrl, result, nextLink, type) {
  6762. var def = new $.Deferred();
  6763. if (nextLink === null) {
  6764. return def.resolve(baseUrl, result, null, null);
  6765. }
  6766. var url = baseUrl;
  6767. if (type === 'offset') {
  6768. url += nextLink ? ('&offset=' + nextLink) : '';
  6769. } else {
  6770. url += nextLink ? ('&last_timeline=' + nextLink) : '';
  6771. }
  6772. console.log('load Url=', url);
  6773.  
  6774. $.ajax({
  6775. url: url,
  6776. timeout: 30000
  6777. }).then(
  6778. function(resp) {
  6779. var $dom = $(resp),
  6780. $nextPageLink = $dom.find('.next-page-link'),
  6781. hasNextPage = $nextPageLink.length > 0;
  6782.  
  6783. parseItemList($dom).each(function() {
  6784. result.push(parseNicorepoItem(this));
  6785. });
  6786.  
  6787. var nextLinkReg = /(last_timeline|offset)=(\d+)/;
  6788. if (hasNextPage) {
  6789. var href = $nextPageLink.attr('href');
  6790. if (nextLinkReg.test(href)) {
  6791. def.resolve(baseUrl, result, RegExp.$2, RegExp.$1);
  6792. } else {
  6793. def.resolve(baseUrl, result, null, null);
  6794. }
  6795. } else {
  6796. def.resolve(baseUrl, result, null, null);
  6797. }
  6798. },
  6799. function() {
  6800. def.reject();
  6801. });
  6802.  
  6803. return def.promise();
  6804. };
  6805.  
  6806. var pipeRequest = function(baseUrl, result, maxPages, callback) {
  6807. var def = new $.Deferred(), p = def.promise();
  6808.  
  6809. for (var i = maxPages; i >= 0; i--) {
  6810. p = p.then(loadPage);
  6811. if (i > 0) p = p.then(Util.Deferred.wait(300));
  6812. }
  6813.  
  6814. p.then(
  6815. function() {
  6816. var uniq = {}, uniq_items = [];
  6817. for (var i = result.rawData.list.length - 1; i >= 0; i--) {
  6818. var item = result.rawData.list[i], id = item.id, mc = item.mylist_comment;
  6819. if (uniq[id + mc]) {
  6820. uniq[id + mc]._info.nicorepo_log.push(item.first_retrieve + ' ' + item._info.nicorepo_log[0].replace(/^.*?さん(の|が)動画(が|を) ?/, ''));
  6821. } else {
  6822. uniq[id + mc] = item;
  6823. }
  6824. }
  6825. for (var v in uniq) {
  6826. uniq_items.unshift(uniq[v]);
  6827. }
  6828. result.rawData.list = uniq_items;
  6829. callback(result);
  6830. }
  6831. );
  6832. def.resolve(baseUrl, result, '', '');
  6833.  
  6834. };
  6835.  
  6836. var request = function(param) {
  6837. var url, nickname, userId, type, baseUrl;
  6838. var def = new $.Deferred();
  6839. try {
  6840. url = '';
  6841. nickname = param.nickname || window.WatchController.getMyNick();
  6842. userId = param.userId || window.WatchController.getMyUserId();
  6843. type = param.type || 'user';
  6844. baseUrl = '/my/top/' + type + '?innerPage=1&mode=next_page';
  6845. if (param.userId) {
  6846. baseUrl = '/user/'+ param.userId +'/top?innerPage=1&mode=next_page';
  6847. }
  6848. } catch (e) {
  6849. console.log(e);
  6850. return def.reject({message: 'エラーが発生しました', status: 'fail'});
  6851. }
  6852.  
  6853. var cacheData = Util.Cache.get(baseUrl);
  6854. if (cacheData) {
  6855. return def.resolve(cacheData);
  6856. }
  6857.  
  6858. var
  6859. result = new DummyMylist({
  6860. id: '-10',
  6861. sort: '1',
  6862. default_sort: '1',
  6863. name: getNicorepoTitle(type, param),
  6864. user_id: type === 'owner' ? WatchController.getOwnerId() : userId,
  6865. user_nickname: type === 'owner' ? WatchController.getOwnerName() : nickname
  6866. });
  6867.  
  6868. pipeRequest(baseUrl, result, 2, function(result) {
  6869. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  6870. });
  6871.  
  6872. return def.promise();
  6873. };
  6874.  
  6875. var load = function(callback, param) {
  6876. return request(param)
  6877. .then(function(result) {
  6878. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6879. result.sortItem(param.sort || 1, true);
  6880. result.setPage(viewPage);
  6881. if (typeof result === 'function') { callback(result); }
  6882. return this.done(result);
  6883. }, function() {
  6884. return this.fail({message: 'ニコレポの取得に失敗しました', status: 'fail'});
  6885. });
  6886. };
  6887.  
  6888. var self = {
  6889. load: load,
  6890. REPO_ALL: -10,
  6891. REPO_USER: -11,
  6892. REPO_CHCOM: -12,
  6893. REPO_MYLIST: -13,
  6894. REPO_OWNER: -14,
  6895. loadAll: function(callback, p) {
  6896. p = p || {};
  6897. p.type = 'all';
  6898. return self.load(callback, p);
  6899. },
  6900. loadUser: function(callback, p) {
  6901. p = p || {};
  6902. p.type = 'user';
  6903. return self.load(callback, p);
  6904. },
  6905. loadChCom: function(callback, p) {
  6906. p = p || {};
  6907. p.type = 'chcom';
  6908. return self.load(callback, p);
  6909. },
  6910. loadMylist: function(callback, p) {
  6911. p = p || {};
  6912. p.type = 'mylist';
  6913. return self.load(callback, p);
  6914. },
  6915. loadOwner: function(callback, p) {
  6916. p = p || {};
  6917. p.type = 'owner';
  6918. p.userId = WatchController.getOwnerId();
  6919. return self.load(callback, p);
  6920. }
  6921. };
  6922. WatchItLater.NicorepoVideo = self;
  6923.  
  6924. return self;
  6925. })(w, Util);
  6926.  
  6927.  
  6928.  
  6929. /**
  6930. * ランキングのRSSをマイリストAPIと互換のある形式に変換することで、ダミーマイリストとして表示してしまう作戦
  6931. */
  6932. var VideoRanking = (function(w, Util) {
  6933. if (!window.PlayerApp) return {};
  6934. var $ = w.jQuery;
  6935.  
  6936. var
  6937. genreIdTable = {
  6938. all: -100,
  6939. g_ent2: -110,
  6940. ent: -111,
  6941. music: -112,
  6942. sing: -113,
  6943. play: -114,
  6944. dance: -115,
  6945. vocaloid: -116,
  6946. nicoindies: -117,
  6947. g_life2: -120,
  6948. animal: -121,
  6949. cooking: -122,
  6950. nature: -123,
  6951. travel: -124,
  6952. sport: -125,
  6953. lecture: -126,
  6954. drive: -127,
  6955. history: -128,
  6956. g_politics: -130,
  6957. g_tech: -140,
  6958. science: -141,
  6959. tech: -142,
  6960. handcraft: -143,
  6961. make: -144,
  6962. g_culture2: -150,
  6963. anime: -151,
  6964. game: -152,
  6965. toho: -153,
  6966. imas: -154,
  6967. radio: -155,
  6968. draw: -156,
  6969. g_other: -160,
  6970. are: -161,
  6971. diary: -162,
  6972. other: -163
  6973. // r18: -170
  6974. },
  6975. genreNameTable = {
  6976. all: 'カテゴリ合算',
  6977. g_ent2: 'エンタメ・音楽',
  6978. ent: 'エンターテイメント',
  6979. music: '音楽',
  6980. sing: '歌ってみた',
  6981. play: '演奏してみた',
  6982. dance: '踊ってみた',
  6983. vocaloid: 'VOCALOID',
  6984. nicoindies: 'ニコニコインディーズ',
  6985. g_life2: '生活・一般・スポ',
  6986. animal: '動物',
  6987. cooking: '料理',
  6988. nature: '自然',
  6989. travel: '旅行',
  6990. sport: 'スポーツ',
  6991. lecture: 'ニコニコ動画講座',
  6992. drive: '車載動画',
  6993. history: '歴史',
  6994. g_politics: '政治',
  6995. g_tech: '科学・技術',
  6996. science: '科学',
  6997. tech: 'ニコニコ技術部',
  6998. handcraft: 'ニコニコ手芸部',
  6999. make: '作ってみた',
  7000. g_culture2: 'アニメ・ゲーム・絵',
  7001. anime: 'アニメ',
  7002. game: 'ゲーム',
  7003. toho: '東方',
  7004. imas: 'アイドルマスター',
  7005. radio: 'ラジオ',
  7006. draw: '描いてみた',
  7007. g_other: 'その他',
  7008. are: '例のアレ',
  7009. diary: '日記',
  7010. other: 'その他',
  7011. r18: 'R-18'
  7012. },
  7013. termIdTable = {
  7014. 'hourly': 0,
  7015. 'daily': -1000,
  7016. 'weekly': -2000,
  7017. 'monthly': -3000,
  7018. 'total': -4000
  7019. },
  7020. termNameTable = {
  7021. 'hourly': '(毎時)',
  7022. 'daily': '(24時間)',
  7023. 'weekly': '(週間)',
  7024. 'monthly': '(月間)',
  7025. 'total': '(合計)'
  7026. },
  7027. idTermTable = {},
  7028. idGenreTable = {}
  7029. ;
  7030. if (conf.debugMode) { genreIdTable['r18'] = -170; }
  7031. for (var genre in genreIdTable) { idGenreTable[genreIdTable[genre]] = genre;}
  7032. for (var term in termIdTable ) { idTermTable [termIdTable [term ]] = term; }
  7033.  
  7034. /**
  7035. * ニコニコ動画ランキングのRSSをマイリストAPI互換のデータ形式に変換
  7036. */
  7037. var rss2mylist = function(xml) {
  7038. var
  7039. $x = $(xml),
  7040. title = $x.find('channel title:first').text(),
  7041. $items = $x.find('channel item'),
  7042. result = new DummyMylist({
  7043. name: title,
  7044. id: '-100'
  7045. });
  7046. $items.each(function() {
  7047. var video = parseRssItem($(this));
  7048. var item = new DummyMylistVideo({
  7049. id: video.id,
  7050. length: video.duration,
  7051. mylist_counter: video.mylistCnt,
  7052. view_counter: video.viewCnt,
  7053. num_res: video.resCnt,
  7054. first_retrieve: video.postedAt,
  7055. thumbnail_url: video.thumbnail,
  7056. title: video.title.replace(/^.*?第(\d+)位/, '第000$1位').replace(/^第\d+(\d{3})位/, '第$1位'),
  7057. _info: {first_retrieve: video.postedAt},
  7058. description_short: video.description.substring(0, 50)
  7059. });
  7060. result.push(item);
  7061. });
  7062. return result;
  7063. };
  7064.  
  7065. var parseRssItem = function($item) {
  7066. var
  7067. desc_cdata = $item.find('description').text(),
  7068. $desc = $('<div>' + desc_cdata + '</div>');
  7069. return {
  7070. title : $item.find('title') .text(),
  7071. id : $item.find('guid') .text().split('/').reverse()[0],
  7072. duration : $desc.find('.nico-info-length') .text(),
  7073. viewCnt : $desc.find('.nico-info-total-view') .text().replace(/,/g, ''),
  7074. resCnt : $desc.find('.nico-info-total-res') .text().replace(/,/g, ''),
  7075. mylistCnt : $desc.find('.nico-info-total-mylist').text().replace(/,/g, ''),
  7076. postedAt : $desc.find('.nico-info-date') .text()
  7077. .replace(/(年|月)/g, '-')
  7078. .replace(/:/g, ':')
  7079. .replace(/(日)/g, ''),
  7080. description : $desc.find('.nico-description') .text(),
  7081. thumbnail : $desc.find('.nico-thumbnail img').attr('src')
  7082. };
  7083. };
  7084.  
  7085. var pipeRequest = function(baseUrl, result, page, maxPage) {
  7086. var def = new $.Deferred(), p = def.promise();
  7087.  
  7088. var getPipe = function(result, url, page) {
  7089. return function() {
  7090. console.log('load RSS', url, page);
  7091. return $.ajax({
  7092. url: url,
  7093. timeout: 30000,
  7094. data: {rss: '2.0', lang: 'ja-jp', page: page},
  7095. beforeSend: function(xhr) {
  7096. xhr.setRequestHeader('X-Requested-With', {toString: function(){ return ''; }});
  7097. }
  7098. }).then(function(resp) {
  7099. var res = rss2mylist(resp);
  7100. for (var i = 0, len = res.rawData.list.length; i < len; i++) {
  7101. result.push(res.rawData.list[i]);
  7102. }
  7103. });
  7104. };
  7105. };
  7106.  
  7107. for (var i = page; i <= maxPage; i++) {
  7108. p = p.then(getPipe(result, baseUrl, i));
  7109. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  7110. }
  7111.  
  7112. def.resolve();
  7113.  
  7114. return p;
  7115. };
  7116.  
  7117. var CACHE_TIME = 1000 * 60 * 30;
  7118. var request = function(baseUrl, page, maxPage) {
  7119. var def = new $.Deferred();
  7120. var cacheData = Util.Cache.get(baseUrl);
  7121. if (cacheData) {
  7122. return def.resolve(cacheData);
  7123. }
  7124.  
  7125. var result = new DummyMylist({
  7126. name: '総合ランキング',
  7127. id: '-100'
  7128. });
  7129.  
  7130. pipeRequest(baseUrl, result, page, maxPage).then(
  7131. function() {
  7132. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  7133. },
  7134. function() {
  7135. def.reject();
  7136. });
  7137.  
  7138. return def.promise();
  7139. };
  7140.  
  7141. var parseParam = function(param) {
  7142. var
  7143. id = parseInt(param.id || -100, 10),
  7144. genreId = getGenreId(id),
  7145. termId = getTermId(id),
  7146. category = idGenreTable[genreId] || 'all', type = 'fav', term = 'daily', lang= 'ja-jp',
  7147. viewPage = (param && typeof param.page === 'number') ? param.page : 1,
  7148. genreName = genreNameTable[category] || genreNameTable['all'],
  7149. maxRssPage = 1, sort = param.sort || '4';
  7150.  
  7151. term = idTermTable[termId] || idTermTable[0];
  7152. maxRssPage = (category === 'all' && term !== 'hourly') ? 3 : 1;
  7153. return {
  7154. genreId: genreId,
  7155. genreName: genreName,
  7156. category: category,
  7157. type: type,
  7158. term: term,
  7159. lang: lang,
  7160. viewPage: viewPage,
  7161. sort: sort,
  7162. maxRssPage: maxRssPage,
  7163. baseUrl:
  7164. '/ranking/'+ type +'/'+ term + '/'+ category //+'?rss=2.0&lang=' + lang
  7165. };
  7166. };
  7167.  
  7168. var loadRanking = function(param) {
  7169. var p = parseParam(param);
  7170. return request(p.baseUrl, 1, p.maxRssPage)
  7171. .then(function(result) {
  7172. result.name = p.genreName;
  7173. result.setPage(p.viewPage);
  7174.  
  7175. this.done(result);
  7176. });
  7177. };
  7178.  
  7179. var load = function(onload, param) {
  7180. var p = parseParam(param);
  7181. return request(p.baseUrl, 1, p.maxRssPage)
  7182. .then(function(result) {
  7183. result.name = p.genreName;
  7184. result.setPage(p.viewPage);
  7185.  
  7186. if (typeof onload === 'function') {
  7187. onload(result);
  7188. }
  7189. return this.done(result);
  7190. }, function() {
  7191. return this.fail({message: 'ランキングの取得に失敗しました', status: 'fail'});
  7192. });
  7193. };
  7194.  
  7195. var getTermId = function(t) {
  7196. if (typeof t === 'string') {
  7197. return termIdTable[t] || 0;
  7198. } else
  7199. if (typeof t === 'number'){
  7200. return (t - (t % 1000)) % 10000;
  7201. }
  7202. return 0;
  7203. };
  7204. var getGenreId = function(g, term) {
  7205. if (typeof g === 'string') {
  7206. return (genreIdTable[g] || 0) + getTermId(term);
  7207. } else
  7208. if (typeof g === 'number'){
  7209. return g % 1000;
  7210. } else {
  7211. return genreIdTable;
  7212. }
  7213. };
  7214. var getGenreName = function(g) {
  7215. if (typeof g === 'number' || (typeof g === 'string' && g.match(/^-?[0-9]+$/))) {
  7216. g = g % 1000;
  7217. var genre = idGenreTable[g];
  7218. return genreNameTable[genre];
  7219. } else
  7220. if (typeof g === 'string') {
  7221. return genreNameTable[g];
  7222. } else {
  7223. return genreNameTable;
  7224. }
  7225. };
  7226. var getCategory = function(g) {
  7227. if (typeof g === 'number') {
  7228. g = g % 1000;
  7229. return idGenreTable[g - (g %10)];
  7230. } else
  7231. if (typeof g === 'string') {
  7232. g = genreIdTable[g];
  7233. return idGenreTable[g - (g %10)];
  7234. } else {
  7235. return 'all';
  7236. }
  7237. };
  7238.  
  7239. var self = {
  7240. load: load,
  7241. getTermId: getTermId,
  7242. getGenreId: getGenreId,
  7243. getGenreName: getGenreName,
  7244. getCategory: getCategory
  7245. };
  7246. WatchItLater.VideoRanking = self;
  7247. return self;
  7248. })(w, Util);
  7249.  
  7250.  
  7251.  
  7252. /**
  7253. * チャンネル動画一覧をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  7254. */
  7255. var ChannelVideoList = (function(w, Util){
  7256. if (!window.PlayerApp) return {};
  7257. var
  7258. CACHE_TIME = 1000 * 60 * 1, MAX_PAGE = 3,
  7259. getPipe = function(baseUrl, result, page) {
  7260. return function(hasPage) {
  7261. var def = new $.Deferred();
  7262. if (!hasPage) return def.resolve(hasPage);
  7263. var url = baseUrl + '?page=' + page;
  7264. console.log('load page', url);
  7265.  
  7266. $.ajax({url: url, timeout: 30000}).then(function(resp) {
  7267. var hasNextPage = parseItems(resp, result);
  7268. def.resolve(hasNextPage);
  7269. }, function(err) {
  7270. def.reject(err);
  7271. });
  7272. return def.promise();
  7273. };
  7274. },
  7275. pipeRequest = function(baseUrl, result) {
  7276. var def = new $.Deferred(), p = def.promise();
  7277.  
  7278. var maxPage = MAX_PAGE;
  7279. for (var i = 1; i <= maxPage; i++) {
  7280. p = p.then(getPipe(baseUrl, result, i));
  7281. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  7282. }
  7283.  
  7284. p.then(function() {
  7285. this.done(result);
  7286. });
  7287.  
  7288. def.resolve(true);
  7289. return p;
  7290. },
  7291. load = function(callback, params) {
  7292. var myId, url, id, ownerName, def = new $.Deferred();
  7293. try{
  7294. id = params.id.toString().replace(/^ch/, '');
  7295. ownerName = params.ownerName;
  7296. url = 'http://ch.nicovideo.jp/channel/ch'+ id + '/video';
  7297. myId = WatchController.getMyUserId();
  7298. } catch (e) {
  7299. console.log(e);
  7300. throw { message: 'エラーが発生しました', status: 'fail'};
  7301. }
  7302.  
  7303. var CACHE_KEY = 'ch-' + id, cacheData = Util.Cache.get(CACHE_KEY);
  7304. if (cacheData) {
  7305. if (typeof callback === 'function') {
  7306. setTimeout(function() { callback(cacheData); } , 0);
  7307. }
  7308. return def.resolve(cacheData);
  7309. }
  7310.  
  7311.  
  7312. var result = new DummyMylist({
  7313. id: 'ch' + id,
  7314. sort: '1',
  7315. name: ownerName + 'の動画',
  7316. user_id: myId,
  7317. user_name: 'ニコニコ動画'
  7318. });
  7319.  
  7320. pipeRequest(url, result).then(function() {
  7321. Util.Cache.set(CACHE_KEY, result, CACHE_TIME);
  7322. if (typeof callback === 'function') callback(result);
  7323. def.resolve(result);
  7324. });
  7325.  
  7326. return def.promise();
  7327. },
  7328. parseItems = function(html, result) {
  7329. var $html = $(html), $list = $html.find('.contents_list .item');
  7330. var hasNextPage = false;
  7331. $list.each(function() {
  7332. var $item = $(this);
  7333. var id = $item.find('.title a').attr('href').split('/').reverse()[0];
  7334. var $counts = $item.find('.counts'), first_retrieve = $item.find('.time var').attr('title');
  7335. w.$item = $item;
  7336. result.push(new DummyMylistVideo({
  7337. id: id,
  7338. length: $item.find('.length').text(),
  7339. mylist_counter: $counts.find('.mylist var').text().split(',').join(''),
  7340. view_counter: $counts.find('.view var').text().split(',').join(''),
  7341. num_res: $counts.find('.comment var').text().split(',').join(''),
  7342. first_retrieve: first_retrieve,
  7343. thumbnail_url: $item.find('.lazyimage').data('original'),
  7344. title: $item.find('.title').text().trim(),
  7345. _info: {first_retrieve: first_retrieve, is_channel: true},
  7346. description_short: $item.find('.description').text().trim()
  7347. }));
  7348. });
  7349. if ($html.find('.pager .next:not(.disabled)').length > 0) {
  7350. hasNextPage = true;
  7351. }
  7352. return hasNextPage;
  7353. },
  7354. loadOwnerVideo = function(callback) {
  7355. if (!WatchController.isChannelVideo()) {
  7356. throw {message: 'チャンネル情報の取得に失敗しました', status: 'fail'};
  7357. }
  7358. var params = {
  7359. id: WatchController.getOwnerId(),
  7360. ownerName: WatchController.getOwnerName()
  7361. };
  7362. var def = new $.Deferred();
  7363. load(callback, params).then(function(result) {
  7364. if (typeof callback === 'function') callback(result);
  7365. def.resolve(result);
  7366. }, function() {
  7367. def.reject({message: 'チャンネル動画の取得に失敗しました', status: 'fail'});
  7368. });
  7369. return def.promise();
  7370. };
  7371.  
  7372. var self = {
  7373. load: load,
  7374. loadOwnerVideo: loadOwnerVideo
  7375. };
  7376. WatchItLater.ChannelVideo = self;
  7377. return self;
  7378. })(w, Util);
  7379.  
  7380.  
  7381.  
  7382. var niconicodoRedirect = function() {
  7383. // www.nicovideo.jp/stampを watchにパラメータを中継するためのクッションページとして使う。
  7384. // watchと同じドメインならどこでもいいけど、ここはDBアクセスもなさそうな静的ページため採用
  7385. var hash = location.hash.toString();
  7386. if (hash.indexOf('#json={') !== 0) {
  7387. return;
  7388. }
  7389. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  7390.  
  7391. LocationHashParser.initialize();
  7392. var blankWatchId = 'sm20353707';
  7393. var redirectWatchId = LocationHashParser.getValue('redirectWatchId');
  7394. // 見た目が残念なので消す
  7395. document.body.innerHTML = '';
  7396.  
  7397. window.sessionStorage.setItem('watchItLater_redirectedHash', location.hash);
  7398. var redirectTo = '/watch/' + (redirectWatchId ? redirectWatchId : blankWatchId);
  7399. location.replace(redirectTo);
  7400. };
  7401.  
  7402.  
  7403. /**
  7404. * GINZAwatch上でのあれこれ
  7405. * 無計画に増築中
  7406. *
  7407. * watch.jsを解析すればわかる
  7408. *
  7409. */
  7410. var ZeroFunc = function(w) { // Zero Watch
  7411. var
  7412. video_id = '', watch_id = '',
  7413. // WatchApp = w.WatchApp, WatchJsApi = w.WatchJsApi,
  7414. isTouchActive = false,
  7415. console = conf.debugMode ? window.console : {log: _.noop, trace: _.noop, time: _.noop, timeEnd: _.noop},
  7416. watch = WatchApp.ns.init,
  7417. watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  7418. if (!WatchApp.mixin) {
  7419. WatchApp.mixin = _.mixin;
  7420. }
  7421.  
  7422. console.log('%cGinza', 'background: lightgreen;');
  7423.  
  7424. /**
  7425. * ゆっくり再生(スロー再生)メニュー
  7426. */
  7427. var Yukkuri = (function($, conf, w) {
  7428. var self, $content = null, $button = null, timer = null, cnt = 0, isActive = false;
  7429.  
  7430. function createDom() {
  7431. $content = $('<div id="yukkuriPanel" />');
  7432. $button = $('<button>yu</button>').addClass('yukkuriButton').attr({title: 'ゆっくり(スロー再生)'});
  7433. $button.click(function() {
  7434. toggleActive();
  7435. });
  7436. $content.append($button);
  7437.  
  7438. $('body').append($content);
  7439. }
  7440.  
  7441. function show() {
  7442. if ($content === null) {
  7443. createDom();
  7444. }
  7445. updateView();
  7446. $content.show();
  7447. }
  7448. function hide() {
  7449. $content.hide();
  7450. }
  7451. function updateView() {
  7452. $button.toggleClass('active', isActive);
  7453. }
  7454.  
  7455. function start() {
  7456. if (timer !== null) {
  7457. clearInterval(timer);
  7458. }
  7459. isActive = true;
  7460. updateView();
  7461. timer = setInterval(function() {
  7462. var v = cnt++ % 4;
  7463. if (v === 0) {
  7464. WatchController.play();
  7465. } else
  7466. if (v === 1) {
  7467. WatchController.pause();
  7468. }
  7469. }, 20);
  7470. }
  7471. function stop() {
  7472. if (timer !== null) {
  7473. clearInterval(timer);
  7474. timer = null;
  7475. }
  7476. isActive = false;
  7477. updateView();
  7478. WatchController.pause();
  7479. }
  7480.  
  7481. function toggleActive() {
  7482. if (isActive) {
  7483. stop();
  7484. } else {
  7485. start();
  7486. }
  7487. return isActive;
  7488. }
  7489.  
  7490. self = {
  7491. show: show,
  7492. hide: hide,
  7493. start: start,
  7494. stop: stop
  7495. };
  7496. return self;
  7497. })($, conf, w);
  7498.  
  7499. function onWindowResizeEnd() {
  7500. setTimeout(function() {
  7501. EventDispatcher.dispatch('onWindowResizeEnd');
  7502. }, 1000);
  7503. }
  7504.  
  7505. /**
  7506. * デフォルトの市場貼付ボタンはなぜかページの一番上までスクロールするという意地悪な仕様だが、
  7507. * こっちはがんばって見やすい位置に調整して開く
  7508. */
  7509. function ichibaSearch(word, shopCode) {
  7510. var wait = 10, opened = false;
  7511. //shopCode = shopCode || 'az'; // az = amazon
  7512. var search = function() {
  7513. if ($('#ichibaConsole').is(':visible')) {
  7514. setTimeout(function() {
  7515. w.WatchApp.ns.util.WindowUtil.scrollFitMinimum('#ichibaConsole', 300);
  7516. }, 1000);
  7517. if (!word) {
  7518. return;
  7519. }
  7520. if ($('#ichiba_search_form_query').is(':visible')) {
  7521. $('#ichiba_search_form_query').val(word);
  7522. w.ichiba.search(shopCode, 0, 'all');
  7523. setTimeout(function() {$('#ichiba_search_form_query').focus();}, 1000);
  7524. } else {
  7525. if (!opened) {
  7526. if(shopCode) { w.ichiba.showRelatedTagItems(shopCode, 0, 'all'); }
  7527. opened = true;
  7528. }
  7529. if (wait-- > 0) setTimeout(search, 1000);
  7530. }
  7531. } else {
  7532. if (wait-- > 0) setTimeout(search, 1000);
  7533. }
  7534. };
  7535. search();
  7536. w.ichiba.showConsole();
  7537. }
  7538. WatchController.ichibaSearch = ichibaSearch;
  7539.  
  7540. function initVideoCounter() {
  7541. var
  7542. playerAreaConnector = watch.PlayerInitializer.playerAreaConnector,
  7543. counter = {mylistCount: 0, viewCount: 0, commentCount: 0},
  7544. blinkItem = function($elm) {
  7545. $elm.removeClass('animateBlink').addClass('blink');
  7546. setTimeout(function() {
  7547. $elm.addClass('animateBlink').removeClass('blink');
  7548. $elm = null;
  7549. }, 500);
  7550. };
  7551. var setVideoCounter = function(watchId, title) {
  7552. var $tpl = $(
  7553. '<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>'
  7554. );
  7555. assignVideoCountToDom($tpl, counter);
  7556.  
  7557. if ((conf.popupViewCounter === 'always') ||
  7558. (conf.popupViewCounter === 'full' && $('body').hasClass('full_with_browser'))
  7559. ) {
  7560. Popup.show(
  7561. $('<div/>')
  7562. .append(
  7563. $('<a/>')
  7564. .text(window._.unescape(title))
  7565. .attr('href', 'http://nico.ms/' + watchId)
  7566. )
  7567. .html() +
  7568. '<br/><span style="margin-left:10px; font-size: 90%;">'+ $tpl.html() + '</span>'
  7569. );
  7570. }
  7571. $('#fullScreenToggleContainer').html([
  7572. '<img class="ownerIcon" src="', WatchController.getOwnerIcon(), '">',
  7573. '<div class="title">', title, '</div>',
  7574. '<p class="postedAt">',$('.videoPostedAt:last').text(), '</p>',
  7575. '<p class="videoCounter">', $tpl.html(), '</p>',
  7576. ''].join(''))
  7577. .toggleClass('favorite', WatchController.isFavoriteOwner())
  7578. .find('img').attr('title', WatchController.getOwnerName());
  7579.  
  7580. if (conf.headerViewCounter) {
  7581. var vc = $('#videoCounter');
  7582. if (vc.length < 1) {
  7583. var li = $('<li></li>')[0];
  7584. li.id = 'videoCounter';
  7585. $('#siteHeaderLeftMenu').after(li);
  7586. vc = $('#videoCounter');
  7587. }
  7588. vc.empty().append($tpl);
  7589. }
  7590. };
  7591.  
  7592. playerAreaConnector.addEventListener('onWatchCountUpdated', function(c) {
  7593. var diff = c - counter.viewCount;
  7594. if (diff === 0) return;
  7595. counter.viewCount = c;
  7596. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'viewCount', diff);
  7597. });
  7598. playerAreaConnector.addEventListener('onCommentCountUpdated', function(c) {
  7599. var diff = c - counter.commentCount;
  7600. if (diff === 0) return;
  7601. counter.commentCount = c;
  7602. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'commentCount', diff);
  7603. });
  7604. playerAreaConnector.addEventListener('onMylistCountUpdated', function(c) {
  7605. var diff = c - counter.mylistCount;
  7606. if (diff === 0) return;
  7607. counter.mylistCount = c;
  7608. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'mylistCount', diff);
  7609. });
  7610.  
  7611. EventDispatcher.addEventListener('onWatchInfoReset', function(watchInfoModel){
  7612. counter.mylistCount = watchInfoModel.mylistCount;
  7613. counter.viewCount = watchInfoModel.viewCount;
  7614. counter.commentCount = watchInfoModel.commentCount;
  7615.  
  7616. setVideoCounter(watchInfoModel.v, watchInfoModel.title);
  7617. });
  7618. EventDispatcher.addEventListener('onVideoCountUpdated', function(c, type, diff) {
  7619. var $target = $('.sidePanel .videoInfo, #fullScreenMenuContainer, #videoCounter');
  7620. assignVideoCountToDom($target, c);
  7621. $target.find('.' + type + 'Diff').text(diff).toggleClass('down', diff < 0);
  7622. blinkItem($target.find('.' + type + ', .' + type + 'Diff'));
  7623. });
  7624.  
  7625. } //
  7626.  
  7627. var isFirst = true;
  7628. function onVideoInitialized() {
  7629. watch = WatchApp.ns.init;
  7630. AnchorHoverPopup.hidePopup().updateNow();
  7631. tagv = watch.TagInitializer.tagViewController;
  7632. WatchCounter.add();
  7633.  
  7634. if (isFirst) {
  7635. if (conf.autoPlayIfWindowActive === 'yes' && w.document.hasFocus()) {
  7636. // ウィンドウがアクティブの時だけ自動再生する。 複数タブ開いてるときは便利
  7637. setTimeout(function() { WatchController.play(); }, 2000);
  7638. }
  7639.  
  7640. if (isFirst && conf.commentVisibility !== 'visible') {
  7641. if (conf.commentVisibility === 'hidden') {
  7642. console.log('comment off');
  7643. WatchController.commentVisibility(false);
  7644. } else {
  7645. console.log('last state', conf.lastCommentVisibility);
  7646. WatchController.commentVisibility(conf.lastCommentVisibility === 'visible');
  7647. }
  7648. }
  7649. EventDispatcher.dispatch('onFirstVideoInitialized');
  7650. }
  7651.  
  7652. EventDispatcher.dispatch('onVideoInitialized', isFirst);
  7653. isFirst = false;
  7654. } //
  7655.  
  7656. function onVideoChangeStatusUpdated(isChanging) {
  7657. AnchorHoverPopup.hidePopup();
  7658. if (isChanging) {
  7659. $('.sidePanel .sideVideoInfo').removeClass('show');
  7660. }
  7661. if ((conf.enableAutoPlaybackContinue || conf.debugMode) && watch.PlayerInitializer.noUserOperationController.autoPlaybackModel._isAutoPlayback) {
  7662. watch.PlayerInitializer.noUserOperationController.autoPlaybackModel.setCount(0);
  7663. }
  7664. EventDispatcher.dispatch('onVideoChangeStatusUpdated', isChanging);
  7665. }
  7666.  
  7667. var $sideInfoPanelTemplate = $([
  7668. '<div class="sideVideoInfoInner">',
  7669.  
  7670. '<div class="videoTitleContainer"><h3 class="videoTitle"></h3></div>',
  7671. '<div class="videoOwnerInfoContainer">',
  7672. '<div class="channelIconContainer"><a target="_blank" class="channelIconLink">',
  7673. '<img class="channelIcon"></a>',
  7674. '<span class="channelName">提供: ',
  7675. '<a class="showOtherVideos" target="_blank"><span class="channelNameInner"></span></a></span>',
  7676. '</span>',
  7677. '</div>',
  7678. '<div class="userIconContainer"><a target="_blank" class="userIconLink">',
  7679. '<img class="userIcon"></a>',
  7680. '<span class="userName">投稿者: ',
  7681. '<span class="userNameInner notPublic"></span>',
  7682. '<span class="isPublic"><a class="showOtherVideos"><span class="userNameInner"></span></a></span>',
  7683. '</span>',
  7684. '</div>',
  7685. '</div>',
  7686. '<div class="videoInfo">',
  7687. '<span class="videoPostedAt"></span>',
  7688. '<ul class="videoStats">',
  7689. '<li style="position: relative;">再生: <span class="viewCountDiff videoCountDiff"></span><span class="videoCount viewCount"></span></li>',
  7690. '<li style="position: relative;">コメント: <span class="commentCountDiff videoCountDiff"></span><span class="videoCount commentCount"></span></li>',
  7691. '<li style="position: relative;">マイリスト: <span class="mylistCountDiff videoCountDiff"></span><span class="videoCount mylistCount"></span></li>',
  7692. '</ul>',
  7693. '</div>',
  7694.  
  7695.  
  7696. '<div class="videoThumbnailContainer" style="display: none;">',
  7697. '<img class="videoThumbnailImage">',
  7698. '</div>',
  7699. '<div class="videoDetails">',
  7700. '<div class="videoDescription">',
  7701. '<div class="videoDescriptionInner">',
  7702. '</div>',
  7703. '</div>',
  7704. '</div>',
  7705. '</div>',
  7706. ''].join(''));
  7707.  
  7708.  
  7709. // - 左パネル乗っ取る
  7710. function initLeftPanel($, conf, w) {
  7711.  
  7712. var $tab = $([
  7713. '<ul id="leftPanelTabContainer">',
  7714. '<li class="tab ichiba" data-selection="ichiba" >市場</li>',
  7715. '<li class="tab videoInfo" data-selection="videoInfo">情報</li>',
  7716. '</ul>'].join(''));
  7717.  
  7718. var
  7719. $sidePanel = $('<div id="leftPanel" />').addClass('sidePanel'),
  7720. $infoPanel = $('<div/>').attr({'id': 'leftVideoInfo', 'class': 'sideVideoInfo sidePanelInner'}),
  7721. $ichibaPanel = $('<div/>').attr({'id': 'leftIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7722. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel);
  7723. $('#playerTabWrapper').after($sidePanel);
  7724.  
  7725. var
  7726. onTabSelect = function(e) {
  7727. e.preventDefault();
  7728. AnchorHoverPopup.hidePopup();
  7729. var selection = $(e.target).attr('data-selection');
  7730. if (typeof selection === 'string') {
  7731. conf.setValue('lastLeftTab', selection);
  7732. changeTab(selection);
  7733. }
  7734. },
  7735. changeTab = function(selection) {
  7736. $sidePanel.removeClass('videoInfo ichiba').addClass(selection);
  7737. if (selection === 'ichiba') {
  7738. resetIchiba(false);
  7739. }
  7740. },
  7741. lastIchibaVideoId = '',
  7742. resetIchiba = function(force) {
  7743. var videoId = watchInfoModel.id;
  7744. if (lastIchibaVideoId === videoId && !force) {
  7745. return;
  7746. }
  7747. lastIchibaVideoId = videoId;
  7748. resetSideIchibaPanel($ichibaPanel, true);
  7749. },
  7750. resetScroll = function() {
  7751. $(this).animate({scrollTop: 0}, 600);
  7752. };
  7753.  
  7754. $infoPanel .on('dblclick', resetScroll);
  7755. $ichibaPanel.on('dblclick', resetScroll);
  7756.  
  7757. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7758. changeTab(conf.lastLeftTab);
  7759.  
  7760. var refreshPanel = function(isFirst) {
  7761. if (isFirst) { return; }
  7762.  
  7763. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  7764. if ($ichibaPanel.is(':visible')) {
  7765. resetIchiba(true);
  7766. }
  7767. };
  7768. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  7769. refreshPanel();
  7770.  
  7771. } // end of initLeftPanel
  7772.  
  7773. function initRightPanel($, conf, w) {
  7774. var $rightPanel = $('#playerTabWrapper').addClass('sidePanel');
  7775. initRightPanelVerticalTab($rightPanel);
  7776. initRightPanelHorizontalTab($, conf, w);
  7777. var $playerTabWrapper = $rightPanel, wideCss = null;
  7778. var
  7779. createWideCommentPanelCss = function (targetWidth) {
  7780. var px = targetWidth - $rightPanel.outerWidth();
  7781. var elms = [
  7782. '#playerTabWrapper', //'#playerTabWrapper',
  7783. '#commentDefaultHeader',
  7784. '#playerCommentPanel .commentTable',
  7785. '#playerCommentPanel .commentTable .commentTableContainer'
  7786. ];
  7787. var css = [
  7788. 'body.videoExplorer #content.w_adjusted #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7789. 'body:not(.full_with_browser) .w_wide #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7790. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }\n', // 960 + 140
  7791. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }\n\n' // 1186 + 140
  7792. ];
  7793. for (var v in elms) {
  7794. var $e = $(elms[v]), newWidth = $e.width() + px;
  7795. css.push([
  7796. '.w_wide #playerTabWrapper ', elms[v],
  7797. ' , body.videoExplorer #content.w_adjusted ',
  7798. elms[v], '\n{ width: ', newWidth,'px !important; }\n\n'
  7799. ].join(''));
  7800. }
  7801. wideCss = addStyle(css.join(''), 'wideCommentPanelCss');
  7802. console.log(css.join(''));
  7803. },
  7804. toggleWide = function(v) {
  7805. $('#content').toggleClass('w_wide', v);
  7806. EventDispatcher.dispatch('onWindowResizeEnd');
  7807. };
  7808.  
  7809. var wideCommentPanelCss = Util.here(function() {/*
  7810. body.videoExplorer #content.w_adjusted #playerTabWrapper { width: 420px; }
  7811. body:not(.full_with_browser) .w_wide #playerTabWrapper { width: 420px; }
  7812.  
  7813. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }
  7814. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }
  7815.  
  7816. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerTabWrapper,
  7817. body.videoExplorer #content.w_adjusted #playerTabWrapper
  7818. { width: 420px !important; }
  7819.  
  7820. body:not(.full_with_browser) .w_wide #playerTabWrapper #commentDefaultHeader,
  7821. body.videoExplorer #content.w_adjusted #commentDefaultHeader
  7822. { width: 408px !important; }
  7823.  
  7824. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable,
  7825. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable
  7826. { width: 406px !important; }
  7827.  
  7828. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable .commentTableContainer,
  7829. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable .commentTableContainer
  7830. { width: 406px !important; }
  7831. */});
  7832. addStyle(wideCommentPanelCss, 'wideCommentPanelCss');
  7833.  
  7834. EventDispatcher.addEventListener('on.config.wideCommentPanel', toggleWide);
  7835. toggleWide(!!conf.wideCommentPanel);
  7836.  
  7837. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  7838.  
  7839. //EventDispatcher.dispatch('onWindowResizeEnd');
  7840. //createWideCommentPanelCss(420);
  7841.  
  7842. var $div = $([
  7843. '<div id="sharedNgSettingContainer" style="display: none;">NG共有: ',
  7844. '<select id="sharedNgSetting">',
  7845. '<option value="HIGH">高</option>',
  7846. '<option value="MIDDLE">中</option>',
  7847. '<option value="LOW">低</option>',
  7848. '<option value="NONE">無</option>',
  7849. '</select>',
  7850. '</div>',
  7851. ''].join('')), $ngs = $div.find('select');
  7852.  
  7853. $ngs
  7854. .val(watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get().ngScoringFilteringLevel)
  7855. .on('change', function() {
  7856. var val = this.value;
  7857. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({ngScoringFilteringLevel: this.value});
  7858. });
  7859. $('#commentDefaultHeader').append($div);
  7860.  
  7861. EventDispatcher.addEventListener('on.config.enableSharedNgSetting', function(newValue, oldValue) {
  7862. if (newValue) {
  7863. $div.show();
  7864. } else {
  7865. $div.hide();
  7866. }
  7867. });
  7868. if (conf.enableSharedNgSetting) { $div.show(); }
  7869. });
  7870.  
  7871. if (conf.removeCommentPanelHoverEvent) {
  7872. $("#commentDefault").find(".commentTableContainerInner") .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7873. $('#playerCommentPanel .section .commentTable .commentTableContainer') .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7874. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7875. .off('contextmenu')
  7876. .on('contextmenu', '.cell',
  7877. $.proxy(Util.Closure.commentPanelContextMenu(), watch.PlayerInitializer.commentPanelViewController.commentContent)
  7878. );
  7879. }
  7880. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function(isChanging) {
  7881. if (isChanging) {
  7882. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7883. .find('.cell').off();
  7884. }
  7885. });
  7886. } // end initRightPanel
  7887.  
  7888. function initRightPanelHorizontalTab($, conf, w) {
  7889. } //
  7890.  
  7891. function initRightPanelVerticalTab($sidePanel) {
  7892. if (!conf.rightPanelJack) { return; }
  7893.  
  7894. var $tab = $([
  7895. '<ul id="sidePanelTabContainer">',
  7896. '<li class="tab comment" data-selection="w_comment" >コメント</li>',
  7897. '<li class="tab videoInfo" data-selection="w_videoInfo">動画情報</li>',
  7898. '<li class="tab ichiba" data-selection="w_ichiba" >ニコニコ市場</li>',
  7899. '<li class="tab review" data-selection="w_review" >レビュー</li>',
  7900. '</ul>'].join(''));
  7901.  
  7902. var $infoPanel = $('<div/>').attr({'id': 'rightVideoInfo', 'class': 'sideVideoInfo sidePanelInner'});
  7903. var $ichibaPanel = $('<div/>').attr({'id': 'rightIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7904. var $reviewPanel = $('<div/>').attr({'id': 'rightReviewPanel', 'class': 'sideReviewPanel sidePanelInner'});
  7905. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel).append($reviewPanel);
  7906.  
  7907. var
  7908. onTabSelect = function(e) {
  7909. e.preventDefault();
  7910. AnchorHoverPopup.hidePopup();
  7911. var selection = $(e.target).attr('data-selection');
  7912. if (typeof selection === 'string') {
  7913. if (WatchController.isSearchMode()) {
  7914. conf.setValue('lastRightTabInExplorer', selection);
  7915. } else {
  7916. conf.setValue('lastRightTab', selection);
  7917. }
  7918. changeTab(selection);
  7919. }
  7920. },
  7921. $videoReview = $('#videoReview'),
  7922. toggleReview = function(f) {
  7923. if (f) {
  7924. $reviewPanel.append($videoReview);
  7925. } else {
  7926. $('#playerBottomAd').after($videoReview);
  7927. }
  7928. },
  7929. changeTab = function(selection) {
  7930. if ($sidePanel.hasClass('w_review') && selection !== 'w_review') {
  7931. toggleReview(false);
  7932. }
  7933. $sidePanel.removeClass('w_videoInfo w_comment w_ichiba w_review').addClass(selection);
  7934. if (selection === 'w_ichiba') {
  7935. resetIchiba(false);
  7936. } else
  7937. if (selection === 'w_review') {
  7938. toggleReview(true);
  7939. } else
  7940. if (selection === 'w_comment') {
  7941. setTimeout(function() {
  7942. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  7943. }, 500);
  7944. }
  7945. return changeTab;
  7946. },
  7947. lastIchibaVideoId = '', resetIchiba = function(force) {
  7948. var videoId = watchInfoModel.id;
  7949. if (lastIchibaVideoId === videoId && !force) {
  7950. return;
  7951. }
  7952. lastIchibaVideoId = videoId;
  7953. resetSideIchibaPanel($ichibaPanel, true);
  7954. },
  7955. resetScroll = function() {
  7956. $(this).animate({scrollTop: 0}, 600);
  7957. };
  7958.  
  7959. $infoPanel .on('dblclick', resetScroll);
  7960. $ichibaPanel.on('dblclick', resetScroll);
  7961. $reviewPanel.on('dblclick', resetScroll);
  7962.  
  7963. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7964. changeTab(conf.lastRightTab);
  7965.  
  7966. EventDispatcher.addEventListener('onVideoExplorerOpening', function() {
  7967. changeTab('w_comment');
  7968. });
  7969. EventDispatcher.addEventListener('onVideoExplorerClosing', function() {
  7970. changeTab(conf.lastRightTab);
  7971. });
  7972.  
  7973. var onOuterResize = function() {
  7974. var $body = $('body'), $right = $('#playerTabWrapper');
  7975. if (WatchController.isSearchMode() || $body.hasClass('full_with_browser')) { return; }
  7976. var w = $('#external_nicoplayer').outerWidth(), margin = 124;
  7977. w += $right.is(':visible') ? $right.outerWidth() : 0;
  7978. $('#sidePanelTabContainer').toggleClass('left', (window.innerWidth - w - margin < 0));
  7979. };
  7980. EventDispatcher.addEventListener('onWindowResizeEnd', onOuterResize);
  7981. EventDispatcher.addEventListener('onPlayerAlignmentAreaResize', onOuterResize);
  7982.  
  7983. var refreshPanel = function(isFirst) {
  7984.  
  7985. window.setTimeout(function() {
  7986. $sidePanel
  7987. .toggleClass('reviewEmpty', $('#videoReview').find('.stream').length < 1)
  7988. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  7989. }, 2000);
  7990.  
  7991. if (isFirst) { return; }
  7992.  
  7993. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  7994. if ($ichibaPanel.is(':visible')) {
  7995. resetIchiba(true);
  7996. }
  7997. };
  7998. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  7999.  
  8000. refreshPanel();
  8001.  
  8002. } // end of initRightPanelVerticalTab
  8003.  
  8004.  
  8005. function assignVideoCountToDom($tpl, count) {
  8006. var addComma = WatchApp.ns.util.StringUtil.addComma;
  8007. $tpl
  8008. .find('.viewCount' ).text(addComma(count.viewCount )).end()
  8009. .find('.commentCount').text(addComma(count.commentCount)).end()
  8010. .find('.mylistCount' ).text(addComma(count.mylistCount ));
  8011. return $tpl;
  8012. } //
  8013.  
  8014. function sidePanelRefresh($sideInfoPanel, $ichibaPanel, $sidePanel, $template) {
  8015. var isFavorite = WatchController.isFavoriteOwner();
  8016. //var h = $sideInfoPanel.innerHeight() - 100;
  8017.  
  8018. $template.find('.videoTitle').html(watchInfoModel.title);
  8019.  
  8020. assignVideoCountToDom($template, watchInfoModel);
  8021. $template.find('.videoPostedAt').text($('.videoPostedAt:last').text());
  8022.  
  8023. var $videoDescription = $template.find('.videoDescription');
  8024.  
  8025. $videoDescription.find('.videoDescriptionInner').append(create$videoDescription(watchInfoModel.description));
  8026.  
  8027. var $userIconContainer = $template.find('.userIconContainer');
  8028. var $channelIconContainer = $template.find('.channelIconContainer');
  8029.  
  8030. var info = WatchController.getOwnerInfo();
  8031. if (info.type === 'channel') {
  8032. if (info.id && info.id !== '0') {
  8033. $channelIconContainer
  8034. .find('.channelIcon')
  8035. .attr({'src': info.icon}).end()
  8036. .find('.channelIconLink')
  8037. .attr({'href': info.page})
  8038. .on('click', Util.Closure.openVideoOwnersVideo()).end()
  8039. .find('.channelNameInner')
  8040. .text(info.name).end()
  8041. .find('.showOtherVideos')
  8042. .attr({'href': info.page})
  8043. .on('click', Util.Closure.openVideoOwnersVideo());
  8044. }
  8045. $userIconContainer.remove();
  8046. } else {
  8047. if (info.id && info.id !== '0') { // ユーザーが退会してたりすると情報が無いのでチェックしてから
  8048. $userIconContainer
  8049. .find('.userIcon')
  8050. .attr({'src': info.icon}).end()
  8051. .find('.userIconLink')
  8052. .attr({'href': info.page})
  8053. .on('click', Util.Closure.openVideoOwnersNicorepo()).end()
  8054. .find('.userNameInner')
  8055. .text(info.name).end()
  8056. .find('.showOtherVideos')
  8057. .attr({'href': info.page + '/video'})
  8058. .on('click', Util.Closure.openVideoOwnersVideo() ).end()
  8059. .toggleClass('isUserVideoPublic', info.isVideoPublic);
  8060. $channelIconContainer.remove();
  8061. } else {
  8062. $userIconContainer.remove();
  8063. $channelIconContainer.remove();
  8064. }
  8065. }
  8066.  
  8067. $sideInfoPanel.find('*').unbind();
  8068.  
  8069. $sidePanel
  8070. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  8071.  
  8072. $sideInfoPanel
  8073. .empty()
  8074. .scrollTop(0)
  8075. .toggleClass('isFavorite', isFavorite)
  8076. .toggleClass('isChannel', WatchController.isChannelVideo())
  8077. .append($template);
  8078.  
  8079. window.setTimeout(function() {
  8080. $sideInfoPanel.addClass('show');
  8081. $sideInfoPanel = $ichibaPanel = $sidePanel = $template =
  8082. $videoDescription = $userIconContainer =
  8083. $channelIconContainer = null;
  8084. }, 100);
  8085.  
  8086. } // end of sidePanelRefresh
  8087.  
  8088. /**
  8089. * 説明文中の動画リンク類を加工
  8090. */
  8091. function decorateVideoDescriptionLink($description) {
  8092. var watchLinks = [], watchIds = [];
  8093. var videoReg = /\/watch\/((sm|nm|so|)\d+)$/;
  8094. var seigaReg = /seiga\/im(\d+)/;
  8095. $description.find('a').each(function() {
  8096. var url = this.href, text, $this = $(this);
  8097. if (videoReg.test(url)) {
  8098. var watchId = RegExp.$1;
  8099. var $videoLinkContainer = $([
  8100. '<div class="videoLinkContainer"></div>',
  8101. ''].join(''));
  8102. var $nextButton = $([
  8103. '<div class="nextPlayButton" title="次に再生" onclick="WatchItLater.WatchController.insertVideoToPlaylist(\'', watchId, '\')">次に再生</div>',
  8104. ''].join(''));
  8105. $this.after($videoLinkContainer);
  8106. $videoLinkContainer.append($this).append($nextButton);
  8107.  
  8108. watchLinks.push({id: watchId, $target: $videoLinkContainer});
  8109. watchIds.push(watchId);
  8110. } else if (seigaReg.test(url)) {
  8111. var illustId = RegExp.$1;
  8112. var $thumbnail = $([
  8113. '<div class="descriptionThumbnail illust">',
  8114. '<img src="http://lohas.nicoseiga.jp/thumb/',
  8115. illustId,
  8116. 'z" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  8117. '</div>',
  8118. ''].join(''));
  8119. $this.after($thumbnail);
  8120. }
  8121. });
  8122.  
  8123. if (conf.enableDescriptionThumbnail && watchIds.length > 0) {
  8124. var ac = function(s) {
  8125. s = parseInt(s, 10);
  8126. s = s < 1 ? '-' : s;
  8127. return '<span class="count">' + WatchApp.ns.util.StringUtil.addComma(s) + '</span>';
  8128. };
  8129. var onWatchIdInfoReady = function(result) {
  8130. $(watchLinks).each(function(i, watchLink) {
  8131. var id = watchLink.id, $target = watchLink.$target;
  8132. if (result[id]) {
  8133. var info = result[id];
  8134. var $thmb = $([
  8135. '<div class="descriptionThumbnail video">',
  8136. '<img src="', info.thumbnail_url, '" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  8137. '<span class="uploadAt">', info.first_retrieve ,' 投稿</span>',
  8138. '<p>', info.title, ' (', info.length, ')</p>',
  8139. '<div class="counterContainer">',
  8140. '<span class="view">再生: ', ac(info.view_counter) ,'</span> ',
  8141. '<span class="comment">コメ: ', ac(info.num_res) ,'</span> ',
  8142. '<span class="mylist">マイ: ', ac(info.mylist_counter),'</span>',
  8143. '</div>',
  8144. '</div>'].join(''));
  8145. $target.after($thmb);
  8146. }
  8147. $target = watchLink = null;
  8148. });
  8149. watchIds = watchLinks = null;
  8150. };
  8151. var onWatchIdInfoFail = function() {
  8152. watchIds = watchLinks = null;
  8153. };
  8154. window.setTimeout(function() {
  8155. window.WatchItLater.loader.videoArrayAPILoader.load(watchIds).then(onWatchIdInfoReady, onWatchIdInfoFail);
  8156. }, 1000);
  8157. } else {
  8158. watchIds = watchLinks = null;
  8159. }
  8160. $description = null;
  8161. }
  8162.  
  8163. /**
  8164. * 動画説明文のクリックイベント類を割り当てる
  8165. */
  8166. function bindDescriptionEvents($description) {
  8167. $description.on('click', function(e) {
  8168. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8169.  
  8170. var elm = e.target;
  8171. if (elm.tagName !== 'A') { return; }
  8172. if (elm.className === 'otherSite') return;
  8173.  
  8174. var $elm = $(elm);
  8175.  
  8176. if (elm.textContent.indexOf('mylist/') === 0) {
  8177. e.preventDefault(); e.stopPropagation();
  8178. var mylistId = elm.textContent.split('/').reverse()[0];
  8179.  
  8180. WatchController.showMylist(mylistId);
  8181. } else
  8182. if (elm.className === 'seekTime') {
  8183. e.preventDefault(); e.stopPropagation();
  8184. var data = $elm.attr('data-seekTime').split(":"),
  8185. vpos = (data[0] * 60 + parseInt(data[1], 10)) * 1000;
  8186. WatchController.vpos(vpos);
  8187. }
  8188. });
  8189. $description.find('.watch').unbind('click');
  8190. $description = null;
  8191. }
  8192.  
  8193. function create$videoDescription(html) {
  8194. var linkmatch = /<a.*?<\/a>/, links = [], n;
  8195. html = html.split('<br />').join(' <br /> ');
  8196. while ((n = linkmatch.exec(html)) !== null) {
  8197. links.push(n);
  8198. html = html.replace(n, ' <!----> ');
  8199. }
  8200.  
  8201. // (htttp://example.com) -> ( htttp://example.com ) にして、 閉じカッコがリンクされるのを抑止
  8202. html = html.replace(/\((https?:\/\/[\x21-\x3b\x3d-\x7e]+)\)/gi, '( $1 )');
  8203. html = html.replace(/(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi, '<a href="$1" target="_blank" class="otherSite">$1</a>');
  8204. for (var i = 0, len = links.length; i < len; i++) {
  8205. html = html.replace(' <!----> ', links[i]);
  8206. }
  8207. html = html.split(' <br /> ').join('<br />');
  8208. var $description = $('<p class="videoDescription description">' + html + '</p>');
  8209.  
  8210. bindDescriptionEvents($description);
  8211. decorateVideoDescriptionLink($description);
  8212. return $description;
  8213. } //
  8214.  
  8215. function resetSideIchibaPanel($ichibaPanel, force) {
  8216.  
  8217. $ichibaPanel.scrollTop(0).find('*').unbind().empty();
  8218. var $inner = $('<div class="ichibaPanelInner" />');
  8219. var $header = $('<div class="ichibaPanelHeader"><p class="logo">ニコニコ市場出張所</p></div>');
  8220. $inner.append($header);
  8221.  
  8222.  
  8223. var items = [];
  8224. $('#ichibaMain').find('.ichiba_mainitem>div').each(function() {
  8225. var $item = $(this).clone().attr('id', null);
  8226. var $dl = $('<dl class="ichiba_mainitem" />').append($item);
  8227. $item.find('.thumbnail span').css({fontSize: ''});
  8228. // 誤クリックしやすいのでサムネはリンクを外す
  8229. $item.find('.thumbnail a img, .blomagaThumbnail, .blomagaText')
  8230. .parent().attr('href', null).attr('style', null).css({'text-decoration': 'none'});
  8231. $item.find('a').attr('onclick', null);
  8232. items.push($dl);
  8233. $inner.append($dl);
  8234. });
  8235. if (items.length > 0) {
  8236. for (var i = items.length -1; i >= 0; i--) {
  8237. $inner.find('#watch' + i + '_mq').attr('id', null).addClass('ichibaMarquee');
  8238. }
  8239. }
  8240. $inner.find('.nicoru').remove();
  8241.  
  8242. var $footer = $('<div class="ichibaPanelFooter"></div>');
  8243.  
  8244. var $addIchiba = $('<button class="addIchiba">商品を貼る</button>');
  8245. $addIchiba.click(function() {
  8246. AnchorHoverPopup.hidePopup();
  8247. ichibaSearch();
  8248. });
  8249. $footer.append($addIchiba);
  8250.  
  8251. var $reloadIchiba = $('<button class="reloadIchiba">リロード</button>');
  8252. $reloadIchiba.click(function() {
  8253. resetSideIchibaPanel($ichibaPanel, true);
  8254. $ichibaPanel = null;
  8255. });
  8256. $footer.append($reloadIchiba);
  8257.  
  8258. $inner.append($footer);
  8259. $inner.hide();
  8260. $ichibaPanel.append($inner);
  8261. $inner.fadeIn();
  8262. $inner = $header = $footer = $addIchiba = $reloadIchiba = null;
  8263. } //
  8264.  
  8265. function initHidariue() {
  8266. // 再生終了時に勝手にメニューが開閉するのを止める
  8267. window.WatchApp.ns.init.PlayerInitializer.videoendViewController.videoHeaderViewController = {openVideoMenu: function(){}, closeVideoMenu: function() {}};
  8268. var hidariue = null;
  8269. var resetHidariue = function() {
  8270. // var dt = new Date();
  8271. // if (dt.getMonth() < 1 && dt.getDate() <=1) {
  8272. // $('#videoMenuTopList').append('<li style="font-size:50%"> \ │ /<br>  / ̄\   / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄<br>─( ゚ ∀ ゚ )< しんねんしんねん!<br>  \_/   \_________<br> / │ \</li>');
  8273. // }
  8274. if (!conf.hidariue) { return; }
  8275. if (!hidariue) {
  8276. $('#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>');
  8277. hidariue = $('#hidariue')[0];
  8278. }
  8279. hidariue.src = 'http://res.nimg.jp/img/base/head/icon/nico/' +
  8280. (1000 + Math.floor(Math.random() * 1000)).toString().substr(1) + '.gif';
  8281. };
  8282. EventDispatcher.addEventListener('onVideoInitialized', resetHidariue);
  8283. } //
  8284.  
  8285.  
  8286. var VideoExplorerToggleMenu = function(title, titleLink) {
  8287. this.initializeBaseDom(title, titleLink);
  8288. };
  8289. WatchApp.mixin(VideoExplorerToggleMenu.prototype, {
  8290. initializeBaseDom: function(title, titleLink) {
  8291.  
  8292. this._$toggle = $('<li style="display:none;" class="toggleVideoExplorerMenu watchItLaterMenu"></li>');
  8293. this._$menu = $('<li class="slideMenu"><ul></ul></li>');
  8294. this._$list = this._$menu.find('ul');
  8295.  
  8296. var $a = $('<a/>').text(title).attr('href', titleLink);
  8297. this._$toggle.append($a);
  8298.  
  8299. this._initializeToggleEvent();
  8300. this._initializeItemEvent();
  8301. },
  8302. _initializeToggleEvent: function() {
  8303. this._$toggle.on('click', $.proxy(function(e) {
  8304. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8305. e.preventDefault(); e.stopPropagation();
  8306.  
  8307. var isVisible = this._$menu.hasClass('open');
  8308. this._$toggle.addClass('opening');
  8309. this._$menu.toggleClass('open', !isVisible);
  8310.  
  8311. window.setTimeout($.proxy(function() {
  8312. this._$toggle.toggleClass('open', !isVisible);
  8313. this._$toggle.removeClass('opening');
  8314. }, this), 500);
  8315. }, this));
  8316. },
  8317. _initializeItemEvent: function() {
  8318. this._$menu.on('click', function(e) {
  8319. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8320.  
  8321. var elm = e.target;
  8322. if (elm.tagName !== 'A') { return; }
  8323. var $elm = $(elm);
  8324. var type = $elm.attr('data-menu-type');
  8325.  
  8326. if (type === 'mylist') {
  8327. e.preventDefault(); e.stopPropagation();
  8328. var mylistId = $elm.attr('data-mylist-id');
  8329. WatchController.showMylist(mylistId);
  8330. } else
  8331. if (type === 'deflist') {
  8332. e.preventDefault(); e.stopPropagation();
  8333. WatchController.showDeflist();
  8334. } else
  8335. if (type === 'tag') {
  8336. e.preventDefault(); e.stopPropagation();
  8337. var tag = $elm.attr('data-search-tag');
  8338. WatchController.nicoSearch(tag, 'tag');
  8339. }
  8340. });
  8341. },
  8342. attach: function() {
  8343. $('.videoExplorerMenu').find('ul:first li:first').after(this._$menu).after(this._$toggle);
  8344. },
  8345. detach: function() {
  8346. this._$toggle.detach();
  8347. this._$menu.detach();
  8348. },
  8349. add$listItem: function($item) {
  8350. this._$list.append($item);
  8351. },
  8352. addItem: function(name, title, attr) {
  8353. var
  8354. $a = $('<a/>')
  8355. .attr(attr)
  8356. .text(title),
  8357. $li = $('<li/>').addClass(iconType);
  8358. $li.append($a);
  8359. this._$list.append($li);
  8360. return $a;
  8361. },
  8362. addMylistItem: function(name, mylistId, title, iconType) {
  8363. var
  8364. $a = $('<a/>')
  8365. .text(name)
  8366. .attr({
  8367. href: '/mylist/' + mylistId,
  8368. title: title,
  8369. 'data-menu-type': 'mylist',
  8370. 'data-mylist-id': mylistId,
  8371. }),
  8372. $li = $('<li/>').addClass(iconType || '');
  8373. $li.append($a);
  8374. this._$list.append($li);
  8375. return $a;
  8376. },
  8377. show: function() {
  8378. this._$toggle.fadeIn(500);
  8379. }
  8380. });
  8381.  
  8382. WatchItLater.videoExplorerMenu = {};
  8383. WatchItLater.videoExplorerMenu.favMylists = (function() {
  8384. var toggleMenu;
  8385.  
  8386. var initialize = function() {
  8387. initialize = window._.noop;
  8388. console.log('%cinitialize WatchItLater.videoExplorerMenu.favMylists', 'background: lightgreen;');
  8389.  
  8390. toggleMenu = new VideoExplorerToggleMenu('お気に入りマイリスト', '/my/fav/mylist');
  8391.  
  8392. window.WatchItLater.loader.favMylists.load(function(mylists) {
  8393. if (mylists.length < 1) {
  8394. return;
  8395. }
  8396. for (var i = 0, len = mylists.length; i < len; i++) {
  8397. var mylist = mylists[i], lastVideo = mylist.lastVideo, $li = $('<li/>'),
  8398. title = [
  8399. '/mylist/', mylist.id, '\n',
  8400. mylist.description, '\n',
  8401. '最新動画: ', lastVideo.title, '\n',
  8402. '投稿日時: ', lastVideo.postedAt, '\n',
  8403. ''].join('');
  8404. toggleMenu.addMylistItem(
  8405. mylist.name,
  8406. mylist.id,
  8407. title,
  8408. mylist.iconType
  8409. );
  8410. }
  8411. toggleMenu.show();
  8412. });
  8413. };
  8414.  
  8415. return {
  8416. attach: function() {
  8417. initialize();
  8418. toggleMenu.attach();
  8419. },
  8420. detach: function() {
  8421. if (!toggleMenu) {
  8422. return;
  8423. }
  8424. console.log(toggleMenu);
  8425. toggleMenu.detach();
  8426. }
  8427. };
  8428. })();
  8429.  
  8430. WatchItLater.videoExplorerMenu.favTags = (function() {
  8431. var toggleMenu;
  8432.  
  8433. var initialize = function() {
  8434. initialize = window._.noop;
  8435. console.log('%cinitialize WatchItLater.videoExplorerMenu.favTags', 'background: lightgreen;');
  8436.  
  8437. toggleMenu = new VideoExplorerToggleMenu('お気に入りタグ', '/my/fav/tag');
  8438.  
  8439. window.WatchItLater.loader.favTags.load(function(tags) {
  8440. if (tags.length < 1) {
  8441. $toggle.remove();
  8442. return;
  8443. }
  8444. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  8445. for (var i = 0, len = tags.length; i < len; i++) {
  8446. var tag = tags[i], $li = $('<li/>'),
  8447. $a = $('<a/>')
  8448. .attr({
  8449. href: '/tag/' + encodeURIComponent(tag.name + ' ' + conf.defaultSearchOption) + sortOrder,
  8450. 'data-menu-type': 'tag',
  8451. 'data-search-tag': tag.name
  8452. })
  8453. .text(tag.name);
  8454. toggleMenu.add$listItem($li.append($a));
  8455. }
  8456. toggleMenu.show();
  8457. });
  8458. };
  8459.  
  8460. return {
  8461. attach: function() {
  8462. initialize();
  8463. toggleMenu.attach();
  8464. },
  8465. detach: function() {
  8466. if (!toggleMenu) {
  8467. return;
  8468. }
  8469. toggleMenu.detach();
  8470. }
  8471. };
  8472. })();
  8473.  
  8474.  
  8475. WatchItLater.videoExplorerMenu.myShortcuts = (function() {
  8476. var toggleMenu;
  8477.  
  8478. var initialize = function() {
  8479. initialize = window._.noop;
  8480. console.log('%cinitialize WatchItLater.videoExplorerMenu.myShortcuts', 'background: lightgreen;');
  8481.  
  8482. toggleMenu = new VideoExplorerToggleMenu('マイショートカット', '/my/mylist');
  8483. toggleMenu.attach();
  8484.  
  8485. window.WatchItLater.mylist.loadMylistList(function(mylistList) {
  8486. toggleMenu.add$listItem(
  8487. $('<li/>').append(
  8488. $('<a/>')
  8489. .addClass('defMylist')
  8490. .attr({href: '/my/mylist', 'data-menu-type': 'deflist'})
  8491. .text('とりあえずマイリスト')
  8492. ));
  8493. var items = [
  8494. {id: -1, href: '/my/history', name: '視聴履歴'},
  8495. {id: -2, href: '/recommendations', name: 'あなたにオススメの動画'},
  8496. {id: NicorepoVideo.REPO_ALL, href: '/my/top/all', name: '【ニコレポ】すべての動画'},
  8497. {id: NicorepoVideo.REPO_USER, href: '/my/top/user', name: '【ニコレポ】お気に入りユーザー'},
  8498. {id: NicorepoVideo.REPO_CHCOM, href: '/my/top/chcom', name: '【ニコレポ】チャンネル&コミュニティ'},
  8499. {id: NicorepoVideo.REPO_MYLIST, href: '/my/top/mylist', name: '【ニコレポ】お気に入りマイリスト'}
  8500. ];
  8501. for (var v in items) {
  8502. var item = items[v];
  8503. toggleMenu
  8504. .addMylistItem(item.name, item.id)
  8505. .addClass('defMylist')
  8506. .attr({
  8507. href: item.href
  8508. });
  8509. }
  8510. for (var i = 0, len = mylistList.length; i < len; i++) {
  8511. var mylist = mylistList[i];
  8512. toggleMenu.addMylistItem(mylist.name, mylist.id, '', 'folder' + mylist.icon_id);
  8513. }
  8514.  
  8515. toggleMenu.show();
  8516. });
  8517. };
  8518.  
  8519. return {
  8520. attach: function() {
  8521. initialize();
  8522. toggleMenu.attach();
  8523. },
  8524. detach: function() {
  8525. if (!toggleMenu) {
  8526. return;
  8527. }
  8528. toggleMenu.detach();
  8529. }
  8530. };
  8531. })();
  8532.  
  8533. WatchItLater.videoExplorerMenu.videoRanking = (function() {
  8534. var toggleMenu;
  8535.  
  8536. var VideoRankingToggleMenu = function(title, titleLink) {
  8537. WatchApp.extend(this, VideoRankingToggleMenu, VideoExplorerToggleMenu, [title, titleLink]);
  8538.  
  8539. this._$menu.addClass('videoRankingList');
  8540. this.initializeCategoryToggleEvents();
  8541. };
  8542. WatchApp.mixin(VideoRankingToggleMenu.prototype, {
  8543. initializeCategoryToggleEvents: function() {
  8544. this._$menu.on('click', '.rankingCategoryToggle', function(e) {
  8545. e.preventDefault(); e.stopPropagation();
  8546.  
  8547. var $target = $(e.currentTarget), category = $target.attr('data-category');
  8548. var $popup = $target.closest('.slideMenu');
  8549. var isClose = $popup.find('li.' + category).toggleClass('categoryClose').hasClass('categoryClose');
  8550.  
  8551. conf.setValue('rankingCategory_' + category + '_Close', isClose);
  8552. });
  8553. },
  8554. addRankingItem: function($, genre, id, name, category, term) {
  8555. var $a =
  8556. $('<a/>')
  8557. .attr({
  8558. href: '/ranking/fav/' + term + '/' + genre,
  8559. 'data-menu-type': 'mylist',
  8560. 'data-mylist-id': id
  8561. })
  8562. .text(name)
  8563. .addClass(genre);
  8564. var $li = $('<li/>');
  8565.  
  8566. if (genre === category) {
  8567. $li.addClass('isCategory'); // nameと同じならカテゴリランキング、違うならジャンルランキング
  8568. if (genre !== 'all' && genre !== 'g_politics' && genre !== 'r18') {
  8569. var $button = $([
  8570. '<button class="rankingCategoryToggle">',
  8571. '<span class="open" title="サブカテゴリを開く">▼</span>',
  8572. '<span class="close" title="サブカテゴリを閉じる">▲</span>',
  8573. '</button>'
  8574. ].join(''));
  8575. $button.attr('data-category', category);
  8576. $li.append($button);
  8577. }
  8578. }
  8579. var isClose = conf.getValue('rankingCategory_' + category + '_Close');
  8580. $li
  8581. .toggleClass('categoryClose', isClose)
  8582. .attr({'data-genre': genre, 'data-category': category})
  8583. .addClass(category).addClass(genre)
  8584. .append($a);
  8585.  
  8586. this._$list.append($li);
  8587. return $a;
  8588. }
  8589. });
  8590.  
  8591.  
  8592. var initialize = function() {
  8593. initialize = window._.noop;
  8594. console.log('%cinitialize WatchItLater.videoExplorerMenu.videoRanking', 'background: lightgreen;');
  8595.  
  8596. toggleMenu = new VideoRankingToggleMenu('動画ランキング', '/ranking');
  8597. toggleMenu.attach();
  8598.  
  8599. // TODO: マジックナンバーを
  8600. toggleMenu.addRankingItem($, 'all', -100, 'カテゴリ合算(毎時)', 'all', 'hourly');
  8601. toggleMenu.addRankingItem($, 'all', -1100, 'カテゴリ合算(24時間)', 'all', 'daily');
  8602. // toggleMenu.addRankingItem($, 'all', -4100, 'カテゴリ合算(合計)', 'all', 'total');
  8603.  
  8604. var genreId = VideoRanking.getGenreId();
  8605. for (var genre in genreId) {
  8606. if (genre === 'all') { continue; }
  8607. var id = genreId[genre], name = VideoRanking.getGenreName(genre), category = VideoRanking.getCategory(id);
  8608. toggleMenu.addRankingItem($, genre, id, name, category, 'hourly');
  8609. }
  8610.  
  8611. window.setTimeout(function() { toggleMenu.show(); }, 100);
  8612. };
  8613.  
  8614. return {
  8615. attach: function() {
  8616. initialize();
  8617. toggleMenu.attach();
  8618. },
  8619. detach: function() {
  8620. if (!toggleMenu) {
  8621. return;
  8622. }
  8623. toggleMenu.detach();
  8624. }
  8625. };
  8626. })();
  8627.  
  8628.  
  8629. var WatchingVideoView = function() { this.initialize.apply(this, arguments); };
  8630. WatchingVideoView.prototype = {
  8631. _params: null,
  8632. _$view: null,
  8633. _watchInfoModel: null,
  8634. _type: null,
  8635. initialize: function(params) {
  8636. this._content = params.content;
  8637. this._watchInfoModel = params.watchInfoModel;
  8638. this._$view = params.$view;
  8639. this._mylistController = params.mylistController;
  8640. this._type = params.type;
  8641.  
  8642. this._$title = this._$view.find('.title');
  8643. this._$thumb = this._$view.find('.thumbnail');
  8644. this._$add = this._$view.find('.add');
  8645. this._$remove = this._$view.find('.remove');
  8646.  
  8647. this._$add .on('click', $.proxy(this._onAddClick, this));
  8648. this._$remove.on('click', $.proxy(this._onRemoveClick, this));
  8649.  
  8650. EventDispatcher.addEventListener('onWatchInfoReset', $.proxy(this.onVideoChange, this));
  8651. },
  8652. getView: function() {
  8653. return this._$view;
  8654. },
  8655. detach: function() {
  8656. this._$view.detach();
  8657. },
  8658. update: function() {
  8659. $('.videoExplorerBody').toggleClass('containsWatchingVideo', this._content.containsWatchId());
  8660. },
  8661. onVideoChange: function() {
  8662. this._$title.html(this._watchInfoModel.title);
  8663. this._$thumb
  8664. .attr('src', this._watchInfoModel.thumbnail)
  8665. .off('click').on('click', Util.Closure.showLargeThumbnail(this._watchInfoModel.thumbnail));
  8666. if (this._content.isActive()) {
  8667. this.update();
  8668. }
  8669. },
  8670. _setIsUpdating: function() {
  8671. this._$view.addClass('updating');
  8672. setTimeout($.proxy(this._clearIsUpdating, this), 3000);
  8673. },
  8674. _clearIsUpdating: function() {
  8675. this._$view.removeClass('updating');
  8676. },
  8677. _getIsUpdating: function() {
  8678. return this._$view.hasClass('updating');
  8679. },
  8680. _onAddClick: function() {
  8681. var watchId = WatchController.getWatchId();
  8682. this._setIsUpdating();
  8683. if (this._type === 'deflist') {
  8684. this._mylistController.addDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8685. } else {
  8686. var mylistId = this._content.getMylistId();
  8687. this._mylistController.addMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8688. }
  8689. },
  8690. _onRemoveClick: function() {
  8691. var watchId = WatchController.getWatchId();
  8692. this._setIsUpdating();
  8693. if (this._type === 'deflist') {
  8694. this._mylistController.deleteDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8695. } else {
  8696. var mylistId = this._content.getMylistId();
  8697. this._mylistController.deleteMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8698. }
  8699. },
  8700. _onMylistUpdate: function(status, result) {
  8701. if (status === 'ok') {
  8702. if (this._type === 'deflist') {
  8703. WatchController.clearDeflistCache();
  8704. }
  8705. } else {
  8706. Popup.alert('更新に失敗: ' + result.error.description);
  8707. }
  8708. this._content.setFilter(null);
  8709. setTimeout(
  8710. $.proxy(function() {
  8711. //this._content.changeState({page: 1});
  8712. this.contentRefresh();
  8713. this._clearIsUpdating();
  8714. }, this), 500);
  8715. },
  8716. contentRefresh: function() {
  8717. var params = this._content.getParams();
  8718. params.page = 1;
  8719. this._content.changeState(params);
  8720. this._content.refresh({page: 1});
  8721. }
  8722. }; // end WatchingVideoView.prototype
  8723.  
  8724.  
  8725. var GrepOptionView = function() { this.initialize.apply(this, arguments); };
  8726. GrepOptionView.prototype = {
  8727. _params: null,
  8728. _$view: null,
  8729. initialize: function(params) {
  8730. this._content = params.content;
  8731. this._$view = params.$view;
  8732. this._$form = this._$view.find('form');
  8733. this._$input = this._$view.find('.grepInput').attr('list', params.listName);
  8734. this._$community = this._$view.find('.community');
  8735. this._$alive = this._$view.find('.alive');
  8736. this._$duration = this._$view.find('.duration');
  8737. this._$invert = this._$view.find('.invert');
  8738. this._$checkboxes = this._$view.find('input[type=checkbox]');
  8739. this._$selectors = this._$view.find('select');
  8740.  
  8741. this._not = false;
  8742.  
  8743. this._$view.toggleClass('debug', !!conf.debugMode);
  8744.  
  8745. this._$list = $('<datalist />').attr('id', params.listName);
  8746. $('body').append(this._$list);
  8747.  
  8748. this._$form.on('submit', $.proxy(this._onFormSubmit, this));
  8749. this._$checkboxes.on('click', $.proxy(this._onCheckClick, this));
  8750. this._$selectors
  8751. .on('click', $.proxy(this._onSelectorClick, this))
  8752. .on('change', $.proxy(this._onSelectorChange, this));
  8753.  
  8754. this._$input.on('click', $.proxy(function(e) {
  8755. e.stopPropagation();
  8756. }, this)) .on('focus', $.proxy(function(e) {
  8757. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  8758. }, this));
  8759.  
  8760. this._$view .on('click', $.proxy(function(e) {
  8761. this._$input.focus();
  8762. }, this));
  8763. },
  8764. getView: function() {
  8765. return this._$view;
  8766. },
  8767. detach: function() {
  8768. this._$view.detach();
  8769. },
  8770. clear: function() {
  8771. this._$input.val('');
  8772. this._$checkboxes.prop('checked', false);
  8773. this._$view.removeClass('active');
  8774. this._$selectors.val('');
  8775. },
  8776. update: function() {
  8777. var list = this._content.getRawList();
  8778. var tmp = [];
  8779. for (var i = list.length -1; i >= 0; i--) {
  8780. tmp.push('<option>');
  8781. tmp.push(list[i].title); // 既にエスケープされてる
  8782. tmp.push('</option>');
  8783. }
  8784. this._$list.html(tmp.join(''));
  8785.  
  8786. if (this._getWord().length > 0) {
  8787. window.setTimeout($.proxy(function() { this._$input.focus(); }, this), 100);
  8788. }
  8789. },
  8790. _isActive: function() {
  8791. return (this._$input.val().length > 0 ||
  8792. !!this._$community.prop('checked') ||
  8793. !!this._$duration.val() ||
  8794. !!this._$alive .prop('checked'));
  8795. },
  8796. _getWord: function() {
  8797. return $.trim(this._$input.val());
  8798. },
  8799. _onCheckClick: function(e) {
  8800. e.stopPropagation();
  8801. this._submit();
  8802. },
  8803. _onSelectorClick: function(e) {
  8804. e.stopPropagation();
  8805. },
  8806. _onSelectorChange: function(e) {
  8807. e.stopPropagation();
  8808. this._submit();
  8809. },
  8810. _onFormSubmit: function(e) {
  8811. e.preventDefault();
  8812. e.stopPropagation();
  8813. this._submit();
  8814. },
  8815. _submit: function() {
  8816. var isActive = this._isActive();
  8817. this._$view.toggleClass('active', isActive);
  8818.  
  8819. if (isActive) {
  8820. this._content.setFilter(this._getFilter());
  8821. } else {
  8822. this._content.setFilter(null);
  8823. }
  8824.  
  8825. this.contentRefresh();
  8826. },
  8827. contentRefresh: function() {
  8828. var params = this._content.getParams();
  8829. params.page = 1;
  8830. this._content.changeState(params);
  8831. this._content.refresh({page: 1});
  8832. },
  8833. _getFilter: function() {
  8834. var to_h = function(str) {
  8835. return str.replace(/[A-Za-z0-9]/g, function(s) {
  8836. return String.fromCharCode(s.charCodeAt(0) - 65248);
  8837. }).toLowerCase();
  8838. };
  8839. var word = to_h(this._getWord());
  8840. var communityReg = /^so|^\d+$/;
  8841. var wordFilter = word.length > 0;
  8842. var durationVal = parseInt(this._$duration.val(), 10);
  8843. var communityFilter = !!this._$community.prop('checked');
  8844. var aliveFilter = !!this._$alive.prop('checked');
  8845. var durationFilter = !isNaN(durationVal);
  8846. var isInvert = !!this._$invert.prop('checked');
  8847.  
  8848.  
  8849. var isCommunity = function(item) {
  8850. return communityReg.test(item.id);
  8851. };
  8852. var isMatch = function(item) {
  8853. var title = item.title;
  8854. var desc = item.description_full || item.description_short || '';
  8855. var mc = item.mylist_comment || '';
  8856. var text = to_h([title, desc, mc].join('\n'));
  8857.  
  8858. return text.indexOf(word) >= 0;
  8859. };
  8860. var isAlive = function(item) {
  8861. var thumbnail = item.thumbnail_url || '';
  8862. if (thumbnail.indexOf('http://res.nimg.jp/img/common/video_deleted') < 0) {
  8863. return true;
  8864. }
  8865. return false;
  8866. };
  8867.  
  8868. var durationMatch = function(item) {
  8869. var itemDuration;
  8870. if (item.length_seconds) {
  8871. itemDuration = item.length_seconds;
  8872. } else {
  8873. var tmp = item.length.split(':');
  8874. itemDuration = parseInt(tmp[0], 10) * 60 + parseInt(tmp[1], 10);
  8875. }
  8876.  
  8877. if (durationVal < 0) {
  8878. return itemDuration <= Math.abs(durationVal);
  8879. } else {
  8880. return itemDuration >= durationVal;
  8881. }
  8882. };
  8883.  
  8884. var grepFilter = function(item) {
  8885. var result = true, i = func.length, f;
  8886. while (--i >= 0 && (result || isInvert)) {
  8887. f = func[i];
  8888. result &= f(item);
  8889. }
  8890. return isInvert ? !result : result;
  8891. };
  8892.  
  8893. var func = [], f;
  8894. if (wordFilter) { func.push(isMatch); }
  8895. if (communityFilter) { func.push(isCommunity); }
  8896. if (aliveFilter) { func.push(isAlive); }
  8897. if (durationFilter) { func.push(durationMatch); }
  8898.  
  8899. if (func.length < 1) { return null; }
  8900.  
  8901. return grepFilter;
  8902. }
  8903. }; // end GrepOptionView.prototype
  8904.  
  8905. function initMylistContent($, conf, w) {
  8906. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  8907. var ContentView = WatchApp.ns.components.videoexplorer.view.content.MylistVideoContentView;
  8908. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  8909. var explorer = vec.getVideoExplorer();
  8910. var myUserId = WatchController.getMyUserId();
  8911. var content = explorer.getContentList().getContent(ContentType.MYLIST_VIDEO);
  8912. var loader = content._mylistVideoAPILoader;
  8913. var pager = content._pager;
  8914. var watchingVideoView = new WatchingVideoView({
  8915. content: content,
  8916. watchInfoModel: watchInfoModel,
  8917. mylistController: Mylist,
  8918. type: 'mylist',
  8919. $view: $([
  8920. '<div class="watchingVideo">',
  8921. '<img class="thumbnail">',
  8922. '<p class="title"></p>',
  8923. '<span class="contains" >この動画はリストに登録されています</span>',
  8924. '<span class="not_contains">この動画はリストにありません</span>',
  8925. '<span class="edit">',
  8926. '<button class="add" >登録</button>',
  8927. '<button class="remove">外す</button>',
  8928. '</span>',
  8929. '</div>',
  8930. ''].join(''))
  8931. });
  8932.  
  8933. var grepOptionView = new GrepOptionView({
  8934. content: content,
  8935. listName: 'suggestMylistTitle',
  8936. $view: $([
  8937. '<div class="grepOption">',
  8938. '<form>',
  8939. '<input type="search" class="grepInput" autocomplete="on" placeholder="タイトル・説明文で絞り込む(G)" accesskey="g">',
  8940. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  8941. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  8942. '<label class="durationFilter filter">',
  8943. '動画時間<select class="duration">',
  8944. '<option value="">指定無し</option>',
  8945. '<option value="-180">3分以内</option>',
  8946. '<option value="180" >3分以上</option>',
  8947. '<option value="-300">5分以内</option>',
  8948. '<option value="300" >5分以上</option>',
  8949. '<option value="-600">10分以内</option>',
  8950. '<option value="600" >10分以上</option>',
  8951. '<option value="-1800">30分以内</option>',
  8952. '<option value="1800" >30分以上</option>',
  8953. '</select>',
  8954. '</label>',
  8955. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  8956. '</form>',
  8957. '</div>',
  8958. ].join(''))
  8959. });
  8960.  
  8961.  
  8962. pager._pageItemCount = conf.searchPageItemCount;
  8963. pager._displayPageCount = 5;
  8964. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  8965. pager._pageItemCount = v;
  8966. });
  8967.  
  8968. content._isOwnerNicorepo = false;
  8969. content._isRanking = false;
  8970. content.getIsMine = $.proxy(function() {
  8971. return parseInt(this.getUserId(), 10) === parseInt(myUserId, 10) && parseInt(this.getMylistId(), 10) > 0;
  8972. }, content);
  8973. content.getIsDummy = $.proxy(function() {
  8974. var id = this.getMylistId();
  8975. return parseInt(id, 10) <= 0 || id.toString().indexOf('repo') === 0;
  8976. }, content);
  8977. content.getIsOwnerNicorepo = $.proxy(function() { return this._isOwnerNicorepo; }, content);
  8978. content.getIsRanking = $.proxy(function() { return this._isRanking; }, content);
  8979.  
  8980. // grep対応のための拡張
  8981. content._rawList = [];
  8982. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  8983. content._filter = null;
  8984. content.setFilter = $.proxy(function(filter) {
  8985. this._filter = filter;
  8986. }, content);
  8987. content.getFilter = $.proxy(function() { return this._filter; }, content);
  8988.  
  8989.  
  8990. content.clear_org = content.clear;
  8991. content.clear = $.proxy(function() {
  8992. this.setFilter(null);
  8993. this.clear_org();
  8994. grepOptionView.clear();
  8995. }, content);
  8996.  
  8997. content.getNickname = $.proxy(function() {
  8998. if (this._nickname && this._nickname.length > 0) {
  8999. return this._nickname;
  9000. }
  9001. return 'no-name';
  9002. }, content);
  9003.  
  9004. content.onLoad_org = content.onLoad;
  9005. content.onLoad = $.proxy(function(err, result) {
  9006. this._isOwnerNicorepo = result.isOwnerNicorepo;
  9007. this._isRanking = result.isRanking;
  9008.  
  9009. var filter = this.getFilter();
  9010. if (err === null && result.list && result.list.length) {
  9011. if (!result.rawList) result.rawList = result.list.concat();
  9012. if (filter) {
  9013. var list = [];
  9014.  
  9015. for (var i = result.rawList.length - 1; i >= 0; i--) {
  9016. var item = result.rawList[i];
  9017. if (item.title && filter(item)) {
  9018. list.unshift(item);
  9019. }
  9020. }
  9021. result.list = list;
  9022. } else {
  9023. result.list = result.rawList.concat();
  9024. }
  9025. } else
  9026. if (result.rawList) {
  9027. result.list = result.rawList.concat();
  9028. }
  9029.  
  9030. this._rawList = result.rawList || [];
  9031. this.onLoad_org(err, result);
  9032. if (this.getIsMine()) {
  9033. EventDispatcher.dispatch('onMyMylistLoad', this.getMylistId(), {
  9034. name: this.getName(),
  9035. items: result.rawList
  9036. });
  9037. }
  9038. }, content);
  9039.  
  9040. content.containsWatchId = $.proxy(function(watchId) {
  9041. var list = this.getRawList();
  9042. if (!watchId) { watchId = WatchController.getWatchId(); }
  9043.  
  9044. for (var i = list.length - 1; i >= 0; i--) {
  9045. if (list[i].id === watchId) { return true; }
  9046. }
  9047. return false;
  9048. }, content);
  9049.  
  9050. loader.load_org = loader.load;
  9051. loader.load = $.proxy(function(params, callback) {
  9052. var isOwnerNicorepo = false, isRanking = false;
  9053. var id = params.id;
  9054. if (typeof id === 'string' && id.indexOf('repo-owner-') === 0) {
  9055. id = NicorepoVideo.REPO_OWNER;
  9056. }
  9057.  
  9058. var applyFilter = function(err, result) {
  9059. result.isOwnerNicorepo = isOwnerNicorepo;
  9060. result.isRanking = isRanking;
  9061. callback(err, result);
  9062. };
  9063. if (id < 0) {
  9064. var timeoutTimer = null;
  9065. var onload = function(result) {
  9066. window.clearTimeout(timeoutTimer);
  9067. // 投稿者ニコレポが0件で、投稿動画一覧を公開していたらそっちを開くタイマーをセット
  9068. if (result.list.length < 1 &&
  9069. parseInt(id, 10) === NicorepoVideo.REPO_OWNER &&
  9070. WatchController.isVideoPublic()) {
  9071. window.setTimeout(function() {
  9072. WatchController.openVideoOwnersVideo();
  9073. }, 500);
  9074. }
  9075. applyFilter(null, result);
  9076. };
  9077. var onerror = function(result) {
  9078. window.clearTimeout(timeoutTimer);
  9079. callback('error', result);
  9080. };
  9081. timeoutTimer = window.setTimeout(function() {
  9082. onload = onerror = window._.noop;
  9083. onerror({message: '通信がタイムアウトしました:' + id, status: 'fail'});
  9084. }, 30 * 1000);
  9085.  
  9086. // マイリストIDに負の数字(通常ないはず)が来たら乗っ取るサイン
  9087. // そもそもマイリストIDはstringのようなので数字にこだわる必要なかったかも
  9088. //
  9089. try {
  9090. if (typeof VideoRanking.getGenreName(id) === 'string') {
  9091. isRanking = true;
  9092. VideoRanking.load(null, {id: id}).then(onload, onerror);
  9093. return;
  9094. }
  9095. // TODO: マジックナンバーを
  9096. switch (parseInt(id, 10)) {
  9097. case -1:
  9098. VideoWatchHistory.load(onload);
  9099. break;
  9100. case -2:
  9101. VideoRecommendations.load(onload);
  9102. break;
  9103. case -3:
  9104. ChannelVideoList.loadOwnerVideo(null).then(onload, onerror);
  9105. break;
  9106. case NicorepoVideo.REPO_ALL:
  9107. NicorepoVideo.loadAll() .then(onload, onerror);
  9108. break;
  9109. case NicorepoVideo.REPO_USER:
  9110. NicorepoVideo.loadUser() .then(onload, onerror);
  9111. break;
  9112. case NicorepoVideo.REPO_CHCOM:
  9113. NicorepoVideo.loadChCom() .then(onload, onerror);
  9114. break;
  9115. case NicorepoVideo.REPO_MYLIST:
  9116. NicorepoVideo.loadMylist().then(onload, onerror);
  9117. break;
  9118. case NicorepoVideo.REPO_OWNER:
  9119. isOwnerNicorepo = true;
  9120. NicorepoVideo.loadOwner() .then(onload, onerror);
  9121. break;
  9122. default:
  9123. throw {message: '未定義のIDです:' + id, status: 'fail'};
  9124. }
  9125. } catch(e) {
  9126. // TODO: ここのエラーをちゃんと投げる
  9127. if (e.message && e.status) {
  9128. onerror({
  9129. status: e.status,
  9130. message: e.message
  9131. });
  9132. } else {
  9133. console.log(e); console.trace();
  9134. onerror({message: 'エラーが発生しました:' + id, status: 'fail'});
  9135. }
  9136. }
  9137. } else {
  9138. this.load_org(params, applyFilter);
  9139. }
  9140. }, loader);
  9141.  
  9142.  
  9143. var __css__ = Util.here(function() {/*
  9144. #videoExplorer .watchingVideo { display: none; }
  9145. #videoExplorer .watchingVideo .title { display: none; }
  9146. #videoExplorer .watchingVideo.updating * {
  9147. cursor: wait; opacity: 0.5;
  9148. }
  9149. #videoExplorer .watchingVideo button {
  9150. padding: 2px 12px; margin: 12px 24px;
  9151. }
  9152. #videoExplorer .isMine .watchingVideo {
  9153. display: block; background: #f4f4f4; border: 1px solid #ccc;
  9154. margin: auto; width: 500px; min-height: 48px; padding: 16px;
  9155. }
  9156.  
  9157. #videoExplorer .watchingVideo .thumbnail {
  9158. float: left; width: 72px; margin-right: 24px; cursor: pointer;
  9159. }
  9160.  
  9161.  
  9162. #videoExplorer .watchingVideo .title {
  9163. font-weight: bolder;
  9164. }
  9165. #videoExplorer .watchingVideo .title:before { content: ''; }
  9166. #videoExplorer .watchingVideo .title:after { content: ' '; }
  9167.  
  9168. #videoExplorer .watchingVideo .contains { display: none; }
  9169. #videoExplorer .containsWatchingVideo .watchingVideo .contains { display: inline; }
  9170. #videoExplorer .watchingVideo .not_contains { display: inline; }
  9171. #videoExplorer .containsWatchingVideo .watchingVideo .not_contains { display: none; }
  9172.  
  9173. #videoExplorer .watchingVideo .edit { display: none; }
  9174. #videoExplorer .isMine .watchingVideo .edit { display: inline-block; }
  9175.  
  9176. #videoExplorer .watchingVideo .add { display: inline-block; }
  9177. #videoExplorer .containsWatchingVideo .watchingVideo .add { display: none; }
  9178.  
  9179. #videoExplorer .watchingVideo .remove{ display: none; }
  9180. #videoExplorer .containsWatchingVideo .watchingVideo .remove{ display: inline-block; }
  9181.  
  9182. .isMine .editFavorite {
  9183. display: none; {* 自分のマイリストにはお気に入り登録ボタンを出さない *}
  9184. }
  9185. .watchingVideo button {
  9186. cursor: pointer;
  9187. }
  9188.  
  9189. .grepOption {
  9190. padding: 16px;
  9191. width: 500px;
  9192. margin: 16px auto;
  9193. background: #f4f4f4; border: 1px solid #ccc;
  9194. }
  9195. .grepOption .grepInput {
  9196. font-size: 120%;
  9197. width: 100%;
  9198. }
  9199.  
  9200. .grepOption .filter {
  9201. display: block; margin: 8px;
  9202. }
  9203. .grepOption .filter:hover {
  9204. background: #ccc;
  9205. }
  9206. .grepOption .filter.invertFilter {
  9207. display: none;
  9208. }
  9209. .grepOption.active .filter.invertFilter {
  9210. display: block; text-align: right;
  9211. }
  9212.  
  9213.  
  9214. */});
  9215. addStyle(__css__, 'mylistContentCss');
  9216.  
  9217. var MylistDetailView = WatchApp.ns.components.videoexplorer.view.content.parts.MylistDetailView;
  9218. MylistDetailView.prototype.update_org = MylistDetailView.prototype.update;
  9219. MylistDetailView.prototype.update = function(id, name, description, count) {
  9220. this.update_org(id, name, description, count);
  9221. if (id.toString().match(/repo-owner-(\d+)/)) {
  9222. this._$name.attr('href', '/user/' + RegExp.$1);
  9223. } else
  9224. if (parseInt(id, 10) <= 0) {
  9225. this._$name.attr('href', '');
  9226. }
  9227. };
  9228.  
  9229. var
  9230. overrideContentView = function(proto, watchingVideoView, grepOptionView) {
  9231. var updateCssClass = function(content) {
  9232. $('.videoExplorerBody')
  9233. .toggleClass('dummyMylist', content.getIsDummy())
  9234. .toggleClass('isMine', content.getIsMine())
  9235. .toggleClass('ownerNicorepo', content.getIsOwnerNicorepo())
  9236. .toggleClass('ranking', content.getIsRanking())
  9237. ;
  9238. };
  9239.  
  9240. proto.detach_org = proto.detach;
  9241. proto.detach = function() {
  9242. this.detach_org();
  9243. watchingVideoView.detach();
  9244. grepOptionView.detach();
  9245. };
  9246.  
  9247. proto.onUpdate_org = proto.onUpdate;
  9248. proto.onUpdate = function() {
  9249. this.onUpdate_org();
  9250. updateCssClass(this._content);
  9251. watchingVideoView.update();
  9252. grepOptionView.update();
  9253. this._$content.find('.mylistSortOrder').before(watchingVideoView.getView());
  9254. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9255. };
  9256.  
  9257. proto.onError_org = proto.onError;
  9258. proto.onError = function() {
  9259. this.onError_org();
  9260. updateCssClass(this._content);
  9261. watchingVideoView.update();
  9262. grepOptionView.update();
  9263. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9264. };
  9265.  
  9266. };
  9267.  
  9268. overrideContentView(ContentView.prototype, watchingVideoView, grepOptionView);
  9269. } // end initMylistContent
  9270.  
  9271. function initDeflistContent($, conf, w) {
  9272. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  9273. var ContentView = WatchApp.ns.components.videoexplorer.view.content.DeflistVideoContentView;
  9274. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  9275. var explorer = vec.getVideoExplorer();
  9276. var content = explorer.getContentList().getContent(ContentType.DEFLIST_VIDEO);
  9277. var loader = content._deflistVideoAPILoader;
  9278. var pager = content._pager;
  9279. var watchingVideoView = new WatchingVideoView({
  9280. content: content,
  9281. watchInfoModel: watchInfoModel,
  9282. mylistController: Mylist,
  9283. type: 'deflist',
  9284. $view: $([
  9285. '<div class="watchingVideo">',
  9286. '<img class="thumbnail">',
  9287. '<p class="title"></p>',
  9288. '<span class="contains" >この動画はリストに登録されています</span>',
  9289. '<span class="not_contains">この動画はリストにありません</span>',
  9290. '<span class="edit">',
  9291. '<button class="add" >登録</button>',
  9292. '<button class="remove">外す</button>',
  9293. '</span>',
  9294. '</div>',
  9295. ''].join(''))
  9296. });
  9297. var grepOptionView = new GrepOptionView({
  9298. content: content,
  9299. listName: 'suggestDeflistTitle',
  9300. $view: $([
  9301. '<div class="grepOption">',
  9302. '<form>',
  9303. '<input type="search" class="grepInput" autocomplete="on" placeholder="とりマイをタイトル・説明文で絞り込む(G)" accesskey="g">',
  9304. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  9305. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  9306. '<label class="durationFilter filter">',
  9307. '動画長<select class="duration">',
  9308. '<option value="">指定無し</option>',
  9309. '<option value="-180">3分以内</option>',
  9310. '<option value="180" >3分以上</option>',
  9311. '<option value="-300">5分以内</option>',
  9312. '<option value="300" >5分以上</option>',
  9313. '<option value="-600">10分以内</option>',
  9314. '<option value="600" >10分以上</option>',
  9315. '<option value="-1800">30分以内</option>',
  9316. '<option value="1800" >30分以上</option>',
  9317. '</select>',
  9318. '</label>',
  9319. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  9320. '</form>',
  9321. '</div>',
  9322. ].join(''))
  9323. });
  9324.  
  9325.  
  9326.  
  9327. pager._pageItemCount = conf.searchPageItemCount;
  9328. pager._displayPageCount = 5;
  9329. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  9330. pager._pageItemCount = v;
  9331. });
  9332.  
  9333.  
  9334. content.changeState_org = content.changeState;
  9335. content.changeState = $.proxy(function(params, callback) {
  9336. console.log('deflist refresh! ', params, callback);
  9337. if (!this.isActive()) {
  9338. WatchController.clearDeflistCache();
  9339. }
  9340. this.changeState_org(params, callback);
  9341. }, content);
  9342.  
  9343. content.getIsMine = function() { return true; };
  9344.  
  9345. // grep対応のための拡張
  9346. content._rawList = [];
  9347. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  9348. content._filter = null;
  9349. content.setFilter = $.proxy(function(filter) {
  9350. this._filter = filter;
  9351. }, content);
  9352. content.getFilter = $.proxy(function() { return this._filter; }, content);
  9353.  
  9354. content.onLoad_org = content.onLoad;
  9355. content.onLoad = $.proxy(function(err, result) {
  9356. var filter = this.getFilter();
  9357. if (err === null && result.list && result.list.length) {
  9358. if (!result.rawList) result.rawList = result.list.concat();
  9359. if (filter) {
  9360. var list = [];
  9361.  
  9362. for (var i = result.rawList.length - 1; i >= 0; i--) {
  9363. var item = result.rawList[i];
  9364. if (item.title && filter(item)) {
  9365. list.unshift(item);
  9366. }
  9367. }
  9368. result.list = list;
  9369. } else {
  9370. result.list = result.rawList.concat();
  9371. }
  9372. } else
  9373. if (result.rawList) {
  9374. result.list = result.rawList.concat();
  9375. }
  9376. this._rawList = result.rawList || [];
  9377.  
  9378. this.onLoad_org(err, result);
  9379. }, content);
  9380.  
  9381. content.clear_org = content.clear;
  9382. content.clear = $.proxy(function() {
  9383. this.setFilter(null);
  9384. this.clear_org();
  9385. grepOptionView.clear();
  9386. }, content);
  9387.  
  9388. content.containsWatchId = $.proxy(function(watchId) {
  9389. var list = this.getRawList();
  9390. if (!watchId) { watchId = WatchController.getWatchId(); }
  9391.  
  9392. for (var i = list.length - 1; i >= 0; i--) {
  9393. if (list[i].id === watchId) { return true; }
  9394. }
  9395. return false;
  9396. }, content);
  9397.  
  9398. var
  9399. overrideContentView = function(proto, watchingVideoView) {
  9400. var updateCssClass = function(content) {
  9401. $('.videoExplorerBody').toggleClass('isMine', true);
  9402. };
  9403.  
  9404. proto.detach_org = proto.detach;
  9405. proto.detach = function() {
  9406. this.detach_org();
  9407. watchingVideoView.detach();
  9408. grepOptionView.detach();
  9409. };
  9410.  
  9411. proto.onUpdate_org = proto.onUpdate;
  9412. proto.onUpdate = function() {
  9413. this.onUpdate_org();
  9414. updateCssClass(this._content);
  9415. watchingVideoView.update();
  9416. grepOptionView.update();
  9417. this._$content.find('.deflistSortOrder').before(watchingVideoView.getView());
  9418. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9419. };
  9420.  
  9421. proto.onError_org = proto.onError;
  9422. proto.onError = function() {
  9423. this.onError_org();
  9424. updateCssClass(this._content);
  9425. watchingVideoView.update();
  9426. grepOptionView.update();
  9427. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9428. };
  9429.  
  9430. };
  9431.  
  9432. overrideContentView(ContentView.prototype, watchingVideoView);
  9433. } // end initDeflistContent
  9434.  
  9435.  
  9436.  
  9437.  
  9438. function showLargeThumbnail(baseUrl) {
  9439. var largeUrl = baseUrl, size;
  9440. if (baseUrl.indexOf('smilevideo.jp') >= 0) {
  9441. largeUrl = baseUrl.replace(/\.([LM])/, '') + '.L';
  9442. size = 'width: 360px; height: 270px; max-height: 500px;';
  9443. } else {
  9444. largeUrl = baseUrl.replace(/z$/, 'l');
  9445. size = 'width: 360px; max-height: 500px;';
  9446. }
  9447. var
  9448. html = [
  9449. '<div class="largeThumbnailPopup" onmousedown="if (event.button == 0) { $(\'#popupMarquee\').removeClass(\'show\'); event.preventDefault(); }" style="width: 360px; height: 270px; background-image: url(' , largeUrl, ')">',
  9450. '<img src="', largeUrl, '" style="display: none;" onload="$(\'#popupMarquee .largeThumbnailPopup *\').hide()">',
  9451. // '<img src="', baseUrl, '" style="', size, ' z-index: 2;">',
  9452. '<div style="', size, ' background-image: url(' + baseUrl + '); "></div>',
  9453. '</div>',
  9454. ''].join('');
  9455. Popup.show(html);
  9456. } //
  9457. WatchController.showLargeThumbnail = showLargeThumbnail;
  9458.  
  9459. function onVideoStopped() {
  9460. EventDispatcher.dispatch('onVideoStopped');
  9461. }
  9462.  
  9463. function onVideoEnded() {
  9464. EventDispatcher.dispatch('onVideoEnded');
  9465. }
  9466.  
  9467. var videoExplorerOpenCount = 0;
  9468. function onVideoExplorerOpened(params) {
  9469. window.console.timeEnd('onVideoExplorerOpen');
  9470. var target = params.target, contentList = params.contentList, content = params.content;
  9471. if (videoExplorerOpenCount++ === 0) {
  9472. EventDispatcher.dispatch('onFirstVideoExplorerOpened', content);
  9473. }
  9474. EventDispatcher.dispatch('onVideoExplorerOpened', content);
  9475.  
  9476. AnchorHoverPopup.hidePopup().updateNow();
  9477. }
  9478.  
  9479. function onVideoExplorerOpening(params) {
  9480. window.console.time('onVideoExplorerOpen');
  9481. var target = params.target, contentList = params.contentList, content = params.content;
  9482. if (videoExplorerOpenCount === 0) {
  9483. EventDispatcher.dispatch('onFirstVideoExplorerOpening', params);
  9484. }
  9485. EventDispatcher.dispatch('onVideoExplorerOpening', params);
  9486. }
  9487.  
  9488. function onVideoExplorerClosing(params) {
  9489. var target = params.target, contentList = params.contentList, content = params.content;
  9490. EventDispatcher.dispatch('onVideoExplorerClosing', content);
  9491. }
  9492.  
  9493. function onVideoExplorerClosed(params) {
  9494. var target = params.target, contentList = params.contentList, content = params.content;
  9495. AnchorHoverPopup.hidePopup().updateNow();
  9496. EventDispatcher.dispatch('onVideoExplorerClosed', content);
  9497. setTimeout(function() {
  9498. watch.PlaylistInitializer.playlistView.resetView();
  9499. }, 1000);
  9500. }
  9501.  
  9502. function onVideoExplorerRefreshStart(params) {
  9503. window.console.time('videoExplorerRefresh');
  9504. var target = params.target, contentList = params.contentList, content = params.content;
  9505. var
  9506. ContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  9507. type = content.getType(),
  9508. $ve = $('#videoExplorer')
  9509. .removeClass('w_user').removeClass('w_upload').removeClass('w_mylist')
  9510. .removeClass('w_deflist').removeClass('w_related').removeClass('w_search'),
  9511. $body = $ve.find('.videoExplorerBody')
  9512. .removeClass('isMine').removeClass('dummyMylist')
  9513. .removeClass('isRanking').removeClass('isOwnerNicorepo'),
  9514. className = 'w_user';
  9515. switch (type) {
  9516. case ContentType.USER_VIDEO:
  9517. className = 'w_user';
  9518. break;
  9519. case ContentType.UPLOADED_VIDEO:
  9520. className = 'w_uploaded';
  9521. break;
  9522. case ContentType.MYLIST_VIDEO:
  9523. className = 'w_mylist';
  9524. break;
  9525. case ContentType.DEFLIST_VIDEO:
  9526. className = 'w_deflist';
  9527. break;
  9528. case ContentType.RELATED_VIDEO:
  9529. className = 'w_related';
  9530. break;
  9531. case ContentType.SEARCH:
  9532. className = 'w_search';
  9533. break;
  9534. }
  9535. $ve.addClass(className);
  9536.  
  9537. EventDispatcher.dispatch('onVideoExplorerRefreshStart', content);
  9538. }
  9539. function onVideoExplorerRefreshEnd(params) {
  9540. window.console.timeEnd('videoExplorerRefresh');
  9541. var target = params.target, contentList = params.contentList, content = params.content;
  9542. EventDispatcher.dispatch('onVideoExplorerRefreshEnd', content);
  9543. }
  9544. function onVideoExplorerChangePage(params) {
  9545. var target = params.target, contentList = params.contentList, content = params.content;
  9546. EventDispatcher.dispatch('onVideoExplorerChangePage', content);
  9547. }
  9548.  
  9549. /**
  9550. * 検索中の動画サイズを無理矢理でっかくするよ。
  9551. */
  9552. var videoExplorerStyle = null, lastAvailableWidth = 0, lastBottomHeight = 0;
  9553. function adjustVideoExplorerSize(force) {
  9554. if (force !== true && (!conf.videoExplorerHack || !WatchController.isSearchMode())) { return; }
  9555. $('#videoExplorer, #content, #bottomContentTabContainer').toggleClass('w_adjusted', conf.videoExplorerHack);
  9556. var
  9557. isWindows = window.navigator.platform.toLowerCase().indexOf('win') >= 0,
  9558. scrollBarMargin = isWindows ? 16 : 0,
  9559. rightAreaWidth = $('.videoExplorerBody').outerWidth(), // 592
  9560. availableWidth = Math.max($(window).innerWidth() - rightAreaWidth, 300),
  9561. commentInputHeight = $('#playerContainer').hasClass('oldTypeCommentInput') ? 30 : 0,
  9562. controlPanelHeight = $('#playerContainer').hasClass('controll_panel') ? 46 : 0;
  9563.  
  9564. var
  9565. defPlayerWidth = 300, otherPluginsHeight = 0,
  9566. defPlayerHeight = (defPlayerWidth - 32) * 9 / 16 + 10,
  9567. ratio = availableWidth / defPlayerWidth , availableHeight = defPlayerHeight * ratio + commentInputHeight + controlPanelHeight,
  9568. xdiff = (availableWidth - defPlayerWidth /*- 20 */), windowHeight = $(window).innerHeight(),
  9569. bottomHeight = windowHeight - availableHeight - (WatchController.isFixedHeader() ? $('#siteHeader').outerHeight() : 0) - otherPluginsHeight;
  9570.  
  9571. if (ratio < 1 || availableWidth <= 0 || bottomHeight <= 0 || (lastAvailableWidth === availableWidth && lastBottomHeight === bottomHeight)) { return; }
  9572.  
  9573. var seekbarWidth = 675, scaleX = (availableWidth) / seekbarWidth;
  9574.  
  9575. lastAvailableWidth = availableWidth;
  9576. lastBottomHeight = bottomHeight;
  9577.  
  9578. // コメントパネル召喚
  9579. var commentPanelWidth = 420;
  9580. var dynamic_css = [//'<style type="text/css" id="explorerHack">',
  9581. 'body.videoExplorer #content.w_adjusted #playerContainerWrapper, \n',
  9582. 'body.videoExplorer #content.w_adjusted #playerAlignmentArea, \n',
  9583. 'body.videoExplorer #content.w_adjusted #playerContainer, \n',
  9584. 'body.videoExplorer #content.w_adjusted #nicoplayerContainer ,\n',
  9585. 'body.videoExplorer #content.w_adjusted #external_nicoplayer \n',
  9586. '{',
  9587. 'width: ', availableWidth, 'px !important; height: ', availableHeight, 'px !important;padding: 0; margin: 0; ',
  9588. '}\n',
  9589. 'body.videoExplorer #content.w_adjusted .videoExplorerMenu { ',
  9590. 'position: absolute; width: 300px;',
  9591. 'margin-top: ', availableHeight, 'px !important; left: ', (xdiff - 2), 'px; ',
  9592. 'max-height: ', bottomHeight + 'px; overflow-y: auto; overflow-x: hidden; height: auto;',
  9593. '}\n',
  9594. 'body.videoExplorer #videoExplorer.w_adjusted { ',
  9595. 'margin-left: ', availableWidth, 'px !important;',
  9596. 'min-height: ', (windowHeight + 200) ,'px !important;',
  9597. 'overflow-x: hidden;',
  9598. ' }\n',
  9599. 'body.videoExplorer #content.w_adjusted #playlist { margin-left: ', xdiff, 'px; }\n',
  9600.  
  9601. 'body.videoExplorer #content.w_adjusted #nicoHeatMap {',
  9602. '-webkit-transform: scaleX(', availableWidth / 100, ');',
  9603. 'transform: scaleX(', availableWidth / 100, ');',
  9604. '}\n',
  9605. 'body.videoExplorer #content.w_adjusted #nicoHeatMapContainer {',
  9606. 'width: ', availableWidth, 'px;',
  9607. '}\n',
  9608. 'body.videoExplorer #content.w_adjusted #smart_music_kiosk {',
  9609. '-webkit-transform: scaleX(', scaleX, ');',
  9610. 'left: ', ((availableWidth - seekbarWidth) / 2) ,'px !important;',
  9611. '}\n',
  9612. 'body.videoExplorer #content.w_adjusted #songrium_logo_mini {',
  9613. 'left: ', (availableWidth + 5) ,'px !important;',
  9614. '}\n',
  9615. 'body.videoExplorer #content.w_adjusted #inspire_category {',
  9616. 'left: ', (availableWidth + 32) ,'px !important;',
  9617. '}\n',
  9618.  
  9619.  
  9620. 'body.videoExplorer #content.w_adjusted #leftPanel {',
  9621. ' display: block !important; top: ', (availableHeight + otherPluginsHeight - 1), 'px; max-height: ', bottomHeight, 'px; width: ', (xdiff - 4 + 1), 'px; left: 0;',
  9622. ' height:', bottomHeight, 'px; display: block; border-radius: 0;',
  9623. '}',
  9624. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo, body.size_small.no_setting_panel.videoExplorer #content.w_adjusted #leftPanel .sideIchibaPanel {',
  9625. 'width: ', Math.max((xdiff - 4), 130), 'px; border-radius: 0;',
  9626. '}',
  9627.  
  9628. ((xdiff >= 400) ?
  9629. [
  9630. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoTitleContainer{',
  9631. 'margin-left: 158px; border-radius: 0 0 ;background: #ddd; border: solid; border-color: #ccc; border-width: 1px 1px 0;',
  9632. '}',
  9633. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoThumbnailContainer{',
  9634. 'position: absolute; max-width: 150px; top: 0; ',
  9635. '}',
  9636. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoInfo{',
  9637. 'background: #ddd; margin-left: 158px; border-radius: 0 0; border: solid; border-color: #ccc; border-width: 0 1px 1px;',
  9638. '}',
  9639. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoDetails{',
  9640. 'margin-left: 158px; height: 100%; ',
  9641. '}',
  9642. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoOwnerInfoContainer{',
  9643. 'position: absolute; width: 150px; top: 0; border: 1px solid #ccc; margin: 0;',
  9644. '}',
  9645. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .userIconContainer, body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .ch_profile{',
  9646. 'background: #ddd; max-width: 150px; float: none; border-radius: 0;',
  9647. '}',
  9648. 'body.videoExplorer:not(.content-fix) .w_adjusted .videoDetails, ',
  9649. 'body.videoExplorer:not(.content-fix) .w_adjusted #videoExplorerMenu {',
  9650. // タグ領域三行分 bodyのスクロール位置をタグの場所にしてる時でもパネルは文章の末端までスクロールできるようにするための細工
  9651. // (四行以上あるときは表示しきれないが)
  9652. 'padding-bottom: 72px; ',
  9653. '}',
  9654. ].join('') :
  9655. (
  9656. (xdiff >= 154) ?
  9657. ['body.videoExplorer #content.w_adjusted #leftPanel #leftPanelTabContainer { padding: 4px 2px 3px 2px; }'].join('') :
  9658. ['body.videoExplorer #content.w_adjusted #leftPanel { display: none !important;}'].join('')
  9659. )
  9660. ),
  9661. 'body.videoExplorer #bottomContentTabContainer.w_adjusted .videoExplorerFooterAdsContainer { width: 520px; }\n',
  9662.  
  9663. 'body.videoExplorer #content.w_adjusted #playerTabWrapper {',
  9664. 'height: ', availableHeight, 'px !important;',
  9665. '}',
  9666.  
  9667. 'body.videoExplorer #content.w_adjusted #playerTabWrapper .sidePanelInner {',
  9668. 'height: ', (availableHeight - 2), 'px;',
  9669. '}',
  9670. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active {',
  9671. 'right: -',(commentPanelWidth - 2), 'px !important; top: 0 !important; background: transparent; border: 0; margin-top: 0px;',
  9672. '}',
  9673. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active #playerCommentPanel {',
  9674. 'display: block; background: #dfdfdf; border: 1px solid;',
  9675. '}',
  9676. ''].join('');
  9677.  
  9678. if (videoExplorerStyle) {
  9679. videoExplorerStyle.innerHTML = dynamic_css;
  9680. } else {
  9681. videoExplorerStyle = addStyle(dynamic_css, 'videoExplorerStyle');
  9682. }
  9683.  
  9684. } // end adjustVideoExplorerSize
  9685.  
  9686. function setupVideoExplorerStaticCss() {
  9687. var __css__ = Util.here(function() {/*
  9688. body.videoExplorerOpening {
  9689. overflow-y: scroll;
  9690. }
  9691. body.videoExplorerOpening .videoExplorerMenu {
  9692. {* display: none; *}
  9693. }
  9694. body.videoExplorerOpening #playerTabWrapper {
  9695. visibility: hidden;
  9696. }
  9697. #videoExplorer {
  9698. transition: margin-left 0.4s ease 0.4s; overflow-x: hidden;
  9699. }
  9700. #videoExplorer.w_adjusted .videoExplorerBody, #videoExplorer.w_adjusted .videoExplorerContent .contentItemList {
  9701. width: 592px; padding-left: 0; min-width: 592px; max-width: auto;
  9702. }
  9703. #videoExplorer.w_adjusted .searchBox {
  9704. width: 574px;\
  9705. }
  9706. #videoExplorer.w_adjusted .videoExplorerContent {
  9707. width: 592px;
  9708. }
  9709. #videoExplorer.w_adjusted .videoExplorerBody .resultContentsWrap {
  9710. width: 592px; padding: 16px 0px;
  9711. }
  9712. #videoExplorer.w_adjusted .videoExplorerMenu, #content .videoExplorerMenu:not(.initialized) { display: none; }
  9713. .videoExplorerMenu {
  9714. transition: margin-top 0.4s ease 0.4s; {*, left 0.4s ease-in-out*};
  9715. }
  9716. #leftPanel {
  9717. {* 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;*}
  9718. transition: width 0.4s ease 0.4s, height 0.4s ease 0.4s, left 0.4s ease 0.4s;
  9719. }
  9720. #content.w_adjusted #playlist {
  9721. min-width: 592px;
  9722. }
  9723. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li, body.videoExplorer #content.w_adjusted #videoExplorerExpand {
  9724. height: 26px;
  9725. }
  9726. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li>a,body.videoExplorer #content.w_adjusted #videoExplorerExpand a{
  9727. line-height: 26px; font-size: 100%;
  9728. }
  9729. .errorMessage {
  9730. max-height: 0; line-height: 30px; overflow: hidden; text-align: center; color: #f88; cursor: pointer;
  9731. transition: max-height 0.8s ease;
  9732. }
  9733. .videoErrorOccurred .errorMessage {
  9734. max-height: 100px;
  9735. }
  9736.  
  9737. .w_adjusted .videoExplorerMenu .itemList li .arrow {
  9738. top: 8px;
  9739. }
  9740.  
  9741.  
  9742. .w_adjusted .videoExplorerMenu .closeVideoExplorer {
  9743. width: 300px; position: relative; padding: 2px 10px; background: #f5f5f5;
  9744. }
  9745. .w_adjusted .videoExplorerMenu .closeVideoExplorer:hover {
  9746. background: #dbdbdb;
  9747. }
  9748. .w_adjusted .videoExplorerMenu .closeVideoExplorer a{
  9749. display: block;line-height: 26px; {*color: #CC0000;*}
  9750. }
  9751.  
  9752. #searchResultNavigation > ul > li a:after, #content.w_adjusted #videoExplorerExpand a#closeSearchResultExplorer:after {
  9753. top: 8px;
  9754. }
  9755.  
  9756.  
  9757.  
  9758.  
  9759. #content.w_adjusted #playerContainerWrapper, #content.w_adjusted #playlist { box-shadow: none; }
  9760. #content.w_adjusted #videoExplorerExpand .arrow { display: none; }
  9761.  
  9762. body.videoExplorer #footer.w_adjusted {
  9763. display: none;
  9764. }
  9765. .w_adjusted .uadTagRelated .default .itemList .item .videoTitleContainer {
  9766. width: 130px;
  9767. text-align: center;
  9768. }
  9769. .w_adjusted .uadTagRelated .uadTagRelated {
  9770. margin-bottom: 30px;
  9771. }
  9772. .w_adjusted .uadTagRelated .itemList .item,
  9773. .w_adjusted .uadTagRelated .emptyItem,
  9774. .w_adjusted .uadTagRelated .default .landing {
  9775. width: 130px; margin: 0 10px 0 8px;
  9776. }
  9777. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper .itemImage {
  9778. width: 130px; height: auto; top: 0; left: 0;
  9779. }
  9780. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper {
  9781. width: 130px; height: 100px;
  9782. }
  9783. .w_adjusted .uadTagRelated .emptyItem .emptyMessageContainer {
  9784. width: 130px; height: 100px;
  9785. }
  9786. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link,
  9787. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link .title {
  9788. display: inline;
  9789. }
  9790.  
  9791. #videoExplorer.w_adjusted .videoExplorerContent .column1 .commentBlank {
  9792. width: 96%;
  9793. }
  9794. #videoExplorer.w_adjusted .videoExplorerContent .column4 .commentBlank {
  9795. width: 24%;
  9796. }
  9797. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item .createdTime .submit
  9798. {
  9799. display: none !important;
  9800. }
  9801. .nicorepoResult .column4 .videoInformation {
  9802. display: none;
  9803. }
  9804.  
  9805. #videoExplorer .pager { margin-right: 20px; }
  9806. #videoExplorer .contentItemList { clear: both; }
  9807.  
  9808. body.videoExplorer #content.w_adjusted #playerContainerWrapper { overflow: visible; }
  9809. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContent { padding: 20px 0px; }
  9810. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9811. { margin-left: 0; padding: 20px 340px 20px 0px; }
  9812. body.videoExplorer.playlist #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9813. { margin-left: 0; padding: 164px 340px 20px 0px; }
  9814.  
  9815. {* 謎のスペーサー *}
  9816. {*body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:first *}
  9817. {* body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:not(.videoExplorerMenuInner) { display: none; } *}
  9818. body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:nth-child(1) { display: none; }
  9819.  
  9820. body.videoExplorer #content.w_adjusted .videoExplorerMenu
  9821. { width: 300px; }
  9822.  
  9823. body.videoExplorer #content.w_adjusted .videoExplorerMenuInner
  9824. { position: static !important; top: 0 !important; left: 0 !important; }
  9825.  
  9826. body.videoExplorer #bottomContentTabContainer.w_adjusted { {*background: #ccc;*} }
  9827. body:not(.videoExplorer) .videoExplorerMenu { display: none; }
  9828.  
  9829. body.videoExplorer #content.w_adjusted #nicoplayerContainer {
  9830. z-index: 100;
  9831. }
  9832. body.videoExplorer #content.w_adjusted #playerTabWrapper {
  9833. top: 0px !important; background: #dfdfdf; border-radius: 4px;
  9834. z-index: 99;
  9835. transition: right 0.3s ease-out;
  9836. }
  9837. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column1 .item {
  9838. margin-left: 8px;
  9839. }
  9840. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  9841. width: 130px; margin-left: 8px; margin-right: 10px;
  9842. }
  9843. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .videoInformationOuter {
  9844. width: 414px;
  9845. }
  9846. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .nicorepoResult .videoInformationOuter {
  9847. width: auto;
  9848. }
  9849. #videoExplorer.w_adjusted .contentItemList .folder .column1 .description,
  9850. #videoExplorer.w_adjusted .suggestVideo .folder .column1 .description,
  9851. #videoExplorer.w_adjusted .descriptionShort {
  9852. width: 410px;
  9853. }
  9854.  
  9855. #videoExplorer .descriptionShort {
  9856. line-height: 1.5;
  9857. margin: 0 0 4px;
  9858. word-break: break-all;
  9859. clear: both;
  9860. color: #666;
  9861. font-size: 93%;
  9862. }
  9863.  
  9864. .w_adjusted .column1 .smallThumbnail .createdTime.at {
  9865. width: 130px;
  9866. }
  9867. .w_adjusted .column1 .createdTime.at {
  9868. width: 160px; text-align: center;
  9869. }
  9870. .w_adjusted .createdTime {
  9871. white-space: nowrap;
  9872. }
  9873. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList .folder .container,
  9874. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .suggestVideo .folder .container {
  9875. background-position: -15px -270px; width: 130px; height: 100px;
  9876. }
  9877.  
  9878. body.size_small.no_setting_panel.videoExplorer #content #videoExplorerExpand { {*「閉じる」ボタン *}
  9879. position: static; top: auto; left: auto; margin-top: 0;',
  9880. }
  9881. body.videoExplorer #content.w_adjusted #playerTabWrapper #playerCommentPanel {
  9882. display: none;
  9883. }
  9884.  
  9885. .videoExplorerMenu .item:hover {
  9886. background: #dbdbdb; text-decoration: underline;
  9887. }
  9888. .videoExplorerMenu .item {
  9889. position: relative; border-bottom: 1px solid #CCCCCC; background: #f2f2f2;
  9890. text-decoration: none; cursor: pointer;
  9891. }
  9892. .videoExplorerMenu .item .arrow {
  9893. display: block; position: absolute; top: 14px; right: 12px; width: 9px; height: 12px;
  9894. background: url("http://res.nimg.jp/img/watch_q9/video_explorer/icon_normal.png") no-repeat 0 0;
  9895. }
  9896. .videoExplorerMenu .item .text {
  9897. position: relative; width: 100%; height: 100%; display: block; text-align: left;
  9898. text-decoration: none; padding: 0 12px; color: #000; box-sizing: border-box;
  9899. line-height: 26px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box;
  9900. }
  9901. .videoExplorerMenu .itemList>li:first {
  9902. position: relative;
  9903. }
  9904.  
  9905. #videoExplorer .videoExplorerMenu .closeVideoExplorer, #videoExplorer .quickSearchInput {
  9906. display: none;
  9907. }
  9908.  
  9909. #videoExplorer .videoExplorerBody .videoExplorerContent .contentItemList.column1 .video .column1 .videoInformationOuter .title
  9910. {
  9911. white-space: normal;
  9912. }
  9913.  
  9914. .videoExplorer #playlist {
  9915. transition: margin-left 0.4s ease 0.4s;
  9916. }
  9917.  
  9918. #playerAlignmentArea .toggleCommentPanel {
  9919. display: none;
  9920. position: absolute;
  9921. right: -119px;
  9922. bottom: 70px;
  9923. width: 100px;
  9924. height: 30px;
  9925. cursor: pointer;
  9926. outline: none;
  9927. background: #ccc;
  9928. border-radius: 16px 16px 0 0;
  9929. border: solid 1px black;
  9930. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  9931. transform: rotate(90deg); transform-origin: 0 0 0;
  9932. transition: 0.4s ease-in-out;
  9933. -webkit-transition: 0.4s ease-in-out;
  9934. }
  9935. #playerAlignmentArea .toggleCommentPanel::-moz-focus-inner {
  9936. border: 0px;
  9937. }
  9938. body.videoExplorer .w_adjusted #playerAlignmentArea .toggleCommentPanel { display: block; }
  9939. #playerAlignmentArea .toggleCommentPanel:before {
  9940. content: '↑ ';
  9941. }
  9942. #playerAlignmentArea .toggleCommentPanel:hover {
  9943. right: -129px;
  9944. }
  9945. #playerAlignmentArea .toggleCommentPanel.w_active {
  9946. right: -418px;
  9947. bottom: -29px;
  9948. border-radius: 0 0 16px 16px;
  9949. z-index: 10000;
  9950. -webkit-transform: rotate(360deg);
  9951. transform: rotate(360deg);
  9952. transition: none;
  9953. -webkit-transition: 0.4s ease-in-out;
  9954. {*transition: 0.4s ease-in-out;*}
  9955. }
  9956. #playerAlignmentArea .toggleCommentPanel.w_active:hover {
  9957. {*box-shadow: 2px 2px 2px #888;*}
  9958. right: -418px;
  9959. }
  9960. #playerAlignmentArea .toggleCommentPanel.w_active:before {
  9961. content: '← ';
  9962. }
  9963.  
  9964. .videoExplorerOpening .videoExplorerBody .videoExplorerConfig {
  9965. display: none;
  9966. }
  9967. .videoExplorerBody .videoExplorerConfig {
  9968. cursor: pointer;
  9969. width: 80px; margin-left: -36px; white-space: nowrap;
  9970. border-radius: 0 32px 0 0; border: solid 1px #666; border-width: 1px 1px 0;
  9971. color: #fff; background: #aaa;
  9972. }
  9973. #videoExplorer.w_adjusted .videoExplorerConfig .open,
  9974. #videoExplorer:not(.w_adjusted) .videoExplorerConfig .close {
  9975. display: none;
  9976. }
  9977. .videoExplorerConfig::-moz-focus-inner { border: 0px; }
  9978.  
  9979. .videoExplorer #playerContainer.appli_panel #appliPanel {
  9980. width: auto !important; background: #333;
  9981. }
  9982. .videoExplorerContent {
  9983. background: #fff;
  9984. }
  9985. */});
  9986. return addStyle(__css__, 'videoExplorerStyleStatic');
  9987. } // end setupVideoExplorerStaticCss
  9988.  
  9989. function initAutoComplete($searchInput) {
  9990. var
  9991. $suggestList = $('<datalist id="quickSearchSuggestList"></datalist>'),
  9992. wordSuggest = '',
  9993. favTagsSuggest = '',
  9994. loading = false,
  9995. val = '',
  9996. suggestLoader = new NicoSearchSuggest({}),
  9997. update = _.debounce(function() {
  9998. if (loading) {
  9999. return;
  10000. }
  10001. var value = $searchInput.val();
  10002. if (value.length >= 1 && val !== value) {
  10003. val = $searchInput.val();
  10004. loading = true;
  10005. suggestLoader.load(val, onSuggestLoaded);
  10006. } else {
  10007. loading = false;
  10008. }
  10009. }, 300),
  10010. onSuggestLoaded = function(err, result) {
  10011. if (err) {
  10012. return;
  10013. }
  10014. if (result.candidates) {
  10015. console.log(result.candidates);
  10016. var candidates = result.candidates;
  10017. var options = [];
  10018. for (var i = candidates.length - 1; i >= 0; i--) {
  10019. options.unshift(['<option value="', candidates[i], '"></option>'].join(''));
  10020. }
  10021. wordSuggest = options.join('');
  10022. refresh();
  10023. }
  10024. loading = false;
  10025. },
  10026. refresh = function() {
  10027. $suggestList.html(wordSuggest + favTagsSuggest);
  10028. },
  10029. bind = function($elm) {
  10030. $elm
  10031. .on('focus', update)
  10032. .on('keydown', update)
  10033. .on('keyup', update)
  10034. .on('keypress', update)
  10035. .on('click', update)
  10036. .on('mousedown', update)
  10037. .on('mouseup', update)
  10038. .attr({'autocomplete': 'on', 'list': 'quickSearchSuggestList', 'placeholder': '検索ワードを入力(Q)'});
  10039. // try {
  10040. // //$elm.attr('type', 'search');
  10041. // //$elm[0].setAttribute('type', 'search');//.attr('type', 'search');
  10042. // } catch (e) {
  10043. // console.log(e);
  10044. // }
  10045. };
  10046.  
  10047. EventDispatcher.addEventListener('onFavTagsLoad', function(tags) {
  10048. var options = [];
  10049. for (var i = tags.length - 1; i >= 0; i--) {
  10050. options.unshift(['<option value="', tags[i], '"></option>'].join(''));
  10051. }
  10052. favTagsSuggest = options.join('');
  10053. refresh();
  10054. });
  10055. $('body').append($suggestList);
  10056.  
  10057. bind($searchInput);
  10058. } //
  10059.  
  10060. function initVideoExplorer($, conf, w) {
  10061. setupVideoExplorerStaticCss();
  10062.  
  10063. var
  10064. _vp = WatchApp.ns.components.videoexplorer,
  10065. initializer = watch.VideoExplorerInitializer,
  10066. controller = initializer.videoExplorerController,
  10067. explorer = controller.getVideoExplorer(),
  10068. explorerConfig = _vp.config.VideoExplorerConfig,
  10069. menu = explorer.getMenu(),
  10070. ContentItemType = _vp.model.ContentItemType,
  10071. ContentType = _vp.model.ContentType,
  10072. watchPageRouter = WatchApp.ns.model.state.WatchPageRouter.getInstance(),
  10073. playerConnector = watch.PlayerInitializer.nicoPlayerConnector,
  10074. searchType = 'tag',
  10075. $menu = $('.videoExplorerMenu'),
  10076. $searchInput = $('<input class="quickSearchInput" type="search" name="q" accesskey="q" required="required" x-webkit-speech />')
  10077. .attr({'title': '検索ワードを入力', 'placeholder': '検索ワードを入力(Q)'}),
  10078. $closeExplorer = $('<div class="closeVideoExplorer"><a href="javascript:;">▲ 画面を戻す</a></div>'),
  10079. $inputForm = $('<form action="javascript:void(0);" />').append($searchInput),
  10080. $toggleCommentPanel = $('<button class="toggleCommentPanel">コメント</button>');
  10081.  
  10082. // init search menu
  10083. $searchInput.on('keyup', function(e) {
  10084. $('.searchText input').val(this.value);
  10085. }).on('click', function(e) {
  10086. e.stopPropagation();
  10087. });
  10088. $inputForm.on('submit', function(e) {
  10089. //e.preventDefault();
  10090. var val = $.trim($searchInput.val());
  10091. if (val.length > 0) {
  10092. if (val.match(/(sm|nm|so)\d+/)) {
  10093. WatchController.nicoSearch(val, 'tag');
  10094. } else {
  10095. WatchController.nicoSearch(val, 'keyword');
  10096. }
  10097. }
  10098. });
  10099.  
  10100. var clearButton = new window.Nico.ClearButton({targetInput: $searchInput});
  10101. EventDispatcher.addEventListener('onSearchStart', function(word, type) {
  10102. searchType = type.replace(/^.*\./, '');
  10103. $searchInput.val(word);
  10104. window.setTimeout(function() {clearButton.refresh(); }, 0);
  10105. });
  10106. initAutoComplete($searchInput);
  10107.  
  10108. $closeExplorer.find('a').on('click', function() {
  10109. WatchController.closeSearch();
  10110. });
  10111.  
  10112. // メニュー拡張
  10113. var
  10114. detachMenuItems = function() {
  10115. WatchItLater.videoExplorerMenu.myShortcuts .detach();
  10116. WatchItLater.videoExplorerMenu.videoRanking.detach();
  10117. WatchItLater.videoExplorerMenu.favTags .detach();
  10118. WatchItLater.videoExplorerMenu.favMylists .detach();
  10119.  
  10120. $inputForm.detach();
  10121. $closeExplorer.detach();
  10122. },
  10123. attachMenuItems = function() {
  10124. if (conf.enableFavTags ) { WatchItLater.videoExplorerMenu.favTags.attach(); }
  10125. if (conf.enableFavMylists) { WatchItLater.videoExplorerMenu.favMylists.attach(); }
  10126. WatchItLater.videoExplorerMenu.videoRanking.attach();
  10127. WatchItLater.videoExplorerMenu.myShortcuts.attach();
  10128.  
  10129.  
  10130. $('.videoExplorerMenu')
  10131. .find('.itemList>li:first').append($inputForm)
  10132. .end().find('.errorMessage').after($closeExplorer);
  10133. };
  10134. controller._refreshMenu_org = controller._refreshMenu;
  10135. controller._refreshMenu = $.proxy(function() {
  10136. detachMenuItems();
  10137. this._refreshMenu_org();
  10138. attachMenuItems();
  10139. }, controller);
  10140. controller.showDeflist_org = controller.showDeflist;
  10141. controller.showMylist_org = controller.showMylist;
  10142. controller.showOtherUserVideos_org = controller.showOtherUserVideos;
  10143. controller.showOwnerVideo_org = controller.showOwnerVideo;
  10144. controller.searchVideo_org = controller.searchVideo;
  10145. controller.showDeflist = $.proxy(function() {
  10146. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10147. location.href = "/my/mylist";
  10148. return;
  10149. }
  10150. this.showDeflist_org();
  10151. }, controller);
  10152. controller.showMylist = $.proxy(function(id) {
  10153. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10154. location.href = "/mylist/" + id;
  10155. return;
  10156. }
  10157. this.showMylist_org(id);
  10158. }, controller);
  10159. controller.showOtherUserVideos = $.proxy(function(id, name) {
  10160. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10161. location.href = "/user/" + id;
  10162. return;
  10163. }
  10164. this.showOtherUserVideos_org(id, name);
  10165. }, controller);
  10166. controller.showOwnerVideo = $.proxy(function() {
  10167. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10168. location.href = "/user/" + WatchController.getOwnerId();
  10169. return;
  10170. }
  10171. this.showOwnerVideo_org();
  10172. }, controller);
  10173. controller.searchVideo = $.proxy(function(word, type) {
  10174. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10175. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  10176. location.href = (type === 'tag' ? 'tag' : 'search') + "/" + encodeURIComponent(word) + sortOrder;
  10177. return;
  10178. }
  10179. this.searchVideo_org(word, type);
  10180. }, controller);
  10181.  
  10182. EventDispatcher.addEventListener('onBottomContentTabViewReset', function(name) {
  10183. if (name === 'outline') {
  10184. WatchController.closeSearch();
  10185. }
  10186. });
  10187.  
  10188.  
  10189. EventDispatcher.addEventListener('on.config.enableFavTags',
  10190. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  10191. EventDispatcher.addEventListener('on.config.enableFavMylists',
  10192. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  10193.  
  10194. EventDispatcher.addEventListener('onVideoExplorerOpened', function(content) {
  10195. setTimeout(function() {
  10196. if (conf.videoExplorerHack) {
  10197. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  10198. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  10199. }
  10200. }, 100);
  10201. clearButton.refresh();
  10202.  
  10203. $('body').removeClass('videoExplorerOpening');
  10204. $('.videoExplorerMenu').addClass('initialized');
  10205. });
  10206. EventDispatcher.addEventListener('onVideoExplorerRefreshEnd', function(content) {
  10207. if (content.getType() === ContentType.USER_VIDEO) {
  10208. var items = content.getItems();
  10209. if (items.length === 1 && items[0].getContentItemType() !== ContentItemType.VIDEO) {
  10210. // ユーザーの投稿動画一覧が公開マイリスト一つだけだったら自動でそれを開く
  10211. items[0].stepIn();
  10212. return;
  10213. }
  10214. }
  10215. });
  10216. EventDispatcher.addEventListener('onVideoExplorerRefreshStart', function(content) {
  10217. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  10218. });
  10219. EventDispatcher.addEventListener('onVideoExplorerOpening', function(content) {
  10220. $('body').addClass('videoExplorerOpening');
  10221. adjustVideoExplorerSize(true);
  10222. });
  10223. EventDispatcher.addEventListener('onVideoExplorerClosing', function(content) {
  10224. });
  10225.  
  10226. EventDispatcher.addEventListener('onBeforeVideoExplorerMenuClear', function() {
  10227. detachMenuItems();
  10228. });
  10229.  
  10230. EventDispatcher.addEventListener('onUpdateSettingPanelVisible', function(isVisible, panel) {
  10231. if (isVisible && WatchController.isSearchMode()) {
  10232. setTimeout(function() {
  10233. WatchController.closeSearch();
  10234. setTimeout(function() {
  10235. playerConnector.updateSettingsPanelVisible(true, panel);
  10236. }, 800);
  10237. }, 100);
  10238. }
  10239. });
  10240.  
  10241. EventDispatcher.addEventListener('onFirstVideoExplorerOpened', function() {
  10242. window.console.time('onFirstVideoExplorerOpen');
  10243. EventDispatcher.addEventListener('onWindowResizeEnd', adjustVideoExplorerSize);
  10244. EventDispatcher.addEventListener('onVideoInitialized', adjustVideoExplorerSize);
  10245. window.console.timeEnd('onFirstVideoExplorerOpen');
  10246. });
  10247.  
  10248. var duration_match = /^([0-9]+):([0-9]+)/;
  10249. controller._item2playlistItem = function (item) {
  10250. // 動画長が入るようにする
  10251. var length_seconds = 0, len = item.getLength ? item.getLength() : '', m;
  10252. if (typeof len === 'string' && (m = duration_match.exec(len)) !== null) {
  10253. length_seconds = m[1] * 60 + m[2] * 1;
  10254. }
  10255. return new WatchApp.ns.model.playlist.PlaylistItem({
  10256. id : item.getId(),
  10257. title : item.getTitle(),
  10258. thumbnail_url : item.getThumbnailUrl(),
  10259. view_counter : item.getViewCounter(),
  10260. num_res : item.getNumRes(),
  10261. mylist_counter: item.getMylistCounter(),
  10262. mylist_comment: item.getMylistComment(),
  10263. first_retrieve: item.getFirstRetrieve(),
  10264. ads_counter : item.getUadCounter(),
  10265. length_seconds: length_seconds
  10266. });
  10267. };
  10268.  
  10269. initVideoExplorerItemContent();
  10270.  
  10271. $('#playerAlignmentArea').append($toggleCommentPanel);
  10272. $toggleCommentPanel.on('click', function() {
  10273. AnchorHoverPopup.hidePopup();
  10274. $('#playerTabWrapper').toggleClass('w_active');
  10275. $toggleCommentPanel.toggleClass('w_active', $('#playerTabWrapper').hasClass('w_active'));
  10276. setTimeout(function() {
  10277. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  10278. }, 1000);
  10279. }).on('mouseover', function() {
  10280. AnchorHoverPopup.hidePopup();
  10281. });
  10282.  
  10283. var toggleVideoExplorerHack = function(v) {
  10284. $('#videoExplorer, #content, #footer, #bottomContentTabContainer').toggleClass('w_adjusted', v);
  10285. if (v) {
  10286. $('#content').append($('.videoExplorerMenu'));
  10287. if (WatchController.isSearchMode()) {
  10288. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  10289. adjustVideoExplorerSize(true);
  10290. }
  10291. } else {
  10292. $('.videoExplorerContentWrapper').before($('.videoExplorerMenu'));
  10293. setTimeout(function() {
  10294. if (WatchController.isSearchMode()) {
  10295. playerConnector.updatePlayerConfig({playerViewSize: 'small'});
  10296. WatchApp.ns.util.WindowUtil.shake();
  10297. }
  10298. }, 1000);
  10299. }
  10300. };
  10301. EventDispatcher.addEventListener('on.config.videoExplorerHack', toggleVideoExplorerHack);
  10302. toggleVideoExplorerHack(conf.videoExplorerHack);
  10303.  
  10304.  
  10305. watchPageRouter._prepareState_org = watchPageRouter._prepareState;
  10306. watchPageRouter._prepareState = $.proxy(function(state) {
  10307. if (
  10308. conf.videoExplorerHack &&
  10309. WatchController.isSearchMode() &&
  10310. state.getVideoId() !== this._currentState.getVideoId()
  10311. ) {
  10312. state.prepare({
  10313. video: {id: this._watchInfoModel.v}
  10314. });
  10315. return state;
  10316. } else {
  10317. return this._prepareState_org(state);
  10318. }
  10319. }, watchPageRouter);
  10320. window.WatchApp.ns.model.state.WatchPageState.prototype.isVideoStateChange =
  10321. function(state) {
  10322. return this.getVideoId() !== state.getVideoId();
  10323. };
  10324.  
  10325.  
  10326. } // end initVideoExplorer
  10327.  
  10328. function initVideoExplorerItemContent() {
  10329.  
  10330. // 動画情報表示のテンプレートを拡張
  10331. var
  10332. overrideItemTemplate = function() {
  10333. var menu =
  10334. '<div class="thumbnailHoverMenu">' +
  10335. '<button class="showLargeThumbnail" onclick="WatchItLater.onShowLargeThumbnailClick(this);" title="大きいサムネイルを表示">+</button>' +
  10336. '<button class="deleteFromMyMylist" onclick="WatchItLater.onDeleteFromMyMylistClick(this);">マイリスト外す</button>' +
  10337. '</div>', $menu = $(menu);
  10338.  
  10339. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html());
  10340. $template.find('.column1 .thumbnailContainer').append($menu).end()
  10341. .find('.column4 .balloon').before($menu.clone()).end()
  10342. .find('.column4 .balloon').remove().end()
  10343. .find('.messageContainer').remove().end()
  10344. .find('.lastResBody')
  10345. .before($('<p class="descriptionShort"/><p class="itemMylistComment mylistComment"/>')).end()
  10346. .find('.noImage').remove().end()
  10347. //.find.remove('div.descriptionShort').end()
  10348. // .before($('<p class="descriptionShort"/>')).end()
  10349. // .find('.descriptionShort')
  10350. // .after($('<p class="itemMylistComment mylistComment"/>')).end()
  10351. .find('.createdTime').after($('<div class="nicorepoOwnerIconContainer"><a target="_blank"><img /></a></div>'));
  10352. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html($template.html());
  10353. $template = $menu = null;
  10354.  
  10355. },
  10356. onDeleteFromMyMylistClick = function(elm) {
  10357. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  10358. var contentList = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer.getContentList();
  10359. var
  10360. $elm = $(elm),
  10361. $item = $elm.closest('.item'),
  10362. $videoItem = $item, //.find(''),//$elm.parent().parent(),
  10363. watchId = $item.attr('data-watch-id'),
  10364. ac = contentList.getActiveContent(),
  10365. type = contentList.getActiveContentType(),
  10366. onUpdate = function(status, result) {
  10367. if (status !== "ok") {
  10368. Popup.alert('削除に失敗: ' + result.error.description);
  10369. } else {
  10370. $videoItem.animate({opacity: 0.3}, 500);
  10371. }
  10372. };
  10373.  
  10374. if (type === ContentType.MYLIST_VIDEO) {
  10375. if (!ac.getIsMine()) { return; }
  10376. Mylist.deleteMylistItem(watchId, ac.getMylistId(), onUpdate, 0);
  10377. } else
  10378. if (type === ContentType.DEFLIST_VIDEO) {
  10379. Mylist.deleteDefListItem(watchId, onUpdate, 0);
  10380. }
  10381. },
  10382. onShowLargeThumbnailClick = function (elm) {
  10383. var $item = $(elm).closest('.item');
  10384. var src = $item.attr('data-thumbnail');
  10385. if (!src) { return; }
  10386. showLargeThumbnail(src);
  10387. };
  10388. overrideItemTemplate();
  10389. WatchItLater.onDeleteFromMyMylistClick = onDeleteFromMyMylistClick;
  10390. WatchItLater.onShowLargeThumbnailClick = onShowLargeThumbnailClick;
  10391.  
  10392. // 動画情報表示の拡張
  10393. var ItemView = WatchApp.ns.components.videoexplorer.view.content.item.AbstractVideoContentItemView;
  10394. ItemView.prototype._setView_org = ItemView.prototype._setView;
  10395.  
  10396. ItemView.prototype.update_org = ItemView.prototype.update;
  10397. ItemView.prototype.update = function() {
  10398. // 動画情報表示をゴリゴリいじる場所
  10399. var item = this._item, $item = this._$item;
  10400. this.update_org(item);
  10401.  
  10402. this._$item.find('.deleteFromMyMylist').data('watchId', this._item.getId());
  10403. if (item._mylistComment) { // マイリストコメント
  10404. $item.find('.mylistComment').css({display: 'block'});
  10405. } else {
  10406. $item.find('.mylistComment').remove();
  10407. }
  10408.  
  10409. var lastResBody = item.getLastResBody();
  10410. if (lastResBody.length > 0) {
  10411. this._$lastResBody.css('cssText', 'display: block !important').text(lastResBody);
  10412. } else {
  10413. this._$lastResBody.remove();
  10414. }
  10415.  
  10416. var thumbnail = this._$thumbnail.attr('src').replace('.jpg.M', '');
  10417. if (item.isMiddleThumbnail()) {
  10418. this._$item.find('.thumbnailContainer')
  10419. .css('background-image', 'url(' + thumbnail + ')');
  10420. this._$thumbnail.remove();
  10421. } else {
  10422. this._$item
  10423. .addClass('smallThumbnail')
  10424. .find('.thumbnailContainer')
  10425. .css('background-image', 'url(' + thumbnail + ')');
  10426. }
  10427. $item.attr({
  10428. 'data-thumbnail': thumbnail,
  10429. 'data-watch-id': item.getId()
  10430. });
  10431.  
  10432.  
  10433. if (item._seed && item._seed._info) {
  10434. var info = item._seed._info;
  10435. if (info.nicorepo_owner) { // ニコレポ
  10436. $item.addClass(info.nicorepo_className).addClass('nicorepoResult');
  10437. var owner = info.nicorepo_owner;
  10438. var $iconContainer = $item.find('.nicorepoOwnerIconContainer'), $icon = $iconContainer.find('img'), $link = $iconContainer.find('a');
  10439. $icon.attr('src', owner.icon);
  10440. $link.attr({'href': owner.page, 'data-ownerid': owner.id, 'title': owner.name + ' さん'});
  10441. if (info.nicorepo_className.indexOf('log-user-') >= 0) {
  10442. $link.attr(
  10443. 'onclick',
  10444. 'if (arguments[0].button > 0) return; arguments[0].preventDefault();' +
  10445. 'WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController.showOtherUserVideos(this.dataset.ownerid, this.title);' +
  10446. 'WatchApp.ns.util.WindowUtil.scrollFit($("#videoExplorer"));'
  10447. );
  10448. }
  10449. if (info.nicorepo_log.length > 1) {
  10450. $item.find('.descriptionShort').html(info.nicorepo_log.join('<br>'));
  10451. }
  10452. // ニコレポは再生数が取れないので-で埋める
  10453. this._$viewCount .html('-');
  10454. this._$commentCount.html('-');
  10455. this._$mylistCount .html('-');
  10456. $iconContainer = $icon = $link = null;
  10457. }
  10458. }
  10459. if (item._seed && typeof item._seed.description_full === 'string' && item._seed.description_full.length > 150) {
  10460. this._$descriptionShort.attr('title', item._seed.description_full);
  10461. }
  10462. $item = null;
  10463.  
  10464. };
  10465. ItemView = null;
  10466.  
  10467. } // end initVideoExplorerItemContent
  10468.  
  10469.  
  10470.  
  10471. var lastVideoOwnerJson = '';
  10472. function onWatchInfoReset(watchInfoModel) {
  10473. $('body').toggleClass('w_channel', watchInfoModel.isChannelVideo());
  10474. EventDispatcher.dispatch('onWatchInfoReset', watchInfoModel);
  10475. var owner = WatchController.getOwnerInfo(), owner_json = JSON.stringify(owner);
  10476. if (lastVideoOwnerJson.length > 0 && lastVideoOwnerJson !== owner_json) {
  10477. EventDispatcher.dispatch('onVideoOwnerChanged', owner);
  10478. }
  10479. lastVideoOwnerJson = owner_json;
  10480. }
  10481.  
  10482. function onScreenModeChange(sc) {
  10483. setTimeout(function() {
  10484. EventDispatcher.dispatch('onScreenModeChange', sc);
  10485. }, 500);
  10486. }
  10487.  
  10488. function initMylistPanel($, conf, w) {
  10489. var iframe = Mylist.getPanel('');
  10490. iframe.id = "mylist_add_frame";
  10491. iframe.className += " fixed";
  10492. w.document.body.appendChild(iframe);
  10493. iframe.hide(); // ページの初期化が終わるまでは表示しない
  10494.  
  10495. var $iframe = $(iframe);
  10496. $iframe.find('.mylistSelect').attr('accesskey', ':');
  10497.  
  10498. var toggleMylistMenuInFull = function(v) {
  10499. $('.mylistPopupPanel')
  10500. .toggleClass('hideInFull', v === 'hide')
  10501. .toggleClass('hideAllInFull', v === 'hideAll');
  10502. };
  10503. EventDispatcher.addEventListener('on.config.hideMenuInFull', toggleMylistMenuInFull);
  10504. toggleMylistMenuInFull(conf.hideMenuInFull);
  10505.  
  10506. var setMylistPanelPosition = function(v) {
  10507. $iframe
  10508. .toggleClass('left', v.indexOf('left') >= 0)
  10509. .toggleClass('top', v.indexOf('top') >= 0);
  10510. setTimeout(function() {
  10511. $('#yukkuriPanel')
  10512. .toggleClass('mylistPanelLeft', v.indexOf('left') >= 0 && v.indexOf('top') < 0);
  10513. }, 500);
  10514. };
  10515. EventDispatcher.addEventListener('on.config.mylistPanelPosition', setMylistPanelPosition);
  10516. if (conf.mylistPanelPosition !== '') setMylistPanelPosition(conf.mylistPanelPosition);
  10517.  
  10518. var $footer = $('#footer'), $window = $(window);
  10519. var toggleMylistPanelStyle = function() {
  10520. if ($footer.is(':visible')) {
  10521. $iframe.toggleClass('black', $window.scrollTop() + $window.innerHeight() - $footer.offset().top >= 0);
  10522. } else {
  10523. $iframe.removeClass('black');
  10524. }
  10525. };
  10526.  
  10527. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10528. toggleMylistPanelStyle();
  10529. var newVideoId = watchInfoModel.id;
  10530. var newWatchId = watchInfoModel.v;
  10531. iframe.watchId(newVideoId, newWatchId);
  10532. if (isFirst) iframe.show();
  10533. });
  10534. EventDispatcher.addEventListener('onScrollEnd', toggleMylistPanelStyle);
  10535. EventDispatcher.addEventListener('onWindowResizeEnd', toggleMylistPanelStyle);
  10536. EventDispatcher.addEventListener('onScreenModeChange', toggleMylistPanelStyle);
  10537. EventDispatcher.addEventListener('on.config.hidePlaylist', toggleMylistPanelStyle);
  10538. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', toggleMylistPanelStyle);
  10539. } //
  10540.  
  10541. function initScreenMode() {
  10542. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10543. if (conf.autoBrowserFull) {
  10544. setTimeout(function() {
  10545. if ($('body').hasClass('up_marquee') && conf.disableAutoBrowserFullIfNicowari) {
  10546. // ユーザーニコ割があるときは自動全画面にしない
  10547. return;
  10548. }
  10549. if (WatchController.isSearchMode()) { // TODO: localStorageに直接アクセスすんな
  10550. var settingSize = (localStorage["PLAYER_SETTINGS.LAST_PLAYER_SIZE"] === '"normal"') ? 'normal' : 'medium';
  10551. WatchController.changeScreenMode(settingSize);
  10552. }
  10553. WatchController.changeScreenMode('browserFull');
  10554. onWindowResizeEnd(); // TODO:;;;;
  10555. }, 100);
  10556. } else {
  10557. if (conf.autoOpenSearch && !WatchController.isSearchMode() && !$('body').hasClass('full_with_browser')) {
  10558. WatchController.openSearch();
  10559. }
  10560. if (conf.autoScrollToPlayer) {
  10561. // 初回のみ、プレイヤーが画面内に納まっていてもタグの位置まで自動スクロールさせる。(ファーストビューを固定するため)
  10562. // 二回目以降は説明文や検索結果からの遷移なので、必要最小限の動きにとどめる
  10563. if (!WatchController.isSearchMode() || isFirst) {
  10564. WatchController.scrollToVideoPlayer(isFirst);
  10565. }
  10566. }
  10567. }
  10568. });
  10569.  
  10570.  
  10571. var lastPlayerConfig = null, lastScreenMode = '';
  10572.  
  10573. function hideIfNeed() {
  10574. if (conf.controllerVisibilityInFull === 'hidden') {
  10575. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({oldTypeCommentInput: true, oldTypeControlPanel: false});
  10576. $('body').addClass('hideCommentInput');
  10577. } else {
  10578. var $w = $(window), iw = $w.innerWidth(), ih = $w.innerHeight();
  10579. var controllerH = 46, inputH = 30;
  10580. }
  10581. }
  10582.  
  10583. function restoreVisibility() {
  10584. if (lastPlayerConfig !== null) {
  10585. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10586. {oldTypeCommentInput: !!lastPlayerConfig.oldTypeCommentInput, oldTypeControlPanel: !!lastPlayerConfig.oldTypeControlPanel}
  10587. );
  10588. $(window).resize();
  10589. }
  10590. $('body').removeClass('hideCommentInput');
  10591. }
  10592.  
  10593. function togglePlaylist(v) {
  10594. v = (typeof v === 'boolean') ? v : !$('body').hasClass('fullWithPlaylist');
  10595. $('body').toggleClass('fullWithPlaylist', v);
  10596. if (v) {
  10597. watch.PlaylistInitializer.playlistView.resetView();
  10598. }
  10599. return v;
  10600. }
  10601.  
  10602. function initShield() {
  10603. var shield = $('<div id="fullScreenMenuContainer" />');
  10604. shield.click(function(e) {
  10605. e.stopPropagation();
  10606. togglePlaylist();
  10607. });
  10608. $('#external_nicoplayer').after(shield);
  10609. shield = null;
  10610. }
  10611. initShield();
  10612.  
  10613. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  10614. var mode = sc.mode;
  10615. $('body').removeClass('w_fullScreenMenu');
  10616. if (mode === 'browserFull' && lastScreenMode !== mode) {
  10617. lastPlayerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get();
  10618. conf.setValue('lastControlPanelPosition', lastPlayerConfig.oldTypeControlPanel ? 'bottom' : 'over');
  10619.  
  10620. hideIfNeed();
  10621. //if (conf.enableTrueBrowserFull) togglePlaylist(conf.enableTrueBrowserFull);
  10622. } else
  10623. if (lastScreenMode === 'browserFull' && mode !== 'browserFull') {
  10624. conf.setValue('lastControlPanelPosition', '');
  10625. $('#playerContainerSlideArea').css({height: ''}); // wall bug fix
  10626. restoreVisibility();
  10627. }
  10628. lastScreenMode = mode;
  10629. });
  10630.  
  10631. $(window).on('beforeunload.watchItLater', function(e) {
  10632. conf.setValue('lastControlPanelPosition', '');
  10633. restoreVisibility();
  10634. });
  10635.  
  10636. var wheelCounter = 0, wheelTimer = null;
  10637. EventDispatcher.addEventListener('onWheelNoButton', function(e, delta) {
  10638. if (!conf.enableFullScreenMenu) return;
  10639. if ((e.target.tagName !== 'OBJECT' && e.target.tagName !== 'HTML') ||
  10640. !WatchController.isFullScreen()) return;
  10641. if (wheelTimer) {
  10642. wheelCounter += delta;
  10643. } else {
  10644. wheelCounter = 0;
  10645. wheelTimer = setTimeout(function() {//
  10646. wheelTimer = null;
  10647. if (Math.abs(wheelCounter) > 3) {
  10648. EventDispatcher
  10649. .dispatch('onToggleFullScreenMenu',
  10650. $('body').toggleClass('w_fullScreenMenu', wheelCounter < 0).hasClass('w_fullScreenMenu')
  10651. );
  10652. AnchorHoverPopup.hidePopup();
  10653. }
  10654. }, 500);
  10655. }
  10656. });
  10657.  
  10658. TouchEventDispatcher.onflick(function(e) {
  10659. if (!conf.enableFullScreenMenu) return;
  10660. if ((e.direction !=='up' && e.direction !=='down') || e.startEvent.target.tagName !== 'OBJECT' || !WatchController.isFullScreen()) return;
  10661. if (wheelTimer) {
  10662. clearTimeout(wheelTimer);
  10663. wheelTimer = null;
  10664. }
  10665. EventDispatcher
  10666. .dispatch('onToggleFullScreenMenu',
  10667. $('body').toggleClass('w_fullScreenMenu', e.direction === 'down').hasClass('w_fullScreenMenu')
  10668. );
  10669. AnchorHoverPopup.hidePopup();
  10670. });
  10671.  
  10672. var $fullScreenMenuContainer = $('<div id="fullScreenMenuContainer"/>');
  10673. var $fullScreenModeSwitch = $([
  10674. '<button class="fullScreenModeSwitch button">',
  10675. 'プレイリスト: ',
  10676. '<span class="modeStatus playlistOpening">▼</span>',
  10677. '<span class="modeStatus playlistClosing">▲</span>',
  10678. '</button>'
  10679. ].join('')).click(togglePlaylist);
  10680. var $toggleStageVideo = $([
  10681. '<button class="stageVideoSwitch button">',
  10682. 'アクセラレーション: ',
  10683. '<span class="modeStatus mode_off">OFF</span>',
  10684. '<span class="modeStatus mode_on">ON</span>',
  10685. '</button>'
  10686. ].join('')).attr('title', 'ハードウェアアクセラレーションのON/OFF').click(function() { WatchController.toggleStageVideo(); });
  10687. var $toggleSetting = $([
  10688. '<button class="toggleSetting button">',
  10689. '</button>'
  10690. ].join('')).text('⛭設定').attr('title', '設定パネルを開閉します').click(function() { ConfigPanel.toggle();});
  10691. $fullScreenMenuContainer.append($fullScreenModeSwitch).append($toggleStageVideo).append($toggleSetting);
  10692. $('#nicoplayerContainerInner').append($fullScreenMenuContainer);
  10693.  
  10694.  
  10695. if (conf.lastControlPanelPosition === 'bottom' || conf.lastControlPanelPosition === 'over') {
  10696. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  10697. console.log('restore oldTypeControlPanel ? ', conf.lastControlPanelPosition === 'bottom');
  10698. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10699. {oldTypeControlPanel: conf.lastControlPanelPosition === 'bottom'}
  10700. );
  10701. });
  10702. }
  10703.  
  10704. } // end initScreenMode()
  10705.  
  10706.  
  10707.  
  10708.  
  10709. function initPlaylist($, conf, w) {
  10710. var
  10711. playlist = watch.PlaylistInitializer.playlist,
  10712. blankVideoId = 'sm20353707', blankVideoUrl = 'http://www.nicovideo.jp/watch/' + blankVideoId + '?',
  10713. redirectPageUrl = 'http://www.nicovideo.jp/stamp',
  10714. items = {},
  10715. toCenter = function() { // 表示位置調整
  10716. var
  10717. pm = WatchApp.ns.view.playlist.PlaylistManager,
  10718. pv = watch.PlaylistInitializer.playlistView,
  10719. pl = playlist,
  10720. current = pl.getPlayingIndex(),
  10721. cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()),
  10722. center = Math.round(cols / 2);
  10723.  
  10724. if (cols < 1) { return; }
  10725. var currentLeft = pm.getLeftSideIndex();
  10726. pv.scroll(Math.max(0, current - center + 1));
  10727. },
  10728. scroll = function(d) {
  10729. var isEffectEnabled = watch.PlaylistInitializer.playlistView.isEffectEnabled;
  10730. var left = WatchApp.ns.view.playlist.PlaylistManager.getLeftSideIndex();
  10731. watch.PlaylistInitializer.playlistView.isEffectEnabled = false;
  10732. watch.PlaylistInitializer.playlistView.scroll(Math.max(0, left + d));
  10733. watch.PlaylistInitializer.playlistView.isEffectEnabled = isEffectEnabled;
  10734. };
  10735.  
  10736. playlist.isAutoPlay = playlist.isContinuous; // 互換用
  10737. playlist.enableAutoPlay = playlist.enableContinuous;
  10738. playlist.disableAutoPlay = playlist.disableContinuous;
  10739.  
  10740. $('#playlist').find('.playlistInformation').on('dblclick.watchItLater', function(e) {
  10741. e.preventDefault();
  10742. e.stopPropagation();
  10743. toCenter();
  10744. });
  10745.  
  10746. EventDispatcher.addEventListener('onVideoInitialized', function() {
  10747. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView, pl = watch.PlaylistInitializer.playlist;
  10748. var current = pl.getPlayingIndex(), cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()), center = Math.floor(cols / 2);
  10749. if (pm.getLeftSideIndex() + cols <= pl.getNextPlayingIndex()) { toCenter(); }
  10750. });
  10751.  
  10752. $('#playlistContainer .prevArrow, #playlistContainer .nextArrow').on('mousewheel.watchItLater', function(e, delta) {
  10753. if (WatchController.isFullScreen()) { return; }
  10754. e.preventDefault();
  10755. e.stopPropagation();
  10756. scroll(delta *-1);
  10757. }).attr('title', 'ホイールで左右にスクロール');
  10758.  
  10759. // フルスクリーン中はプレイリストのどこでもスクロールできたほうがいいね
  10760. $('#playlist').on('mousewheel.watchItLater', function(e, delta) {
  10761. if (WatchController.isFullScreen() || WatchController.isSearchMode() || $('#footer').hasClass('noBottom')) {
  10762. e.preventDefault();
  10763. e.stopPropagation();
  10764. scroll(delta *-1);
  10765. }
  10766. });
  10767.  
  10768. EventDispatcher.addEventListener('onWheelAndButton', function(e, delta, button) {
  10769. if (WatchController.isFullScreen()) { return; }
  10770. if ($('#playlist').hasClass('dragging')) {
  10771. e.preventDefault();
  10772. scroll(delta *-1);
  10773. }
  10774. });
  10775.  
  10776. var
  10777. updatePos = function() {
  10778. if (
  10779. conf.hashPlaylistMode === 2 || (conf.hashPlaylistMode === 1 && WatchController.isPlaylistActive())) {
  10780. LocationHashParser.setValue('playlist', exportPlaylist());
  10781. LocationHashParser.updateHash();
  10782. }
  10783. if (conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') {
  10784. setTimeout(function() {
  10785. w[conf.storagePlaylistMode].setItem('watchItLater_playlist', JSON.stringify(exportPlaylist()));
  10786. }, 0);
  10787. }
  10788.  
  10789. var pos = Math.max((playlist.getPlayingIndex() + 1), 1) + '/' + Math.max(playlist.getItems().length, 1);
  10790. $('.generationMessage').text(pos + " - \n" + $('.generationMessage').text().replace(/^.*\n/, ''));
  10791. },
  10792. resetView = function() {
  10793. watch.PlaylistInitializer.playlistView.resetView();
  10794. },
  10795. exportPlaylist = function(option, type, continuous, shuffle) {
  10796. var
  10797. items = playlist.currentItems,
  10798. list = [],
  10799. current = 0,
  10800. len = conf.debugMode ? Math.min(600, items.length) : Math.min(300, items.length);
  10801.  
  10802. for (var i = 0; i < len; i++) {
  10803. var item = items[i];
  10804. if (item._isPlaying) current = i;
  10805. list.push([
  10806. item.id,
  10807. parseInt(item.mylistCounter, 10).toString(36),
  10808. parseInt(item.viewCounter, 10).toString(36),
  10809. parseInt(item.numRes, 10).toString(36),
  10810. (item.thumbnailUrl ? parseInt(item.thumbnailUrl.split('?i=')[1], 10).toString(36) : 'c490r'),
  10811. ].join(',') + ':' + item.title
  10812. );
  10813. }
  10814. return {
  10815. a: (typeof continuous === 'boolean') ? continuous : WatchController.isPlaylistContinuous(),
  10816. r: (typeof shuffle === 'boolean') ? shuffle : WatchController.isPlaylistRandom(),
  10817. o: option || playlist.option,
  10818. t: type || playlist.type,
  10819. i: list,
  10820. c: current
  10821. };
  10822. },
  10823. importPlaylist = function(list) {
  10824. var PlaylistItem = WatchApp.ns.model.playlist.PlaylistItem, newItems = [], uniq = {}, currentIndex = -1;
  10825.  
  10826. WatchController.clearPlaylist();
  10827. var currentItem = playlist.currentItems[0];
  10828. if (!currentItem) {
  10829. var wm = watchInfoModel;
  10830. currentItem = new PlaylistItem({
  10831. id: wm.v,
  10832. title: wm.title,
  10833. mylist_counter: wm.mylistCount,
  10834. view_counter: wm.viewCount,
  10835. num_res: wm.commentCount,
  10836. thumbnail_url: wm.thumbnail,
  10837. first_retriee: wm.postedAt
  10838. });
  10839. }
  10840.  
  10841. for (var i = 0, len = list.i.length; i < len; i++) {
  10842. var
  10843. dat = list.i[i],
  10844. c = dat.split(':')[0].split(','),
  10845. title = dat.replace(/^.*:/, ''),
  10846. id = c[0],
  10847. thumbnailId = parseInt(c[4], 36);
  10848.  
  10849. if (uniq[id] || typeof id !== 'string') { continue; }
  10850. uniq[id] = true;
  10851. if (id === watchInfoModel.v) {
  10852. currentIndex = i;
  10853. newItems.push(currentItem);
  10854. } else {
  10855. var item = new PlaylistItem({
  10856. id: id,
  10857. title: title.replace('<', '&lt;').replace('>', '&gt;'), // ないはずだけど一応
  10858. mylist_counter: parseInt(c[1], 36),
  10859. view_counter: parseInt(c[2], 36),
  10860. num_res: parseInt(c[3], 36),
  10861. thumbnail_url: 'http://tn-skr' + ((thumbnailId % 4) + 1) + '.smilevideo.jp/smile?i=' + thumbnailId,
  10862. first_retrieve: null
  10863. });
  10864. newItems.push(item);
  10865. }
  10866. }
  10867. // 復元するリストの中に現在の動画がなかった
  10868. if (currentIndex === -1) {
  10869. if (typeof list.c === 'number') {
  10870. if (list.c < newItems.length) {
  10871. currentIndex = list.c + 1;
  10872. newItems.splice(currentIndex, 0, currentItem);
  10873. } else {
  10874. currentIndex = list.length;
  10875. newItems.push(currentItem);
  10876. }
  10877. } else {
  10878. newItems.unshift(currentItem);
  10879. currentIndex = 0;
  10880. }
  10881. }
  10882.  
  10883. var isAutoPlay = playlist.isContinuous();//isAutoPlay();
  10884. playlist.reset(newItems, 'WatchItLater', list.t, list.o);
  10885. if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  10886. playlist.disableContinuous();
  10887. }
  10888. if (currentIndex >= 0) { playlist.playingItem = newItems[currentIndex]; }
  10889. if (list.a) { playlist.enableContinuous(); }
  10890. if (list.r) {
  10891. if (watchInfoModel.id === blankVideoId) {
  10892. setTimeout(function() {
  10893. WatchController.shufflePlaylist();
  10894. }, 3000);
  10895. } else {
  10896. playlist.enableContinuous();
  10897. }
  10898. }
  10899. },
  10900. $dialog = null, $savelink = null, $continuous, $shuffle,
  10901. openSaveDialog = function() {
  10902. function resetLink() {
  10903. var playlist = exportPlaylist(null, null, $continuous.is(':checked'), $shuffle.is(':checked'));
  10904. playlist.o = playlist.o || [];
  10905. playlist.o.name = $savelink.text();
  10906. playlist.t = 'mylist';
  10907. LocationHashParser.setValue('playlist', playlist);
  10908. if (!playlist.r) {
  10909. LocationHashParser.setValue('redirectWatchId', watchInfoModel.id);
  10910. } else {
  10911. LocationHashParser.deleteValue('redirectWatchId');
  10912. }
  10913. $savelink
  10914. //.attr('href', blankVideoUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10915. .attr('href', redirectPageUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10916. .unbind();
  10917. }
  10918. function closeDialog() {
  10919. $dialog.removeClass('show');
  10920. }
  10921.  
  10922. if (!$dialog) {
  10923. $dialog = $('<div id="playlistSaveDialog" />');
  10924. $dialog.append($([
  10925. '<div class="shadow"></div>',
  10926. '<div class="formWindow"><div class="formWindowInner">',
  10927. '<h3>プレイリスト保存用リンク(実験中)</h3>',
  10928. '<p class="link"><a target="_blank" class="playlistSaveLink">保存用リンク</a><button class="editButton">編集</button></p>',
  10929. '<label><input type="checkbox" class="continuous">開始時に連続再生をONにする</label><br>',
  10930. '<label><input type="checkbox" class="shuffle">開始時にリストをシャッフルする</label>',
  10931. '<p class="desc">リンクを右クリックしてコピーやブックマークする事で、現在のプレイリストを保存する事ができます。</p>',
  10932. '<button class="closeButton">閉じる</button>',
  10933. '</div></div>',
  10934. ''].join('')));
  10935. $savelink = $dialog.find('a').attr('added', 1);
  10936. $continuous = $dialog.find('.continuous');
  10937. $shuffle = $dialog.find('.shuffle');
  10938. $dialog.find('.shadow').on('click', closeDialog);
  10939. $dialog.find('.editButton').on('click', function() {
  10940. var newTitle = prompt('タイトルを編集', $savelink.text());
  10941. if (newTitle) {
  10942. $savelink.text(newTitle);
  10943. resetLink();
  10944. }
  10945. });
  10946. $continuous.on('click', resetLink);
  10947. $shuffle .on('click', resetLink);
  10948. $dialog.find('.closeButton').on('click', closeDialog);
  10949.  
  10950. $('body').append($dialog);
  10951. }
  10952. $savelink.text(
  10953. $('#playlist .generationMessage')
  10954. .text()
  10955. .replace(/^.*?\n/, '')
  10956. .replace(/^.*「/, '')
  10957. .replace(/」.*?$/, '')
  10958. .replace(/ *- \d{4}-\d\d-\d\d \d\d:\d\d$/, '') +
  10959. ' - ' + WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M', new Date())
  10960. );
  10961. $continuous.attr('checked', WatchController.isPlaylistActive());
  10962. $shuffle .attr('checked', WatchController.isPlaylistRandom());
  10963. resetLink();
  10964. $dialog.addClass('show');
  10965. },
  10966. PlaylistMenu = (function($, conf, w, playlist) {
  10967. var $popup = null, $generationMessage = $('#playlist').find('.generationMessage'), self;
  10968.  
  10969. var
  10970. enableContinuous = function() {
  10971. playlist.enableContinuous();
  10972. },
  10973. createDom = function() {
  10974. $popup = $('<div class="playlistMenuPopup popupMenu"/>')
  10975. .addClass('pop')
  10976. .toggleClass('w_touch', isTouchActive);
  10977. var $ul = $('<ul/>');
  10978. $popup.click(function() {
  10979. self.hide();
  10980. });
  10981. var $shuffle = $('<li>シャッフル: 全体</li>').click(function(e) {
  10982. WatchController.shufflePlaylist();
  10983. enableContinuous();
  10984. });
  10985. $ul.append($shuffle);
  10986. var $shuffleR = $('<li>シャッフル: 右</li>').click(function(e) {
  10987. WatchController.shufflePlaylist('right');
  10988. enableContinuous();
  10989. });
  10990. $ul.append($shuffleR);
  10991.  
  10992. var $next = $('<li>検索結果を追加: 次に再生</li>').click(function() {
  10993. WatchController.appendSearchResultToPlaylist('next');
  10994. enableContinuous();
  10995. });
  10996. $ul.append($next);
  10997.  
  10998. var $insert = $('<li>検索結果を追加: 末尾</li>').click(function() {
  10999. WatchController.appendSearchResultToPlaylist();
  11000. enableContinuous();
  11001. });
  11002. $ul.append($insert);
  11003.  
  11004. var $clear = $('<li>リストを消去: 全体</li>').click(function() {
  11005. WatchController.clearPlaylist();
  11006. //watch.PlaylistInitializer.playlist.setPlaybackMode('normal');
  11007. });
  11008. $ul.append($clear);
  11009.  
  11010. var $clearLeft = $('<li>リストを消去: 左</li>').click(function() {
  11011. WatchController.clearPlaylist('left');
  11012. });
  11013. $ul.append($clearLeft);
  11014. var $clearRight = $('<li>リストを消去: 右</li>').click(function() {
  11015. WatchController.clearPlaylist('right');
  11016. });
  11017. $ul.append($clearRight);
  11018.  
  11019. var $saver = $('<li>リストを保存(実験中)</li>').click(function() {
  11020. openSaveDialog();
  11021. });
  11022.  
  11023. $ul.append($saver);
  11024. $popup.append($ul);
  11025. $('body').append($popup);
  11026. },
  11027. show = function() {
  11028. if ($popup === null) { createDom(); }
  11029. var offset = $generationMessage.offset(), $window = $(window) , pageBottom = $window.scrollTop() + $window.innerHeight();
  11030. $popup.css({
  11031. left: offset.left,
  11032. top: Math.min(offset.top + 24, pageBottom - $popup.outerHeight())
  11033. }).show();
  11034. },
  11035. hide = function() {
  11036. if ($popup) { $popup.hide(); }
  11037. },
  11038. toggle = function() {
  11039. if ($popup === null || !$popup.is(':visible')) {
  11040. show();
  11041. } else {
  11042. hide();
  11043. }
  11044. };
  11045.  
  11046. $generationMessage.click(function(e) {
  11047. e.preventDefault();
  11048. self.toggle();
  11049. });
  11050.  
  11051. $('body').on('click.watchItLater', function(e) {
  11052. var tagName = e.target.tagName, className = e.target.className;
  11053. if (className !== 'generationMessage') {
  11054. self.hide();
  11055. }
  11056. });
  11057. self = {
  11058. show: show,
  11059. hide: hide,
  11060. toggle: toggle
  11061. };
  11062. return self;
  11063. })($, conf, w, playlist);
  11064.  
  11065.  
  11066. var hashlist = LocationHashParser.getValue('playlist');
  11067. if (hashlist && hashlist.i && hashlist.i.length > 0) {
  11068. try {
  11069. console.log('restore playlist!!');
  11070. importPlaylist(hashlist);
  11071. if (conf.hashPlaylistMode < 1) {
  11072. LocationHashParser.removeHash();
  11073. }
  11074. setTimeout(function() { resetView(); } , 3000);
  11075. } catch (e) {
  11076. console.log(e);
  11077. console.trace();
  11078. }
  11079. } else
  11080. if ((conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') && w[conf.storagePlaylistMode] && !playlist.isContinuous()) {
  11081. try {
  11082. console.log('restore playlist:' + conf.storagePlaylistMode);
  11083. var list = JSON.parse(w[conf.storagePlaylistMode].getItem('watchItLater_playlist'));
  11084. if (list !== null) { importPlaylist(list); }
  11085. setTimeout(function() { resetView(); } , 3000);
  11086. } catch (e) {
  11087. console.log('プレイリストの復元に失敗!', e);
  11088. }
  11089. } else {
  11090. updatePos();
  11091. }
  11092.  
  11093.  
  11094. // EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  11095. // if ($('body').hasClass('full_with_browser')) {
  11096. // // フル画面時プレイリストを閉じる
  11097. // if (conf.autoClosePlaylistInFull) { $('#content').find('.browserFullPlaylistClose:visible').click(); }
  11098. // }
  11099. // });
  11100.  
  11101. EventDispatcher.addEventListener('onVideoExplorerOpened', function() {
  11102. // 2013/09/26 本家側で開閉を記録するようになった -> 2014/03/03 また記憶しなくなった
  11103.  
  11104. // 通常画面でプレイリストを表示にしてるなら、開いた状態をデフォルトにする
  11105. if (conf.hidePlaylistInVideoExplorer === false) {
  11106. playlist.open();
  11107. }
  11108. });
  11109. $('#playlist .browserFullOption a').on('click', function() {
  11110. if (WatchController.isSearchMode()) {
  11111. conf.setValue('hidePlaylistInVideoExplorer', !conf.hidePlaylistInVideoExplorer);
  11112. }
  11113. });
  11114.  
  11115. EventDispatcher.addEventListener('on.config.hashPlaylistMode', function(v) {
  11116. if (v === 0) {
  11117. LocationHashParser.deleteValue('playlist');
  11118. LocationHashParser.removeHash();
  11119. } else
  11120. if (v === 1 || v === 2) {
  11121. var msg = [
  11122. '【警告】「プレイリストが消えないモード」は実験中の機能です。',
  11123. '',
  11124. 'この機能を使うと、ページをリロードしたりブックマークしてもプレイリストが消えなくなりますが、',
  11125. 'データを力技で保持するため、ページのURLがものすごく長く(※)なります。',
  11126. '',
  11127. 'そのため、ブラウザのパフォーマンスが低下したり、未知の不具合が発生する可能性があります。',
  11128. 'それでもこの機能を使ってみたい!という方だけ「OK」を押してください。',
  11129. '',
  11130. '※ 数千~数万文字くらい!',
  11131. ''].join('\n');
  11132. if (confirm(msg)) {
  11133. LocationHashParser.setValue('playlist', exportPlaylist());
  11134. LocationHashParser.updateHash();
  11135. } else {
  11136. conf.setValue('hashPlaylistMode', 0);
  11137. ConfigPanel.refresh();
  11138. }
  11139. }
  11140. });
  11141.  
  11142. $('#playlist .browserFullOption').on('click.bugfix', resetView);
  11143.  
  11144. $('.generationMessage, .prevArrow, .nextArrow, .playbackOption').on('mouseover', function() {
  11145. AnchorHoverPopup.hidePopup();
  11146. });
  11147.  
  11148. playlist.addEventListener('changePlaybackMode', function(mode) {
  11149. console.log('changePlaybackMode', mode, conf.hashPlaylistMode);
  11150. if (mode === 'normal' && conf.hashPlaylistMode < 2) {
  11151. LocationHashParser.removeHash();
  11152. } else {
  11153. updatePos();
  11154. }
  11155. });
  11156.  
  11157. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11158. updatePos();
  11159. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  11160. resetView();
  11161. });
  11162. EventDispatcher.addEventListener('onWatchInfoReset', function() {
  11163. updatePos();
  11164. });
  11165. playlist.addEventListener('reset', function() {
  11166. EventDispatcher.dispatch('onPlaylistReset');
  11167. updatePos();
  11168. });
  11169. playlist.addEventListener('update', function() {
  11170. EventDispatcher.dispatch('onPlaylistUpdate');
  11171. updatePos();
  11172. });
  11173. });
  11174.  
  11175. var togglePlaylistDisplay = function(v) {
  11176. var $playlist = $('#playlist');
  11177. if (!v) {
  11178. $playlist.addClass('w_show').removeClass('w_closing');
  11179. } else {
  11180. $playlist.addClass('w_closing');
  11181. setTimeout(function() { $playlist.removeClass('w_show');}, 500);
  11182. }
  11183. };
  11184. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  11185. togglePlaylistDisplay(conf.hidePlaylist);
  11186.  
  11187. // プレイリスト消えないモードの時はプレイリストを勝手におすすめに置き換える機能をキャンセル
  11188. (function() {
  11189. var ld = WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController._videoExplorerPlaylistResetArgumentsLoader;
  11190. ld.load_org = ld.load;
  11191. ld.load = $.proxy(function(a, b, c) {
  11192. if (conf.storagePlaylistMode !== '') {
  11193. return;
  11194. }
  11195. this.load_org(a, b, c);
  11196. }, ld);
  11197. ld = null;
  11198. })();
  11199.  
  11200. } // end initPlaylist
  11201.  
  11202.  
  11203. function initPageHeader($, conf, w) {
  11204. $('.videoDetailExpand h2').addClass('videoDetailToggleButton');
  11205. } // end initPageHeader
  11206.  
  11207.  
  11208. function initVideoTagContainer($, conf, w) {
  11209. var $videoHeaderTagEditLinkArea = null, $toggleTagEditText = null, baseTagHeight = 72, currentHeight = 72;
  11210. var tagListView = watch.TagInitializer.tagViewController.tagListView, $videoHeader = $('.videoHeaderOuter');
  11211.  
  11212. tagListView.getCurrentDefaultHeight_org = tagListView.getCurrentDefaultHeight;
  11213. tagListView.getCurrentDefaultHeight = function() {
  11214. if ($('body').hasClass('full_with_browser')) {
  11215. return tagListView.getCurrentDefaultHeight_org();
  11216. }
  11217. return currentHeight;
  11218. };
  11219.  
  11220. $videoHeaderTagEditLinkArea = $('.toggleTagEditInner .videoHeaderTagEditLinkArea');
  11221. $('.toggleTagEdit').append($videoHeaderTagEditLinkArea);
  11222. $toggleTagEditText = $('<span class="toggleText">' + $('.toggleTagEditInner').text() + '</span>');
  11223. $('.toggleTagEditInner').empty().append($toggleTagEditText).append($videoHeaderTagEditLinkArea);
  11224.  
  11225. var onTagReset = function() {
  11226. try {
  11227. // タグが2行以下だったら自動的に狭くする処理
  11228. if (!conf.enableAutoTagContainerHeight) { return; }
  11229. currentHeight = Math.min(baseTagHeight, $('#videoTagContainer').find('.tagInner').innerHeight());
  11230.  
  11231. if (baseTagHeight !== currentHeight) {
  11232. var $toggle = $('#videoTagContainer').find('.toggleTagEdit');
  11233. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  11234.  
  11235. if (currentHeight < 36) { // 1行以下の時
  11236. $videoHeader.addClass('tag1Line');
  11237. } else {
  11238. if (currentHeight <= 60) { // 2行以下の時
  11239. $videoHeader.addClass('tag2Lines');
  11240. }
  11241. }
  11242. watch.TagInitializer.tagViewController.tagListView.fit();
  11243. } else {
  11244. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  11245. watch.TagInitializer.tagViewController.tagListView.fit();
  11246. }
  11247. } catch (e) {
  11248. console.log(e);
  11249. }
  11250. };
  11251.  
  11252. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11253. EventDispatcher.addEventListener('onVideoInitialized', onTagReset);
  11254. });
  11255. watch.TagInitializer.tagList.addEventListener('reset', onTagReset);
  11256. if (conf.enableAutoTagContainerHeight) {
  11257. watch.TagInitializer.tagViewController.tagViewPinStatus.changeStatus(true);
  11258. }
  11259. window.setTimeout(onTagReset, 1000);
  11260.  
  11261. $videoHeaderTagEditLinkArea = $toggleTagEditText = null;
  11262.  
  11263.  
  11264. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org = WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived;
  11265. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived = function(a) {
  11266. //console.log('onTagDataReceived', a);
  11267. if (conf.disableTagReload) {
  11268. return;
  11269. }
  11270. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org(a);
  11271. };
  11272.  
  11273. } // end initVideoTagContainer
  11274.  
  11275.  
  11276.  
  11277. function initVideoReview($, conf, w) {
  11278. var __css__ = Util.here(function() {/*
  11279. .sidePanel #videoReview { margin: 0 auto; }
  11280. #outline.w_compact #videoReview { width: 300px; }
  11281. #outline.w_compact textarea.newVideoReview { width: 277px; }
  11282. #outline.w_compact #videoReviewHead { width: 283px; }
  11283. #outline.w_compact #videoReview .stream { width: 300px; }
  11284. #outline.w_compact #videoReview .inner { width: 300px; }
  11285. #outline.w_compact .commentContent { width: 278px; }
  11286. #outline.w_compact .commentContentBody { width: 232px; }
  11287. .sidePanel.w_review #videoReview { width: 308px; }
  11288. .sidePanel.w_review textarea.newVideoReview { width: 286px; }
  11289. .sidePanel.w_review #videoReviewHead { width: 291px; }
  11290. .sidePanel.w_review #videoReview .stream { width: 308px; }
  11291. .sidePanel.w_review #videoReview .inner { width: 308px; }
  11292. .sidePanel.w_review .commentContent { width: 286px; }
  11293. .sidePanel.w_review .commentContentBody { width: 240px; }
  11294. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview { width: 400px; }
  11295. body:not(.full_with_browser) .w_wide .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11296. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReviewHead { width: 383px; }
  11297. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .stream { width: 400px; }
  11298. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .inner { width: 400px; }
  11299. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContent { width: 378px; }
  11300. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContentBody { width: 332px; }
  11301. body.videoExplorer .sidePanel.w_review #videoReview { width: 400px; }
  11302. body.videoExplorer .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11303. body.videoExplorer .sidePanel.w_review #videoReviewHead { width: 383px; }
  11304. body.videoExplorer .sidePanel.w_review #videoReview .stream { width: 400px; }
  11305. body.videoExplorer .sidePanel.w_review #videoReview .inner { width: 400px; }
  11306. body.videoExplorer .sidePanel.w_review .commentContent { width: 378px; }
  11307. body.videoExplorer .sidePanel.w_review .commentContentBody { width: 332px; }
  11308.  
  11309. body:not(.videoExplorer) .sidePanel .commentUserProfile, body:not(.videoExplorer) .sidePanel .panelTrigger {
  11310. display: none !important;
  11311. }
  11312. body.videoExplorer .sidePanel .commentUserProfile {
  11313. position: fixed;
  11314. top: 36px !important;
  11315. left: auto !important;
  11316. right: 0 !important;
  11317. z-index: 11000;
  11318. }
  11319.  
  11320. .sidePanel .getMoreReviewComment {
  11321. margin-bottom: 256px;
  11322. }
  11323. */});
  11324. var reviewCss = addStyle(__css__, 'videoReviewCss');
  11325.  
  11326. /*
  11327. EventDispatcher.addEventListener('onFirstVideoInitialized', function() { setTimeout(function() {
  11328. var elms = [
  11329. '#videoReview',
  11330. 'textarea.newVideoReview',
  11331. '#videoReviewHead',
  11332. '#videoReview .stream',
  11333. '#videoReview .inner',
  11334. '.commentContent',
  11335. '.commentContentBody'
  11336. ];
  11337. var css = [], $baseElement = $('#videoReview');
  11338. var makeCss = function (targetWidth, preSel) {
  11339. var px = targetWidth - $baseElement.outerWidth();
  11340. for (var v in elms) {
  11341. var $e = $(elms[v]), newWidth = $e.width() + px;
  11342. css.push([
  11343. preSel, elms[v], ' { width: ', newWidth,'px; }\n'
  11344. ].join(''));
  11345. }
  11346. };
  11347. makeCss(300, '#outline.w_compact ');
  11348. makeCss(308, '.sidePanel.w_review ');
  11349. makeCss(400, '.sidePanel.w_review.w_wide ');
  11350. makeCss(400, 'body.videoExplorer .sidePanel.w_review ');
  11351. console.log(css.join(''));
  11352. var reviewCss = addStyle(css.join(''), 'videoReviewCss');
  11353. }, 3000);});
  11354. */
  11355. } // end initVideoReview
  11356.  
  11357. function initNews() {
  11358. var stopNicoNewsPolling = function() {
  11359. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemDispatcher.stop();
  11360. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemList.list.length = 0;
  11361. };
  11362. var toggleNoNews = function() {
  11363. $('#content').toggleClass('noNews', conf.hideNicoNews || conf.customPlayerSize !== '');
  11364. if ($('#content').hasClass('noNews')) {
  11365. stopNicoNewsPolling();
  11366. }
  11367. };
  11368.  
  11369. EventDispatcher.addEventListener('on.config.hideNicoNews', toggleNoNews);
  11370. EventDispatcher.addEventListener('on.config.customPlayerSize', toggleNoNews);
  11371.  
  11372. toggleNoNews();
  11373.  
  11374. if (conf.enableNewsHistory) { NicoNews.initialize(w); }
  11375. } //
  11376.  
  11377.  
  11378. function initEvents() {
  11379. var pac = watch.PlayerInitializer.playerAreaConnector;
  11380.  
  11381. pac.addEventListener("onVideoInitialized", onVideoInitialized);
  11382. pac.addEventListener("onVideoEnded", onVideoEnded);
  11383. pac.addEventListener("onVideoStopped", onVideoStopped);
  11384. // pac.addEventListener('onSystemMessageFatalErrorSended', onSystemMessageFatalErrorSended);
  11385. // watch.WatchInitializer.watchModel.addEventListener('error', function() {console.log(arguments);});
  11386.  
  11387. pac.addEventListener('updateSettingsPanelVisible', function(isVisible, panel) {
  11388. EventDispatcher.dispatch('onUpdateSettingPanelVisible', isVisible, panel);
  11389. });
  11390.  
  11391. watchInfoModel.addEventListener('reset', onWatchInfoReset);
  11392. watchInfoModel.addEventListener('beforeReset', function() {
  11393. window.console.time('watchInfoModelReset');
  11394. EventDispatcher.dispatch('onWatchInfoBeforeReset');
  11395. });
  11396. watchInfoModel.addEventListener('afterReset', function() {
  11397. window.console.timeEnd('watchInfoModelReset');
  11398. EventDispatcher.dispatch('onWatchInfoAfterReset');
  11399. });
  11400. watch.PlayerInitializer.playerScreenMode.addEventListener('change', onScreenModeChange);
  11401.  
  11402. var explorer = watch.VideoExplorerInitializer.videoExplorer;
  11403. explorer.addEventListener('openStart', onVideoExplorerOpening);
  11404. explorer.addEventListener('openEnd', onVideoExplorerOpened);
  11405. explorer.addEventListener('closeStart', onVideoExplorerClosing);
  11406. explorer.addEventListener('closeEnd', onVideoExplorerClosed);
  11407. explorer.addEventListener('refreshStart', onVideoExplorerRefreshStart);
  11408. explorer.addEventListener('refreshEnd', onVideoExplorerRefreshEnd);
  11409. explorer.addEventListener('changeCurrentPage', onVideoExplorerChangePage); //
  11410.  
  11411.  
  11412. $('body').dblclick(function(e){
  11413. var tagName = e.target.tagName, cls = e.target.className || '';
  11414. if (tagName === 'SELECT' || tagName === 'INPUT' || tagName === 'BUTTON' || cls.match(/mylistPopupPanel/)) {
  11415. return;
  11416. }
  11417. if (!WatchController.isFullScreen()) {
  11418. AnchorHoverPopup.hidePopup();
  11419. if (conf.doubleClickScroll) {
  11420. e.preventDefault();
  11421. EventDispatcher.dispatch('onScrollReset');
  11422. WatchController.scrollToVideoPlayer(true);
  11423. }
  11424. }
  11425. });
  11426.  
  11427. var bottomContentTabView = WatchApp.ns.view.BottomContentTabView.getInstance();
  11428. bottomContentTabView.addEventListener('changeContent', function(name) {
  11429. EventDispatcher.dispatch('onBottomContentTabViewReset', name);
  11430. });
  11431.  
  11432.  
  11433. Mylist.onDefMylistUpdate(function() {
  11434. //WatchController.clearDeflistCache();
  11435. });
  11436. Mylist.onMylistUpdate(function(info) {
  11437. WatchController.clearMylistCache(info.groupId);
  11438. });
  11439.  
  11440. $(window).on('beforeunload.watchItLater', function(e) {
  11441. conf.setValue('lastCommentVisibility', WatchController.commentVisibility() ? 'visible' : 'hidden');
  11442. }).on('resize', window._.debounce(function() {
  11443. AnchorHoverPopup.hidePopup();
  11444. EventDispatcher.dispatch('onWindowResizeEnd');
  11445. }, 1000));
  11446.  
  11447. //$(document).on('scroll', WatchApp.ns.event.EventDispatcher.throttle(function() {
  11448. $(document).on('scroll', function() {
  11449. if (document.body.style.pointerEvents !== 'none') {
  11450. document.body.style.pointerEvents = 'none';
  11451. }
  11452. });
  11453. $(document).on('scroll', window._.debounce(function() {
  11454. document.body.style.pointerEvents = '';
  11455. EventDispatcher.dispatch('onScrollEnd');
  11456. }, 500));
  11457.  
  11458. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11459. pac.addEventListener('onVideoChangeStatusUpdated', onVideoChangeStatusUpdated);
  11460. });
  11461.  
  11462. window.addEventListener('message', function(event) {
  11463. if (event.origin.indexOf('nicovideo.jp') < 0) return;
  11464. try {
  11465. var data = JSON.parse(event.data);
  11466. if (data.id !== 'WatchItLater') { return; }
  11467.  
  11468. EventDispatcher.dispatch('onMessage', data.body, data.type);
  11469.  
  11470. } catch (e) {
  11471. console.log(
  11472. '%cError: window.onMessage - ',
  11473. 'color: red; background: yellow',
  11474. e, event.origin, event.data);
  11475. }
  11476. });
  11477. } //
  11478.  
  11479. function initAdditionalButtons() {
  11480.  
  11481. var createPlaylistToggle = function() {
  11482. var $playlistToggle = $('<button title="プレイリスト表示/非表示" class="playlistToggle">プレイリスト</button>');
  11483.  
  11484. $playlistToggle.on('click', function() {
  11485. AnchorHoverPopup.hidePopup();
  11486. conf.setValue('hidePlaylist', !!!conf.hidePlaylist);
  11487. });
  11488.  
  11489. var togglePlaylistDisplay = function(v) {
  11490. $playlistToggle.toggleClass('w_show', !v);
  11491. };
  11492.  
  11493. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  11494. togglePlaylistDisplay(conf.hidePlaylist);
  11495.  
  11496. return $playlistToggle;
  11497. };
  11498. var createOpenExplorer = function() {
  11499. return $('<button class="openVideoExplorer">検索▼</button>').on('click', function() {
  11500. WatchController.openSearch();
  11501. if (!$('body').hasClass('content-fix')) {
  11502. WatchController.scrollToVideoPlayer(true);
  11503. }
  11504. });
  11505. };
  11506. var $div = $('<div class="bottomAccessContainer"/>').append(createPlaylistToggle()).append(createOpenExplorer());
  11507.  
  11508.  
  11509. var $headerMenu = $('<li class="watchItLaterSettingMenu"><a href="javascript:;" title="WatchItLaterの設定">WatchItLater設定</a></li>');
  11510. $('#siteHeaderRightMenuFix').after($headerMenu);
  11511.  
  11512. $('#outline .outer').before($div);
  11513. var $container = $('<div class="bottomConfButtonContainer" />'), $conf = $('<button title="WatchItLaterの設定">設定</button>');
  11514. var $explorerConf = $('<button><span class="open">`・ω・´</span><span class="close">´・ω・`</span></button>');
  11515. var toggleConf = function(e) {
  11516. e.stopPropagation();
  11517. AnchorHoverPopup.hidePopup();
  11518. ConfigPanel.toggle();
  11519. };
  11520. $container.append($conf);
  11521. $conf.addClass('openConfButton');
  11522. $conf.on('click', toggleConf);//.attr('accesskey', 'p');
  11523. $('#outline .outer').before($container);
  11524. $headerMenu.find('a').on('click', toggleConf);//.attr('accesskey', 'p');
  11525.  
  11526.  
  11527. $('.videoExplorerBody').append($explorerConf);
  11528. $explorerConf
  11529. .on('click',
  11530. function() { WatchItLater.config.set('videoExplorerHack', !WatchItLater.config.get('videoExplorerHack')); })
  11531. .addClass('videoExplorerConfig');
  11532.  
  11533. var $body = $('body'), $window = $(window);
  11534. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  11535. if (WatchController.isSearchMode() || WatchController.isFullScreen()) { return; }
  11536. var w = $div.outerWidth(), threshold = ($(window).innerWidth() - 960) / 2;
  11537. $('#outline').toggleClass('under960', w > threshold && !$('#footer').hasClass('noBottom'));
  11538. });
  11539. } // end initAdditionalButtons
  11540.  
  11541.  
  11542. function initSearchContent($, conf, w) {
  11543. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11544. var searchStatus = WatchApp.ns.components.nicosearchstatus ?
  11545. WatchApp.ns.components.nicosearchstatus : WatchApp.ns.components.videoexplorer;
  11546. var SearchSortOrder = searchStatus.model.SearchSortOrder;
  11547. var SearchType = searchStatus.model.SearchType;
  11548. var View = WatchApp.ns.components.videoexplorer.view.content.SearchContentView;
  11549. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11550. var explorer = vec.getVideoExplorer();
  11551. var content = explorer.getContentList().getContent(ContentType.SEARCH);
  11552. var relatedTag = new NicoSearchRelatedTag({});
  11553. var newSearch = new NewNicoSearch({});
  11554. var newSearchWrapper = new NewNicoSearchWrapper({search: newSearch});
  11555. var pager = content._pager;
  11556. var __css__ = Util.here(function() {/*
  11557. .newSearchOption {
  11558. text-align: center; margin-bottom: 16px; padding: 8px;
  11559. background: #eee;
  11560. display: none;
  11561. }
  11562. .newSearchOption select, .newSearchOption label{
  11563. margin-right: 32px;
  11564. }
  11565. .newSearchOption .reset{
  11566. cursor: pointer; background: #eee;
  11567. }
  11568. .newSearchOption p{
  11569. margin: 8px;
  11570. }
  11571. .newSearchOption .ownerName {
  11572. }
  11573. .w_sugoiSearch .newSearchOption {
  11574. display: block;
  11575. }
  11576.  
  11577. .relatedTagList {
  11578. }
  11579. .relatedTagList p{
  11580. display: inline-block; margin: 4px;
  11581. }
  11582. .relatedTagList li, .relatedTagList ul {
  11583. display: inline;
  11584. margin: 0 8px 0 0;
  11585. list-style: none;
  11586. word-break: break-all;
  11587. }
  11588. .relatedTagList li {
  11589. background: #f4f4f4; padding: 2px 4px;
  11590. border: solid 1px #999;
  11591. border-radius: 4px;
  11592. line-height: 180%;
  11593. }
  11594. .relatedTagList li:hover {
  11595. }
  11596. .relatedTagList li:hover a{
  11597. text-decoration: none;
  11598. }
  11599.  
  11600. .sugoiOption {
  11601. display: none;
  11602. }
  11603. .w_sugoiSearch .sugoiOption {
  11604. display: block; background: #eef;
  11605. }
  11606. .w_sugoiSearch optgroup.sugoiOption {
  11607. font-weight: bolder;
  11608. }
  11609.  
  11610. */});
  11611. addStyle(__css__, 'searchContent');
  11612.  
  11613. // 動画表示のテンプレート拡張
  11614. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html());
  11615. $template.find('.searchSortOrder')
  11616. .append([
  11617. '<optgroup label="新検索専用" class="sugoiOption">',
  11618. '<option value="sort=_hot&amp;order=d"" class="sugoiOption">人気が高い順</option>',
  11619. '<option value="sort=_popular&amp;order=d" class="sugoiOption">並び順指定なし</option>',
  11620. '<option value="sort=_explore&amp;order=d" class="sugoiOption">新着優先</option>',
  11621. '</optgroup>'
  11622. ].join(''));
  11623. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html($template.html());
  11624. $template = null;
  11625.  
  11626.  
  11627.  
  11628. var RelatedTagView = function() { this.initialize.apply(this, arguments); };
  11629. RelatedTagView.prototype = {
  11630. _$view: null,
  11631. _relatedTag: null,
  11632. initialize: function(params) {
  11633. this._relatedTag = params.relatedTag;
  11634. this._$view = params.$view;
  11635. this._$list = this._$view.find('ul');
  11636. },
  11637. getView: function() {
  11638. return this._$view;
  11639. },
  11640. detach: function() {
  11641. this._$view.detach();
  11642. },
  11643. update: function(candidates) {
  11644. if (!candidates || candidates.length < 1) {
  11645. this.detach();
  11646. return;
  11647. }
  11648. if (candidates.length > 10) {
  11649. candidates = candidates
  11650. .map(function(a){return {weight:Math.random(), value:a};})
  11651. .sort(function(a, b){return a.weight - b.weight;})
  11652. .map(function(a){return a.value;});
  11653. }
  11654. var $ul = this._$list.empty();
  11655. for (var i = 0, len = Math.min(10, candidates.length); i < len; i++) {
  11656. $ul.append(this._create$tag(candidates[i].tag));
  11657. }
  11658. },
  11659. clear: function() {
  11660. this._$list.empty();
  11661. },
  11662. _create$tag: function(text) {
  11663. var
  11664. $a = $('<a/>')
  11665. .html(text)
  11666. .attr('href', 'http://search.nicovideo.jp/video/tag/' + encodeURIComponent(text))
  11667. .on('click', Util.Closure.openNicoSearch(text)),
  11668. $tag = $('<li/>').append($a);
  11669. return $tag;
  11670. }
  11671. };
  11672.  
  11673. var NewSearchOptionView = function() { this.initialize.apply(this, arguments); };
  11674. NewSearchOptionView.prototype = {
  11675. _content: null,
  11676. _$view: null,
  11677. _$startTimeRange: null,
  11678. _$lengthSecondsRange: null,
  11679. initialize: function(params) {
  11680. this._content = params.content;
  11681. this._$view = params.$view;
  11682. this._$startTimeRange = this._$view.find('.startTimeRange');
  11683. this._$lengthSecondsRange = this._$view.find('.lengthSecondsRange');
  11684. this._$musicDlFilter = this._$view.find('.musicDlFilter');
  11685. this._$ownerFilter = this._$view.find('.ownerFilter');
  11686. this._$ownerName = this._$view.find('.ownerName');
  11687. this._$resetButton = this._$view.find('.reset');
  11688.  
  11689. this._$startTimeRange .val(params.startTimeRange || '');
  11690. this._$lengthSecondsRange.val(params.lengthSecondsRange || '');
  11691. this._$musicDlFilter .attr('checked', !!params.musicDlFilter);
  11692.  
  11693. this._$startTimeRange .on('change', $.proxy(this._onStartTimeRangeSelect , this));
  11694. this._$lengthSecondsRange.on('change', $.proxy(this._onLengthSecondsRangeSelect, this));
  11695. this._$musicDlFilter .on('click', $.proxy(this._onMusicDlFilterChange , this));
  11696. this._$ownerFilter .on('click', $.proxy(this._onOwnerFilterChange , this));
  11697. this._$resetButton .on('click', $.proxy(this.reset , this));
  11698.  
  11699. EventDispatcher.addEventListener('onVideoOwnerChanged', $.proxy(this.onVideoOwnerChange, this));
  11700. this._$ownerName.text(WatchController.getOwnerName());
  11701. },
  11702. getView: function() {
  11703. return this._$view;
  11704. },
  11705. detach: function() {
  11706. this._$view.detach();
  11707. },
  11708. update: function() {
  11709. },
  11710. onVideoOwnerChange: function(ownerInfo) {
  11711. this._content.setOwnerFilter(false);
  11712. this._$ownerFilter.prop('checked', false);
  11713. this._$ownerName.text(ownerInfo.name);
  11714. },
  11715. _onStartTimeRangeSelect: function() {
  11716. this._content.setStartTimeRange(this._$startTimeRange.val());
  11717. this.contentRefresh();
  11718. },
  11719. _onLengthSecondsRangeSelect: function() {
  11720. this._content.setLengthSecondsRange(this._$lengthSecondsRange.val());
  11721. this.contentRefresh();
  11722. },
  11723. _onMusicDlFilterChange: function() {
  11724. this._content.setMusicDlFilter(!!this._$musicDlFilter.prop('checked'));
  11725. this.contentRefresh();
  11726. },
  11727. _onOwnerFilterChange: function() {
  11728. this._content.setOwnerFilter(!!this._$ownerFilter.prop('checked'));
  11729. this.contentRefresh();
  11730. },
  11731. contentRefresh: function() {
  11732. var params = this._content.getParams();
  11733. params.page = 1;
  11734. this._content.changeState(params);
  11735. this._content.refresh({page: 1});
  11736. },
  11737. refresh: function() {
  11738. //console.log('refresh!', this._content.getOwnerFilter(), this._content.getMusicDlFilter(false));
  11739. this._$startTimeRange .val(this._content.getStartTimeRange('') || '');
  11740. this._$lengthSecondsRange.val(this._content.getLengthSecondsRange('') || '');
  11741. this._$musicDlFilter .prop('checked', !!this._content.getMusicDlFilter(false));
  11742. this._$ownerFilter .prop('checked', !!this._content.getOwnerFilter());
  11743. },
  11744. reset: function() {
  11745. var v = this._$startTimeRange.val() + this._$lengthSecondsRange.val();
  11746. if (v !== '') {
  11747. this._content.setStartTimeRange('');
  11748. this._content.setLengthSecondsRange('');
  11749. this._content.setMusicDlFilter(false);
  11750. this._$startTimeRange.val('');
  11751. this._$lengthSecondsRange.val('');
  11752. this._$musicDlFilter.prop('checked', false);
  11753. this._content.changeState({ page: 1 });
  11754. //this._content.refresh({ page: 1 });
  11755. }
  11756. }
  11757. };
  11758.  
  11759. var relatedTagView = new RelatedTagView({
  11760. relatedTag: relatedTag,
  11761. $view: $('<div class="relatedTagList"><p>関連タグ: </p><ul></ul></div>')
  11762. });
  11763. var newSearchOptionView = new NewSearchOptionView({
  11764. content: content,
  11765. startTimeRange: conf.searchStartTimeRange,
  11766. lengthSecondsRange: conf.searchLengthSecondsRange,
  11767. musicDlFilter: conf.searchMusicDlFilter,
  11768. $view: $([
  11769. '<div class="newSearchOption">',
  11770. '<span>投稿日時: </span>',
  11771. '<select class="startTimeRange" name="u">',
  11772. '<option selected="selected" value="" >指定なし</option>',
  11773. '<option value="24h">24時間以内</option>',
  11774. '<option value="1w" >1週間以内</option>',
  11775. '<option value="1m" >1ヶ月(30日)以内</option>',
  11776. '<option value="3m" >3ヶ月(90日)以内</option>',
  11777. '<option value="6m" >6ヶ月(180日)以内</option>',
  11778. '</select>',
  11779. '<span>再生時間: </span>',
  11780. '<select class="lengthSecondsRange" name="l">',
  11781. '<option selected="selected" value="" >指定なし</option>',
  11782. '<option value="short">5分以内</option>',
  11783. '<option value="long" >20分以上</option>',
  11784. '</select>',
  11785. '<p>',
  11786. '<label>',
  11787. '<input type="checkbox" name="m" class="musicDlFilter">音楽DL対応のみ</input>',
  11788. '</label>',
  11789. '<label>',
  11790. '<input type="checkbox" name="owner" class="ownerFilter"><span class="ownerName">この投稿者</span>&nbsp;の動画のみ</input>',
  11791. '</label>',
  11792. '</p>',
  11793. '</div>',
  11794. ''].join(''))
  11795. });
  11796.  
  11797.  
  11798.  
  11799. content._originalWord = '';
  11800. content.changeState_org = content.changeState;
  11801. content.changeState = $.proxy(function(params, callback) {
  11802. var word = WatchApp.get(params, 'searchWord', 'string', '');
  11803. var type = WatchApp.get(params, 'searchType', 'string', this.getSearchType());
  11804. if (typeof word === 'string' && word.length > 0) {
  11805. this._originalWord = word;
  11806.  
  11807. if (conf.defaultSearchOption && conf.defaultSearchOption !== '') {
  11808. if (word.indexOf(conf.defaultSearchOption) < 0 && !word.match(/(sm|nm|so)\d+/)) {
  11809. params.searchWord += " " + conf.defaultSearchOption;
  11810. }
  11811. }
  11812. }
  11813. AnchorHoverPopup.hidePopup();
  11814.  
  11815. EventDispatcher.dispatch('onSearchStart', this._originalWord, type);
  11816. this.changeState_org(params, callback);
  11817. }, content);
  11818.  
  11819. // ニコニコ新検索エンジンを使うための布石
  11820. content._searchEngineType = 'sugoi';//conf.searchEngine;
  11821. content._lastSearchEngineType = 'sugoi';//conf.searchEngine;
  11822. content.setSearchEngineType = $.proxy(function(type) {
  11823. this._searchEngineType = type;
  11824. this.updateSearchPageItemCount();
  11825. }, content);
  11826. content.updateSearchPageItemCount = $.proxy(function() {
  11827. this._pager._pageItemCount = this._searchEngineType === 'sugoi' ? conf.searchPageItemCount : 32;
  11828. }, content);
  11829. content.getSearchEngineType = $.proxy(function() {
  11830. return this._searchEngineType === 'sugoi' ? 'sugoi' : 'normal';
  11831. }, content);
  11832. content.setLastSearchEngineType = $.proxy(function(type) { this._lastSearchEngineType = type; }, content);
  11833. content.getLastSearchEngineType = $.proxy(function() { return this._lastSearchEngineType; }, content);
  11834. content._newSearchWrapper = newSearchWrapper;
  11835.  
  11836. content._startTimeRange = conf.searchStartTimeRange;
  11837. content._lengthSecondsRange = conf.searchLengthSecondsRange;
  11838. content._musicDlFilter = conf.searchMusicDlFilter;
  11839. content._ownerFilter = false;
  11840.  
  11841. content.getStartTimeRange = $.proxy(function() { return this._startTimeRange; }, content);
  11842. content.getLengthSecondsRange = $.proxy(function() { return this._lengthSecondsRange; }, content);
  11843. content.getMusicDlFilter = $.proxy(function() { return this._musicDlFilter; }, content);
  11844. content.getOwnerFilter = $.proxy(function() { return this._ownerFilter; }, content);
  11845. content.setStartTimeRange = $.proxy(function(value) {
  11846. this._startTimeRange = value;
  11847. conf.setValue('searchStartTimeRange', value);
  11848. }, content);
  11849. content.setLengthSecondsRange = $.proxy(function(value) {
  11850. this._lengthSecondsRange = value;
  11851. conf.setValue('searchLengthSecondsRange',value);
  11852. }, content);
  11853. content.setMusicDlFilter = $.proxy(function(value) {
  11854. this._musicDlFilter = !!value;
  11855. conf.setValue('searchMusicDlFilter', !!value);
  11856. }, content);
  11857. content.setOwnerFilter = $.proxy(function(value) {
  11858. this._ownerFilter = !!value;
  11859. }, content);
  11860.  
  11861. // 新検索独自のソート順への対応
  11862. var _searchSortOrder =
  11863. content._nicoSearchStatus ?
  11864. content._nicoSearchStatus._searchSortOrder : content._searchSortOrder;
  11865. _searchSortOrder._flush_org = _searchSortOrder._flush;
  11866. _searchSortOrder._flush = $.proxy(function() {
  11867. var sort = this._sort[SearchType.KEYWORD];
  11868. if (sort === '_hot' || sort === '_popular' || sort === '_explore') { // 新検索にしかないパラメータは保存しない
  11869. return;
  11870. }
  11871. this._flush_org();
  11872. }, _searchSortOrder);
  11873.  
  11874.  
  11875. EventDispatcher.addEventListener('on.config.searchPageItemCount', function() {
  11876. content.updateSearchPageItemCount();
  11877. });
  11878.  
  11879. content.getParams_org = content.getParams;
  11880. content.getParams = $.proxy(function() {
  11881. var params = this.getParams_org();
  11882. params = $.extend(true, {
  11883. l: this.getLengthSecondsRange(),
  11884. u: this.getStartTimeRange(),
  11885. m: this.getMusicDlFilter(),
  11886. size: this._pager._pageItemCount
  11887. }, params);
  11888. if (this.getOwnerFilter()) {
  11889. if (WatchController.isChannelVideo()) {
  11890. params.channelId = WatchController.getOwnerId();
  11891. } else {
  11892. params.userId = WatchController.getOwnerId();
  11893. }
  11894. }
  11895. return params;
  11896. }, content);
  11897.  
  11898. // タグ検索だけ毎回ソート順がデフォルトにリセットされるようになったので、
  11899. // デフォルト値を書き換えるという力技で対抗
  11900. SearchSortOrder.TAG_DEFAULT_SORT = conf.searchSortType;
  11901. SearchSortOrder.TAG_DEFAULT_ORDER = conf.searchSortOrder;
  11902. _searchSortOrder.getSortFromCookie = function() { return conf.searchSortType; };
  11903. _searchSortOrder.getOrderFromCookie = function() { return conf.searchSortOrder; };
  11904.  
  11905. content.load_org = content.load;
  11906. content.load = $.proxy(function(params, callback) {
  11907. var word = this.getSearchWord();
  11908. if (this.getSearchEngineType() !== 'sugoi' || word.length <= 0 || word.match(/(sm|nm|so)\d+/)) {
  11909. // 新検索ではもしかして~が取得できないため、検索ワードに動画IDっぽい文字列が含まれてる場合は旧タグ検索を使う。
  11910. this.setLastSearchEngineType('normal');
  11911. params.sort = 'n';
  11912. params.order = 'd';
  11913. this.load_org(params, callback);
  11914. } else {
  11915. this.setLastSearchEngineType('sugoi');
  11916. params = this.getParams();
  11917.  
  11918.  
  11919. this._newSearchWrapper.load(params, function(err, result) {
  11920. console.log('%cNewNicoSearchWrapper result', 'color: green;', result);
  11921. callback(err, result);
  11922. });
  11923. }
  11924. }, content);
  11925. content.setSearchEngineType('sugoi');//conf.searchEngine);
  11926.  
  11927. // EventDispatcher.addEventListener('on.config.searchEngine', function(type) {
  11928. // content.setSearchEngineType(type);
  11929. // });
  11930.  
  11931.  
  11932. var
  11933. overrideSearchSortOrder = function(proto) { // ソート順を記憶するためのフック
  11934. proto.getSort_org = proto.getSort;
  11935. proto.getSort = function() {
  11936. var sort = conf.searchSortType;
  11937. if ((sort === '_hot' || sort === '_popular' || sort === '_explore') && content.getLastSearchEngineType() !== 'sugoi') {
  11938. // 通常検索で新検索にしかないソート順だったらデフォルトのnを返す
  11939. return 'n';
  11940. }
  11941. return conf.searchSortType;
  11942. };
  11943.  
  11944. proto.setSort_org = proto.setSort;
  11945. proto.setSort = function(type, sort) {
  11946. conf.setValue('searchSortType', sort);
  11947. SearchSortOrder.TAG_DEFAULT_SORT = sort;
  11948. this.setSort_org(type, sort);
  11949. };
  11950.  
  11951. proto.getOrder_org = proto.getOrder;
  11952. proto.getOrder = function() {
  11953. return conf.searchSortOrder;
  11954. };
  11955.  
  11956. proto.setOrder_org = proto.setOrder;
  11957. proto.setOrder = function(type, order) {
  11958. if (content.getLastSearchEngineType() === 'sugoi') { // 新検索の時だけソート順を記憶
  11959. SearchSortOrder.TAG_DEFAULT_ORDER = order;
  11960. conf.setValue('searchSortOrder', order);
  11961. }
  11962. this.setOrder_org(type, order);
  11963. };
  11964. },
  11965. overrideSearchContentView = function(proto, relatedTag) {
  11966. proto._updateRelatedTag = function() {
  11967. if (!conf.enableRelatedTag) { return; }
  11968. var word = this._content._originalWord;
  11969. relatedTagView.clear();
  11970.  
  11971. if (typeof word === 'string' && word.length > 0) {
  11972. this._$header.append(relatedTagView.getView());
  11973. relatedTag.load(word, function(err, result) {
  11974. console.log('SearchContentView._updateRelatedTag', err, result);
  11975. if (err) {
  11976. console.log('load suggest fail', err, result);
  11977. } else {
  11978. relatedTagView.update(result.values);
  11979. }
  11980. });
  11981. }
  11982. };
  11983.  
  11984. proto.detach_org = proto.detach;
  11985. proto.detach = function() {
  11986. this.detach_org();
  11987. newSearchOptionView.detach();
  11988. relatedTagView.detach();
  11989. };
  11990.  
  11991. proto.onUpdate_org = proto.onUpdate;
  11992. proto.onUpdate = function() {
  11993. this.onUpdate_org();
  11994. this._$content.find('.searchBox').after(newSearchOptionView.getView());
  11995. this._updateRelatedTag();
  11996. var engine = this._content.getLastSearchEngineType();
  11997. newSearchOptionView.refresh();
  11998. $('.videoExplorerBody')
  11999. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  12000. .toggleClass('w_normalSearch', engine !== 'sugoi');
  12001. };
  12002.  
  12003. proto.onError_org = proto.onError;
  12004. proto.onError = function() {
  12005. this.onError_org();
  12006. this._$header.append(newSearchOptionView.getView());
  12007. this._updateRelatedTag();
  12008. var engine = this._content.getLastSearchEngineType();
  12009. $('.videoExplorerBody')
  12010. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  12011. .toggleClass('w_normalSearch', engine !== 'sugoi');
  12012. };
  12013.  
  12014. };
  12015.  
  12016. overrideSearchSortOrder(SearchSortOrder.prototype);
  12017. overrideSearchContentView(View.prototype, relatedTag);
  12018.  
  12019. } // end initSearchContent
  12020.  
  12021. function initUserVideoContent($, conf, w) {
  12022. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  12023. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  12024. var explorer = vec.getVideoExplorer();
  12025. var content = explorer.getContentList().getContent(ContentType.USER_VIDEO);
  12026. var pager = content._pager;
  12027.  
  12028. pager._pageItemCount = conf.searchPageItemCount;
  12029. pager._displayPageCount = 5;
  12030. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  12031. pager._pageItemCount = v;
  12032. });
  12033.  
  12034. }
  12035.  
  12036. function initUploadedVideoContent($, conf, w) {
  12037. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  12038. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  12039. var explorer = vec.getVideoExplorer();
  12040. var content = explorer.getContentList().getContent(ContentType.UPLOADED_VIDEO);
  12041. var pager = content._pager;
  12042.  
  12043.  
  12044. // // 本家のマイナーバグ修正
  12045. // content.setUserId_org = content.setUserId;
  12046. // content.setUserId = $.proxy(function(id) {
  12047. // var currentId = this.getUserId();
  12048. // if (currentId !== id) {
  12049. // this.setPage(1);
  12050. // }
  12051. // this.setUserId_org(id);
  12052. // }, content);
  12053.  
  12054. pager._pageItemCount = conf.searchPageItemCount;
  12055. pager._displayPageCount = 5;
  12056. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  12057. pager._pageItemCount = v;
  12058. });
  12059. }
  12060.  
  12061.  
  12062. var isSquareCssInitialized = false;
  12063. function initSquareThumbnail() {
  12064. var isSquare = true;// !!conf.squareThumbnail;
  12065. if (isSquare && !isSquareCssInitialized) {
  12066. var __css__ = Util.here(function() {/*
  12067. {* 元のCSSを打ち消すためにやや冗長 *}
  12068. #videoExplorer .noImage,
  12069. #videoExplorer.w_adjusted .item .thumbnail {
  12070. display: none !important;
  12071. }
  12072. #videoExplorer .thumbnailContainer {
  12073. background-size: contain;
  12074. background-repeat: no-repeat;
  12075. background-position: center center;
  12076. }
  12077.  
  12078. #videoExplorer.w_adjusted .column4 .thumbnailContainer,
  12079. #videoExplorer.w_adjusted .smallThumbnail .thumbnailContainer {
  12080. width: 130px; height: 100px;
  12081. margin-right: 7px;
  12082. border: 1px solid #888;
  12083. }
  12084.  
  12085. #videoExplorer.w_adjusted .column4 .uadFrame,
  12086. #videoExplorer.w_adjusted .smallThumbnail .uadFrame,
  12087. #videoExplorer.w_adjusted .uadTagRelated .uadFrame {
  12088. width: 130px; height: 100px;
  12089. background-size: 100% 100%;
  12090. }
  12091. #videoExplorer.w_adjusted .uadTagRelated .default .itemList .item .imageContainer {
  12092. width: 130px; height: 100px;
  12093. }
  12094.  
  12095. #videoExplorer.w_adjusted .column1 .item .thumbnailContainer {
  12096. border: 1px solid #888;
  12097. margin-right: 8px;
  12098. }
  12099. #videoExplorer.w_adjusted .column1 .item.smallThumbnail .thumbnailContainer {
  12100. border-width: 0px 15px 0px 15px;
  12101. border-color: #888;
  12102. }
  12103. */});
  12104.  
  12105. addStyle(__css__, 'squareThumbnailCss');
  12106. isSquareCssInitialized = true;
  12107. }
  12108. //$('#videoExplorer').toggleClass('squareThumbnail', isSquare);
  12109. } //
  12110.  
  12111. function initPageBottom($, conf, w) {
  12112. function updateHideVideoExplorerExpand(v) {
  12113. $('#content, #outline').toggleClass('w_hideSearchExpand', v === true);
  12114. }
  12115. function updateIchibaVisibility(v) {
  12116. $('#outline').toggleClass('noIchiba', v === 'hidden');
  12117. }
  12118. function updateReviewVisibility(v) {
  12119. $('#outline').toggleClass('noReview', v === 'hidden');
  12120. }
  12121. function updateBottomContentsVisibility(v) {
  12122. $('#bottomContentTabContainer, #footer').toggleClass('noBottom', v === 'hidden');
  12123. }
  12124.  
  12125. EventDispatcher.addEventListener('on.config.hideVideoExplorerExpand', updateHideVideoExplorerExpand);
  12126. EventDispatcher.addEventListener('on.config.ichibaVisibility', updateIchibaVisibility);
  12127. EventDispatcher.addEventListener('on.config.reviewVisibility', updateReviewVisibility);
  12128. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', updateBottomContentsVisibility);
  12129. if (conf.hideVideoExplorerExpand === true) { updateHideVideoExplorerExpand(true); }
  12130. if (conf.ichibaVisibility !== 'visible') { updateIchibaVisibility(conf.ichibaVisibility); }
  12131. if (conf.reviewVisibility !== 'visible') { updateReviewVisibility(conf.reviewVisibility); }
  12132. if (conf.bottomContentsVisibility !== 'visible') { updateBottomContentsVisibility(conf.bottomContentsVisibility); }
  12133.  
  12134. var $bottomToggle = $('<div class="toggleBottom"><div class="openBottom">▽</div><div class="closeBottom">△</div></div>');
  12135. $bottomToggle.on('click', function() {
  12136. var v = conf.bottomContentsVisibility;
  12137. conf.setValue('bottomContentsVisibility', v === 'hidden' ? 'visible' : 'hidden');
  12138. //ConfigPanel.refresh();
  12139. }).attr('title', '市場・レビューの開閉');
  12140. $('#footer').append($bottomToggle);
  12141. } //
  12142.  
  12143.  
  12144.  
  12145. function initShortcutKey() {
  12146. var list = [
  12147. {name: 'shortcutTogglePlay', exec: function(e) {
  12148. WatchController.togglePlay();
  12149. }},
  12150. {name: 'shortcutDefMylist', exec: function(e) {
  12151. WatchController.addDefMylist();
  12152. }},
  12153. {name: 'shortcutMylist', exec: function(e) {
  12154. $('#mylist_add_frame').find('.mylistAdd').click();
  12155. }},
  12156. {name: 'shortcutOpenDefMylist', exec: function(e) {
  12157. WatchController.showDeflist();
  12158. WatchController.scrollToVideoPlayer(true);
  12159. }},
  12160. {name: 'shortcutOpenSearch', exec: function(e) {
  12161. WatchController.openSearch();
  12162. if (!$('body').hasClass('content-fix')) {
  12163. WatchController.scrollToVideoPlayer(true);
  12164. }
  12165. }},
  12166. {name: 'shortcutOpenRecommend', exec: function(e) {
  12167. WatchController.openRecommend();
  12168. if (!$('body').hasClass('content-fix')) {
  12169. WatchController.scrollToVideoPlayer(true);
  12170. }
  12171. }},
  12172. {name: 'shortcutScrollToNicoPlayer', exec: function(e) {
  12173. WatchController.scrollToVideoPlayer(true);
  12174. }},
  12175. {name: 'shortcutCommentVisibility', exec: function(e) {
  12176. WatchController.commentVisibility('toggle');
  12177. }},
  12178. {name: 'shortcutShowOtherVideo', exec: function(e) {
  12179. WatchController.openVideoOwnersVideo();
  12180. }},
  12181. {name: 'shortcutMute', exec: function(e) {
  12182. WatchController.mute('toggle');
  12183. }},
  12184. {name: 'shortcutDeepenedComment', exec: function(e) {
  12185. WatchController.deepenedComment('toggle');
  12186. }},
  12187. {name: 'shortcutToggleStageVideo', exec: function(e) {
  12188. WatchController.toggleStageVideo();
  12189. }},
  12190. {name: 'shortcutInvisibleInput', exec: function(e) {
  12191. $('.invisibleCommentInput').focus();
  12192. }}
  12193. ];
  12194. for (var v in list) {
  12195. var n = list[v].name;
  12196. list[v].keyMatch = KeyMatch.create(conf[n]);
  12197. }
  12198.  
  12199. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12200. for (var v in list) {
  12201. var n = list[v].name;
  12202. if (n === name) {
  12203. list[v].keyMatch = KeyMatch.create(newValue);
  12204. }
  12205. }
  12206. });
  12207.  
  12208. $('body').on('keydown.watchItLater', function(e) {
  12209. // 一部のキーボードについているMusic Key(正式名称不明)に対応 Chromeしか拾えない?
  12210. if (e.keyCode === 178) { // 停止
  12211. WatchController.togglePlay();
  12212. } else
  12213. if (e.keyCode === 179) { // 一時停止
  12214. WatchController.togglePlay();
  12215. } else
  12216. if (e.keyCode === 177) { // 前の曲
  12217. if (WatchController.vpos() > 2000) {
  12218. WatchController.vpos(0);
  12219. } else {
  12220. WatchController.prevVideo();
  12221. }
  12222. } else
  12223. if (e.keyCode === 176) { // 次の曲
  12224. WatchController.nextVideo();
  12225. }
  12226. if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
  12227. return;
  12228. }
  12229. // 全画面時はFlashにフォーカスがなくてもショートカットキーが効くようにする
  12230.  
  12231. for (var v in list) {
  12232. var n = list[v].name;
  12233. if (list[v].keyMatch.test(e)) {
  12234. list[v].exec(e);
  12235. }
  12236. }
  12237. });
  12238. } //
  12239.  
  12240. function initNicoS($, conf, w) {
  12241. WatchJsApi.nicos.addEventListener('nicoSJump', function(e) {
  12242. if (conf.ignoreJumpCommand) {
  12243. e.cancel();
  12244. Popup.show('「@ジャンプ」コマンドをキャンセルしました');
  12245. }
  12246. });
  12247. var seekCount = 0;
  12248. WatchApp.ns.model.player.NicoSClientConnector.addEventListener('nicoSSeek', function(e) {
  12249. seekCount++;
  12250. if (conf.nicoSSeekCount < 0) return;
  12251. if (seekCount > conf.nicoSSeekCount) {
  12252. e.cancel();
  12253. }
  12254. });
  12255. // 動画が切り替わったか、最後まで視聴したらカウンターリセット
  12256. EventDispatcher.addEventListener('onVideoInitialized', function() {
  12257. seekCount = 0;
  12258. });
  12259. EventDispatcher.addEventListener('onVideoEnded', function() {
  12260. seekCount = 0;
  12261. });
  12262. } //
  12263.  
  12264. function initMouse() {
  12265. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12266. if (name === 'mouseClickWheelVolume') {
  12267. if (oldValue === 0) {
  12268. initWheelWatch();
  12269. } else
  12270. if (newValue === 0) {
  12271. $(document)
  12272. .unbind('mousewheel.watchItLaterWheelWatch')
  12273. .unbind('mousedown.watchItLaterWheelWatch')
  12274. .unbind('mouseup.watchItLaterWheelWatch');
  12275. }
  12276. }
  12277. });
  12278.  
  12279. function initWheelWatch() {
  12280. var leftDown = false, rightDown = false, isVolumeChanged = false;
  12281. var event = {
  12282. cancel: false,
  12283. reset: function() { this.cancel = false; return this; },
  12284. preventDefault: function() { this.cancel = true;}
  12285. };
  12286. $(document).on('mousewheel.watchItLaterWheelWatch', function(e, delta) {
  12287. var button = -1;
  12288. // TODO: マジックナンバーを
  12289. if (typeof e.buttons === 'number') { // firefox
  12290. button = e.buttons;
  12291. } else { // chrome
  12292. if (leftDown) { button = 1; }
  12293. else
  12294. if (rightDown) { button = 2; }
  12295. }
  12296. if (button < 1) {
  12297. EventDispatcher._dispatch('onWheelNoButton', e, delta);
  12298. return;
  12299. }
  12300. EventDispatcher.dispatch('onWheelAndButton', event.reset(), delta, button);
  12301. if (event.cancel) {
  12302. e.preventDefault();
  12303. return;
  12304. }
  12305. if (conf.mouseClickWheelVolume !== button) {
  12306. return;
  12307. }
  12308.  
  12309. var v = WatchController.volume(), r;
  12310. isVolumeChanged = true;
  12311. // 音量を下げる時は「うわ音でけぇ!」
  12312. // 音量を上げる時は「ちょっと聞こえにくいな」…というパターンが多いので、変化の比率が異なる
  12313. if (delta > 0) {
  12314. v = Math.max(v, 1);
  12315. r = (v < 5) ? 1.3 : 1.1;
  12316. v = WatchController.volume(v * r);
  12317. } else {
  12318. v = WatchController.volume(Math.floor(v / 1.2));
  12319. }
  12320. e.preventDefault();
  12321. }).on('mousedown.watchItLaterWheelWatch', function(e) { // chromeはホイールイベントでe.buttonsが取れないため
  12322. if (e.which === 1) leftDown = true;
  12323. if (e.which === 3) rightDown = true;
  12324. }).on('mouseup.watchItLaterWheelWatch', function(e) {
  12325. if (e.which === 1) leftDown = false;
  12326. if (e.which === 3) rightDown = false;
  12327. }).on('contextmenu.watchItLaterWheelWatch', function(e) {
  12328. if (isVolumeChanged) {
  12329. e.preventDefault();
  12330. }
  12331. isVolumeChanged = false;
  12332. });
  12333. }
  12334. window.setTimeout(function() { initWheelWatch(); }, 5000);
  12335. } // end initMouse
  12336.  
  12337. function initTouch() {
  12338. var touchInitialized = false;
  12339. TouchEventDispatcher.onflick(function(e) {
  12340. var se = e.startEvent;
  12341. if (!conf.enableQTouch) {return; }
  12342. if (e.direction === 'right') {
  12343. if (se.target.id === 'playerTabWrapper') {
  12344. $(se.target).addClass('w_active');
  12345. }
  12346. if (!touchInitialized) {
  12347. $('#mylist_add_frame, #leftPanelTabContainer, .videoExplorerMenu, #playerTabWrapper').addClass('w_touch');
  12348. $('.userProfile, .resultPagination, #searchResultContainer select, .playlistMenuPopup').addClass('w_touch');
  12349. isTouchActive = true;
  12350. touchInitialized = true;
  12351. }
  12352. } else
  12353. if (e.direction === 'left') {
  12354. if (se.target.tagName === 'DIV' &&
  12355. $.contains('#playerTabWrapper', se.target)) {
  12356. $('#playerTabWrapper').removeClass('w_active');
  12357. }
  12358. }
  12359. });
  12360. } //
  12361.  
  12362. function initOtherCss() {
  12363. var __dynamic_css_template__ = Util.here(function() {/*
  12364. .full_with_browser.w_fullScreenMenu #nicoHeatMap {
  12365. transform: scaleX($scale); -webkit-transform: scaleX($scale); display: block;
  12366. }
  12367. */});
  12368. var exStyle = null;
  12369. var updateDynamicCss = function() {
  12370. var css = __dynamic_css_template__;
  12371. var innerWidth = $('body').innerWidth();
  12372. css = css.split('$scale').join($('body').innerWidth() / 100);
  12373. if (exStyle) {
  12374. exStyle.innerHTML = css;
  12375. return exStyle;
  12376. } else {
  12377. return addStyle(css, 'expression');
  12378. }
  12379. };
  12380. exStyle = updateDynamicCss();
  12381.  
  12382. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12383. updateDynamicCss();
  12384. });
  12385.  
  12386. var __gpuLayer__ = (function() {/*
  12387. body.videoExplorer.content-fix #playerTabWrapper,
  12388. body.videoExplorer.content-fix .videoExplorerMenu,
  12389. body.videoExplorer.content-fix #playlist,
  12390. {*#playerTabWrapper .playerCommentPanel*}
  12391. body:not(.full_with_browser) .mylistPopupPanel.fixed
  12392. {
  12393. -moz-transform: translateZ(0);
  12394. -webkit-transform: translateZ(0);
  12395. transform: translateZ(0);
  12396. }
  12397. {* Firefoxだと問題がある要素はこちら *}
  12398. body.videoExplorer.content-fix #leftPanel,
  12399. body.videoExplorer.content-fix #content,
  12400. #popupMarquee
  12401. {
  12402. -webkit-transform: translateZ(0);
  12403. }
  12404. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  12405. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  12406. if (conf.enableGpuLayer) {
  12407. addStyle(__gpuLayer__, 'watchItLaterGpuLayer');
  12408. }
  12409.  
  12410. var __debug_css__ = Util.here(function() {/*
  12411. .videoExplorer #playerContainerWrapper, .videoExplorer #external_nicoplayer,
  12412. .videoExplorerOpening #playerContainerWrapper, .videoExplorerOpening #external_nicoplayer
  12413. {
  12414. {*transition: width 0.4s ease, height 0.4s ease;*}
  12415. }
  12416. #playerAlignmentArea .toggleCommentPanel {
  12417. {*transition: 0.4s ease-in-out;*}
  12418. }
  12419. {*
  12420. body:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer,
  12421. body:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea{
  12422. transition: width 0.4s ease;
  12423. }
  12424. body:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer{
  12425. transition: height 0.4s ease 0.4s;
  12426. }
  12427. *}
  12428.  
  12429. */});
  12430. if (conf.debugMode) addStyle(__debug_css__, 'watchItLater_debug_css');
  12431. } // end initOtherCss
  12432.  
  12433. function initCustomPlayerSize($, conf, w) {
  12434. var tpl = Util.here(function() {/*
  12435. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea
  12436. { width: {$alignmentAreaWidth}px; }
  12437. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea
  12438. { width: {$alignmentAreaWideWidth}px; }
  12439.  
  12440. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer {
  12441. height: {$playerHeight}px !important;
  12442. }
  12443. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer
  12444. { width: {$playerWidth}px !important;}
  12445.  
  12446. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #external_nicoplayer
  12447. { width: {$playerWidth}px !important; height: {$playerHeight}px !important; }
  12448.  
  12449. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMapContainer {
  12450. width: {$playerWidth}px !important;
  12451. }
  12452. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMap {
  12453. transform: scaleX({$heatMapScale}); -webkit-transform: scaleX({$heatMapScale});
  12454. }
  12455. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #smart_music_kiosk {
  12456. -webkit-transform: scaleX({$songriumScale}); -webkit-transform-origin: 0 0;
  12457. }
  12458.  
  12459. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #inspire_category {
  12460. left: {$songriumCategoryLeft}px !important;
  12461. }
  12462.  
  12463. */});
  12464. var PROFILE_SET = {
  12465. 'WQHD': [2560, 1440],
  12466. '1080p': [1920, 1080],
  12467. 'HD+': [1600, 900],
  12468. 'WXGA+': [1400, 810],
  12469. 'WXGA': [1366, 768],
  12470. '720p': [1280, 720],
  12471. 'WSVGA+': [1152, 648],
  12472. 'WSVGA': [1024, 576],
  12473. 'QHD': [ 960, 540]//, // 元より小さいパターンもサポートする?
  12474. // 'WVGA': [ 854, 480],
  12475. // 'NORMAL': [ 640, 360],
  12476. // 'SMALL': [ 512, 288],
  12477. // 'ECO': [ 352, 200],
  12478. };
  12479. var CONTROL_HEIGHT = 46, INPUT_HEIGHT = 30, PLAYER_TAB_WIDTH = 326 + 10, PLAYER_TAB_WIDTH_WIDE = 420 + 10, TAB_MARGIN = 0;
  12480. var SONGRIUM_WIDTH = 898;
  12481. var HORIZONTAL_MARGIN = 1.05; // 両端に 2.5% x 2 のマージンがある
  12482. var $videoHeader = $('#videoHeader');
  12483.  
  12484. var getTargetSize = function(targetWidth, targetHeight) {
  12485. var plWidth = Math.round(targetWidth * HORIZONTAL_MARGIN);
  12486. var plHeight = targetHeight + CONTROL_HEIGHT + INPUT_HEIGHT;
  12487. var alWidth = plWidth + PLAYER_TAB_WIDTH;
  12488. var alWidthW = plWidth + PLAYER_TAB_WIDTH_WIDE;
  12489. return {
  12490. playerWidth: plWidth,
  12491. playerHeight: plHeight,
  12492. alignmentAreaWidth: alWidth,
  12493. alignmentAreaWideWidth: alWidthW,
  12494. heatMapScale: plWidth / 100,
  12495. songriumScale: plWidth / SONGRIUM_WIDTH,
  12496. songriumCategoryLeft: plWidth + 32
  12497. };
  12498. };
  12499. var suggestProfile = function() {
  12500. var iw = $(window).innerWidth(), ih = $(window).innerHeight();
  12501. var hh = (WatchController.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  12502. iw -= (conf.wideCommentPanel ? PLAYER_TAB_WIDTH_WIDE : PLAYER_TAB_WIDTH);
  12503. iw -= TAB_MARGIN;
  12504.  
  12505. ih -= hh;
  12506. for (var v in PROFILE_SET) {
  12507. var w = PROFILE_SET[v][0], h = PROFILE_SET[v][1];
  12508. if (w * HORIZONTAL_MARGIN <= iw && h <= ih) {
  12509. return {w: w, h: h, name: v};
  12510. }
  12511. }
  12512. return null;
  12513. };
  12514. var generateCss = function() {
  12515. var profile = '';
  12516. if (PROFILE_SET[conf.customPlayerSize]) {
  12517. var s = PROFILE_SET[conf.customPlayerSize];
  12518. profile = {w: s[0], h: s[1], name: conf.customPlayerSize};
  12519. } else {
  12520. profile = suggestProfile();
  12521. }
  12522. if (!profile) return {css: '', profile: ''};
  12523. var ts = getTargetSize(profile.w, profile.h);
  12524. var css = tpl;
  12525. for (var v in ts) {
  12526. css = css.split('{$' + v + '}').join(ts[v]);
  12527. }
  12528. return {css: css, profile: profile};
  12529. };
  12530. var customStyleElement = null, lastCssName = '';
  12531. var updateStyle = function() {
  12532. if (WatchController.isFullScreen() || WatchController.isSearchMode()) {
  12533. return;
  12534. }
  12535.  
  12536. var result = generateCss();
  12537. var css = result.css, profile = result.profile, name = profile.name;
  12538.  
  12539. if (lastCssName === name) {
  12540. return;
  12541. }
  12542. lastCssName = name;
  12543. if (customStyleElement) {
  12544. customStyleElement.innerHTML = css;
  12545. } else {
  12546. customStyleElement = addStyle(css, 'customPlayerSize');
  12547. }
  12548. EventDispatcher.dispatch('onPlayerAlignmentAreaResize');
  12549. };
  12550. var toggleCustomSize = function(v) {
  12551. if (typeof v === 'boolean') {
  12552. $('body').toggleClass('w_size_custom', v);
  12553. } else {
  12554. $('body').toggleClass('w_size_custom');
  12555. }
  12556. };
  12557. var clearStyle = function() {
  12558. if (customStyleElement) {
  12559. customStyleElement.innerHTML = '';
  12560. toggleCustomSize(false);
  12561. }
  12562. lastCssName = '';
  12563. };
  12564. if (conf.customPlayerSize !== '') {
  12565. updateStyle();
  12566. toggleCustomSize();
  12567. }
  12568. EventDispatcher.addEventListener('on.config.customPlayerSize', function(v) {
  12569. if (v === '') {
  12570. clearStyle();
  12571. } else {
  12572. updateStyle();
  12573. toggleCustomSize(true);
  12574. }
  12575. });
  12576. EventDispatcher.addEventListener('on.config.wideCommentPanel', function(v) {
  12577. if (conf.customPlayerSize !== '') {
  12578. updateStyle();
  12579. }
  12580. });
  12581. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12582. if (conf.customPlayerSize !== '') {
  12583. updateStyle();
  12584. }
  12585. });
  12586.  
  12587. if (conf.debugMode) {
  12588. WatchItLater.debug.customSize = {
  12589. suggestProfile: suggestProfile,
  12590. getTargetSize: getTargetSize,
  12591. generateCss: generateCss,
  12592. updateStyle: updateStyle,
  12593. toggleCustomSize: toggleCustomSize
  12594. };
  12595. }
  12596.  
  12597. } //
  12598.  
  12599. function initStageVideo($, conf, w) {
  12600. var onStageVideoAvailabilityUpdated = function(v) {
  12601. $('#nicoplayerContainerInner').toggleClass('stageVideo', v);
  12602. };
  12603.  
  12604. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  12605. onStageVideoAvailabilityUpdated(WatchController.isStageVideoAvailable());
  12606. if (conf.forceEnableStageVideo) {
  12607. try {$('#external_nicoplayer')[0].setIsForceUsingStageVideo(true); } catch (e) { console.log(e);}
  12608. }
  12609. });
  12610.  
  12611. var pac = watch.PlayerInitializer.playerAreaConnector;
  12612.  
  12613. pac.addEventListener('onStageVideoAvailabilityUpdated', onStageVideoAvailabilityUpdated);
  12614.  
  12615. // console.log('StageVideo', $('#external_nicoplayer')[0].isStageVideoSupported() ? 'supported' : 'not supported');
  12616. // console.log('ColorSpaces', $('#external_nicoplayer')[0].getStageVideoSupportedColorSpaces());
  12617.  
  12618. } //
  12619.  
  12620. /**
  12621. * Chromeなら ALT+C
  12622. * Firefoxなら ALT+SHIFT+C で召喚
  12623. *
  12624. * どこでもコメント入力開始する隠し機能
  12625. *
  12626. * :m[0-9a-p] xxxxx でマイリストする機能もある
  12627. *
  12628. **/
  12629. function initInvisibleCommentInput($, conf, w) {
  12630. var $view = $(Util.here(function() {/*
  12631. <div class="invisibleInput">
  12632. <form action="javascript: void(0);">
  12633. <input type="text" value="" autocomplete="on" name="chat" accesskey="c"
  12634. list="myMylist" placeholder="コメント入力" class="invisibleCommentInput"
  12635. maxlength="75"
  12636. ></form>
  12637. <label><input type="checkbox" class="autoPause" checked="checked">動画を自動で一時停止する</label>
  12638. </div>
  12639. */}));
  12640. var css = Util.here(function() {/*
  12641. .invisibleInput {
  12642. position: fixed; z-index: 10000;
  12643. left: 100px; bottom: -100px; width: 300px;
  12644. padding: 16px;
  12645. border: 1px solid black;
  12646. background: #eee;
  12647.  
  12648. transition: bottom 0.4s ease 0.1s;
  12649. }
  12650. .invisibleInput.active {
  12651. bottom: 50px;
  12652. transition: bottom 0.4s ease;
  12653. box-shadow: 2px 2px 2px #333;
  12654. }
  12655.  
  12656. .invisibleInput .invisibleCommentInput {
  12657. width: 100%; font-size: 140%;
  12658. }
  12659. */});
  12660. var $dataList = $('<datalist id="myMylist"></datalist>');
  12661. var $form = $view.find('form');
  12662. var $input = $view.find('input');
  12663. var $autoPause = $view.find('.autoPause').prop('checked', conf.autoPauseInvisibleInput);
  12664.  
  12665. var prevent = function(e) { e.stopPropagation(); e.preventDefault(); };
  12666. var preventEsc = function(e) { if (e.keyCode === 27) { prevent(e); } };
  12667. var isAutoPause = function() { return !!$autoPause.prop('checked'); };
  12668.  
  12669. var mylistList = [];
  12670. var
  12671. onMylistUpdate = function(status, result) {
  12672. if (status === "ok") {
  12673. Popup.show('マイリストに追加しました');
  12674. } else {
  12675. Popup.alert('マイリスト追加に失敗: ' + result.error.description);
  12676. }
  12677. }, addMylist = function(n, d) {
  12678. var num = parseInt(n, 36);
  12679. var description = d || '';
  12680. if (num === 0) {
  12681. Mylist.addDefListItem(WatchController.getWatchId(), onMylistUpdate, description);
  12682. } else
  12683. if (mylistList[num]) {
  12684. Mylist.addMylistItem (WatchController.getWatchId(), mylistList[num].id, onMylistUpdate, description);
  12685. }
  12686. }, showMylist = function(n) {
  12687. var num = parseInt(n, 36);
  12688. if (num === 0) {
  12689. WatchController.showDeflist();
  12690. } else
  12691. if (mylistList[num]) {
  12692. WatchController.showMylist(mylistList[num].id);
  12693. }
  12694. }, seekVideo = function(v) {
  12695. var vpos = WatchController.vpos(), currentVpos = vpos;
  12696. if (v.match(/^([\-+]\d+)/)) {
  12697. vpos += parseInt(RegExp.$1, 10) * 1000;
  12698. } else
  12699. if (v.match(/^\d+$/)) {
  12700. vpos = parseInt(v, 10) * 1000;
  12701. } else
  12702. if (v.match(/^(\d+):(\d+)$/)) {
  12703. vpos = parseInt(RegExp.$1, 10) * 60 * 1000 + parseInt(RegExp.$2, 10) * 1000;
  12704. }
  12705.  
  12706. vpos = Math.max(vpos, 0);
  12707.  
  12708. if (vpos != currentVpos) {
  12709. console.log('seek video', vpos / 1000);
  12710. WatchController.vpos(vpos);
  12711. }
  12712. };
  12713.  
  12714. var isPlaying = false;
  12715. $input
  12716. .on('focus', function(e) {
  12717. isPlaying = WatchController.isPlaying();
  12718. if (isAutoPause()) {
  12719. WatchController.pause();
  12720. }
  12721. $view.addClass('active');
  12722. }).on('blur', function(e) {
  12723. if (isAutoPause() && isPlaying) {
  12724. WatchController.play();
  12725. }
  12726. $view.removeClass('active');
  12727. }).on('keyup', function(e) {
  12728. if (e.keyCode === 27) { // ESC
  12729. prevent(e);
  12730. $input.blur();
  12731. }
  12732. }).on('keydown', preventEsc).on('keyup', preventEsc);
  12733.  
  12734. $autoPause.on('click', function() {
  12735. conf.setValue('autoPauseInvisibleInput', !!$autoPause.prop('checked'));
  12736. });
  12737.  
  12738. $form
  12739. .on('submit', function(e) {
  12740. //prevent(e);
  12741. var val = $.trim($input.val());
  12742.  
  12743. if (val.match(/^:m([0-9a-p])(.*)/)) {
  12744. addMylist(RegExp.$1, RegExp.$2);
  12745. } else
  12746. if (val.match(/^:o([0-9a-p])/)) {
  12747. showMylist(RegExp.$1);
  12748. } else
  12749. if (val.match(/^:s[  \s](.+)/)) {
  12750. WatchController.nicoSearch(RegExp.$1, 'keyword');
  12751. } else
  12752. if (val.match(/^:t[  \s](.+)/)) {
  12753. WatchController.nicoSearch(RegExp.$1, 'tag');
  12754. } else
  12755. if (val.match(/^:v[  \s]([0-9:+\-]+)$/)) {
  12756. seekVideo(RegExp.$1);
  12757. } else {
  12758. WatchController.postComment(val);
  12759. }
  12760.  
  12761. setTimeout(function() { $input.val(''); } , 100);
  12762. $input.blur();
  12763. });
  12764.  
  12765. Mylist.loadMylistList(function(list) {
  12766. mylistList = list.concat();
  12767. mylistList.unshift({description: '', id: '', name: 'とりあえずマイリスト'});
  12768. var isFx = Util.Browser.isFx();
  12769.  
  12770. var tmp = [];
  12771. for (var i = 0, len = mylistList.length; i < len; i++) {
  12772. var c = i.toString(36);
  12773. // それぞれのブラウザで補完しやすい形式に
  12774. if (isFx) { // Fx
  12775. tmp.push('<option value=":m' + c + '">:m'+c+'\t 「' + mylistList[i].name + '」に追加</option>');
  12776. tmp.push('<option value=":o' + c + '">:o'+c+'\t 「' + mylistList[i].name + '」を開く</option>');
  12777. } else { // Chrome
  12778. tmp.push('<option value=":m' + c + '">「' + mylistList[i].name + '」に追加</option>');
  12779. tmp.push('<option value=":o' + c + '">「' + mylistList[i].name + '」を開く</option>');
  12780. }
  12781. }
  12782. tmp.sort();
  12783. if (isFx) {
  12784. tmp.push('<option value=":s ">:s [キーワード検索]</option>');
  12785. tmp.push('<option value=":t ">:t [タグ検索]</option>');
  12786. tmp.push('<option value=":v ">:v [シーク(秒)]</option>');
  12787. } else {
  12788. tmp.push('<option value=":s ">キーワード検索</option>');
  12789. tmp.push('<option value=":t ">タグ検索</option>');
  12790. tmp.push('<option value=":v ">シーク(秒)</option>');
  12791. }
  12792. $dataList.html(tmp.join('\n'));
  12793. });
  12794.  
  12795.  
  12796. addStyle(css, 'invisibleInput');
  12797. $('body').append($view).append($dataList);
  12798. } //
  12799.  
  12800. function initHeatMap($, conf, w) {
  12801. if (!conf.enableHeatMap) return;
  12802. //if (!w.Worker) return;
  12803. //
  12804. // TODO: Web Workers
  12805. var canvasWidth = 100, canvasHeight = 12;
  12806. var comments = [], duration = 0, canvas = null, context = null;
  12807. var commentReady = false, videoReady = false, updated = false, palette = [];
  12808. var __css__ = Util.here(function(){/*
  12809. #nicoHeatMapContainer {
  12810. position: absolute; z-index: 200;
  12811. bottom: 0px; left: 0;
  12812. width: 672px;
  12813. background: #000; height: 6px;
  12814. overflow: hidden;
  12815. }
  12816. .setting_panel #nicoHeatMapContainer { display: none; opacity: 0; }
  12817. .size_normal #nicoHeatMapContainer {
  12818. width: 898px;
  12819. }
  12820. .oldTypeCommentInput #nicoHeatMapContainer {
  12821. bottom: 29px;
  12822. display: none;
  12823. }
  12824. #nicoHeatMap {
  12825. position: absolute; top: 0; left: 0;
  12826. transform-origin: 0 0 0;-webkit-transform-origin: 0 0 0;
  12827. transform: scaleX(6.72);-webkit-transform: scaleX(6.72);
  12828. }
  12829. {* パズルみたいになってきた *}
  12830. body.size_normal:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12831. body.size_medium:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12832. body.videoExplorer #content.w_adjusted:hover #nicoHeatMapContainer,
  12833. body:not(.full_with_browser) #nicoHeatMapContainer.displayAlways {
  12834. display: block;
  12835. }
  12836. #nicoHeatMapContainer.displayAlways {
  12837. cursor: pointer;
  12838. }
  12839. .size_normal #nicoHeatMap {
  12840. transform: scaleX(8.98); -webkit-transform: scaleX(8.98);
  12841. }
  12842. .setting_panel #nicoHeatMapContainer, .full_with_browser #nicoHeatMapContainer, .size_small #content:not(.w_adjusted) #nicoHeatMapContainer {
  12843. display: none;
  12844. }
  12845.  
  12846. .full_with_browser.w_fullScreenMenu #nicoHeatMapContainer {
  12847. display: block;
  12848. width: 100%;
  12849. }
  12850. .full_with_browser.w_fullScreenMenu .oldTypeCommentInput #nicoHeatMapContainer {
  12851. bottom: 29px;
  12852. height: 6px;
  12853. }
  12854.  
  12855. .full_with_browser.w_fullScreenMenu.hideCommentInput #playerContainer #nicoHeatMapContainer {
  12856. position: fixed !important;
  12857. bottom: 0;
  12858. }
  12859. .full_with_browser.w_fullScreenMenu.hideCommentInput.fullWithPlaylist #playerContainer #nicoHeatMapContainer {
  12860. bottom: 167px;
  12861. }
  12862.  
  12863.  
  12864.  
  12865. */});
  12866. addStyle(__css__, 'NicoHeatMapCss');
  12867.  
  12868. watch.PlayerInitializer.playerAreaConnector.addEventListener('onCommentListInitialized', function() {
  12869. w.setTimeout(function() {
  12870. commentReady = true;
  12871. update();
  12872. }, 1000);
  12873. });
  12874. EventDispatcher.addEventListener('onVideoInitialized', function() {
  12875. videoReady = true;
  12876. update();
  12877. });
  12878. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function() {
  12879. commentReady = videoReady = updated = false;
  12880. clearCanvas();
  12881. });
  12882.  
  12883. var update = function() {
  12884. if (!commentReady || !videoReady || updated) return;
  12885. updated = true;
  12886. initCanvas();
  12887. getComments();
  12888. getDuration();
  12889. if (comments.length < 1 || duration < 1) {
  12890. return;
  12891. }
  12892. getHeatMap(function(map) {
  12893. var scale = duration >= canvasWidth ? 1 : (canvasWidth / duration);
  12894. var blockWidth = (canvasWidth / map.length) * scale;
  12895. for (i = map.length - 1; i >= 0; i--) {
  12896. context.fillStyle = palette[map[i]] || palette[0];
  12897. context.beginPath();
  12898. context.fillRect(i * scale, 0, blockWidth, canvasHeight);
  12899. }
  12900. });
  12901. };
  12902.  
  12903. var getComments = function() {
  12904. comments = [];
  12905.  
  12906. var list = watch.PlayerInitializer.commentPanelViewController.commentLists;
  12907. for (var i = 0; i < list.length; i++) {
  12908. if (list[i].listName === 'commentlist:main' && list[i].comments.length > 0) {
  12909. comments = list[i].comments;
  12910. break;
  12911. }
  12912. var ct = list[i].comments;
  12913. comments = (comments.length < ct.length) ? ct : comments;
  12914. }
  12915. list = null;
  12916. };
  12917. var getDuration = function() {
  12918. var exp = document.getElementById('external_nicoplayer');//$('#external_nicoplayer')[0];
  12919. duration = exp.ext_getTotalTime(); //
  12920. };
  12921. var initCanvas = function() {
  12922. if (!canvas) {
  12923. var $container = $('<div id="nicoHeatMapContainer" />');
  12924. $container.on('dblclick', function(e) {
  12925. e.preventDefault();
  12926. e.stopPropagation();
  12927. var $this = $(this).toggleClass('displayAlways');
  12928. conf.setValue('heatMapDisplayMode', $this.hasClass('displayAlways') ? 'always' : 'hover');
  12929. });
  12930. canvas = document.createElement('canvas');
  12931. canvas.id = 'nicoHeatMap';
  12932. canvas.width = canvasWidth;
  12933. canvas.height = canvasHeight;
  12934. $container.append(canvas);
  12935. $('#nicoplayerContainerInner').append($container);
  12936. context = canvas.getContext('2d');
  12937. if (conf.heatMapDisplayMode === 'always') {
  12938. $container.addClass('displayAlways');
  12939. }
  12940.  
  12941. initPalette();
  12942. }
  12943. clearCanvas();
  12944. };
  12945. var initPalette = function() {
  12946. for (var c = 0; c < 256; c++) {
  12947. var
  12948. r = Math.floor((c > 127) ? (c / 2 + 128) : 0),
  12949. g = Math.floor((c > 127) ? (255 - (c - 128) * 2) : (c * 2)),
  12950. b = Math.floor((c > 127) ? 0 : (255 - c * 2));
  12951. palette.push('rgb(' + r + ', ' + g + ', ' + b + ')');
  12952. }
  12953. };
  12954. var clearCanvas = function() {
  12955. if (!context) return;
  12956. context.fillStyle = palette[0];
  12957. context.beginPath();
  12958. context.fillRect(0, 0, canvasWidth, canvasHeight);
  12959. };
  12960.  
  12961. var getHeatMap = function(callback) {
  12962. var map = new Array(100), i = map.length; while(i > 0) map[--i] = 0;
  12963. var exp = $('#external_nicoplayer')[0];
  12964. var ratio = duration > map.length ? (map.length / duration) : 1;
  12965.  
  12966. for (i = comments.length - 1; i >= 0; i--) {
  12967. var pos = comments[i].vpos , mpos = Math.min(Math.floor(pos * ratio / 1000), map.length -1);
  12968. map[mpos]++;
  12969. }
  12970.  
  12971. var max = 0;
  12972. for (i = map.length - 4; i >= 0; i--) max = Math.max(map[i], max); // 末尾は固まってる事があるので参考にしない
  12973. if (max > 0) {
  12974. var rate = 255 / max;
  12975. for (i = map.length - 1; i >= 0; i--) {
  12976. map[i] = Math.min(255, Math.floor(map[i] * rate));
  12977. }
  12978. }
  12979. if (typeof callback === 'function') {
  12980. callback(map);
  12981. }
  12982. };
  12983. } // end of initHeatMap
  12984.  
  12985. /**
  12986. * 既存のポップアップの難点
  12987. *
  12988. * ・閉じる機能がなく、邪魔でも消えるまで待つしかない
  12989. * ・消えるまでの時間が毎回違う?
  12990. * ・クリックしたら消えるのかなと思ったらマイページに飛ばされる
  12991. * ・Chrome以外では動画プレイヤーの上に表示できない (半透明の部分が欠ける)
  12992. * ・↑によってプレイヤー上でフェードイン・アウトが出来ないため、まったく見えない状態から突然出現したようになる
  12993. * ・タイマー処理がバグっていて、一個目の表示中に2個目を連続表示すると2個目がすぐ消える
  12994. *
  12995. * … という所があんまりなので、パッチをあてて直す。
  12996. * ・Chrome以外は半透明をやめて画面外からのスライドにする
  12997. * ・CSS3アニメーションを使う(jQueryより軽い)
  12998. * ・クリックでマイページに飛ぶのをやめて、クリックで消えるようにする
  12999. * ・マウスオーバーしてる間は引っ込まない
  13000. * ・消えるまでの時間を4秒に固定
  13001. *
  13002. *
  13003. * このパッチでも直らない問題
  13004. * ・自分が動画投稿やレビューをしたという情報がなぜか自分にも通知される
  13005. *
  13006. */
  13007. function initPopupMarquee() {
  13008. if (!conf.replacePopupMarquee) { return; }
  13009. var
  13010. marquee = watch.PopupMarqueeInitializer.popupMarqueeViewController,
  13011. itemList = marquee.itemList,
  13012. $popup = $('#popupMarquee'),
  13013. $inner = $popup.find('.popupMarqueeContent'),
  13014. closeTimer = null,
  13015. popupDuration = 6000;
  13016.  
  13017. var
  13018. resetCloseTimer = function() {
  13019. if (closeTimer) {
  13020. clearTimeout(closeTimer);
  13021. closeTimer = null;
  13022. }
  13023. },
  13024. setCloseTimer = function() {
  13025. resetCloseTimer();
  13026. closeTimer = setTimeout(function() {
  13027. disappear();
  13028. closeTimer = null;
  13029. }, popupDuration);
  13030. },
  13031. onData = function(data) {
  13032. $inner.html(data);
  13033.  
  13034. $popup.removeClass('hide').removeClass('show');
  13035. setTimeout(function() {
  13036. $popup.removeClass('hide').addClass('show');
  13037. }, 100);
  13038. setCloseTimer();
  13039. },
  13040. disappear = function() {
  13041. $popup.removeClass('show');
  13042. resetCloseTimer();
  13043. setTimeout(function() {
  13044. if (!$popup.hasClass('show')) $popup.addClass('hide');
  13045.  
  13046. setTimeout(function() {
  13047. itemList.next();
  13048. }, Math.random() * 5000 + 5000);
  13049.  
  13050. }, 500);
  13051. },
  13052. __css__ = Util.here(function() {/*
  13053. #popupMarquee {
  13054. -webkit-filter: opacity( 0%); {* chrome以外はflashの上に半透明要素を置けない *}
  13055. background: #000 !important;
  13056. transition: -webkit-filter 0.25s ease-in, top 0.5s ease-in, bottom 0.5s ease-in; display: block;
  13057. }
  13058. #popupMarquee.show {
  13059. -webkit-filter: opacity(100%);
  13060. transition: -webkit-filter 1.00s ease-out, top 0.5s ease-out, bottom 0.5s ease-out; display: block;
  13061. }
  13062.  
  13063. #popupMarquee.hide {
  13064. opacity: 0; z-index: -1;
  13065. }
  13066.  
  13067. #popupMarquee.popupMarqueeTopRight:not(.show), #popupMarquee.popupMarqueeTopLeft:not(.show) { top: -600px; }
  13068. #popupMarquee.popupMarqueeBottomRight:not(.show), #popupMarquee.popupMarqueeBottomLeft:not(.show) { bottom: -600px; }
  13069. */});
  13070.  
  13071. addStyle(__css__, 'popupMarqueeFix');
  13072.  
  13073. itemList.eventTypeListenerMap.popup = []; //itemList.removeEventListener('popup', marquee.onData);
  13074. $popup
  13075. .css({opacity: ''})
  13076. .off('click').off('mouseover').off('mouseleave').off('mousemove')
  13077. .on('mouseover', resetCloseTimer)
  13078. .on('mouseout', setCloseTimer)
  13079. .on('click', disappear);
  13080.  
  13081. marquee.onData = $.proxy(onData, marquee);
  13082. marquee.disappear = $.proxy(disappear, marquee);
  13083. itemList.addEventListener('popup', $.proxy(onData, marquee));
  13084. } //
  13085.  
  13086.  
  13087. function initScroll($, conf, w) {
  13088. // 動画切り換え時にページの一番上までスクロールするようになったのを強引に阻止する
  13089. window.WatchApp.ns.model.state.WatchPageRouter.getInstance()._scroll = function() {};
  13090.  
  13091. var beforePlayerOffsetTop = 0, $playerAlignmentArea = $('#playerAlignmentArea');
  13092. var $window = $(window);
  13093. var beforeReset = function() {
  13094. beforePlayerOffsetTop = $playerAlignmentArea.offset().top;
  13095. };
  13096. var afterReset = function() {
  13097. var diff = $playerAlignmentArea.offset().top - beforePlayerOffsetTop;
  13098. var scrollTop = $window.scrollTop();
  13099. $window.scrollTop(scrollTop + diff);
  13100. };
  13101. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  13102. watchInfoModel.addEventListener('beforeReset', beforeReset);
  13103. watchInfoModel.addEventListener('afterReset', afterReset);
  13104.  
  13105.  
  13106. // 動画選択画面閉じた時にページの一番上までスクロールするようになったのを強引に阻止する
  13107. window.WatchApp.ns.util.WindowUtil.scroll_org = window.WatchApp.ns.util.WindowUtil.scroll;
  13108. var no_thanks = function() {
  13109. window.WatchApp.ns.util.WindowUtil.scroll = function() {};
  13110. };
  13111. var restore = function() {
  13112. window.WatchApp.ns.util.WindowUtil.scroll = window.WatchApp.ns.util.WindowUtil.scroll_org;
  13113. };
  13114.  
  13115. var vv = window.WatchApp.ns.init.BottomContentInitializer.videoExplorerModeViewController;
  13116. vv.onVideoExplorerClose_org = vv.onVideoExplorerClose;
  13117. vv.onVideoExplorerClose = $.proxy(function() {
  13118. no_thanks();
  13119. this.onVideoExplorerClose_org();
  13120. restore();
  13121. window.WatchApp.ns.util.WindowUtil.scrollFit('#playerContainerWrapper');
  13122. }, vv);
  13123.  
  13124. $ = conf = w = null;
  13125. } //
  13126.  
  13127. function initOther() {
  13128. if (conf.headerViewCounter) $('#siteHeaderInner').width($('#siteHeaderInner').width() + 200);
  13129.  
  13130. initAdditionalButtons();
  13131. initSquareThumbnail();
  13132.  
  13133. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  13134. if (name === 'squareThumbnail') {
  13135. initSquareThumbnail();
  13136. } else
  13137. if (name === 'enableAutoTagContainerHeight') {
  13138. if (newValue) { watch.TagInitializer.tagViewController.tagViewPinStatus.changeStatus(true); }
  13139. } else
  13140. if (name === 'enableMylistDeleteButton') {
  13141. $('.videoExplorerBody').toggleClass('enableMylistDeleteButton', newValue);
  13142. } else
  13143. if (name === 'enableYukkuriPlayButton') {
  13144. newValue ? Yukkuri.show() : Yukkuri.hide();
  13145. } else
  13146. if (name === 'noNicoru') {
  13147. $('body').toggleClass('w_noNicoru', newValue);
  13148. } else
  13149. if (name === 'playerBgStyle') {
  13150. $('#content')
  13151. .toggleClass('w_flat_gray', newValue === 'gray')
  13152. .toggleClass('w_flat_white', newValue === 'white');
  13153. } else
  13154. if (name === 'compactVideoInfo') {
  13155. $('#content, #outline').toggleClass('w_compact', newValue);
  13156. } else
  13157. if (name === 'disableHorizontalScroll') {
  13158. $('body').toggleClass('w_disableHorizontalScroll', newValue);
  13159. } else
  13160. if (name === 'hideCommentPanelSocialButtons') {
  13161. $('#playerTabContainer').toggleClass('w_noSocial', newValue);
  13162. }
  13163. });
  13164.  
  13165. if (conf.enableMylistDeleteButton) $('.videoExplorerBody').addClass('enableMylistDeleteButton');
  13166.  
  13167. if (conf.noNicoru) $('body').addClass('w_noNicoru');
  13168.  
  13169. if (conf.playerBgStyle !== '') $('#content').addClass('w_flat_' + conf.playerBgStyle);
  13170.  
  13171. if (conf.compactVideoInfo) $('#content, #outline').addClass('w_compact');
  13172. onWatchInfoReset(watchInfoModel);
  13173.  
  13174. if (conf.enableYukkuriPlayButton) { Yukkuri.show(); }
  13175.  
  13176. if (conf.disableHorizontalScroll) $('body').addClass('w_disableHorizontalScroll');
  13177.  
  13178. if (conf.hideCommentPanelSocialButtons) $('#playerTabContainer').addClass('w_noSocial');
  13179.  
  13180. $('#videoHeaderMenu .searchText input').attr({'accesskey': '@'}).on('focus', function() {
  13181. WatchController.scrollTop(0, 400);
  13182. });
  13183.  
  13184. watch.PlayerInitializer.commentPanelViewController.commentPanelContentModel.addEventListener('change', function(name) {
  13185. if (name === 'log_comment') {
  13186. $('.logDateSelect .logTime input')[0].setAttribute('type', 'time');
  13187. }
  13188. });
  13189.  
  13190. if (!w.Ads) {
  13191. // hostsに 0.0.0.0 ads.nicovideo.jp してるとスクリプトエラーがうるさいのでダミーを入れる
  13192. w.Ads = {
  13193. Advertisement: function() { return {set: function() {}}; }
  13194. };
  13195. }
  13196.  
  13197. var overrideGenerateURL = function() {
  13198. var wpc = WatchApp.ns.init.WatchPageInitializer.watchPageController;
  13199. wpc.generateWatchURL_org = wpc.generateWatchURL;
  13200. wpc.generateWatchURL = $.proxy(function(s) {
  13201. var ret = this.generateWatchURL_org(s);
  13202. // これのせいで既読リンクの色が変わらないので除去
  13203. ret = ret.replace(/\/(videoExplorer|ichiba)/, '');
  13204. return ret;
  13205. }, wpc);
  13206. };
  13207. overrideGenerateURL();
  13208.  
  13209. // 再現性不明のエラーをとりあえず握りつぶしつつ自動再生を3/2までの仕様に戻す
  13210. var overrrideWindowUtil = function() {
  13211. var wu = WatchApp.ns.util.WindowUtil;
  13212. wu.checkInview_org = wu.checkInview;
  13213. wu.checkInview = function() { return true; };
  13214. //wu.checkInview = $.proxy(function(a) {
  13215. // if (a.length < 0) { return true; }
  13216. // try {
  13217. // this.checkInview_org(a);
  13218. // } catch (e) {
  13219. // console.log('%cerror in WindowUtil.checkInview', 'color: red; ', e, a);
  13220. // console.trace();
  13221. // }
  13222. //}, wu);
  13223. };
  13224. overrrideWindowUtil();
  13225.  
  13226. // ニコる数を取得するためにコメントパネルがめちゃくちゃ重くなってるのを改善
  13227. WatchApp.ns.model.player.NicoPlayerConnector.getCommentNicoruCount = function(name, num) {
  13228. if (conf.noNicoru) {
  13229. return 0;
  13230. }
  13231. return window.PlayerApp.ns.player.Nicoplayer.getInstance().getCommentNicoruCount(name, num);
  13232. };
  13233.  
  13234. var playerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig;
  13235. if (conf.autoPlayIfWindowActive === 'yes') {
  13236. playerConfig.set({autoPlay: false});
  13237. }
  13238. if (conf.autoPlay2ndVideo) {
  13239. playerConfig.set({autoPlay: false});
  13240. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  13241. WatchController.pause();
  13242. WatchController.vpos(0);
  13243. setTimeout(function() {
  13244. playerConfig.set({autoPlay: true});
  13245. }, 3000);
  13246. });
  13247. $(window).on('beforeunload.watchItLater.autoPlay2ndVideo', function(e) {
  13248. playerConfig.set({autoPlay: false});
  13249. });
  13250. }
  13251.  
  13252. if (conf.debugMode) {
  13253. watch.PopupMarqueeInitializer.popupMarqueeViewController.itemList.addEventListener('popup', function(body) {
  13254. console.log('%c popup: ' + body, 'background: #0ff');
  13255. });
  13256. console.log(JSON.parse($('#watchAPIDataContainer').text()));
  13257.  
  13258. //WatchApp.ns.util.WindowUtil.shake = function() { console.log('%cshake', 'background: lightgreen;');};
  13259. //NicoPlayerConnector.getCommentNicoruCount_org = NicoPlayerConnector.getCommentNicoruCount;
  13260. }
  13261. }
  13262.  
  13263. // ?ref=等がついてるせいで未読既読のリンクの色が変わらなくなる問題の対策
  13264. // ShinjukuWatchと違いこっちはプレイリスト消えないモードがあるので、マイリスト等からの遷移でも遠慮無く全部消す
  13265. if (location.href.indexOf('?') >= 0) {
  13266. window.history.replaceState('', '', location.href.split('?')[0]);
  13267. }
  13268.  
  13269.  
  13270. function initTest(test) {
  13271. var console = window.console;
  13272. var expect = test.expect;
  13273. WatchApp.mixin(WatchItLater.test.spec, {
  13274. testChannelVideo: function(def) {
  13275. ChannelVideoList.load(function(result) {
  13276. console.log('ChannelVideoList.load', result);
  13277. expect(result.name).toEqual('ニコニコアプリちゃんねるの動画', 'チャンネル名');
  13278. expect(result.list.length >= 30).toBeTrue('2013/08/28時点で33件');
  13279. def.resolve();
  13280. }, {id: '55', ownerName: 'ニコニコアプリちゃんねる'});
  13281. },
  13282. testNewNicoSearch: function(def) {
  13283. var size = 15;
  13284. var search = new NewNicoSearch({});
  13285. search.load({query: 'vocaloid', size: size}, function(err, result) {
  13286. console.log('testNewNicoSearch.load', err, result);
  13287. expect(err).toBeNull('err === null');
  13288. expect(result[0].dqnid) .toBeTruthy('先頭にdqnidが含まれる(なんの略?)');
  13289. expect(typeof result[0].values[0].total).toEqual('number', 'ヒット件数');
  13290. expect(result[0].values[0].service) .toEqual('video', '検索の種類');
  13291.  
  13292. expect(result[1].type).toEqual('stats', 'type === stats'); // データの開始?
  13293.  
  13294. expect(result[2].type ).toEqual ('hits', 'type === hits');
  13295. expect(result[2].values ).toBeTruthy('ヒットした内容');
  13296. expect(result[2].values.length ).toEqual (size, 'sizeで指定した件数が返る');
  13297. expect(result[2].values[0].cmsid).toBeTruthy('ヒットした内容にデータが含まれる');
  13298.  
  13299. expect(result[3].type).toEqual('hits', 'type === stats'); // データの終了?
  13300. def.resolve();
  13301. });
  13302. },
  13303. testNewNicoSearchWrapperQuery: function(def) {
  13304. var wrapper = new NewNicoSearchWrapper({search: {}});
  13305. var params = {
  13306. searchWord: 'VOCALOID',
  13307. searchType: 'tag',
  13308. u: '1m',
  13309. l: 'short',
  13310. sort: 'l',
  13311. order: 'a',
  13312. page: 3
  13313. };
  13314. var query = wrapper._buildSearchQuery(params);
  13315.  
  13316. console.log(params, query);
  13317. expect(query.query).toEqual(params.searchWord, '検索ワードのセット');
  13318. expect(query.from).toEqual(params.page * 32 - 32, 'ページ番号 -> fromの変換');
  13319. expect(query.sort_by).toEqual('length_seconds', 'l -> length_seconds');
  13320. expect(query.order).toEqual('asc', 'a -> asc');
  13321.  
  13322. // TODO:
  13323. expect(JSON.stringify(query.search).indexOf('["tags"]') >= 0).toBeTrue('タグ検索');
  13324. var filters = JSON.stringify(query.filters);
  13325. //console.log(filters);
  13326. expect(query.filters.length >= 2).toBeTrue('filters.lengthが2以上');
  13327. expect(filters.indexOf('"field":"start_time"') >= 0).toBeTrue('filtersにstart_timeが含まれる');
  13328. expect(filters.indexOf('"field":"length_seconds"') >= 0).toBeTrue('filtersにlength_secondsが含まれる');
  13329. def.resolve();
  13330. },
  13331. testNewNicoSearchWrapper: function(def) {
  13332. console.log('testNewNicoSearchWrapper');
  13333. var search = new NewNicoSearch({});
  13334. var wrapper = new NewNicoSearchWrapper({search: search});
  13335. wrapper.load({searchWord: 'ぬこぬこ動画', size: 100}, function(err, result) {
  13336. console.log('testNewNicoSearchWrapper.load', err, result);
  13337. expect(err).toBeNull('err === null');
  13338. expect(typeof result.count).toEqual('number', '件数がnumber');
  13339. expect(result.count > 0).toBeTrue('件数が入っている');
  13340. expect(result.list.length).toBeTruthy('データが入っている');
  13341. expect(result.list.length).toEqual(100, 'sizeで指定した件数が入っている');
  13342. expect(result.list[0].type).toEqual(0, 'type === 0');
  13343. expect(/^\d+:\d+/.test(result.list[0].length)).toBeTrue('動画長がmm:dd形式で入ってる');
  13344. def.resolve();
  13345.  
  13346. });
  13347. },
  13348. testNicoSearchRelatedTag: function(def) {
  13349. var related = new NicoSearchRelatedTag({});
  13350. related.load('voiceroid', function(err, result) {
  13351. console.log('testNicoSearchRelatedTag.load', err, result);
  13352. console.log(expect(err));
  13353. expect(err).toBeNull('err === null');
  13354. expect(result.type).toEqual('tags', 'type === "tags"');
  13355. expect(result.values).toBeTruthy('データが入っている');
  13356. expect(typeof result.values[0]._rowid).toEqual('number', 'データに_rowidが入っている');
  13357. expect(typeof result.values[0].tag) .toEqual('string', 'データにtagが入っている');
  13358. def.resolve();
  13359. });
  13360. },
  13361. testSearchSuggest: function(def) {
  13362. var suggest = new NicoSearchSuggest({});
  13363. suggest.load('MMD', function(err, result) {
  13364. console.log('testSearchSuggest.load', err, result);
  13365. console.log(expect(err));
  13366. expect(err).toBeNull('err === null');
  13367. expect(result.candidates).toBeTruthy('suggestの中身がある');
  13368. expect(result.candidates.length).toBeTruthy('suggestのlengthがある');
  13369. def.resolve();
  13370. });
  13371. },
  13372. testUpdateMylistComment: function(def) {
  13373. // 一個以上マイリストがあって先頭のマイリストになにか登録されている必要がある
  13374. var Mylist = WatchItLater.mylist;
  13375. var randomMessage = 'RND: ' + Math.random();
  13376.  
  13377. var d = new $.Deferred();
  13378. d.promise()
  13379. .then(function() {
  13380. var d = new $.Deferred();
  13381. Mylist.loadMylistList(function(mylistList) {
  13382. expect(mylistList.length > 0).toBeTruthy('マイリスト一覧が1件以上');
  13383. console.log('先頭のマイリスト', mylistList[0].id, mylistList[0].name);
  13384. var groupId = mylistList[0].id;
  13385. if (mylistList.length <= 0) {
  13386. d.reject();
  13387. return;
  13388. }
  13389. d.resolve(groupId);
  13390. });
  13391. return d.promise();
  13392. })
  13393. .then(function(groupId) {
  13394. var d = new $.Deferred();
  13395. Mylist.reloadMylist(groupId, function(mylist) {
  13396. expect(mylist.length > 0).toBeTruthy('マイリストアイテムが一個以上');
  13397. var item = mylist[0];
  13398. var watchId = item.item_data.watch_id;
  13399. console.log('マイリスト先頭のアイテム', watchId, item.item_data.title);
  13400. d.resolve(watchId, groupId);
  13401. });
  13402. return d.promise();
  13403. })
  13404. .then(function(watchId, groupId) {
  13405. var d = new $.Deferred();
  13406. Mylist.updateMylistItem(watchId, groupId, function(result) {
  13407. expect(result).toEqual('ok', 'updateMylistItem() result=ok');
  13408. d.resolve(watchId, groupId);
  13409. }, randomMessage);
  13410. return d.promise();
  13411. })
  13412. .then(Util.Deferred.wait(500))
  13413. .then(function(watchId, groupId) {
  13414. var d = new $.Deferred();
  13415. Mylist.reloadMylist(groupId, function(newlist) {
  13416. console.log('reloadMylist', groupId, newlist);
  13417. expect(newlist[0].description)
  13418. .toEqual(randomMessage, 'マイリストコメントが更新できている => ' + newlist[0].description);
  13419. d.resolve();
  13420. });
  13421. return d.promise();
  13422. }).then(function() {
  13423. def.resolve();
  13424. });
  13425. d.resolve();
  13426. },
  13427. testVideoRanking: function(def) {
  13428. VideoRanking.load(null, {id: -4000})
  13429. .then(function(result) {
  13430. console.log('VideoRanking.load result:', result);
  13431. expect(result.name).toEqual('カテゴリ合算', 'ダミーマイリストの名前が一致');
  13432. expect(result.list.length).toEqual(300, 'カテゴリ合算ランキングは300件');
  13433. expect(result.list[ 0].title.indexOf('第001位')).toEqual(0,'ランキング1位のタイトル');
  13434. expect(result.list[299].title.indexOf('第300位')).toEqual(0,'ランキング300位のタイトル');
  13435. def.resolve();
  13436. },
  13437. function() {
  13438. def.reject();
  13439. });
  13440. },
  13441. testNicorepoVideo: function(def) {
  13442. NicorepoVideo.loadAll(null, null)
  13443. .then(function(result) {
  13444. console.log('NicorepoVideo.loadAll result:', result);
  13445. expect(result.name).toEqual('【ニコレポ】すべての動画', 'ダミーマイリストの名前が一致');
  13446. expect(result.list).toBeTruthy('ニコレポがある');
  13447. def.resolve();
  13448. },
  13449. function() {
  13450. def.reject();
  13451. });
  13452. },
  13453. testVideoInfoLoader: function(def) {
  13454. var loader = new VideoInfoLoader({});
  13455. $.when(
  13456. loader.load('sm9').then(function(result) {
  13457. expect(result.id).toEqual('sm9', '存在する動画ID');
  13458. expect(result.length).toEqual('5:19', 'length');
  13459. return this.done();
  13460. }, function(err) {
  13461. return this.fail();
  13462. }),
  13463.  
  13464. loader.load('sm1').then(function(result) {
  13465. return new $.Deferred().reject().promise();
  13466. }, function(resp) {
  13467. expect(resp.status).toEqual('fail', '存在しない動画ID');
  13468. return new $.Deferred().resolve().promise();
  13469. })
  13470.  
  13471. ).then(function() { def.resolve(); }, function() { def.reject(); });
  13472. },
  13473. testRelatedVideo: function(def) {
  13474. var loader = new RelatedVideo({});
  13475. loader.load('sm9').then(function(result) {
  13476. console.log('RelatedVideo', result);
  13477. expect(result.list).toBeTruthy('関連動画がある');
  13478. expect(result.list[0].title).toBeTruthy('タイトルがある');
  13479. expect(result.list[0].title.length >= 0).toBeTrue('タイトル長がある');
  13480. expect(typeof result.list[0].type === 'number').toBeTrue('type属性がある');
  13481. def.resolve();
  13482. });
  13483. },
  13484. testVideoArray: function(def) {
  13485. window.WatchItLater.loader.videoArrayAPILoader.load(['sm9', 'sm13']).then(function(result) {
  13486. console.log('VideoArrayAPILoader', result);
  13487. expect(result['sm9']).toBeTruthy('動画情報');
  13488. expect(result['sm13'].title).toBeTruthy('タイトルがある');
  13489. expect(result['sm13'].title.length >= 0).toBeTrue('タイトル長がある');
  13490. window.WatchItLater.loader.videoArrayAPILoader.load(['sm13', '1394785382']).then(function(result) {
  13491. console.log('VideoArrayAPILoader', result);
  13492. expect(result['1394785382']).toBeTruthy('スレッドIDでも引ける');
  13493. expect(result['1394785382'].title).toEqual('鬼灯の冷徹 第10話「十王の晩餐」「ダイエットは地獄みたいなもの」', '動画タイトル一致');
  13494. def.resolve();
  13495. });
  13496. });
  13497. },
  13498. testCeAPIVideoArray: function(def) {
  13499. window.WatchItLater.loader.ceAPIClient.videoArray(['sm9', 'sm13']).then(function(result) {
  13500. console.log('ceAPIAPIClient.videoArray', result);
  13501. expect(result.status).toEqual('ok', 'status');
  13502. expect(result.video_info).toBeTruthy('動画情報がある');
  13503. expect(result.video_info[0].video.id).toEqual('sm9', '動画id');
  13504. def.resolve();
  13505. });
  13506. }
  13507.  
  13508.  
  13509.  
  13510. }); // end WatchApp.mixin
  13511.  
  13512. } // end initTest
  13513.  
  13514. window.console.time('init WatchItLater');
  13515. // window.console.profile('init WatchItLater');
  13516. LocationHashParser.initialize();
  13517. initNews();
  13518. initShortcutKey();
  13519. initMouse();
  13520. initTouch();
  13521. initEvents();
  13522.  
  13523. initSearchContent($, conf, w);
  13524. initUserVideoContent($, conf, w);
  13525. initMylistContent($, conf, w);
  13526. initUploadedVideoContent($, conf, w);
  13527. initDeflistContent($, conf, w);
  13528. initVideoExplorer($, conf, w);
  13529.  
  13530. initRightPanel($, conf, w);
  13531. initLeftPanel($, conf, w);
  13532. initVideoReview($, conf, w);
  13533.  
  13534. initHidariue();
  13535. initVideoCounter();
  13536. initScreenMode();
  13537. initPlaylist($, conf, w);
  13538.  
  13539. initPageBottom($, conf, w);
  13540. initPageHeader($, conf, w);
  13541. initVideoTagContainer($, conf, w);
  13542.  
  13543. initNicoS($, conf, w);
  13544. initInvisibleCommentInput($, conf, w);
  13545. initOtherCss();
  13546. initCustomPlayerSize($, conf, w);
  13547.  
  13548. initStageVideo($, conf, w);
  13549. initHeatMap($, conf, w);
  13550. initPopupMarquee();
  13551. initMylistPanel($, conf, w);
  13552. initScroll($, conf, w);
  13553. initOther();
  13554. // window.console.profileEnd('init WatchItLater');
  13555. window.console.timeEnd('init WatchItLater');
  13556.  
  13557. onWindowResizeEnd();
  13558.  
  13559. if (conf.debugMode) {
  13560. initTest(WatchItLater.test);
  13561. }
  13562. };
  13563.  
  13564. if (window.PlayerApp) {
  13565. (function() {
  13566. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  13567. if (watchInfoModel.initialized) {
  13568. window.WatchItLater.WatchController =
  13569. window.WatchController =
  13570. _watchController(window);
  13571. ZeroFunc(window);
  13572. } else {
  13573. var onReset = function() {
  13574. watchInfoModel.removeEventListener('reset', onReset);
  13575. window.setTimeout(function() {
  13576. window.WatchItLater.WatchController =
  13577. window.WatchController =
  13578. _watchController(window);
  13579.  
  13580. ZeroFunc(window);
  13581. }, 0);
  13582. };
  13583. watchInfoModel.addEventListener('reset', onReset);
  13584. }
  13585. })();
  13586. } else
  13587. if (location.host === 'www.nicovideo.jp' && location.pathname ==='/stamp') {
  13588. niconicodoRedirect();
  13589. }
  13590.  
  13591.  
  13592. /**
  13593. * 原宿プレイヤーでのあれこれ
  13594. *
  13595. * マイリストパネルだけ追加
  13596. *
  13597. */
  13598. (function() {
  13599. if (!w.Video) return;
  13600. if (!location.href.match(/\/watch\/(sm\d+|nm\d+|so\d+|\d+)/)) return;
  13601. var watchId = void 0, videoId = void 0;
  13602. if (w.Video === null) {
  13603. watchId = RegExp.$1;
  13604. w.Video = {id: watchId};
  13605. } else {
  13606. Video = w.Video;
  13607. watchId = Video.v;
  13608. videoId = Video.id;
  13609. }
  13610. watchId = RegExp.$1;
  13611. var iframe = Mylist.getPanel('');
  13612. iframe.id = "mylist_add_frame";
  13613. iframe.setAttribute('style', 'position: fixed; right: 0; bottom: 0;');
  13614.  
  13615. document.body.appendChild(iframe);
  13616. iframe.watchId(watchId, videoId);
  13617. })();
  13618.  
  13619.  
  13620. /**
  13621. * キーボードイベント他
  13622. *
  13623. */
  13624. (function() {
  13625. w.document.body.addEventListener('keydown', function(e) {
  13626. if (e.keyCode === 27 || e.keyCode === 88) { // ESC or x
  13627. AnchorHoverPopup.hidePopup();
  13628. Popup.hide();
  13629. }
  13630. }, false);
  13631. w.document.body.addEventListener('click', function(e) {
  13632. var tagName = e.target.tagName, className = e.target.className;
  13633. //console.log(tagName, className);
  13634. if (tagName !== 'BUTTON' && tagName !== 'SELECT' && tagName !== 'OPTION' && className !== 'popupTagItem' && className.indexOf('mylistPopupPanel') < 0) {
  13635. AnchorHoverPopup.hidePopup();
  13636. }
  13637.  
  13638. }, false);
  13639. var touchInitialized = false;
  13640. TouchEventDispatcher.onflick(function(e) {
  13641. if (e.direction === 'right') {
  13642. if (!touchInitialized) {
  13643. document.getElementById('videoTagPopupContainer').className += ' w_touch';
  13644. touchInitialized = true;
  13645. }
  13646. }
  13647. }, false);
  13648. // w.document.body.addEventListener('dblclick', function(e) {var tagName = e.target.tagName, className = e.target.className;console.log(tagName, className);});
  13649.  
  13650. })(w);
  13651.  
  13652. //===================================================
  13653. //===================================================
  13654. //===================================================
  13655.  
  13656. }); // end of monkey();
  13657.  
  13658. /**
  13659. * スマートフォン用APIを利用して動画情報を取得する Firefox + Greasemonkey用
  13660. */
  13661. var spapi = function() {
  13662. if (window.name.indexOf('watchItLaterAPILoader') < 0 ) { return; }
  13663. var resp = document.getElementsByTagName('nicovideo_video_response');
  13664. var session = location.hash.length > 1 ? location.hash.substr(1) : location.search;
  13665. var origin = 'http://' + location.host.replace(/^.*?\./, 'www.');
  13666. var xml = '';
  13667. if (resp.length > 0) {
  13668. xml = resp[0].outerHTML;
  13669. }
  13670.  
  13671. try {
  13672. parent.postMessage(JSON.stringify({
  13673. id: 'WatchItLater',
  13674. type: 'VideoArrayAPILoader',
  13675. body: {
  13676. session: session,
  13677. xml: xml
  13678. }
  13679. }),
  13680. origin);
  13681. } catch (e) {
  13682. console.log('err', e);
  13683. }
  13684. };
  13685.  
  13686. /**
  13687. * Vita/3DS用APIを利用して情報を取得する Chrome + Tampermonkey用
  13688. * 参考: http://www59.atwiki.jp/nicoapi/pages/24.html
  13689. */
  13690. var ceapi = function() {
  13691. if (window.name.indexOf('ceAPILoader') < 0 ) { return; }
  13692. var origin = 'http://www.nicovideo.jp'; //'http://' + location.host.replace(/^.*?\./, 'www.');
  13693.  
  13694. var xmlHttpRequest = function(options) {
  13695. try {
  13696. var req = new XMLHttpRequest();
  13697. var method = options.method || 'GET';
  13698. req.onreadystatechange = function() {
  13699. if (req.readyState === 4) {
  13700. if (typeof options.onload === 'function') options.onload(req);
  13701. }
  13702. };
  13703. req.open(method, options.url, true);
  13704. if (options.headers) {
  13705. for (var h in options.headers) {
  13706. req.setRequestHeader(h, options.headers[h]);
  13707. }
  13708. }
  13709.  
  13710. req.send(options.data || null);
  13711. } catch (e) {
  13712. console.error(e);
  13713. }
  13714. };
  13715.  
  13716. window.addEventListener('message', function(event) {
  13717. var data = JSON.parse(event.data), timeoutTimer = null, isTimeout = false;
  13718. if (!data.url) { return; }
  13719. var sessionId = data.sessionId;
  13720. xmlHttpRequest({
  13721. url: data.url,
  13722. onload: function(resp) {
  13723.  
  13724. if (isTimeout) { return; }
  13725. else { window.clearTimeout(timeoutTimer); }
  13726.  
  13727. try {
  13728. parent.postMessage(JSON.stringify({
  13729. id: 'WatchItLater',
  13730. type: 'ceAPILoader',
  13731. body: {
  13732. sessionId: sessionId,
  13733. status: 'ok',
  13734. url: data.url,
  13735. body: resp.responseText
  13736. }
  13737. }),
  13738. origin);
  13739. } catch (e) {
  13740. console.log(
  13741. '%cError: parent.postMessage - ',
  13742. 'color: red; background: yellow',
  13743. e, event.origin, event.data);
  13744. }
  13745. }
  13746. });
  13747.  
  13748. timeoutTimer = window.setTimeout(function() {
  13749. isTimeout = true;
  13750. parent.postMessage(JSON.stringify({
  13751. id: 'WatchItLater',
  13752. type: 'ceAPILoader',
  13753. body: {
  13754. sessionId: sessionId,
  13755. status: 'timeout',
  13756. url: data.url
  13757. }
  13758. }),
  13759. origin);
  13760. }, 30000);
  13761.  
  13762. });
  13763.  
  13764. try {
  13765. parent.postMessage(JSON.stringify({
  13766. id: 'WatchItLater',
  13767. type: 'ceAPILoader',
  13768. body: {
  13769. status: 'initialized'
  13770. }
  13771. }),
  13772. origin);
  13773. } catch (e) {
  13774. console.log('err', e);
  13775. }
  13776. };
  13777.  
  13778.  
  13779. try {
  13780. if (location.host === 'flapi.nicovideo.jp') {
  13781. return;
  13782. } else
  13783. if (location.host === 'i.nicovideo.jp') {
  13784. spapi();
  13785. } else
  13786. if (location.host === 'api.ce.nicovideo.jp') {
  13787. ceapi();
  13788. } else
  13789. if (location.host.indexOf('smile-') >= 0) {
  13790. return;
  13791. } else
  13792. if (location.host.indexOf('localhost.') === 0 || location.host.indexOf('www.') === 0 || !this.GM_getValue || this.GM_getValue.toString().indexOf("not supported")>-1) {
  13793. isNativeGM = false;
  13794. var inject = document.createElement("script");
  13795. inject.id = "monkey";
  13796. inject.setAttribute("type", "text/javascript");
  13797. inject.setAttribute("charset", "UTF-8");
  13798.  
  13799. inject.appendChild(document.createTextNode("(" + monkey + ")(false)"));
  13800. // inject.appendChild(document.createTextNode("try {(" + monkey + ")(false) } catch(e) { console.log(e); }"));
  13801.  
  13802. if (document.body) {
  13803. document.body.appendChild(inject);
  13804. } else {
  13805. document.documentElement.appendChild(inject);
  13806. }
  13807. } else {
  13808. // やや古いFirefoxはここらしい
  13809. monkey(true);
  13810. }
  13811.  
  13812. } catch(e) {
  13813. // 最近のFirefoxはここに飛んでくる
  13814. monkey(true);
  13815. }
  13816. })();
  13817.  
  13818.