WatchItLater

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

目前為 2014-05-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://*.nicovideo.jp/*
  20. // @match http://ext.nicovideo.jp/*
  21. // @match http://search.nicovideo.jp/*
  22. // @grant GM_xmlhttpRequest
  23. // @version 1.140524
  24. // ==/UserScript==
  25.  
  26. /**
  27. *
  28. *
  29. * やりたい事・アイデア
  30. * ・検索画面にコミュニティ動画一覧を表示 (チャンネルより難しい。力技でなんとかする)
  31. * ・シークバーのサムネイルを並べて表示するやつ (単体スクリプトのほうがよさそう)
  32. * ・キーワード検索/タグ検索履歴の改修
  33. * ・ユーザーの投稿動画一覧に「xxさんのニコレポ」という架空のマイリストフォルダを出す
  34. * ・動画ランキングを「ニコニコ動画さん」という架空ユーザーの公開マイリストにする
  35. * ・「ニコニコチャンネルさん」という架空ユーザーを作って各ジャンルの新着を架空マイリストにする
  36. * ・横スクロールを賢くする
  37. * ・お気に入りユーザーの時は「@ジャンプ」許可
  38. * ・軽量化
  39. * ・綺麗なコード
  40. *
  41. * ・タグ領域の圧縮方法をShinjukuWatch形式にする
  42. */
  43. //
  44. // * ver 1.140524
  45. // * ver 1.140522
  46. // - 本家のサムネイル仕様変更に対応
  47.  
  48. // * ver 1.140428
  49. // - 本家の仕様変更で使えなくなっていた、プレイリストのブックマーク保存機能を復活
  50.  
  51. // * ver 1.140319
  52. // - 謎の技術によって、ニコメンドがなくても説明文の動画リンクにサムネイルを出せるように
  53. // - 細かなスタイル調整
  54.  
  55. // * ver 1.140303
  56. // - 動画選択画面で再生リストの開閉が記憶されなくなったのに対抗
  57. // - 動画切換え時に一番上までスクロールするようになったのに対抗
  58. // - 本家の内部仕様変更(jQuery ver up等)に対応
  59.  
  60. // * ver 1.140227
  61. // - タグ検索のソート順が毎回リセットされるようになったのに対抗
  62.  
  63. // * ver 1.140218
  64. // - コメント重複を勝手に直してたけど不要になったので除去
  65. // - 二本目以降の動画だけ自動再生を追加
  66.  
  67. // * ver 1.140207
  68. // - テレビちゃんメニューの表示修正
  69. // - スレッドIDのリンクからもタグを取得できるように(watchページ内のみ)
  70. // - マイリスト選択メニュー部分の右クリックでとりマイの位置に戻る隠し機能
  71.  
  72. // * ver 1.140122
  73. // - テレビちゃんメニューをShinjukuWatch仕様に
  74.  
  75. // * ver 1.140110
  76. // - 検索フォームのオートコンプリートを調整
  77. // - ニコメンドまわりのコード除去
  78. // - 微妙にNicorenizerとの相性を改善
  79.  
  80. (function() {
  81. var isNativeGM = true;
  82. var monkey =
  83. (function(isNativeGM){
  84. var w;
  85. try { w = unsafeWindow || window; } catch (e) { w = window;}
  86. var document = w.document;
  87.  
  88. var conf = {
  89. autoBrowserFull: false, // 再生開始時に自動全画面化
  90. disableAutoBrowserFullIfNicowari: false, // ユーザーニコ割があるときは自動全画面化しない
  91. autoNotFull: true, // 再生完了時にフルスクリーン解除(原宿と同じにする)
  92. autoTagPin: false,
  93. topPager: true, // 検索ボックスのページャを上にする
  94. hideLeftIchiba: false,
  95. autoClosePlaylistInFull: true, // 全画面時にプレイリストを自動で閉じる
  96. autoOpenSearch: false, // 再生開始時に自動検索画面
  97. autoScrollToPlayer: true, // プレイヤー位置に自動スクロール(自動全画面化オフ時)
  98. hideNewsInFull: true, // 全画面時にニュースを閉じる
  99. wideCommentPanel: false, // コメントパネルをワイドにする
  100. removeLeftPanel: true, // 左パネルを消滅させる
  101. leftPanelJack: false, // 左パネルに動画情報を表示
  102. rightPanelJack: true, // 右パネルに動画情報を表示
  103. headerViewCounter: false, // ヘッダに再生数コメント数を表示
  104. popupViewCounter: 'full', // 動画切り替わり時にポップアップで再生数を表示
  105. ignoreJumpCommand: false, // @ジャンプ無効化
  106. nicoSSeekCount: -1, // @ジャンプによるシーク(ループなど)を許可する回数 -1=無限 0以上はその回数だけ許可
  107. doubleClickScroll: true, // 空白部分ををダブルクリックで動画の位置にスクロールする
  108. hidePlaylist: true, // プレイリストを閉じる
  109. hidePlaylistInVideoExplorer: true, // 動画選択画面でプレイリストを閉じる
  110. hidariue: false, // てれびちゃんメニュー内に、原宿以前のランダム画像復活
  111. videoExplorerHack: true, // 検索画面を乗っ取る
  112. squareThumbnail: true, // 検索画面のサムネを4:3にする
  113. enableFavTags: false, // 動画検索画面にお気に入りタグを表示
  114. enableFavMylists: false, // 動画検索画面にお気に入りマイリストを表示
  115. searchPageItemCount: 50, // 検索モードの1ページあたりの表示数
  116. enableMylistDeleteButton: false, // 動画検索画面で、自分のマイリストから消すボタンを追加する
  117. enableHoverPopup: true, // 動画リンクのマイリストポップアップを有効にする
  118. enableAutoTagContainerHeight: false, // タグが2行以内なら自動で高さ調節(ピン留め時のみ
  119. autoSmallScreenSearch: false, // ポップアップからのタグ検索でもプレイヤーを小さくする
  120. enableNewsHistory: false, // ニコニコニュースの履歴を保持する
  121. defaultSearchOption: '', // 検索時のデフォルトオプション
  122. autoPlayIfWindowActive: 'no', // 'yes' = ウィンドウがアクティブの時だけ自動再生する
  123. autoPlay2ndVideo: false, // 2本目以降の動画だけ自動再生
  124. enableYukkuriPlayButton: false, // スロー再生ボタンを表示する
  125. noNicoru: false, // ニコるボタンをなくす
  126. enoubleTouchPanel: false, // タッチパネルへの対応を有効にする
  127. mouseClickWheelVolume: 0, // マウスボタン+ホイールで音量調整を有効にする 1 = 左ボタン 2 = 右ボタン
  128. enableQTouch: false, // タッチパネルモード有効
  129. commentVisibility: 'visible', // 'visible', 'hidden', 'lastState'
  130. lastCommentVisibility: 'visible',
  131. controllerVisibilityInFull: '', // 全画面時に操作パネルとコメント入力欄を出す設定
  132. enableTrueBrowserFull: false, // フチなし全画面モードにする (Chromeは画面ダブルクリックで切り替え可能)
  133. enableSharedNgSetting: false, //
  134. hideNicoNews: false, // ニコニコニュースを消す
  135. hashPlaylistMode: 0, // location.hashにプレイリストを保持 0 =無効 1=連続再生時 2=常時
  136. storagePlaylistMode: '', // localStorageにプレイリストを保持
  137. compactVideoInfo: true, //
  138. hoverMenuDelay: 0.4, // リンクをホバーした時のメニューが出るまでの時間(秒)
  139. enableFullScreenMenu: true, // 全画面時にホイールでメニューを出す
  140. enableHeatMap: false, //
  141. heatMapDisplayMode: 'hover', // 'always' 'hover'
  142. replacePopupMarquee: true, //
  143. enableRelatedTag: true, // 関連タグを表示するかどうか
  144. // playerTabAutoOpenNicommend: 'enable', // 終了時にニコメンドを自動で開くかどうか 'enable' 'auto' 'disable'
  145. autoPauseInvisibleInput: true, //
  146. customPlayerSize: '', //
  147. removeCommentPanelHoverEvent: false, //
  148. disableVideoExplorer: false, //
  149. disableTagReload: false, //
  150. disableHorizontalScroll: false, // 横スクロールバーを出なくする
  151. hideCommentPanelSocialButtons: false, // コメントパネル下のソーシャルボタンを隠す
  152. mylistPanelPosition: '',
  153. enableDescriptionThumbnail: false, // 説明文の動画リンクにサムネイルとタイトル表示
  154.  
  155. enableLocalMylistCache: false,
  156.  
  157. rankingCategory_g_ent2_Close: true,
  158. rankingCategory_g_life2_Close: true,
  159. rankingCategory_g_tech_Close: true,
  160. rankingCategory_g_culture2_Close: true,
  161. rankingCategory_g_other_Close: true,
  162.  
  163. searchEngine: 'sugoi', // 'normal' 'sugoi'
  164. searchStartTimeRange: '', //
  165. searchLengthSecondsRange: '', //
  166. searchMusicDlFilter: false, //
  167.  
  168. hideVideoExplorerExpand: true, // 「動画をもっと見る」ボタンを小さくする
  169. // nicommendVisibility: 'visible', // ニコメンドの表示 'visible', 'underIchiba', 'hidden'
  170. ichibaVisibility: 'visible', // 市場の表示 '', 'visible', 'hidden'
  171. reviewVisibility: 'visible', // レビューの表示 'visible', 'hidden'
  172. bottomContentsVisibility: 'hidden', // 動画下のコンテンツ表示表示非表示
  173. hideMenuInFull: 'hide', // 全画面時にマイリストメニューを隠す '', 'hide' = 目立たなくする, 'hideAll' = 完全非表示
  174.  
  175. flatDesignMode: '', // 'on' グラデーションや角丸をなくす。 7/25からQwatchがフラットデザインになったので不要になった
  176. playerBgStyle: '', // ↑ の後継パラメータ 'gray' 'white' ''
  177.  
  178. shortcutTogglePlay: {char: 'P', shift: false, ctrl: false, alt: true, enable: false}, // 停止/再生
  179. shortcutDefMylist: {char: 'M', shift: true, ctrl: false, alt: false, enable: false}, // とりマイ登録のショートカット
  180. shortcutMylist: {char: 'M', shift: false, ctrl: true , alt: false, enable: false}, // マイリスト登録のショートカット
  181. shortcutOpenSearch: {char: 'S', shift: true, ctrl: false, alt: false, enable: false}, // 検索オープンのショートカット
  182. shortcutOpenDefMylist: {char: 'D', shift: true, ctrl: false, alt: false, enable: false}, // とりマイオープンのショートカット
  183. shortcutOpenRecommend: {char: 'R', shift: true, ctrl: false, alt: false, enable: false}, // 関連動画(オススメ)を開くショートカット
  184. shortcutCommentVisibility: {char: 'V', shift: true, ctrl: false, alt: false, enable: false}, // コメント表示ON/OFFのショートカット
  185. shortcutScrollToNicoPlayer: {char: 'P', shift: true, ctrl: false, alt: false, enable: false}, // プレイヤーまでスクロールのショートカット
  186. shortcutShowOtherVideo: {char: 'U', shift: true, ctrl: false, alt: false, enable: false}, // 投稿者の関連動画表示のショートカット
  187. shortcutMute: {char: 'T', shift: true, ctrl: false, alt: false, enable: false}, // 音量ミュートのショートカット
  188. shortcutDeepenedComment: {char: 'B', shift: true, ctrl: false, alt: false, enable: false}, // コメント背面表示
  189. shortcutToggleStageVideo: {char: 'H', shift: true, ctrl: false, alt: false, enable: false}, // ハードウェアアクセラレーション(StageVideo)のショートカット
  190.  
  191. shortcutInvisibleInput: {char: 'C', shift: false, ctrl: false, alt: true, enable: true}, // 停止/再生
  192.  
  193. watchCounter: 0, // お前は今までに見た動画の数を覚えているのか?をカウントする
  194. forceEnableStageVideo: false,
  195. forceExpandStageVideo: false,
  196. enableAutoPlaybackContinue: false, // 一定時間操作しなかくても自動再生を続行
  197. lastLeftTab: 'videoInfo',
  198. lastRightTab: 'w_videoInfo',
  199. lastRightTabInExplorer: 'comment',
  200. lastControlPanelPosition: '',
  201. enableSortTypeMemory: true, // 検索のソート順を記憶する
  202. searchSortType: 'n', //
  203. searchSortOrder: 'd', // 'd'=desc 'a' = asc
  204. fxInterval: 40, // アニメーションのフレームレート 40 = 25fps
  205. enableGpuLayer: false, // 一部の要素でGPU描画を有効にしてみる?
  206. debugMode: false
  207. };
  208.  
  209.  
  210. //===================================================
  211. //===================================================
  212. //===================================================
  213.  
  214. function addStyle(styles, id) {
  215. var elm = document.createElement('style');
  216. window.setTimeout(function() {
  217. elm.type = 'text/css';
  218. if (id) { elm.id = id; }
  219.  
  220. var text = styles.toString();
  221. text = document.createTextNode(text);
  222. elm.appendChild(text);
  223. var head = document.getElementsByTagName('head');
  224. head = head[0];
  225. head.appendChild(elm);
  226. }, 0);
  227. return elm;
  228. }
  229.  
  230. if (!isNativeGM) {
  231. this.GM_xmlhttpRequest = function(options) {
  232. try {
  233. var req = new XMLHttpRequest();
  234. var method = options.method || 'GET';
  235. req.onreadystatechange = function() {
  236. if (req.readyState === 4) {
  237. if (typeof options.onload === "function") options.onload(req);
  238. }
  239. };
  240. req.open(method, options.url, true);
  241. if (options.headers) {
  242. for (var h in options.headers) {
  243. req.setRequestHeader(h, options.headers[h]);
  244. }
  245. }
  246.  
  247. req.send(options.data || null);
  248. } catch (e) {
  249. if (conf.debugMode) console.log(e);
  250. }
  251. };
  252. }
  253.  
  254. (function() { // 各ページ共通
  255. var __css__ = (function() {/*
  256. .tagItemsPopup {
  257. background: #eef;
  258. }
  259. .popupMenu {
  260. position: absolute;
  261. min-width: 200px;
  262. font-Size: 12pt;
  263. z-index: 2000000;
  264. box-shadow: 2px 2px 2px #888;
  265. }
  266. .popupMenu ul, .popupMenu ul li {
  267. position: relative;
  268. list-style-type: none;
  269. margin: 0; padding: 0;
  270. white-space: nowrap;
  271. }
  272. .tagItemsPopup .icon{
  273. width: 17px;
  274. height: 15px;
  275. }
  276. .tagItemsPopup .nicodic, .tagItemsPopup .newsearch {
  277. margin: 1px 4px 1px 1px;
  278. }
  279. .tagItemsPopup .nicodic:hover, .tagItemsPopup .newsearch:hover {
  280. margin: 0px 3px 0px 0px;
  281. border: 1px outset;
  282. }
  283.  
  284. .mylistListPopup {
  285. position:absolute;
  286. background: #fff;
  287. overflow: visible;
  288. padding: 8px;
  289. border: 1px outset #333;
  290. transition: opacity 0.3s ease;
  291. }
  292. .mylistListPopup.popupMenu ul li {
  293. position: relative;
  294. margin: 2px 8px;
  295. overflow-y: visible;
  296. }
  297. .mylistListPopup:not(.show) {
  298. left: -9999px;
  299. top: -9999px;
  300. opacity: 0;
  301. }
  302. .mylistListPopup.show {
  303. opacity: 1;
  304. }
  305. .mylistListPopup .listInner {
  306. -webkit-column-width: auto; -moz-column-width: auto;
  307. -webkit-column-count: 1 !important; {* Chromeだけバグるので *}
  308. }
  309. .mylistListPopup .icon {
  310. display: inline-block;
  311. width: 18px;
  312. height: 14px;
  313. margin: -4px 4px 0 0;
  314. vertical-align: middle;
  315. margin-right: 15px;
  316. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  317. transform: scale(1.5); -webkit-transform: scale(1.5);
  318. transform-origin: 0 0 0; -webkit-transform-origin: 0 0 0;
  319. transition: transform 0.1s ease, box-shadow 0.1s ease;
  320. -webkit-transition: -webkit-transform 0.1s ease, box-shadow 0.1s ease;
  321. cursor: pointer;
  322. }
  323. .mylistListPopup .icon:hover {
  324. background-color: #ff9;
  325. transform: scale(2); -webkit-transform: scale(2);
  326. }
  327. .mylistListPopup .icon:after {
  328. content: '開く';
  329. position: absolute;
  330. bottom: 0px;
  331. right: 12px;
  332. padding: 2px;
  333. opacity: 0;
  334. transform: scale(0.5); -webkit-transform: scale(0.5);
  335. z-index: -1;
  336. }
  337. .mylistListPopup .icon:hover:after {
  338. {*box-shadow: 2px 2px 2px #888;*}
  339. background: #fff;
  340. z-index: 100;
  341. opacity: 1;
  342. }
  343. .mylistListPopup .deflist .icon { background-position: 0 -253px; }
  344. .mylistListPopup .folder1 .icon { background-position: 0 -23px;}
  345. .mylistListPopup .folder2 .icon { background-position: 0 -46px;}
  346. .mylistListPopup .folder3 .icon { background-position: 0 -69px;}
  347. .mylistListPopup .folder4 .icon { background-position: 0 -92px;}
  348. .mylistListPopup .folder5 .icon { background-position: 0 -115px;}
  349. .mylistListPopup .folder6 .icon { background-position: 0 -138px;}
  350. .mylistListPopup .folder7 .icon { background-position: 0 -161px;}
  351. .mylistListPopup .folder8 .icon { background-position: 0 -184px;}
  352. .mylistListPopup .folder9 .icon { background-position: 0 -207px;}
  353.  
  354.  
  355. .mylistListPopup .name {
  356. display: inline-block;
  357. vertical-align: middle;
  358. font-size: 110%;
  359. color: #666;
  360. text-derocation: none !important;
  361. }
  362. .mylistListPopup .name:hover {
  363. color: #000;
  364. background-color: #ff9;
  365. }
  366. .mylistListPopup .name:after {
  367. content: ' に登録';
  368. font-size: 75%;
  369. color: #fff;
  370. }
  371. .mylistListPopup .name.exist:after {
  372. content: ' に登録済';
  373. color: #933;
  374. }
  375. .mylistListPopup .name:hover:after {
  376. color: #666;
  377. }
  378.  
  379. {* マイリスト登録パネル *}
  380. .mylistPopupPanel {
  381. height: 24px;
  382. z-index: 10000;
  383. {*border: 1px solid silver;
  384. border-radius: 3px; *}
  385. padding: 0;
  386. margin: 0;
  387. overflow: hidden;
  388. display: inline-block;
  389. background: #eee;
  390. }
  391. .mylistPopupPanel.fixed {
  392. position: fixed; right: 0; bottom: 0;
  393. transition: right 0.1s ease-out;
  394. }
  395. .mylistPopupPanel.fixed.left {
  396. right: auto;
  397. left: 0;
  398. }
  399. .mylistPopupPanel.fixed.top {
  400. bottom: auto;
  401. top: 0;
  402. }
  403.  
  404. .mylistPopupPanel.fixed>* {
  405. transition: opacity 0.1s ease-out;
  406. }
  407. .mylistPopupPanel>*>* {
  408. transition: background 0.5s ease 0.5s, color 0.5s ease 0.5s;
  409. }
  410. .full_with_browser .mylistPopupPanel, .mylistPopupPanel.black {
  411. background: #000; border: 0;
  412. }
  413. body.full_with_browser .mylistPopupPanel *,body .mylistPopupPanel.black.fixed * {
  414. background: #000; color: #888; border-color: #333;
  415. }
  416. .full_with_browser .mylistPopupPanel.hideAllInFull{
  417. display: none;
  418. }
  419. .full_with_browser .mylistPopupPanel.hideInFull.fixed:not(:hover) {
  420. right: -100px;
  421. transition: right 0.8s ease-in-out 0.5s;
  422. }
  423. .full_with_browser .mylistPopupPanel.hideInFull.fixed.right:not(:hover) {
  424. left: -100px; right: auto;
  425. transition: left 0.8s ease-in-out 0.5s;
  426. }
  427. .full_with_browser .mylistPopupPanel.hideInFull:not(:hover)>*{
  428. opacity: 0;
  429. transition: opacity 0.3s ease-out 1s;
  430. }
  431. .mylistPopupPanel.w_touch {
  432. height: 40px;
  433. }
  434. iframe.popup {
  435. position: absolute;
  436. }
  437. {* マウスホバーで出るほうのマイリスト登録パネル *}
  438. .mylistPopupPanel.popup {
  439. position: absolute;
  440. z-index: 1000000;
  441. box-shadow: 2px 2px 2px #888;
  442. }
  443. {* マイリスト登録パネルの中の各要素 *}
  444. .mylistPopupPanel .mylistSelect {
  445. width: 64px;
  446. margin: 0;
  447. padding: 0;
  448. font-size: 80%;
  449. white-space: nowrap;
  450. {*background: #eee;*}
  451. border: 1px solid silver;
  452. }
  453. .mylistSelect:focus {
  454. border-style: outset;
  455. }
  456. {* 誤操作を減らすため、とりマイの時だけスタイルを変える用 *}
  457. .mylistPopupPanel.w_touch button {
  458. padding: 8px 18px;
  459. }
  460. .mylistPopupPanel.w_touch .mylistSelect {
  461. font-size: 170%; width: 130px; border: none;
  462. }
  463. .mylistPopupPanel.w_touch .mylistSelect:focus {
  464. border: 1px dotted;
  465. }
  466. .mylistPopupPanel.deflistSelected button {
  467. }
  468. .mylistPopupPanel.mylistSelected button {
  469. color: #ccf;
  470. }
  471. .mylistPopupPanel button {
  472. margin: 0;
  473. font-weight: bolder;
  474. cursor: pointer;
  475. }
  476. .mylistPopupPanel button:active, #outline .playlistToggle:active, #outline .openVideoExplorer:active, #content .openConfButton:active {
  477. border:1px inset !important
  478. }
  479. .mylistPopupPanel button:hover, #outline .playlistToggle:hover, #outline .openVideoExplorer:hover, #outline .openConfButton:hover, #yukkuriPanel .yukkuriButton:hover {
  480. border:1px outset
  481. }
  482. .mylistPopupPanel .mylistAdd, .mylistPopupPanel .tagGet, #yukkuriPanel .yukkuriButton {
  483. 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;
  484. }
  485. .mylistPopupPanel .mylistAdd:focus, .mylistPopupPanel .tagGet:focus, #yukkuriPanel .yukkuriButton:focus {
  486. border-style: outset; color: orange;
  487. }
  488. .mylistPopupPanel.deflistSelected {
  489. color: #ff9;
  490. }
  491. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  492. 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;
  493. }
  494. .mylistPopupPanel.deflistSelected {
  495. color: #ff9;
  496. }
  497. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  498. 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;
  499. }
  500. .mylistPopupPanel.mylistSelected .deflistRemove {
  501. display: none;
  502. }
  503. .mylistPopupPanel .closeButton{
  504. color: #339;
  505. padding: 0;
  506. margin: 0;
  507. font-size: 80%;
  508. text-decoration: none;
  509. }
  510. .mylistPopupPanel .newTabLink{
  511. padding: 0 2px; text-decoration: underline; text-shadow: -1px -1px 0px #442B2B;
  512. }
  513. .mylistPopupPanel.fixed .newTabLink, .mylistPopupPanel.fixed .closeButton {
  514. display: none;
  515. }
  516. .w_fullScreenMenu .mylistPopupPanel.fixed { bottom: 2px; }
  517.  
  518. {* ポイントが無いときは表示しない *}
  519. .item:not(.silver):not(.gold) .uadContainer {
  520. display: none !important;
  521. }
  522.  
  523. .watchItLaterAPILoaderFrame {
  524. width: 1px;
  525. height: 1px;
  526. position: fixed;
  527. top: -100px;
  528. left: -100px;
  529. border: 0;
  530. overflow: hidden;
  531. }
  532.  
  533. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  534. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  535. addStyle(__css__, 'watchItLaterCommonStyle');
  536. })(); // end of commoncss
  537.  
  538.  
  539.  
  540.  
  541. (function() { // watchページだけのstyle
  542. if (!w.WatchApp) { return; }
  543. var __css__ = (function() { /*
  544. {* 動画タグとプレイリストのポップアップ *}
  545. #videoTagPopupContainer {
  546. }
  547. #videoTagPopupContainer.w_touch {
  548. line-height: 200%; font-size: 130%;
  549. }
  550. #videoTagPopupContainer.w_touch .nicodic{
  551. margin: 4px 14px;
  552. }
  553. .playlistMenuPopup {
  554. background: #666; color: white; padding: 4px 8px;
  555. }
  556. .playlistMenuPopup.w_touch {
  557. line-height: 250%;
  558. }
  559. #playlistSaveDialog {
  560. display: none;
  561. }
  562. #playlistSaveDialog.show {
  563. display: block;
  564. }
  565. #playlistSaveDialog.show .shadow{
  566. position: fixed;
  567. top: 0; left: 0; width: 100%; height: 100%;
  568. background: #000; opacity: 0.5;
  569. z-index: 30000;
  570. }
  571. #playlistSaveDialog.show .formWindow{
  572. position: fixed;
  573. margin: 0 auto;
  574. text-align: center;
  575. width: 100%;
  576. top: 45%;
  577. z-index: 30001;
  578. }
  579. #playlistSaveDialog .formWindow .formWindowInner{
  580. -webkit-transition: opacity 1s ease-out;
  581. transition: opacity 1s ease-out;
  582. opacity: 0;
  583. }
  584. #playlistSaveDialog.show .formWindow .formWindowInner{
  585. text-align: left;
  586. opacity: 1;
  587. margin: 0 auto;
  588. background: #f4f4f4;
  589. width: 500px;
  590. padding: 8px;
  591. border: 1px solid;
  592. }
  593. #playlistSaveDialog.show .formWindow .formWindowInner a{
  594. font-weight: bolder;
  595. }
  596. #playlistSaveDialog.show .formWindow .formWindowInner a:hover{
  597. text-decoration: underline; background: white;
  598. }
  599. #playlistSaveDialog.show .formWindow .formWindowInner label{
  600. margin: 8px;
  601. }
  602. #playlistSaveDialog.show .formWindow .formWindowInner input{
  603.  
  604. }
  605. #playlistSaveDialog.show .formWindow .formWindowInner .desc{
  606. font-size: 80%;
  607. }
  608. .playlistMenuPopup ul li {
  609. cursor: pointer;
  610. }
  611. .playlistMenuPopup ul li.savelist {
  612. color: #aaa;
  613. }
  614. .playlistMenuPopup ul li:hover {
  615. text-decoration: underline; background: #888;
  616. }
  617. #yukkuriPanel .yukkuriButton.active {
  618. border:1px inset
  619. }
  620.  
  621. #content .openConfButton {
  622. 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;
  623. }
  624. #outline .playlistToggle, #outline .openVideoExplorer, #outline .openConfButton {
  625. 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;
  626. height: 24px; border-radius: 0 0 8px 8px;
  627. }
  628. #outline .openConfButton { padding: 0 8px; letter-spacing: 4px; width: 60px; }
  629.  
  630. {* 全画面時にタグとプレイリストを表示しない時*}
  631. body.full_and_mini.full_with_browser #playerAlignmentArea{
  632. margin-bottom: 0 !important;
  633. }
  634. body.full_and_mini.full_with_browser #playlist{
  635. z-index: auto;
  636. }
  637. body.full_and_mini.full_with_browser .generationMessage{
  638. display: inline-block;
  639. }
  640. {* 全画面時にタグとプレイリストを表示する時 *}
  641. body.full_with_browser #playlist{
  642. z-index: 100;
  643. }
  644. body.full_with_browser .generationMessage{
  645. display: none;
  646. }
  647. body.full_with_browser .browserFullOption{
  648. padding-right: 200px;
  649. }
  650. {* 全画面時にニュースを隠す時 *}
  651. body.full_with_browser.hideNewsInFull #playerAlignmentArea{
  652. margin-bottom: -37px;
  653. }
  654. {* 少しでも縦スクロールを減らすため、動画情報を近づける。人によっては窮屈に感じるかも *}
  655. #outline {
  656. margin-top: -16px;
  657. }
  658. #outline #feedbackLink{
  659.  
  660. }
  661. #outline .videoEditMenuExpand{
  662. position: absolute;right: 0;top: 26px; z-index: 1;
  663. }
  664. {* ヘッダに表示する再生数 *}
  665. #videoCounter {
  666. color: #ff9; font-size: 70%;
  667. }
  668. {* 右に表示する動画情報 *}
  669. .sidePanel .sideVideoInfo, .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  670. padding: 0px 0px 0 0px; width: 196px; height: 100%; z-index: 10019;
  671. position:absolute; top:0; right:0; border: 1px solid #000;
  672. overflow-x: visible; overflow-y: auto;
  673. }
  674. {* 右に表示する動画情報 *}
  675. #playerTabWrapper.sidePanel .sideVideoInfo, #playerTabWrapper.sidePanel .sideIchibaPanel, #playerTabWrapper.sidePanel .sideReviewPanel {
  676. padding: 0px 0px 0 0px; width: 324px; height: 100%;
  677. position: absolute; top: 0; right:0;
  678. }
  679.  
  680. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideVideoInfo,
  681. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideIchibaPanel,
  682. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideReviewPanel,
  683. .videoExplorer #playerTabWrapper .sideVideoInfo,
  684. .videoExplorer #playerTabWrapper .sideIchibaPanel,
  685. .videoExplorer #playerTabWrapper .sideReviewPanel {
  686. width: 418px;
  687. }
  688. #playerTabWrapper.w_videoInfo #appliPanel, #playerTabWrapper.w_ichiba #appliPanel, #playerTabWrapper.w_review #appliPanel {
  689. top: -9999px;
  690. }
  691. .sidePanel .sideVideoInfo {
  692. background: #f4f4f4; text-Align: left; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%; border: 1px solid black;
  693. }
  694. .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  695. background: #f4f4f4; text-Align: center; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%;
  696. }
  697. .sidePanel .sideVideoInfo .sideVideoInfoInner {
  698. padding: 0 4px; position: relative;
  699. }
  700. .sidePanel .sideVideoInfo .videoTitleContainer {
  701. background: #ddd; text-align: center; color: #000; margin: 6px 6px 0;
  702. border: solid; border-width: 2px 2px 0; border-color: #888;
  703. }
  704. .sidePanel .sideVideoInfo .videoOwnerInfoContainer {
  705. margin: 0 6px; border: solid; border-width: 0 2px 0; border-color: #888;
  706. }
  707. .sidePanel .sideVideoInfo .videoThumbnailContainer {
  708. background: #ddd; text-align: center; color: #000; margin: 0;
  709. }
  710. .sidePanel .sideVideoInfo .videoThumbnailContainer img {
  711. cursor: pointer;
  712. }
  713. .sidePanel .sideVideoInfo .videoTitle {
  714. padding: 8px;
  715. }
  716. .sidePanel .sideVideoInfo .videoPostedAt {
  717. color: #333;
  718. }
  719. .sidePanel .sideVideoInfo .videoStats{
  720. font-size:90%;
  721. }
  722. .sidePanel .sideVideoInfo .videoStats li{
  723. display: inline-block; margin: 0 2px;
  724. }
  725. .sidePanel .sideVideoInfo .videoStats li span{
  726. font-weight: bolder;
  727. }
  728. .sidePanel .sideVideoInfo .videoStats .ranking{
  729. display: none !important;
  730. }
  731. .sidePanel .sideVideoInfo .videoInfo{
  732. background: #ddd; text-align: center; padding: 4px; margin: 0 6px 6px;
  733. border: solid; border-width: 0 2px 2px; border-color: #888;
  734. }
  735. .sideVideoInfo .sideVideoInfoInner{
  736. -webkit-transition: opacity 1s ease-out, color 3s ease-out;
  737. transition: opacity 1s ease-out, color 3s ease-out;
  738. opacity: 0;
  739. }
  740. .sideVideoInfo.show .sideVideoInfoInner{
  741. opacity: 1;
  742. }
  743. .videoCount.blink {
  744. color: #ccc;
  745. }
  746. .sidePanel .videoCountDiff {
  747. position: absolute; color: white; right: 0; opacity: 0; z-index: 100; text-shadow: 1px 1px 0 orange;
  748. }
  749. .sidePanel .videoCountDiff.blink {
  750. opacity: 1; color: white;
  751. }
  752. #siteHeader .videoCount, #siteHeader .videoCountDiff {
  753. min-width: 32px; text-align: right; display: inline-block;
  754. }
  755. #siteHeader .videoCountDiff, #trueBrowserFullShield .videoCountDiff {
  756. position: absolute; color: yellow; opacity: 0; font-weight: bolder; text-shadow: 1px 1px 0 red;
  757. }
  758. #siteHeader .videoCountDiff.blink, #trueBrowserFullShield .videoCountDiff.blink {
  759. opacity: 1; color: yellow;
  760. }
  761. #trueBrowserFullShield .blink, #videoCounter .blink {
  762. color: #000;
  763. }
  764. .videoCountDiff:before {content: '+';}
  765. .videoCountDiff.down:before {content: ''; }
  766. #popupMarquee .videoCountDiff {display: none;}
  767. .sidePanel .sideVideoInfo .videoDescription{
  768. overflow-x: hidden; text-align: left;
  769. }
  770. .sidePanel .sideVideoInfo .videoDescriptionInner{
  771. margin: 0 8px;
  772. }
  773. .sidePanel .sideVideoInfo .videoDetails{
  774. min-width: 150px;
  775. }
  776. .sidePanel .sideVideoInfo .videoDetails a{
  777. margin: auto 4px;
  778. }
  779. .sidePanel .sideVideoInfo .videoDetails a:visited{
  780. color: #04618c;
  781. }
  782. .sidePanel .sideVideoInfo .videoDetails a.watch{
  783. margin: auto 30px auto 4px;
  784. display:inline-block;
  785. }
  786. .sideVideoInfo .userName, .sideVideoInfo .channelName{
  787. display: block;
  788. font-size: 120%;
  789. cursor: pointer;
  790. }
  791. .sideVideoInfo .userIconContainer, .sideVideoInfo .channelIconContainer {
  792. background: #ddd; width: 100%; text-align: center; float: none;
  793. }
  794. .sidePanel .userIcon, .sidePanel .channelIcon{
  795. min-width: 128px; max-width: 150px; width: auto; height: auto; border: 0;
  796. box-shadow: 0 0 4px #666; cursor: pointer;
  797. }
  798. .sideVideoInfo .descriptionThumbnail {
  799. text-align: left; font-size: 90%; padding: 4px; background: #ddd;
  800. min-height: 50px; margin: 4px 8px; font-weight: normal; color: black;
  801. }
  802. .sideVideoInfo .descriptionThumbnail p {
  803. margin: 0 8px;
  804. font-weight: bolder;
  805. }
  806. .sideVideoInfo .descriptionThumbnail .uploadAt {
  807. font-size: 90%;
  808. margin: 4px;
  809. color: #333;
  810. }
  811. .sideVideoInfo .descriptionThumbnail .counterContainer {
  812. text-align: center;
  813. }
  814. .sideVideoInfo .descriptionThumbnail .view,
  815. .sideVideoInfo .descriptionThumbnail .comment,
  816. .sideVideoInfo .descriptionThumbnail .mylist
  817. {
  818. font-size: 90%;
  819. white-space: nowrap;
  820. margin-right: 4px;
  821. color: #333;
  822. }
  823. .sideVideoInfo .descriptionThumbnail .count {
  824. font-weight: bolder;
  825. }
  826.  
  827. .sideVideoInfo .descriptionThumbnail.video img{
  828. height: 50px; cursor: pointer; float: left;
  829. }
  830. .sideVideoInfo .descriptionThumbnail.mylist img{
  831. height: 40px; cursor: pointer;
  832. }
  833. .sideVideoInfo .descriptionThumbnail.illust img{
  834. height: 60px; cursor: pointer;
  835. }
  836. .sideVideoInfo a.otherSite {
  837. font-weight: bolder; text-decoration: underline;
  838. }
  839. body:not(.videoExplorer) #leftPanel.removed {
  840. display: none; left: 0px;
  841. }
  842. body:not(.videoExplorer) #leftPanel.removed .sideVideoInfo {
  843. display: none; width: 0px !important; border: none; margin: 0; padding: 0; right: auto;
  844. }
  845. .sideVideoInfo .userIconContainer.isUserVideoPublic .notPublic { display: none; }
  846. .sideVideoInfo .userIconContainer .isPublic { display: none; }
  847. .sideVideoInfo .userIconContainer.isUserVideoPublic .isPublic { display: inline; }
  848. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem,
  849. .sidePanel .sideIchibaPanel .ichiba_mainitem {
  850. width: 180px; display:inline-block; vertical-align: top;
  851. margin: 4px 3px; border 1px solid silver;
  852. }
  853.  
  854. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  855. font-size: 60px;
  856. }
  857. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem>div>dt {
  858. height: 50px;position: relative;
  859. }
  860. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  861. position: absolute;width: 100%;
  862. }
  863. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  864. position: absolute;
  865. }
  866. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonShita {
  867. position: absolute;
  868. }
  869.  
  870. .sidePanel.videoInfo, .sidePanel.ichiba{
  871. background: none;
  872. }
  873.  
  874. .sideVideoInfo.isFavorite .userName:after, .sideVideoInfo.isFavorite.isChannel .videoOwnerInfoContainer .channelName:after{
  875. content: ' ★ '; color: gold; text-shadow: 1px 1px 1px black;
  876. }
  877.  
  878. .sidePanel.videoInfo #leftPanelContent, .sidePanel.ichiba #leftPanelContent {
  879. display: none;
  880. }
  881. .sidePanel.w_comment #playerTabContainer,
  882. .sidePanel.videoInfo .sideVideoInfo,
  883. .sidePanel.ichiba .sideIchibaPanel,
  884. .sidePanel.w_videoInfo .sideVideoInfo,
  885. .sidePanel.w_ichiba .sideIchibaPanel,
  886. .sidePanel.w_review .sideReviewPanel {
  887. display: block; z-index: 10060;
  888. }
  889. .sidePanel:not(.w_comment) .watchWatchContainer {
  890. display: none;
  891. }
  892.  
  893. #leftPanelTabContainer {
  894. display:none; background: #666; position: absolute; right: 4px; top: -27px; list-style-type: none; padding: 4px 6px 3px 60px; height: 20px;
  895. }
  896. #sidePanelTabContainer {
  897. display: none;
  898. position: absolute; list-style-type: none;
  899. padding: 5px 10px 0; right: -408px; top: 0; width: 350px; height: 34px;
  900. transform: rotate(90deg); transform-origin: 0 0 0;
  901. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  902. z-index: 1000;
  903. }
  904. .full_with_browser #sidePanelTabContainer {
  905. background: #000 !important;
  906. }
  907. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left {
  908. background: #000; right: auto; left: -375px; padding: 0; height: 27px;
  909. transform: rotate(-90deg); transform-origin: 100% 0 0;
  910. -webkit-transform: rotate(-90deg); -webkit-transform-origin: 100% 0 0;
  911. }
  912.  
  913. #leftPanelTabContainer.w_touch {
  914. top: -40px; height: 33px;
  915. }
  916. .sidePanel:hover #sidePanelTabContainer, .sidePanel:hover #leftPanelTabContainer {
  917. display: block;
  918. }
  919. #leftPanelTabContainer .tab{
  920. display: inline-block; cursor: pointer; background: #999; padding: 2px 4px 0px; border-width: 2px 2px 0px;
  921. }
  922. #leftPanelTabContainer.w_touch .tab, #sidePanelTabContainer.w_touch .tab {
  923. padding: 8px 12px 8px;
  924. }
  925. #sidePanelTabContainer .tab {
  926. background: none repeat scroll 0 0 #999999; border-width: 2px 2px 0; cursor: pointer;
  927. display: inline-block; font-size: 13px; padding: 5px 10px 8px;
  928. border-radius: 8px 8px 0px 0px;
  929. }
  930. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left .tab {
  931. display: inline-block; font-size: 13px; padding: 5px 10px 0px;
  932. }
  933. #leftPanel.videoInfo .tab.videoInfo, #leftPanel.ichiba .tab.ichiba {
  934. background: #f4f4f4; border-style: outset;
  935. }
  936. #playerTabWrapper.w_comment .tab.comment,
  937. #playerTabWrapper.w_videoInfo .tab.videoInfo,
  938. #playerTabWrapper.w_ichiba .tab.ichiba,
  939. #playerTabWrapper.w_review .tab.review
  940. {
  941. background: #dfdfdf; border-style: outset;
  942. }
  943.  
  944. #playerTabWrapper.w_videoInfo #playerCommentPanel,
  945. #playerTabWrapper.w_ichiba #playerCommentPanel,
  946. #playerTabWrapper.w_review #playerCommentPanel {
  947. {*display: none;*} top: -9999px;
  948. }
  949. .sidePanel.ichibaEmpty .tab.ichiba, .sidePanel.reviewEmpty .tab.review {
  950. color: #ccc;
  951. }
  952.  
  953. .sideIchibaPanel .ichibaPanelInner {
  954. margin:0; color: #666;
  955. }
  956. .sideIchibaPanel .ichibaPanelHeader .logo{
  957. text-shadow: 1px 1px 1px #666; cursor: pointer; padding: 4px 0px 4px; font-size: 125%;
  958. }
  959. .sideIchibaPanel .ichibaPanelFooter{
  960. text-align: center;
  961. }
  962. .sideIchibaPanel .ichiba_mainitem {
  963. margin: 0 0 8px 0; background: white; border-bottom : 1px dotted #ccc;
  964. }
  965. .sideIchibaPanel .ichiba_mainitem a:hover{
  966. background: #eef;
  967. }
  968. .sideIchibaPanel .ichiba_mainitem>div {
  969. max-width: 266px; margin: auto; text-align: center;
  970. }
  971. .sideIchibaPanel .ichiba_mainitem .blomagaArticleNP {
  972. background: url("http://ichiba.nicovideo.jp/embed/zero/img/bgMainBlomagaArticleNP.png") no-repeat scroll 0 0 transparent;
  973. height: 170px;
  974. margin: 0 auto;
  975. width: 180px;
  976. }
  977. .sideIchibaPanel .ichiba_mainitem .blomagaLogo {
  978. color: #FFFFFF;font-size: 9px;font-weight: bold;padding-left: 10px;padding-top: 8px;
  979. }
  980. .sideIchibaPanel .ichiba_mainitem .blomagaLogo span{
  981. background: none repeat scroll 0 0 #AAAAAA;padding: 0 3px;
  982. }
  983. .sideIchibaPanel .ichiba_mainitem .blomagaText {
  984. color: #666666;font-family: 'HGS明朝E','MS 明朝';font-size: 16px;height: 100px;
  985. padding: 7px 25px 0 15px;text-align: center;white-space: normal;word-break: break-all;word-wrap: break-word;
  986. }
  987. .sideIchibaPanel .ichiba_mainitem .blomagaAuthor {
  988. color: #666666; font-size: 11px;padding: 0 20px 0 10px;text-align: right;
  989. }
  990. .sideIchibaPanel .ichiba_mainitem .balloonUe{
  991. bottom: 12px; display: block; max-width: 266px;
  992. }
  993. .sideIchibaPanel .ichiba_mainitem .balloonUe a{
  994. background: url("/img/watch_zero/ichiba/imgMainBalloonUe.png") no-repeat scroll center top transparent;
  995. color: #666666 !important;
  996. display: block;
  997. font-size: 108%;
  998. line-height: 1.2em;
  999. margin: 0 auto;
  1000. padding: 8px 15px 3px;
  1001. text-align: center;
  1002. text-decoration: none;
  1003. word-wrap: break-word;
  1004. }
  1005. .sideIchibaPanel .ichiba_mainitem .balloonShita{
  1006. height: 12px; bottom: 0; left: 0;
  1007. }
  1008. .sideIchibaPanel .ichiba_mainitem .balloonShita img{
  1009. vertical-align: top !important;
  1010. }
  1011. .sideIchibaPanel .ichiba_mainitem .ichibaMarquee {
  1012. display: none;
  1013. }
  1014. .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  1015. font-size: 22px; color: #0066CC;
  1016. font-family: 'ヒラギノ明朝 Pro W3','Hiragino Mincho Pro','MS P明朝','MS PMincho',serif;
  1017. }
  1018. .sideIchibaPanel .ichiba_mainitem .action {
  1019. font-size: 85%;
  1020. }
  1021. .sideIchibaPanel .ichiba_mainitem .action .buy {
  1022. font-weight: bolder; color: #f60;
  1023. }
  1024. .sideIchibaPanel .ichiba_mainitem .itemname {
  1025. font-weight: bolder;
  1026. }
  1027. .sideIchibaPanel .ichiba_mainitem .maker {
  1028. font-size: 77%; margin-bottom: 2px;
  1029. }
  1030. .sideIchibaPanel .ichiba_mainitem .price {
  1031. }
  1032. .sideIchibaPanel .ichiba_mainitem .action .click {
  1033. font-weight: bolder;
  1034. }
  1035. .sideIchibaPanel .ichiba_mainitem .goIchiba {
  1036. font-size: 77%; margin: 5px 0;
  1037. }
  1038. .sideIchibaPanel .addIchiba, .sideIchibaPanel .reloadIchiba {
  1039. cursor: pointer;
  1040. }
  1041. .sideIchibaPanel .noitem {
  1042. cursor: pointer;
  1043. }
  1044.  
  1045. #outline .bottomAccessContainer {
  1046. position: absolute; top: 12px;
  1047. }
  1048. #outline .bottomConfButtonContainer {
  1049. position: absolute; top: 12px; right: 0px;
  1050. }
  1051. body.videoExplorer .bottomAccessContainer{
  1052. display: none;
  1053. }
  1054. #outline.under960 .bottomAccessContainer{
  1055. right: 60px;
  1056. }
  1057. .watchItLaterSettingMenu {
  1058. font-weight: bolder;
  1059. white-space: nowrap;
  1060. }
  1061. #outline .sidebar {
  1062. -webkit-transition: margin-top 0.3s ease-out;
  1063. transition: margin-top 0.3s ease-out;
  1064. }
  1065. #outline.under960 .sidebar {
  1066. margin-top: 24px;
  1067. }
  1068. #videoHeader.menuClosed .watchItLaterMenu, #videoHeader.menuClosed .hidariue { display: none; }
  1069. #videoHeader .watchItLaterMenu {
  1070. position: absolute; width: 100px; left: -55px; top: 32px;
  1071. }
  1072. {* タイトルクリックでヘッダが開閉できるのをわかりやすく *}
  1073. .videoDetailToggleButton:hover {
  1074. text-decoration: underline;
  1075. }
  1076. .videoDetailToggleButton:hover:after {
  1077. content: '▼';
  1078. position: absolute;
  1079. width: 32px;
  1080. height: 20px;
  1081. top: 0;
  1082. bottom: 0;
  1083. right: -32px;
  1084. margin: auto;
  1085. color: #888;
  1086. font-size: 80%;
  1087. }
  1088. .infoActive .videoDetailToggleButton:hover:after {
  1089. content: '▲';
  1090. }
  1091.  
  1092. {* プレイリスト出したり隠したり *}
  1093. #playlist>* {
  1094. -webkit-transition: opacity 0.6s; transition: opacity 0.6s;
  1095. }
  1096. body:not(.full_with_browser):not(.videoExplorer) #playlist.w_closing>* {
  1097. opacity: 0;
  1098. }
  1099. body:not(.full_with_browser):not(.videoExplorer) #playlist:not(.w_show){
  1100. position: absolute; top: -9999px;
  1101. }
  1102. #playlist.w_show{
  1103. {*max-height: 180px;*}
  1104. }
  1105. .playlistToggle:after {
  1106. content: "▼";
  1107. }
  1108. .playlistToggle.w_show:after {
  1109. content: "▲";
  1110. }
  1111.  
  1112. body.videoExplorer #content.w_adjusted #playlist .playlistInformation {
  1113. white-space: nowrap;
  1114. }
  1115. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .playbackOption {
  1116. position: absolute;
  1117. }
  1118. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .generationMessage{
  1119. margin-left: 90px; max-width: 350px; overflow: hidden; text-overflow: ellipsis;
  1120. }
  1121. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption {
  1122. position: absolute; right: 0; top: 0;
  1123. }
  1124. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption a {
  1125. background: #444;
  1126. }
  1127. #playlistContainerInner .thumbContainer, #playlistContainerInner .balloon{
  1128. cursor: move;
  1129. }
  1130.  
  1131.  
  1132. {* ページャーの字が小さくてクリックしにくいよね *}
  1133. #resultPagination {
  1134. padding: 5px; font-weight: bolder; border: 1px dotted silter; font-size: 130%;
  1135. }
  1136.  
  1137. #playlistContainer #playlistContainerInner .playlistItem .balloon {
  1138. bottom: auto; top: -2px; padding: auto;
  1139. }
  1140.  
  1141. body.w_channel #leftPanel .userIconContainer{
  1142. display: none;
  1143. }
  1144. {* WatchItLater設定パネル *}
  1145. #watchItLaterConfigPanel {
  1146. position: fixed; bottom: 0px; right: 16px; z-index: 10001;
  1147. width: 460px; padding: 0;
  1148. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1149. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1150. transform: scaleY(0); -webkit-transform: scaleY(0);
  1151. }
  1152. #watchItLaterConfigPanel.open {
  1153. transform: scaleY(1); -webkit-transform: scaleY(1);
  1154. }
  1155. #watchItLaterConfigPanelShadow {
  1156. position: fixed; bottom: 16px; right: 16px; z-index: 10000;
  1157. width: 460px; height: 559px; padding: 0;
  1158. background: #000; {*box-shadow: 0 0 2px black; border-radius: 8px;*} -webkit-filter: opacity(70%);
  1159. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1160. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1161. transform: scaleY(0); -webkit-transform: scaleY(0);
  1162. }
  1163. #watchItLaterConfigPanelShadow.open {
  1164. transform: scaleY(1); -webkit-transform: scaleY(1);
  1165. }
  1166. #watchItLaterConfigPanelShadowTop {
  1167. position: fixed; bottom: 563px; right:0px; z-index: 10000; background: #333;
  1168. width: 492px; height: 20px; padding: 0; border-radius: 32px; -webkit-filter: opacity(90%); display: none;
  1169. }
  1170. #watchItLaterConfigPanelOverShadow {
  1171. position: fixed; bottom: 575px; right: 0px; width: 488px; height: 8px;
  1172. box-shadow: 0 4px 16px #333;z-index: 10002; display: none;
  1173. }
  1174. #watchItLaterConfigPanel .head {
  1175. background-color: #CCCCCC;border-radius: 0;color: black;height: 50px;
  1176. overflow: hidden;padding: 5px 0 0 16px;position: relative;
  1177. }
  1178. #watchItLaterConfigPanel .head h2 {
  1179. font-size: 135%;
  1180. }
  1181. #watchItLaterConfigPanel .inner{
  1182. height: 500px; overflow-y: auto;border-width: 4px 16px 16px 16px; border-radius: 0 0 16px 16px;
  1183. border-style: solid;border-color: #ccc;
  1184. }
  1185. #watchItLaterConfigPanel ul{
  1186. border-style: inset; border-color: #ccc; border-width: 0 1px 0;
  1187. }
  1188. #watchItLaterConfigPanel ul.shortcutContainer{
  1189. border-width: 0 1px 1px;
  1190. }
  1191. #watchItLaterConfigPanel ul.videoStart{
  1192. border-width: 1px 1px 0;
  1193. }
  1194. #watchItLaterConfigPanel li{
  1195. }
  1196. #watchItLaterConfigPanel li:hover{
  1197. {*background: #ddd;*}
  1198. }
  1199. #watchItLaterConfigPanel li.buggy{
  1200. color: #888;
  1201. }
  1202. #watchItLaterConfigPanel label{
  1203. margin: 0 5px;
  1204. }
  1205. #watchItLaterConfigPanel label:hover{
  1206. }
  1207. #watchItLaterConfigPanel .foot {
  1208. text-align: right; padding: 0 12px;
  1209. }
  1210. #watchItLaterConfigPanel .closeButton{
  1211. border: 0 none;border-radius: 0 0 4px 4px;box-shadow: 0 1px 2px white;color: #666; border: 1px solid #999;
  1212. cursor: pointer;float: right;margin-top: 8px;position: absolute;right: 16px;
  1213. text-shadow: 0 1px 0 white;top: -10px; width: 60px;
  1214. }
  1215. #watchItLaterConfigPanel.autoBrowserFull_false .disableAutoBrowserFullIfNicowari,
  1216. #watchItLaterConfigPanel.autoBrowserFull_true .autoScrollToPlayer,
  1217. #watchItLaterConfigPanel.autoBrowserFull_true .autoOpenSearch,
  1218. #watchItLaterConfigPanel.removeLeftPanel_true .leftPanelJack {
  1219. color: #ccc; text-shadow: -1px -1px 0 #888;
  1220. }
  1221. #watchItLaterConfigPanel .reload .title:after {
  1222. content: ' (※)'; font-size: 80%; color: #900;
  1223. }
  1224. #watchItLaterConfigPanel .debugOnly {
  1225. display: none;
  1226. }
  1227. #watchItLaterConfigPanel.debugMode .debugOnly {
  1228. display: block; background: #888;
  1229. }
  1230. #watchItLaterConfigPanel .section {
  1231. border-style: solid;border-width: 10px 12px 10px 12px;color: white; font-size: 135%; position: relative;
  1232. font-weight: bolder; cursor: pointer; {*text-shadow: 2px 2px 1px #000000;*}
  1233. 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;
  1234. }
  1235. #watchItLaterConfigPanel .open .section {
  1236. border-width: 20px 12px 12px 12px;
  1237. transition: border-width 0.2s ease-in-out ; -webkit-transition: border-width 0.2s ease-in-out ;
  1238. }
  1239. #watchItLaterConfigPanel .section:hover:after {
  1240. content: '▼';
  1241. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1242. transition: transform 0.2s ease-in-out 0.4s; -webkit-transition: -webkit-transform 0.2s ease-in-out 0.4s;
  1243. }
  1244. #watchItLaterConfigPanel .open .section:after {
  1245. content: '▼';
  1246. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1247. transform: rotate(180deg); -webkit-transform: rotate(180deg);
  1248. transition: transform 0.2s ease-in-out ; -webkit-transition: -webkit-transform 0.2s ease-in-out;
  1249. }
  1250. #watchItLaterConfigPanel .section > div {
  1251. padding: 8px 0 8px 12px; box-shadow: 0 0 4px black;
  1252. }
  1253. #watchItLaterConfigPanel .section > div > span {
  1254. {*background: #333;*}
  1255. }
  1256. #watchItLaterConfigPanel li:not(.section) {
  1257. background: #fff; border-width: 0px 0px 0px 24px; border-style: solid; border-color: #fff;
  1258. max-height: 0px; overflow: hidden;
  1259. transition: max-height 0.4s ease-in-out , border-width 0.4s ease-in-out;
  1260. }
  1261. #watchItLaterConfigPanel .open li:not(.section) {
  1262. max-height: 100px; border-width: 4px 0px 4px 24px;
  1263. transition: max-height 0.4s ease-in-out 0.2s, border-width 0.4s ease-in-out 0.2s;
  1264. }
  1265. #watchItLaterConfigPanel .section .description{
  1266. display: block; font-size: 80%;;
  1267. }
  1268. #watchItLaterConfigPanel .shortcutSetting:not(.enable) span :not(.enable){
  1269. color: silver;
  1270. }
  1271. #watchItLaterConfigPanel .shortcutSetting .enable {
  1272. cursor: pointer; margin: auto 10px;
  1273. }
  1274. #watchItLaterConfigPanel .shortcutSetting .enable:before {
  1275. content: '○ ';
  1276. }
  1277. #watchItLaterConfigPanel .shortcutSetting.enable .enable:before {
  1278. content: '㋹ '; color: blue;
  1279. }
  1280. #watchItLaterConfigPanel .shortcutSetting .ctrl, #watchItLaterConfigPanel .shortcutSetting .alt, #watchItLaterConfigPanel .shortcutSetting .shift {
  1281. cursor: pointer; border: 2px outset; margin: 4px 4px; padding: 2px 4px; width: 180px; border-radius: 4px;background: #eee;
  1282. }
  1283. #watchItLaterConfigPanel .shortcutSetting.ctrl .ctrl, #watchItLaterConfigPanel .shortcutSetting.alt .alt, #watchItLaterConfigPanel .shortcutSetting.shift .shift {
  1284. border: 2px inset; color: blue;
  1285. }
  1286. #watchItLaterConfigPanel .hoverMenuDelay input {
  1287. width: 50px; ime-mode: disabled; text-align: center;
  1288. }
  1289.  
  1290.  
  1291. {* 動画検索画面に出るお気に入りタグ・お気に入りマイリスト *}
  1292. .videoExplorerMenu .watchItLaterMenu.open,
  1293. .videoExplorerMenu .watchItLaterMenu.opening {
  1294. background: -moz-linear-gradient(center top , #D1D1D1, #FDFDFD) repeat scroll 0 0 transparent !important;
  1295. background: -webkit-gradient(linear, left top, left bottom, from(#D1D1D1), to(#FDFDFD)) !important;
  1296. border-bottom: 0 !important;
  1297. }
  1298. .videoExplorerMenu .watchItLaterMenu {
  1299. position: relative;
  1300. {*background: -moz-linear-gradient(center top , whitesmoke 0%, #E1E1E1 100%) repeat scroll 0 0 transparent;*}
  1301. {*box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.1) inset;*}
  1302. {*background: #f5f5f5;*}
  1303. border-bottom: 1px solid #CCCCCC;
  1304. }
  1305. .videoExplorerMenu .watchItLaterMenu:hover{
  1306. background: #dbdbdb;
  1307. }
  1308. .videoExplorerMenu .watchItLaterMenu {
  1309. padding: 0 12px; display: block; color: black;
  1310. }
  1311. .videoExplorerMenu .slideMenu{
  1312. width: 100%; height: auto !important;
  1313. overflow-x: hidden;
  1314. overflow-y: auto;
  1315. padding: 0;
  1316. background: #fdfdfd;
  1317. border-top: 0 !important;
  1318. display: block;
  1319. max-height: 0;
  1320. transition: max-height 0.5s ease-in-out;
  1321. }
  1322. .videoExplorerMenu .slideMenu.open {
  1323. max-height: 2000px;
  1324. transition: max-height 1s ease-in-out;
  1325. }
  1326. .videoExplorerMenu .toggleVideoExplorerMenu a {
  1327. color: black; display: block;
  1328. }
  1329. .videoExplorerMenu .toggleVideoExplorerMenu a:after {
  1330. content: "▼"; position: absolute; background: none; top: 0px; right: 10px; color: #ccc;
  1331. }
  1332. .videoExplorerMenu .toggleVideoExplorerMenu.open a:after {
  1333. content: "▲";
  1334. }
  1335.  
  1336. .videoRankingList .isCategory {
  1337. position: relative;
  1338. }
  1339.  
  1340. .rankingCategoryToggle {
  1341. position: absolute;
  1342. display: none;
  1343. height: 20px;
  1344. padding: 0px 8px;
  1345. right: 14px;
  1346. top: 0;
  1347. cursor: pointer;
  1348. border: 1px solid;
  1349. color: #666;
  1350. outline: none;
  1351. }
  1352. .rankingCategoryToggle::-moz-focus-inner {
  1353. border: 0px;
  1354. }
  1355. .slideMenu.open .isCategory:hover .rankingCategoryToggle {
  1356. display: block;
  1357. }
  1358. .categoryClose .rankingCategoryToggle .close, .rankingCategoryToggle .open{
  1359. display: none;
  1360. }
  1361. .categoryClose .rankingCategoryToggle .open{
  1362. display: inline;
  1363. }
  1364. .videoRankingList li:not(.isCategory) {
  1365. transition: max-height 0.5s;
  1366. max-height: 50px; overflow:hidden;
  1367. margin-left: 8px;
  1368. }
  1369. .videoRankingList .categoryClose:not(.isCategory) {
  1370. max-height: 0px;
  1371. }
  1372.  
  1373.  
  1374. .videoExplorerMenu .slideMenu ul{
  1375. }
  1376. .videoExplorerMenu .slideMenu ul li{
  1377. background: #fdfdfd; padding: 0; border: 0;font-size: 90%; height: auto !important;
  1378. }
  1379. .videoExplorerMenu .slideMenu ul li a{
  1380. line-height: 165%; background: none; display: block;
  1381. }
  1382. .videoExplorerMenu.w_touch .slideMenu ul li a{
  1383. line-height: 300%; font-size: 120%; color: black;
  1384. }
  1385. .videoExplorerMenu .slideMenu ul li a:before{
  1386. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  1387. display: inline-block;
  1388. height: 14px;
  1389. margin: -4px 4px 0 0;
  1390. vertical-align: middle;
  1391. width: 18px;
  1392. content: ""
  1393. }
  1394. .videoExplorerMenu .slideMenu ul li a.defMylist:before{ background-position: 0 -253px;}
  1395. .videoExplorerMenu .slideMenu ul li.folder0 a:before{ background-position: 0 0;}
  1396. .videoExplorerMenu .slideMenu ul li.folder1 a:before{ background-position: 0 -23px;}
  1397. .videoExplorerMenu .slideMenu ul li.folder2 a:before{ background-position: 0 -46px;}
  1398. .videoExplorerMenu .slideMenu ul li.folder3 a:before{ background-position: 0 -69px;}
  1399. .videoExplorerMenu .slideMenu ul li.folder4 a:before{ background-position: 0 -92px;}
  1400. .videoExplorerMenu .slideMenu ul li.folder5 a:before{ background-position: 0 -115px;}
  1401. .videoExplorerMenu .slideMenu ul li.folder6 a:before{ background-position: 0 -138px;}
  1402. .videoExplorerMenu .slideMenu ul li.folder7 a:before{ background-position: 0 -161px;}
  1403. .videoExplorerMenu .slideMenu ul li.folder8 a:before{ background-position: 0 -184px;}
  1404. .videoExplorerMenu .slideMenu ul li.folder9 a:before{ background-position: 0 -207px;}
  1405.  
  1406. .videoExplorerMenu .slideMenu ul li.g_ent2 a:before { background-position: 0 -23px;}
  1407. .videoExplorerMenu .slideMenu ul li.g_life2 a:before { background-position: 0 -46px;}
  1408. .videoExplorerMenu .slideMenu ul li.g_politics a:before { background-position: 0 -69px;}
  1409. .videoExplorerMenu .slideMenu ul li.g_tech a:before { background-position: 0 -92px;}
  1410. .videoExplorerMenu .slideMenu ul li.g_culture2 a:before { background-position: 0 -115px;}
  1411. .videoExplorerMenu .slideMenu ul li.g_other a:before { background-position: 0 -138px;}
  1412. .videoExplorerMenu .slideMenu ul li.r18 a:before { background-position: 0 -207px;}
  1413. .videoExplorerMenu .slideMenu ul li.all a.all,
  1414. .videoExplorerMenu .slideMenu ul li.g_ent2 a.g_ent2,
  1415. .videoExplorerMenu .slideMenu ul li.g_life2 a.g_life2,
  1416. .videoExplorerMenu .slideMenu ul li.g_politics a.g_politics,
  1417. .videoExplorerMenu .slideMenu ul li.g_tech a.g_tech,
  1418. .videoExplorerMenu .slideMenu ul li.g_culture2 a.g_culture2,
  1419. .videoExplorerMenu .slideMenu ul li.g_other a.g_other,
  1420. .videoExplorerMenu .slideMenu ul li.r18 a.r18
  1421. { font-weight: bolder; border-top: 1px dotted #ccc; }
  1422.  
  1423.  
  1424. .videoExplorerMenu .slideMenu ul li a:after{
  1425. background: none !important;
  1426. }
  1427. .videoExplorerMenu .slideMenu ul li a:hover{
  1428. background: #f0f0ff;
  1429. }
  1430. .videoExplorerMenu .slideMenu ul .reload{
  1431. cursor: pointer; border: 1px solid; padding: 0;
  1432. }
  1433.  
  1434. .videoExplorerMenu .tagSearchHistory {
  1435. border-radius: 0px; margin-top: 2px; padding: 4px; background: #ccc;
  1436. }
  1437. .videoExplorerMenu .itemList > li, #videoExplorerExpand {
  1438. background: #f5f5f5;
  1439. }
  1440. .videoExplorerMenu .itemList ul > li:hover {
  1441. background: #e7e7e7;
  1442. }
  1443. .videoExplorerMenu .itemList ul > li.active {
  1444. background: #343434;
  1445. }
  1446.  
  1447.  
  1448. {* 動画タグが1行以下の時 *}
  1449. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1450. height: 12px; padding: 6px 4px 2px;
  1451. }
  1452. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit .toggleText{
  1453. display: none;
  1454. }
  1455. {* 動画タグが2行以下の時 *}
  1456. body:not(.full_with_browser) .tag2Lines #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1457. height: 36px;
  1458. }
  1459. {* タグ領域とプレイヤーの隙間をなくす *}
  1460. body:not(.full_with_browser) #videoTagContainer, body:not(.full_with_browser) #videoHeader .videoMenuToggle {
  1461. margin-bottom: -10px;
  1462. }
  1463. #videoHeaderMenu .searchContainer .searchText {
  1464. margin-top: -8px;
  1465. }
  1466.  
  1467. body.size_small #playerContainerWrapper {
  1468. padding: 0;
  1469. }
  1470.  
  1471. {* ニュース履歴 *}
  1472. body.videoExplorer #textMarquee .openNewsHistory, body.videoExplorer #textMarquee .newsHistory {
  1473. display: none;
  1474. }
  1475. #textMarquee .openNewsHistory {
  1476. position: absolute; width: 30px;
  1477. font-size: 13px; padding: 0; margin: 0; height: 28px;
  1478. cursor: pointer;
  1479. bottom: 0;
  1480. background: none repeat scroll 0 0 transparent;
  1481. border: 1px none;
  1482. border-radius: 2px 2px 2px 2px;
  1483. cursor: pointer;
  1484. right: 18px;
  1485. z-index: 200;
  1486. }
  1487. #textMarquee .newsHistory {
  1488. position: absolute;
  1489. bottom: 0px; right: 0px; width: 100%;
  1490. max-height: 132px;
  1491. min-height: 40px;
  1492. overflow-y: auto;
  1493. overflow-x: hidden;
  1494. z-index: 1;
  1495. padding: 4px;
  1496. display: none;
  1497. background: #333;
  1498. text-align: left;
  1499. font-size: 14px;
  1500. padding: 0;
  1501. }
  1502. #textMarquee .newsHistory li{
  1503. padding: 0 2px;
  1504. }
  1505. #textMarquee .newsHistory li:nth-child(odd){
  1506. background: #444;
  1507. }
  1508. #textMarquee .newsHistory li:nth-child(even){
  1509. background: #333;
  1510. }
  1511. body.full_with_browser.hideNewsInFull #textMarquee .newsHistory {
  1512. display: none !important;
  1513. }
  1514. body #popupMarquee {
  1515. width: 360px;
  1516. }
  1517. {* 半透明だとflashの上に来ると描画されないので強制的に黒にする(Chromeは平気) *}
  1518. body.full_with_browser #popupMarquee.popupMarqueeBottomLeft {
  1519. background: #000 !important; width: 400px; opacity: 1;
  1520. }
  1521. body.full_with_browser #playerContainer {
  1522. margin-left: 0 !important;
  1523. }
  1524. body:not(.full_with_browser) #playerContainer {
  1525. {*top: -8px;*}
  1526. }
  1527. body:not(.full_with_browser) #playerContainerWrapper {
  1528. padding: 0px;
  1529. }
  1530. body.full_with_browser #playerContainer, body.size_small #playerContainer {
  1531. top: auto;
  1532. }
  1533. body.full_with_browser.no_setting_panel .videoExplorerMenu {
  1534. display:none;
  1535. }
  1536.  
  1537.  
  1538. body:not(.videoExplorer) {*#playlist:not(.nico-bucket-videoExplorer-b)*} #videoExplorerExpand {
  1539. display: none;
  1540. }
  1541. #outline .openVideoExplorer {
  1542. display: none;
  1543. }
  1544. #outline.w_hideSearchExpand .openVideoExplorer {
  1545. display: inline-block;
  1546. }
  1547.  
  1548. .videoExplorerMenu .quickSearchInput {
  1549. background: none repeat scroll 0 0 #F4F4F4;
  1550. border: 1px inset silver;
  1551. left: 60px;
  1552. padding-left: 4px;
  1553. position: absolute;
  1554. top: 2px;
  1555. width: 180px;
  1556. }
  1557. .videoExplorerMenu.w_touch .quickSearchInput {
  1558. top: 4px; font-size: 20px;
  1559. }
  1560.  
  1561. .videoExplorerContent .contentItemList .column4 {
  1562. text-align: center;
  1563. }
  1564. .videoExplorerContent .contentItemList .column4 .balloon {
  1565. bottom: auto; top: 10px;
  1566. }
  1567. .videoExplorerContent .contentItemList .column4 .videoInformation>.info {
  1568. font-size: 85%;
  1569. }
  1570. .videoExplorerContent .contentItemList .column4 .videoInformation>.info .info{
  1571. color: #000;
  1572. }
  1573. .videoExplorerContent .contentItemList .column4 .videoInformationOuter {
  1574. width: 100px; height: 48px; margin: auto; color: #666; text-align: left;
  1575. }
  1576. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  1577. height: 220px;
  1578. }
  1579. .column1 .itemMylistComment {
  1580. font-size: 85%; color: #666; display: none;
  1581. color: #400; border: 1px solid #ccc; padding: 0 4px 0px; line-height: 130%; border-radius: 4px;
  1582. }
  1583. .column1 .itemMylistComment:before {
  1584. content: '本人コメント ';
  1585. background: #ccc; border-radius: 0 0 8px 0; display: inline-block; margin: 0 4px 4px -4px; padding: 2px;
  1586. }
  1587. .column1 .itemMylistComment:after {
  1588. content: '';
  1589. }
  1590. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer {
  1591. display: none;
  1592. }
  1593. .videoExplorerContent .contentItemList .nicorepoResult .column1 .nicorepoOwnerIconContainer {
  1594. float: right; display: block;
  1595. padding: 24px 14px 0 4px;
  1596. }
  1597. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer img {
  1598. height: 48px;
  1599. }
  1600.  
  1601. .videoExplorerBody.dummyMylist #searchResultContainer .favMylistEditContainer,
  1602. .videoExplorerBody.dummyMylist:not(.ranking) #searchResultMylistSortOptions,
  1603. .videoExplorerBody.dummyMylist .favMylistEditContainer,
  1604. .videoExplorerBody.dummyMylist:not(.ownerNicorepo) #searchResultHeader {
  1605. display: none !important;
  1606. }
  1607.  
  1608. .videoExplorerContent .contentItemList .thumbnailHoverMenu {
  1609. position: absolute; padding: 0; z-index: 100;
  1610. display: none;
  1611. bottom: -1px; left: 0px;
  1612. }
  1613. .videoExplorerContent .contentItemList .deleteFromMyMylist {
  1614. cursor: pointer; font-size: 70%; border: 1px solid #ccc; padding: 0;
  1615. display: none;
  1616. }
  1617. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1618. cursor: pointer; font-size: 70%; border: 1px solid #ccc;;
  1619. }
  1620. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1621. padding: 0 4px;
  1622. }
  1623. .videoExplorerContent .contentItemList .item:hover .thumbnailHoverMenu {
  1624. display: block;
  1625. }
  1626. .videoExplorerContent .contentItemList .log-user-video-upload {
  1627. background: #ffe; border-radius: 4px;
  1628. }
  1629. .videoExplorerContent .contentItemList .nicorepoResult .itemVideoDescription, .videoExplorerContent .contentItemList .nicorepoResult .videoTitle{
  1630. }
  1631. .videoExplorerContent .contentItemList.channelGuideVideo {
  1632. background: #eff; {* 検索結果にチャンネル動画が紛れ込むようになったのでわかりやすく *}
  1633. }
  1634.  
  1635. #videoExplorer.w_deflist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist,
  1636. #videoExplorer.w_mylist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist
  1637. {
  1638. display: inline-block;
  1639. }
  1640.  
  1641. #playlist .generationMessage {
  1642. cursor: pointer;
  1643. }
  1644. #playlist .generationMessage:hover {
  1645. text-decoration: underline;
  1646. }
  1647. #playlist .generationMessage:after {
  1648. content: "▼";
  1649. }
  1650.  
  1651. #yukkuriPanel {
  1652. position: fixed; z-index: 1500; bottom: 0; left: 0; display: inline-block;
  1653. transition: bottom 0.2s ease;
  1654. }
  1655. #yukkuriPanel.mylistPanelLeft {
  1656. bottom: 24px;
  1657. }
  1658. body.w_noNicoru .nicoru-button{
  1659. left: -9999; display: none !important;
  1660. }
  1661. body.w_noNicoru .menuOpened #videoMenuTopList li.videoMenuListNicoru .nicoru-button{
  1662. display: block !important;
  1663. }
  1664. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li {
  1665. margin: 0 18px 4px 0;
  1666. }
  1667. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlContainer, body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlEditContainer {
  1668. padding: 1px 0;
  1669. }
  1670.  
  1671. .userProfile.w_touch {
  1672. font-size: 150%; line-height: 120%;
  1673. }
  1674. .resultPagination.w_touch {
  1675. font-size: 200%;
  1676. }
  1677. .resultPagination.w_touch li{
  1678. padding: 4px 16px;
  1679. }
  1680. select.w_touch {
  1681. font-size: 200%;
  1682. }
  1683. {* 真・browserFullモード *}
  1684. body.full_with_browser.hideCommentInput #nicoplayerContainerInner {
  1685. {* コメント入力欄は動画上表示にするのではなく、画面外に押し出す事によって見えなくする *}
  1686. margin-top: -10px; margin-bottom: -30px;
  1687. }
  1688. body.full_with_browser.trueBrowserFull #playerContainerWrapper {
  1689. margin: 0 !important;
  1690. }
  1691. body.full_with_browser.trueBrowserFull #playlist {
  1692. display: none;
  1693. }
  1694. body.full_with_browser.trueBrowserFull:not(.w_fullScreenMenu) .mylistPopupPanel.fixed,body.full_with_browser.trueBrowserFull .yukkuriButton { display:none; }
  1695. #trueBrowserFullShield {
  1696. -webkit-transition: opacity 0.2s ease-out;
  1697. position:absolute;
  1698. display: none;
  1699. }
  1700. body.full_with_browser #trueBrowserFullShield {
  1701. background: black;
  1702. display: block;
  1703. bottom: 100px;
  1704. right: 50px;
  1705. z-index: 10000;
  1706. min-width: 400px;
  1707. cursor: nw-resize;
  1708. opacity: 0;
  1709. color: white;
  1710. box-shadow: 2px 2px 2px silver;
  1711. border-radius: 4px;
  1712. }
  1713. body.full_with_browser #trueBrowserFullShield .title {
  1714. color: #ffc; font-size: 120%;
  1715. }
  1716. body.full_with_browser #trueBrowserFullShield .ownerIcon {
  1717. float: left; height: 55px; padding: 8px;
  1718. }
  1719. body.full_with_browser #trueBrowserFullShield:hover, body.full_with_browser #trueBrowserFullShield.active, body.w_fullScreenMenu #trueBrowserFullShield {
  1720. opacity: 1;
  1721. }
  1722. body:not(.full_with_browser) #trueBrowserFullShield { display: none; }
  1723.  
  1724. #sharedNgSettingContainer {
  1725. display: inline-block; font-size: 80%; position: absolute; top: -18px; left: 5px;
  1726. }
  1727. #sharedNgSetting {
  1728. background: #ddd; border: 1px solid silver;
  1729. }
  1730. {* ニュース消す *}
  1731. #content.noNews #textMarquee {
  1732. display: none !important;
  1733. }
  1734. body:not(.videoExplorer):not(.full_with_browser) #content.noNews #playerContainer {
  1735. min-height: 461px;
  1736. }
  1737. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper {
  1738. height: auto !important; position: absolute; bottom: 18px;
  1739. }
  1740. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabContainer {
  1741. bottom: -17px;
  1742. }
  1743. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #playerTabContainer {
  1744. bottom: 20px;
  1745. }
  1746. #playerTabWrapper.w_videoInfo #playerTabContainer, #playerTabWrapper.w_ichiba #playerTabContainer, #playerTabWrapper.w_review #playerTabContainer {
  1747. bottom: 0px !important;
  1748. }
  1749. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_videoInfo,
  1750. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_ichiba,
  1751. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_review
  1752. {
  1753. height: auto !important; position: absolute; bottom: 2px;
  1754. }
  1755. {* body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #leftPanel {
  1756. height: auto !important; position: absolute; bottom: 2px;
  1757. }*}
  1758. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerCommentPanel {
  1759. height: 100% !important;
  1760. }
  1761. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #appliPanel {
  1762. bottom: -18px !important;
  1763. }
  1764. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer {
  1765. height: auto;
  1766. }
  1767. #outline.noIchiba #nicoIchiba, #outline.noReview #videoReview{
  1768. display: none;
  1769. }
  1770. #bottomContentTabContainer.noBottom .outer, #bottomContentTabContainer.noBottom #pageFooter {
  1771. display: none;
  1772. }
  1773. #bottomContentTabContainer.noBottom #outline {
  1774. background: #141414; padding-top: 0; padding-bottom: 35px;
  1775. }
  1776.  
  1777. #content.w_flat_gray #playerContainerWrapper {
  1778. background: #666;
  1779. }
  1780. #content.w_flat_white #playerContainerWrapper {
  1781. background: #f4f4f4;
  1782. }
  1783. #content.w_flat_gray #wallImageContainer, #content.w_flat_white #wallImageContainer,
  1784. #content.w_flat_gray #chipWallList, #content.w_flat_white #chipWallList {
  1785. display: none !important;
  1786. }
  1787. #content #chipWallList {
  1788. right: auto; left: -42px;
  1789. }
  1790. #content #playlist .playlistInformation {
  1791. background: #444;
  1792. }
  1793. #content #videoExplorerExpand a {
  1794. text-shadow: none;
  1795. }
  1796.  
  1797. .videoMenuToggle {
  1798. -webkit-transform-origin: 100% 100%; -webkit-transition: -webkit-transform 0.4s;
  1799. transform-origin: 100% 100%; transition: transform 0.4s;
  1800. z-index: 1000;
  1801. }
  1802. #content.w_compact .videoHeaderTitle {
  1803. letter-spacing: -1px;
  1804. }
  1805. #content.w_compact .videoDetailExpand .arrow {
  1806. position: absolute; top: 8px; right: -24px;
  1807. }
  1808. #content.w_compact .tag1Line .videoMenuToggle {
  1809. transform: scale(0.8, 0.41); -webkit-transform: scale(0.8, 0.41);
  1810. }
  1811. #content.w_compact .tag2Lines .videoMenuToggle {
  1812. transform: scale(0.8); -webkit-transform: scale(0.8);
  1813. }
  1814. #content.w_compact #topVideoInfo .parentVideoInfo {
  1815. margin-top: -9px; margin-bottom: 9x;
  1816. }
  1817. #content.w_compact #topVideoInfo .parentVideoInfo .cct{
  1818. margin-bottom: 0;
  1819. }
  1820. #content.w_compact #topVideoInfo .parentVideoInfo .videoThumb{
  1821. margin-top: 4px;
  1822. }
  1823. #content.w_compact #topVideoInfo .ch_prof, #content.w_compact #topVideoInfo .userProfile {
  1824. min-width: 297px; margin-top: -1px; border: 1px solid #e7e7e7;
  1825. }
  1826. #content.w_compact #videoHeaderDetail .videoDetailExpand{
  1827. height: auto; padding: 0;
  1828. }
  1829. #content.w_compact #topVideoInfo .videoDescription.description {
  1830. background: #fff; margin: 10px 0 0;padding: 4px ;width: 1000px;{* base - 8 *} {*font-size: 90%;*}
  1831. }
  1832.  
  1833. {* 本家の幅が変わったら変える必要がある。 変数化した方が楽かも base = 1008 *}
  1834. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  1835. width: 1318px; {* base + 310 *}
  1836. }
  1837. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact #topVideoInfo .videoDescription.description {
  1838. width: 1226px; {* base + 218 *}
  1839. }
  1840. body:not(.full_with_browser) #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  1841. width: 1092px; {* base + 84 *}
  1842. }
  1843. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoTagContainer {
  1844. width: 1263px; {* base + 255 *}
  1845. }
  1846. body:not(.full_with_browser) #content.w_compact.w_wide #videoTagContainer {
  1847. width: 1040px; {* base + 32 *}
  1848. }
  1849. body:not(.full_with_browser) #content.w_compact #videoTagContainer {
  1850. width: 948px; {* base - 60 *}
  1851. }
  1852. body:not(.full_with_browser) #content.w_compact #videoHeader, #foot_inner {
  1853. width: 1008px; {* base + 48 *}
  1854. }
  1855. body:not(.full_with_browser).size_normal #content.w_compact #videoHeader, .size_normal #foot_inner {
  1856. width: 1234px; {* base + 226 *}
  1857. }
  1858. body:not(.full_with_browser) #content.w_compact.w_wide #videoHeader {
  1859. width: 1100px;
  1860. }
  1861. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoHeader {
  1862. width: 1326px;
  1863. }
  1864.  
  1865. #content.w_compact #topVideoInfo .videoMainInfoContainer{
  1866. padding: 0;
  1867. }
  1868. #content.w_compact #videoDetailInformation{
  1869. border-top: 0;
  1870. }
  1871. #content.w_compact #videoHeaderMenu .searchContainer {
  1872. top: -16px;
  1873. }
  1874. #content.w_compact .videoInformation{
  1875. margin: -4px 0 ;
  1876. }
  1877. #content.w_compact #topVideoInfo .videoStats {
  1878. margin-bottom: 2px;
  1879. }
  1880. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1881. width: 72px;
  1882. }
  1883. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList {
  1884. padding-left: 85px;
  1885. }
  1886. body.full_with_browser #videoHeaderTagList { background: #fafafa; }
  1887. #content.w_compact #topVideoInfo {
  1888. margin: 4px 0 4px;
  1889. }
  1890. #content.w_compact #topVideoInfo .videoShareLinks .socialLinks {
  1891. margin-top: -6px;
  1892. }
  1893. #outline.w_compact #videoInfoHead{
  1894. margin: 0 ;
  1895. }
  1896. #outline.w_compact .videoInformation #videoTitle {
  1897. margin: -4px 0 0;
  1898. }
  1899. #outline.w_compact .videoInformation #videoStats {
  1900. margin-top: -4px;
  1901. }
  1902. #outline.w_compact .videoInformation #videoStats .ranking {
  1903. margin: 0 0 4px;
  1904. }
  1905. #outline.w_compact #videoShareLinks {
  1906. margin: 0;
  1907. }
  1908. #outline.w_compact #bottomVideoDetailInformation {
  1909. margin: -18px 0 0;
  1910. }
  1911. #outline.w_compact .infoHeadOuter .videoEditMenuExpand {
  1912. position: absolute; top: 0;
  1913. }
  1914. #outline.w_compact .videoEditMenu {
  1915. margin: 0;
  1916. }
  1917. #outline.w_compact .videoDescription {
  1918. font-size: 90%; margin-top: -8px; padding: 0 0 4px 4px;
  1919. }
  1920. #outline.w_compact #videoComment {
  1921. margin: 0px; border: 1px solid silver; border-radius: 4px 4px 4px 4px; padding: 0 4px;
  1922. }
  1923. #outline.w_compact #videoComment h4{
  1924. padding-left: 4px;
  1925. }
  1926. #outline.w_compact .videoMainInfoContainer {
  1927. border-bottom: 0; margin-bottom: 0;
  1928. }
  1929. #outline.w_compact {
  1930. border-bottom: 0; margin-bottom: 0;
  1931. }
  1932.  
  1933. #outline.w_compact .sidebar { width: 300px; }
  1934.  
  1935. #outline.w_compact #ichibaMain dl.ichiba_mainitem {
  1936. margin: 0 22px 30px 0;
  1937. }
  1938. #footer { z-index: 1; }
  1939.  
  1940. body.en-us #playerAlignmentArea, body.zh-tw #playerAlignmentArea {
  1941. {*padding-right: 0;*}
  1942. }
  1943. #footer .toggleBottom {
  1944. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  1945. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  1946. }
  1947. #footer:hover .toggleBottom {
  1948. border: 1px outset; background: #ccc;
  1949. }
  1950. #footer .toggleBottom:hover {
  1951. box-shadow: 0px 0px 8px #fff;
  1952. }
  1953. #footer.noBottom .toggleBottom {
  1954. border-radius: 0 0 16px 16px;
  1955. }
  1956. #footer .toggleBottom .openBottom, #footer.noBottom .toggleBottom .closeBottom {
  1957. display: none;
  1958. }
  1959. #footer.noBottom .toggleBottom .openBottom {
  1960. display: block;
  1961. }
  1962. #footer .toggleBottom>div {
  1963. -webkit-transform: scaleX(3); transform: scaleX(3);
  1964. }
  1965. #footer .toggleBottom {
  1966. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  1967. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  1968. }
  1969. #footer:hover .toggleBottom {
  1970. border: 1px outset; background: #ccc;
  1971. }
  1972. #footer .toggleBottom:hover {
  1973. box-shadow: 0px 0px 8px #fff;
  1974. }
  1975.  
  1976. #footer.noBottom #foot_inner { padding: 0; }
  1977. #footer.noBottom a:nth-of-type(3):after, #footer.noBottom a:nth-of-type(6):after {
  1978. content: ' | '; color: white;
  1979. }
  1980. #footer.noBottom br { display: none; }
  1981. html { background: #141414; }
  1982. .videoExplorer #videoExplorer,
  1983. .videoExplorer #videoExplorer .videoExplorerBody,
  1984. .videoExplorerContentWrapper
  1985. {
  1986. background: none;
  1987. }
  1988.  
  1989. .animateBlink {
  1990. -webkit-transition: 1s ease-in; transition: 1s ease-in;
  1991. }
  1992.  
  1993. .w_compact .toggleDetailExpand, .w_compact .shortVideoInfo {
  1994. display: none;
  1995. }
  1996. .videoDetailToggleButton {
  1997. cursor: pointer;
  1998. }
  1999. #leftPanel {
  2000. {*border-radius: 4px 4px 4px 4px;*}
  2001. display: none; padding: 0; position: absolute; text-align: left; top: 0; z-index: 101;
  2002. }
  2003. body.ja-jp #leftPanel { display: none; }
  2004. body:not(.videoExplorer) #leftPanel { display: none; }
  2005.  
  2006.  
  2007. body.full_with_browser #playerTabWrapper, body.full_with_browser:not(.videoExplorer) .w_wide #playerTabWrapper {
  2008. top: auto !important; bottom: 3000px !important; right: 50px !important;
  2009. transition: bottom 0.2s ease-out; max-height: 500px;
  2010. }
  2011.  
  2012. body.full_with_browser.w_fullScreenMenu:not(.videoExplorer) #playerTabWrapper {
  2013. top: auto !important; bottom: 200px !important; right: 50px !important;
  2014. }
  2015.  
  2016. #fullScreenMenuContainer { display: none; }
  2017. body.full_with_browser #fullScreenMenuContainer {
  2018. display: block; position: absolute; bottom: 3000px; left: 50px; z-index: 10000;
  2019. background: #fff; cursor: pointer; transition: bottom 0.2s ease-out;
  2020. }
  2021. body.full_with_browser.w_fullScreenMenu #fullScreenMenuContainer {
  2022. bottom: 100px;
  2023. }
  2024.  
  2025. #fullScreenMenuContainer .button {
  2026. cursor: pointer; transition: color 0.4s ease-out;
  2027. }
  2028. #fullScreenMenuContainer .modeStatus { display: none; font-weight: bolder; }
  2029. body.trueBrowserFull #fullScreenMenuContainer .fullScreenModeSwitch { color: blue; }
  2030. body:not(.trueBrowserFull) #fullScreenMenuContainer .fullScreenModeSwitch .mode_normal,
  2031. body.trueBrowserFull #fullScreenMenuContainer .fullScreenModeSwitch .mode_noborder { display: inline; }
  2032.  
  2033. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch { color: blue; }
  2034. #nicoplayerContainerInner:not(.stageVideo) #fullScreenMenuContainer .stageVideoSwitch .mode_off,
  2035. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch .mode_on { display: inline; }
  2036.  
  2037.  
  2038. body.full_with_browser.w_fullScreenMenu .videoHeaderOuter {
  2039. position: absolute; z-index: 1000; width: 100%;
  2040. }
  2041. body.full_with_browser.w_fullScreenMenu #videoTagContainer { width: 100%; display: block; }
  2042.  
  2043. .popupMarqueeContent {
  2044. background: black;
  2045. }
  2046.  
  2047. #videoExplorer, #playlist {
  2048. transition: margin-left 0.2s ease-in-out;
  2049. }
  2050.  
  2051. .dummyMylist .editFavorite {
  2052. display: none;
  2053. }
  2054.  
  2055. {* 不要な時まで横スクロールバーが出てしまうので *}
  2056. #songrium_inline { overflow: hidden; }
  2057.  
  2058. .sideVideoInfo .nextPlayButton {
  2059. position: absolute;
  2060. margin-top: -6px;
  2061. margin-left: -30px;
  2062. width: 30px;
  2063. height: 30px;
  2064. background: url(http://res.nimg.jp/img/watch_q9/icon_nextplay.png);
  2065. {*background: url("http://res.nimg.jp/img/watch_zero/videoexplorer-s90d011f9a7.png") no-repeat scroll -37px 0 rgba(0, 0, 0, 0);*}
  2066. z-index: 100;
  2067. cursor: pointer;
  2068. text-indent: -999em;
  2069. overflow: hidden;
  2070. display: inline-block;
  2071. -webkit-transform: scale(1.0); transform: scale(1.0);
  2072. }
  2073.  
  2074. .nextPlayButton {
  2075. -webkit-transform: scale(1.5); transform: scale(1.5);
  2076. transition: transform 0.1s ease; -webkit-transition: -webkit-transform 0.1s ease;
  2077. }
  2078. .sideVideoInfo .nextPlayButton:hover {
  2079. -webkit-transform: scale(1.5); transform: scale(1.5);
  2080. }
  2081. .nextPlayButton:active, .sideVideoInfo .nextPlayButton:active {
  2082. -webkit-transform: scale(1.2); transform: scale(1.2);
  2083. }
  2084.  
  2085. .sideVideoInfo .nextPlayButton:active {
  2086. background-position-y: 30px;
  2087. }
  2088.  
  2089. body.w_disableHorizontalScroll {
  2090. overflow-x: hidden !important;
  2091. }
  2092.  
  2093. #videoTagContainerPin { display: none !important; } {* タグを固定しているか4行以上の時に現われるピン *}
  2094.  
  2095. .w_adjusted #selectionSideAdAds >* {
  2096. width: 100%; height: auto; max-width: 300px; max-height: 250px;
  2097. }
  2098.  
  2099. {* *}
  2100. .w_noHover {
  2101. pointer-events: none !important;
  2102. }
  2103. .w_noHover #playlist {
  2104. pointer-events: auto !important;
  2105. }
  2106. {* ソーシャルボタン *}
  2107. .area-JP .panel_ads_shown #playerTabContainer.w_noSocial.has_panel_ads .playerTabContent {
  2108. bottom: 80px;
  2109. }
  2110. .area-JP #playerTabContainer.w_noSocial .playerTabContent {
  2111. bottom: 4px;
  2112. }
  2113. #playerTabContainer.w_noSocial .playerTabAds {
  2114. bottom: 0;
  2115. }
  2116. #playerTabContainer.w_noSocial .socialButtons{
  2117. display: none;
  2118. }
  2119. .w_noSocial .nicoSpotAds {
  2120. bottom: 8px;
  2121. }
  2122.  
  2123.  
  2124. {* テレビちゃんメニュー スライドをやめる *}
  2125. body #videoHeader #videoMenuWrapper{
  2126. position: absolute; width: 324px; height: auto !important;
  2127. opacity: 0;
  2128. transition: opacity 0.4s ease;
  2129. right: 0px;
  2130. }
  2131. body #videoHeader.menuOpened #videoMenuWrapper{
  2132. z-index: 1000 !important;
  2133. border: 1px solid #000;
  2134. background: white;
  2135. box-shadow: 0px 0px 4px #000;
  2136. top: 110px;
  2137. bottom: auto;
  2138. opacity: 1;
  2139. }
  2140. body .tag1Line #videoHeader.menuOpened #videoMenuWrapper{
  2141. top: 62px;
  2142. }
  2143. body .tag2Lines #videoHeader.menuOpened #videoMenuWrapper{
  2144. top: 86px;
  2145. }
  2146. body #videoHeader.infoActive.menuOpened #videoMenuWrapper{
  2147. top: auto;
  2148. bottom: 48px;
  2149. }
  2150. {* body #videoHeader #videoMenuWrapper .defmylistButton, body #videoHeader #videoMenuWrapper .mylistButton {
  2151. display: none !important;
  2152. } *}
  2153. body #videoHeader #videoMenuTopList{
  2154. position: relative;
  2155. width: auto;
  2156. }
  2157. body #videoHeader.menuOpened #videoMenuWrapper .videoMenuList{
  2158. display: inline-block;
  2159. width: 60px;
  2160. min-height: 72px;
  2161. }
  2162. body #videoMenuTopList li.videoMenuListNicoru {
  2163. float: right;
  2164. min-height: 72px;
  2165. }
  2166. body #videoHeader.isAdult .videoMenuToggle, body #videoHeader.noAudioDownload .downloadButton {
  2167. display: inline-block;
  2168. opacity: 0.5;
  2169. pointer-events: none !important;
  2170. }
  2171. {* テレビちゃんメニューのスライド殺す *}
  2172. body #videoHeader.menuOpened #videoMenuWrapper {
  2173. margin-bottom: 0;
  2174. }
  2175. body #videoHeader.menuOpened #videoHeaderDetail {
  2176. margin-top: 8px;
  2177. }
  2178.  
  2179.  
  2180. .largeThumbnailPopup, .largeThumbnailPopup div{
  2181. background-color: #000;
  2182. background-size: contain;
  2183. background-repeat: no-repeat;
  2184. background-position: center center;
  2185. background-size: contain;
  2186. -moz-background-size: contain;
  2187. -webkit-background-size: contain;
  2188. -o-background-size: contain;
  2189. -ms-background-size: contain;
  2190. }
  2191.  
  2192. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  2193. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  2194. addStyle(__css__, 'watchItLaterStyle');
  2195. })(); // end of watchItLaterStyle
  2196.  
  2197.  
  2198.  
  2199.  
  2200.  
  2201. conf.load = function() {
  2202. try {
  2203. function loadStorage(key, def) {
  2204. if (window.localStorage[key] === undefined) { return def; }
  2205. return JSON.parse(window.localStorage.getItem(key));
  2206. }
  2207.  
  2208. for (var v in conf) {
  2209. if (typeof conf[v] === 'function') { continue; }
  2210. conf[v] = loadStorage('watchItLater_' + v, conf[v]);
  2211. }
  2212. } catch (e) {
  2213. }
  2214. };
  2215.  
  2216. conf.getValue = function(varName) {
  2217. return conf[varName];
  2218. };
  2219. conf.setValue = function(k, v) {
  2220. var lastValue = conf[k];
  2221. if (lastValue !== v) {
  2222. conf[k] = v;
  2223. window.localStorage.setItem('watchItLater_' + k, JSON.stringify(v));
  2224. EventDispatcher.dispatch('on.config.' + k, v, lastValue);
  2225. }
  2226. };
  2227. conf.load();
  2228.  
  2229. var console = (function(conf) {
  2230. if (conf.debugMode) { return window.console; }
  2231. var noop = function() {};
  2232. return {
  2233. log: noop,
  2234. error: noop,
  2235. trace: noop,
  2236. warn: noop,
  2237. table: noop
  2238. };
  2239. })(conf);
  2240.  
  2241. var ConfigPanel = (function($, conf, w) {
  2242. var pt = function(){};
  2243. var $panel = null, $shadow = null;
  2244. var menus = [
  2245. {title: '再生開始・終了時の設定', className: 'videoStart'},
  2246. {title: '自動で全画面モードにする', varName: 'autoBrowserFull',
  2247. values: {'する': true, 'しない': false}, addClass: true},
  2248. {title: '自動全画面化オンでも、ユーザーニコ割のある動画は', varName: 'disableAutoBrowserFullIfNicowari',
  2249. values: {'全画面化しない': true, '全画面化する': false}},
  2250. {title: '自動で検索モードにする(自動全画面化オフ時)', varName: 'autoOpenSearch',
  2251. values: {'する': true, 'しない': false}},
  2252. {title: '動画の位置に自動スクロール(自動全画面化オフ時)', varName: 'autoScrollToPlayer',
  2253. values: {'する': true, 'しない': false}},
  2254. // {title: '終了時に全画面モードを解除(原宿と同じにする)', varName: 'autoNotFull',
  2255. // values: {'する': true, 'しない': false},
  2256. // description: '連続再生中は解除しません'},
  2257. {title: 'ウィンドウがアクティブの時だけ自動再生する', varName: 'autoPlayIfWindowActive',
  2258. description: 'QWatch側の設定パネルの自動再生はオフにしてください。\n■こんな人におすすめ\n・自動再生ONにしたいけど別タブで開く時は自動再生したくない\n・複数タブ開いたままブラウザ再起動したら全部のタブで再生が始まって「うるせー!」という経験のある人',
  2259. values: {'する': 'yes', 'しない': 'no'}},
  2260. {title: '動画が切り替わる時、ポップアップでタイトルと再生数を表示', varName: 'popupViewCounter',
  2261. description: '全画面状態で連続再生している時などに便利です',
  2262. values: {'する': 'always', '全画面時のみ': 'full', 'しない': 'none'}},
  2263.  
  2264. {title: 'プレイヤーの設定', className: 'playerSetting'},
  2265. {title: 'コメントパネルを広くする', varName: 'wideCommentPanel',
  2266. values: {'する': true, 'しない': false}},
  2267. {title: 'コメントパネルにNG共有設定を表示', varName: 'enableSharedNgSetting',
  2268. values: {'する': true, 'しない': false}, addClass: true},
  2269. {title: 'コメントの表示', varName: 'commentVisibility',
  2270. values: {'オフ': 'hidden', '最後の状態を記憶': 'lastState', 'オン': 'visible'}},
  2271. {title: '右のパネルに動画情報・市場・レビューを表示', varName: 'rightPanelJack', reload: true,
  2272. values: {'する': true, 'しない': false}},
  2273. {title: 'ページのヘッダに再生数表示', varName: 'headerViewCounter', reload: true,
  2274. values: {'する': true, 'しない': false}},
  2275. {title: 'ニコニコニュースの履歴を保持する', varName: 'enableNewsHistory', reload: true,
  2276. values: {'する': true, 'しない': false}},
  2277. {title: 'ニコニコニュースを消す', varName: 'hideNicoNews',
  2278. values: {'消す': true, '消さない': false}},
  2279. {title: 'プレイヤーの背景', varName: 'playerBgStyle',
  2280. description: 'ウォール機能より優先されます',
  2281. values: {'白': 'white', 'グレー': 'gray', 'ウォール': ''}},
  2282. {title: 'コメントの盛り上がりをグラフ表示', varName: 'enableHeatMap', reload: true,
  2283. description: '動画のどのあたりが盛り上がっているのか、わかりやすくなります',
  2284. values: {'する': true, 'しない': false}},
  2285. {title: '大画面をもっと大画面にする', varName: 'customPlayerSize',
  2286. description: '※有効にするとニコニコニュースが表示できなくなります。',
  2287. values: {'フルHD': '1080p', '720p': '720p', '自動調整(推奨)': 'auto', 'しない': ''}},
  2288. {title: 'プレイリスト消えないモード(実験中)', varName: 'storagePlaylistMode', reload: true,
  2289. description: '有効にすると、リロードしてもプレイリストが消えなくなります。',
  2290. values:
  2291. (conf.debugMode ?
  2292. {'ウィンドウを閉じるまで': 'sessionStorage', 'ずっと保持': 'localStorage', 'しない': ''} :
  2293. {'有効(ウィンドウを閉じるまで)': 'sessionStorage', '無効': ''})
  2294. },
  2295. {title: '説明文中の動画IDにサムネイル表示(実験中)', varName: 'enableDescriptionThumbnail', reload: true,
  2296. values: {'有効': true, '無効': false}},
  2297.  
  2298.  
  2299. {title: '検索モードの設定', className: 'videoExplorer'},
  2300. {title: '検索モードを無効化', varName: 'disableVideoExplorer',
  2301. description: '無効にするとタグ検索などが原宿と同じになります。\nただし、自分で検索モードにしている時は検索モードで開きます',
  2302. values: {'する': true, 'しない': false}},
  2303. {title: 'プレイヤーをできるだけ大きくする (コメントやシークも可能にする)', varName: 'videoExplorerHack',
  2304. description: '便利ですがちょっと重いです。\n大きめのモニターだと快適ですが、小さいといまいちかも',
  2305. values: {'する': true, 'しない': false}},
  2306. {title: 'お気に入りタグを表示', varName: 'enableFavTags',
  2307. values: {'する': true, 'しない': false}},
  2308. {title: 'お気に入りマイリストを表示', varName: 'enableFavMylists',
  2309. description: '更新のあったリストが上に来るので、新着動画のチェックに便利です。',
  2310. values: {'する': true, 'しない': false}},
  2311. // {title: 'サムネを4:3にする', varName: 'squareThumbnail',
  2312. // description: '上下がカットされなくなり、サムネの全体が見えるようになります。',
  2313. // values: {'する': true, 'しない': false}},
  2314. {title: '「マイリストから外す」ボタンを表示', varName: 'enableMylistDeleteButton',
  2315. description: 'マイリストの整理に便利。\n ※ 消す時に確認ダイアログは出ないので注意',
  2316. values: {'する': true, 'しない': false}},
  2317. {title: '検索時に関連タグを表示する', varName: 'enableRelatedTag',
  2318. values: {'する': true, 'しない': false}},
  2319. {title: 'niconico新検索βを使う', varName: 'searchEngine',
  2320. description: '投稿期間や動画長による絞り込みができるようになります',
  2321. values: {'使う': 'sugoi', '使わない': 'normal'}},
  2322. {title: '1ページの表示件数', varName: 'searchPageItemCount',
  2323. values: {'100件': 100, '50件': 50, '32件': 32}},
  2324.  
  2325. {title: '全画面モードの設定', className: 'fullScreen'},
  2326. {title: '操作パネルとコメント入力欄を隠す', varName: 'controllerVisibilityInFull',
  2327. description: '全画面の時は少しでも動画を大きくしたい場合に便利',
  2328. values: {'隠す': 'hidden', '隠さない': ''}},
  2329. {title: '右下のマイリストメニュー', varName: 'hideMenuInFull',
  2330. values: {'完全に消す': 'hideAll', '色だけ変える': '', '目立たなくする': 'hide'}},
  2331. {title: 'ホイールを回したら動画情報を出す', varName: 'enableFullScreenMenu',
  2332. description: 'ホイールを大きく下に回すとメニューが出ます。タッチパネルも対応',
  2333. values: {'する': true, 'しない': false}},
  2334.  
  2335. {title: 'ページ下半身の設定', className: 'playerBottom'},
  2336. {title: 'ニコニコ市場の表示', varName: 'ichibaVisibility',
  2337. values: {'非表示': 'hidden', '表示': 'visible'}},
  2338. {title: 'レビューの表示', varName: 'reviewVisibility',
  2339. values: {'非表示': 'hidden', '表示': 'visible'}},
  2340.  
  2341. {title: '省スペース/軽量化設定', className: 'compact'},
  2342. {title: 'タグが2行以内の時に高さを詰める(ピン留め時のみ)', varName: 'enableAutoTagContainerHeight', reload: true,
  2343. values: {'詰める': true, '詰めない': false}},
  2344. {title: '動画情報の空きスペースを詰める', varName: 'compactVideoInfo',
  2345. description: '原宿ぐらいの密度になります。ちょっと窮屈かも',
  2346. values: {'詰める': true, '詰めない': false}},
  2347. // {title: '背景のグラデーションをなくす', varName: 'flatDesignMode',
  2348. // description: '軽い表示になります',
  2349. // values: {'なくす': 'on', 'なくさない': ''}},
  2350. {title: '「ニコる」をなくす', varName: 'noNicoru',
  2351. description: '画面上から見えなくなります。\nまた、コメントパネルの処理が軽くなります',
  2352. values: {'なくす': true, 'なくさない': false}},
  2353. {title: 'コメントパネルのマウスオーバー処理をなくす', varName: 'removeCommentPanelHoverEvent', reload: true,
  2354. description: 'マウスオーバー時のちらちらした物がなくなり、表示が軽くなります',
  2355. values: {'なくす': true, 'なくさない': false}},
  2356. {title: 'タグの自動更新を無効化', varName: 'disableTagReload',
  2357. values: {'する': true, 'しない': false}},
  2358. {title: '横スクロールバーを出なくする', varName: 'disableHorizontalScroll',
  2359. values: {'する': true, 'しない': false}},
  2360. {title: 'コメントパネル下のソーシャルボタン', varName: 'hideCommentPanelSocialButtons',
  2361. values: {'隠す': true, '隠さない': false}},
  2362. {title: 'GPUレイヤーを使用してみる(上級者用)', varName: 'enableGpuLayer', reload: true, debugOnly: true,
  2363. description: '環境によっては軽くなる かも しれません',
  2364. values: {'する': true, 'しない': false}},
  2365.  
  2366. {title: 'その他の設定', className: 'otherSetting'},
  2367. {title: '動画リンクにカーソルを重ねたらマイリストメニューを表示', varName: 'enableHoverPopup', reload: true,
  2368. description: 'マウスカーソルを重ねた時に出るのが邪魔な人はオフにしてください',
  2369. values: {'する': true, 'しない': false}},
  2370. {title: '動画リンクにカーソルを重ねてからメニューが出るまでの時間(秒)', varName: 'hoverMenuDelay',
  2371. type: 'text', description: '単位は秒。 標準は0.4です'},
  2372. {title: 'ニコレポのポップアップを置き換える', varName: 'replacePopupMarquee', reload: true,
  2373. description: '画面隅に出るポップアップの不可解な挙動を調整します',
  2374. values: {'する': true, 'しない': false}},
  2375. {title: '検索時のデフォルトパラメータ', varName: 'defaultSearchOption', type: 'text',
  2376. description: '常に指定したいパラメータ指定するのに便利です\n例: 「-グロ -例のアレ」とすると、その言葉が含まれる動画が除外されます'},
  2377. {title: '「@ジャンプ」を無効化', varName: 'ignoreJumpCommand', reload: true,
  2378. description: '勝手に他の動画に飛ばされる機能を無効化します。',
  2379. values: {'する': true, 'しない': false}},
  2380. {title: '「@ジャンプ」によるシーク無効化(無限ループなど)', varName: 'nicoSSeekCount', reload: true,
  2381. description: '完全に無効にする以外に、一動画あたりの回数を指定できます',
  2382. values: {'2回まで有効': 2, '1回まで有効': 1, '完全無効化': 0, 'しない': -1}},
  2383. {title: 'タッチパネル向けモード(画面を右フリックで開始)', varName: 'enableQTouch',
  2384. description: '指で操作しやすいように、一部のボタンやメニューが大きくなります',
  2385. values: {'使う': true, '使わない': false}},
  2386. {title: 'マイリストメニューの位置', varName: 'mylistPanelPosition',
  2387. values: {'左下': 'left', '右下': ''}},
  2388. {title: '2本目以降の動画だけ自動再生 (※プレミアム用)', varName: 'autoPlay2ndVideo', reload: true,
  2389. values: {'する': true, 'しない': false}},
  2390. {title: 'マイリストのローカルキャッシュ', varName: 'enableLocalMylistCache', reload: true,
  2391. description: '動画がどのマイリストに登録されてるかの情報をキャッシュします。\n「my」ボタンの右クリックを活用する人はおすすめ。',
  2392. values: {'有効': true, '無効': false}},
  2393.  
  2394.  
  2395. {title: 'マウスとキーボードの設定', description: '※Chromeはコメント入力中も反応してしまいます', className: 'shortcut'},
  2396. {title: '背景ダブルクリックで動画の位置にスクロール', varName: 'doubleClickScroll',
  2397. description: 'なにもない場所をダブルクリックすると、動画の位置にスクロールします。\n 市場を見てからプレイヤーに戻りたい時などに便利',
  2398. values: {'する': true, 'しない': false}},
  2399. {title: 'マウスのボタン+ホイールでどこでも音量調整', varName: 'mouseClickWheelVolume',
  2400. description: 'とっさに音量を変えたい時に便利',
  2401. values: {'左ボタン+ホイール': 1, '右ボタン+ホイール': 2, '使わない': 0}},
  2402. {title: '停止/再生', varName: 'shortcutTogglePlay', type: 'keyInput'},
  2403. {title: 'とりあえずマイリスト登録', varName: 'shortcutDefMylist', type: 'keyInput'},
  2404. {title: 'マイリスト登録', varName: 'shortcutMylist', type: 'keyInput',
  2405. description: '右下で選択中のマイリストに登録'},
  2406. {title: 'とりあえずマイリストを開く', varName: 'shortcutOpenDefMylist', type: 'keyInput'},
  2407. {title: '動画投稿者の関連動画を開く', varName: 'shortcutShowOtherVideo', type: 'keyInput'},
  2408. {title: '検索画面を開く', varName: 'shortcutOpenSearch', type: 'keyInput'},
  2409. {title: '関連動画(オススメ)を開く', varName: 'shortcutOpenRecommend', type: 'keyInput'},
  2410. {title: 'コメント表示ON/OFF', varName: 'shortcutCommentVisibility', type: 'keyInput'},
  2411. {title: 'プレイヤーの位置までスクロール', varName: 'shortcutScrollToNicoPlayer', type: 'keyInput'},
  2412. {title: 'ミュート', varName: 'shortcutMute', type: 'keyInput'},
  2413. {title: 'コメントの背面表示ON/FF', varName: 'shortcutDeepenedComment', type: 'keyInput'},
  2414. {title: 'ハードウェアアクセラレーションON/FF', varName: 'shortcutToggleStageVideo', type: 'keyInput'},
  2415.  
  2416. {title: 'その他2(一発ネタ系)', description: 'いつのまにか消えるかもしれません', className: 'shortcut'},
  2417. {title: 'テレビちゃんメニュー内にランダム画像(左上)表示', varName: 'hidariue',
  2418. values: {'する': true, 'しない': false}},
  2419. {title: 'ゆっくり再生(スロー再生)ボタンを表示', varName: 'enableYukkuriPlayButton',
  2420. values: {'する': true, 'しない': false}},
  2421.  
  2422. {title: '実験中の設定', debugOnly: true, className: 'forDebug'},
  2423. // {title: 'プレイリスト消えないモード(※実験中)', varName: 'hashPlaylistMode', debugOnly: true, reload: true,
  2424. // values: {'有効(連続再生中のみ)': 1, '有効(常時)': 2, '無効': 0}},
  2425.  
  2426.  
  2427.  
  2428. ];
  2429.  
  2430. var listener = [];
  2431. function dispatchEvent(name, value, lastValue) {
  2432. for (var i = 0; i < listener.length; i++) {
  2433. (listener[i])(name, value, lastValue);
  2434. }
  2435. }
  2436. pt.createPanelDom = function() {
  2437. if ($panel === null) {
  2438. $panel = w.jQuery([
  2439. '<div id="watchItLaterConfigPanel">',
  2440. '<div class="head"><button class="closeButton" title="閉じる">▲</button><h2>WatchItLaterの設定</h2>(※)のつく項目は、リロード後に反映されます</div>',
  2441. '<div class="inner"></div></div>'
  2442. ].join(''));
  2443. $panel.on('click', function(e) { e.stopPropagation(); });
  2444.  
  2445. var scrollTo = function() {
  2446. var $target = this;
  2447. var isOpen = $target.parent().toggleClass('open').hasClass('open');
  2448. if (isOpen) {
  2449. setTimeout(function() {
  2450. var $inner = $('#watchItLaterConfigPanel .inner');
  2451. $inner.animate({
  2452. scrollTop: $inner.scrollTop() + $target.parent().position().top - 50
  2453. }, 400);
  2454. }, 200);
  2455. }
  2456. };
  2457.  
  2458. var $ul = null, $inner = $panel.find('.inner'), $item; //$panel.find('ul'), $item;
  2459. for (var i = 0, len = menus.length; i < len; i++) {
  2460. if (menus[i].varName) {
  2461. $item = this.createMenuItem(menus[i]);
  2462. } else {
  2463. if (menus[i].description) {
  2464. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span><span class="description">'+ menus[i].description + '</span></div></li>');
  2465. } else {
  2466. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span></div></li>');
  2467. }
  2468. if ($ul) $inner.append($ul);
  2469. $ul =$('<ul class="sectionContainer"/>').addClass(menus[i].className + 'Container');
  2470. $item.click($.proxy(scrollTo, $item));
  2471. }
  2472. $item.toggleClass('debugOnly', menus[i].debugOnly === true).toggleClass('reload', menus[i].reload === true);
  2473. if ($ul) $ul.append($item);
  2474. }
  2475. if ($ul) $inner.append($ul);
  2476. $panel.toggleClass('debugMode', conf.debugMode);
  2477. var $bottom = w.jQuery('<div class="foot"></div>'), self = this;
  2478. $panel.append($bottom);
  2479. $panel.find('.closeButton').click(function() {
  2480. self.close();
  2481. });
  2482. if ($shadow === null) {
  2483. $shadow = $('<div id="watchItLaterConfigPanelShadow" /><div id="watchItLaterConfigPanelShadowTop"/><div id="watchItLaterConfigPanelOverShadow"/>');
  2484. }
  2485. }
  2486. };
  2487.  
  2488. pt.refresh = function() {
  2489. var isVisible = $panel.hasClass('open');
  2490. $panel.remove().empty();
  2491. $panel = null;
  2492. this.createPanelDom();
  2493. if (isVisible) { $panel.show(); }
  2494. };
  2495.  
  2496. pt.createMenuItem = function(menu) {
  2497. if (menu.type === 'text') {
  2498. return this.createTextMenuItem(menu);
  2499. } else
  2500. if (menu.type === 'keyInput') {
  2501. return this.createKeyInputMenuItem(menu);
  2502. } else {
  2503. return this.createRadioMenuItem(menu);
  2504. }
  2505. };
  2506. pt.createRadioMenuItem = function(menu) {
  2507. var title = menu.title, varName = menu.varName, values = menu.values;
  2508. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2509. if (menu.className) { $menu.addClass(menu.className);}
  2510. if (menu.description) { $menu.attr('title', menu.description); }
  2511. var currentValue = conf.getValue(varName);
  2512. $menu.addClass(menu.varName);
  2513. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2514. for (var k in values) {
  2515. var v = values[k];
  2516. var $label = w.jQuery('<label></label>');
  2517. var $chk = w.jQuery('<input>');
  2518. $chk.attr({type: 'radio', name: varName, value: JSON.stringify(v)});
  2519.  
  2520. if (currentValue === v) {
  2521. $chk.prop('checked', 'checked');
  2522. }
  2523. $chk.click(function() {
  2524. var newValue = JSON.parse(this.value), oldValue = conf.getValue(varName);
  2525. if (oldValue !== newValue) {
  2526. if (menu.addClass) {
  2527. $panel.removeClass(menu.varName + '_' + oldValue).addClass(menu.varName + '_' + newValue);
  2528. }
  2529. conf.setValue(menu.varName, newValue);
  2530. if (typeof menu.onchange === 'function') {
  2531. menu.onchange(newValue, oldValue);
  2532. }
  2533. dispatchEvent(menu.varName, newValue, oldValue);
  2534. }
  2535. });
  2536. $label.append($chk).append(w.jQuery('<span>' + k + '</span>'));
  2537. $menu.append($label);
  2538. }
  2539. return $menu;
  2540. };
  2541. pt.createTextMenuItem = function(menu) {
  2542. var title = menu.title, varName = menu.varName;
  2543. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2544. if (menu.className) { $menu.addClass(menu.className);}
  2545. if (menu.description) { $menu.attr('title', menu.description); }
  2546. var currentValue = conf.getValue(varName);
  2547. var $input = w.jQuery('<input type="text" />');
  2548. $menu.addClass(menu.varName);
  2549. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2550. $input.val(currentValue);
  2551. $input.change(function() {
  2552. var newValue = $input.val(), oldValue = conf.getValue(varName);
  2553. if (oldValue !== newValue) {
  2554. conf.setValue(varName, newValue);
  2555. if (typeof menu.onchange === 'function') {
  2556. menu.onchange(newValue, oldValue);
  2557. }
  2558. dispatchEvent(menu.varName, newValue, oldValue);
  2559. }
  2560. });
  2561. $menu.append($input);
  2562. return $menu;
  2563. };
  2564.  
  2565. pt.createKeyInputMenuItem = function(menu) {
  2566. var title = menu.title, varName = menu.varName;
  2567. var currentValue = conf.getValue(varName), currentKey = currentValue.char;
  2568.  
  2569. function update() {
  2570. var newValue = {char: $sel.val(), ctrl: $menu.hasClass('ctrl'), alt: $menu.hasClass('alt'), shift: $menu.hasClass('shift'), enable: $menu.hasClass('enable')};
  2571. conf.setValue(varName, newValue);
  2572. if (typeof menu.onchange === 'function') {
  2573. menu.onchange(newValue);
  2574. }
  2575. dispatchEvent(menu.varName, newValue, conf.getValue(varName));
  2576. }
  2577.  
  2578. var $menu = w.jQuery('<li class="shortcutSetting"><p class="title">' + title + '</p></li>');
  2579. var sel = ['<select>'], $sel;
  2580. for (var v = 48; v <= 90; v++) {
  2581. if (v >= 0x3c && v <= 0x3f) continue;
  2582. var c = String.fromCharCode(v);
  2583. var op = ['<option value="', c, '">', c, '</option>' ].join('');
  2584. sel.push(op);
  2585. }
  2586. sel.push('</select>');
  2587. $sel = w.jQuery(sel.join(''));
  2588. 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) {
  2589. var meta = w.jQuery(e.target).attr('data-meta');
  2590. $menu.toggleClass(meta);
  2591. update();
  2592. });
  2593. $sel.change(update);
  2594.  
  2595. $menu.toggleClass('enable', currentValue.enable).toggleClass('ctrl', currentValue.ctrl).toggleClass('alt', currentValue.alt).toggleClass('shift', currentValue.shift);
  2596. $sel.val(currentKey);
  2597.  
  2598. if (menu.className) { $menu.addClass(menu.className);}
  2599. if (menu.description) { $menu.attr('title', menu.description); }
  2600.  
  2601. $menu.append(w.jQuery('<span/>').append($meta).append($sel));
  2602.  
  2603. return $menu;
  2604. };
  2605.  
  2606. pt.toggleOpenSection = function(sectionName, toggle) {
  2607. $('#watchItLaterConfigPanel .'+ sectionName + 'Container').toggleClass('open', toggle);
  2608. $('#watchItLaterConfigPanel .inner').scrollTop($('#watchItLaterConfigPanel .' + sectionName).position().top - 50);
  2609. };
  2610.  
  2611. pt.addChangeEventListener = function(callback) {
  2612. listener.push(callback);
  2613. };
  2614. pt.open = function() {
  2615. $('body').append($shadow).append($panel);
  2616. setTimeout(function() {
  2617. $shadow.addClass('open'); $panel.addClass('open');
  2618. }, 50);
  2619. setTimeout(function() {
  2620. if (WatchController.isFullScreen()) {
  2621. pt.toggleOpenSection('fullScreen', true);
  2622. } else
  2623. if (WatchController.isSearchMode()) {
  2624. pt.toggleOpenSection('videoExplorer', true);
  2625. }
  2626. }, 1000);
  2627. };
  2628. pt.close = function() {
  2629. $shadow.removeClass('open'); $panel.removeClass('open');
  2630. setTimeout(function() {
  2631. $shadow.detach(); $panel.detach();
  2632. }, 800);
  2633. };
  2634. pt.toggle = function() {
  2635. this.createPanelDom();
  2636. if ($panel.hasClass('open')) {
  2637. this.close();
  2638. } else {
  2639. this.open();
  2640. }
  2641. };
  2642.  
  2643. return pt;
  2644. })(w.jQuery, conf, w);
  2645.  
  2646.  
  2647. /**
  2648. * 通信用
  2649. */
  2650. window.WatchItLater = {
  2651. config: {
  2652. get: function(varName) {
  2653. return conf.getValue(varName);
  2654. },
  2655. set: function(varName, value) {
  2656. conf.setValue(varName, value);
  2657. },
  2658. open: function() {
  2659. ConfigPanel.open();
  2660. }
  2661. },
  2662. loader: {},
  2663. debug: {},
  2664. init: {},
  2665. test: {
  2666. assert: function(v, m) {
  2667. if (v === true) {
  2668. window.console.log('%c OK: ', 'color: black; background: lime;', m);
  2669. } else {
  2670. window.console.log('%cFail: ', 'color: white; background: red;', m);
  2671. throw {message: 'Fail'};
  2672. }
  2673. },
  2674. expect: function(a) {
  2675. try {
  2676. var assert = window.WatchItLater.test.assert, exp = {
  2677. toBeTrue: function( desc) { assert(a === true , desc); },
  2678. toBeFalse: function( desc) { assert(a === false , desc); },
  2679. toEqual: function(b, desc) { assert(a === b , desc); },
  2680. toBeNull: function( desc) { assert(a === null , desc); },
  2681. toBeNotNull: function( desc) { assert(a !== null , desc); },
  2682. toBeDefined: function( desc) { assert(a !== undefined , desc); },
  2683. toBeTruthy: function( desc) { assert(a ? true : false, desc); }
  2684. };
  2685. return exp;
  2686. } catch(e) {
  2687. window.console.log('%c', a);
  2688. }
  2689. },
  2690. spec: {},
  2691. run: function(name) {
  2692. var def = (new $.Deferred()), promise = def.promise();
  2693. var con = function(name) {
  2694. return function() {
  2695. var d = new $.Deferred();
  2696. setTimeout(function() {
  2697. window.console.log('%c RUN: ' + name, 'background: #8ff;');
  2698. d.resolve();
  2699. }, 100);
  2700. return d.promise();
  2701. };
  2702. };
  2703. var wrap = function(self, name) {
  2704. return function() {
  2705. var d = new $.Deferred();
  2706. setTimeout(function() {
  2707. try {
  2708. $.proxy(self.spec[name], self)(d);
  2709. } catch (e) {
  2710. window.console.log(e);
  2711. d.reject();
  2712. }
  2713. }, 0);
  2714. return d.promise();
  2715. };
  2716. };
  2717. var onFail = function(e) {
  2718. window.console.log('%c fail : ','background: red;', e);
  2719. };
  2720.  
  2721. if (name) {
  2722. promise = promise.then(con(name)).then(wrap(this, name), onFail);
  2723. } else {
  2724. for(var v in this.spec) {
  2725. if (!v.match(/^test/)) continue;
  2726. promise = promise.then(con(v)) .then(wrap(this, v), onFail);
  2727. }
  2728. }
  2729. promise.then(
  2730. function() { window.console.log('%cテスト完了', 'background: #8ff'); },
  2731. function() { window.console.log('%cテスト失敗', 'background: #f00'); }
  2732. );
  2733. def.resolve();
  2734. }
  2735. }
  2736. };
  2737. // w.WatchItLater = window.WatchItLater;
  2738.  
  2739.  
  2740. var EventDispatcher = (function(conf) {
  2741. var events = {};
  2742.  
  2743. function addEventListener(name, callback) {
  2744. name = name.toLowerCase();
  2745. if (!events[name]) {
  2746. events[name] = [];
  2747. }
  2748. events[name].push(callback);
  2749. }
  2750.  
  2751. function _dispatch(name) {
  2752. name = name.toLowerCase();
  2753. if (!events[name]) { return; }
  2754. var e = events[name];
  2755. for (var i =0, len = e.length; i < len; i++) {
  2756. try {
  2757. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  2758. } catch (ex) {
  2759. console.log('%c' + name, 'background:red; color: white;', i, e[i], ex);
  2760. }
  2761. }
  2762. }
  2763. function dispatch(name) {
  2764. console.log('%cevent:', 'background: blue; color: white;', name);//, arguments);
  2765. _dispatch.apply(null, arguments);
  2766. }
  2767. return {
  2768. addEventListener: addEventListener,
  2769. dispatch: dispatch,
  2770. _dispatch: _dispatch // コンソール汚したくない用
  2771. };
  2772. })(conf);
  2773. window.WatchItLater.event = EventDispatcher;
  2774.  
  2775. /*
  2776. * 通算視聴回数をカウント。 カウントしても意味はないけど、どれだけ無駄な時間を費やしたかを知りたくて実装。
  2777. */
  2778. var WatchCounter = (function(conf, w) {
  2779. var key = 'watchItLater_watchCounter';
  2780. function get() {
  2781. return JSON.parse(w.localStorage.getItem(key));
  2782. }
  2783. function add() {
  2784. var v = get() + 1;
  2785. w.localStorage.setItem(key, JSON.stringify(v));
  2786. console.log('%cwatchCounter: %c%d', 'color: orange;', 'font-weight: bolder;', v);
  2787. return v;
  2788. }
  2789. var self = {
  2790. get: get,
  2791. add: add
  2792. };
  2793. return self;
  2794. })(conf, w);
  2795. window.WatchItLater.counter = WatchCounter;
  2796.  
  2797. /**
  2798. * 動画タグ取得とポップアップ
  2799. *
  2800. */
  2801. var VideoTags = (function(conf, w){
  2802.  
  2803. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  2804. var pt = function(){};
  2805. var lastPopup = null;
  2806.  
  2807. pt.get = function(watchId, callback) {
  2808. var _get = function(watchId, callback) {
  2809. var url = 'http://' + host + '/tag_edit/' + watchId + '/?res_type=json&cmd=tags';
  2810. //http://www.nicovideo.jp/tag_edit/sm9/?res_type=json&cmd=tags
  2811. var req = {
  2812. method: 'GET',
  2813. url: url,
  2814. onload: function(resp) {
  2815. var result = JSON.parse(resp.responseText);
  2816. if (typeof callback === 'function') callback(result.status, result);
  2817. }
  2818. };
  2819. GM_xmlhttpRequest(req);
  2820. };
  2821.  
  2822. WatchController.getTid2Vid(watchId, function(videoId) {
  2823. _get(videoId, callback);
  2824. });
  2825. };
  2826.  
  2827. pt.hidePopup = function() {
  2828. if (lastPopup) {
  2829. lastPopup.style.display = 'none';
  2830. }
  2831. };
  2832.  
  2833. var uniq = null, $history = null, popupContainer = null;
  2834. pt.popupItems = function(watchId, baseX, baseY) {
  2835. var self = this;
  2836. popupContainer.innerHTML = '';
  2837. this.get(watchId, function(status, resp) {
  2838. if (status === 'ok') {
  2839. var tags = resp.tags;
  2840. self.hidePopup();
  2841. if (tags.length > 0) {
  2842. lastPopup = createPopup(tags, baseX, baseY);
  2843. } else {
  2844. Popup.show('この動画のタグはありません');
  2845. }
  2846. } else {
  2847. Popup.alert(resp.error_message);
  2848. }
  2849. });
  2850.  
  2851. function createPopup(tags, baseX, baseY) {
  2852. var popup = createDOM(tags, baseX, baseY);
  2853. popupContainer.appendChild(popup);
  2854. popup.style.right = null;
  2855. popup.style.left = baseX + 'px';
  2856. popup.style.top = Math.max(baseY - popup.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  2857. if (popup.offsetLeft + popup.offsetWidth > document.body.clientWidth) {
  2858. popup.style.left = null;
  2859. popup.style.right = 0;
  2860. }
  2861.  
  2862. return popup;
  2863. }
  2864.  
  2865. function createDOM(tags) {
  2866. var items = document.createElement('ul');
  2867. for (var i = 0, len = tags.length; i < len; i++) {
  2868. items.appendChild(createItemDOM(tags[i]));
  2869. }
  2870. var popup = createPopupDOM();
  2871.  
  2872. popup.appendChild(items);
  2873. return popup;
  2874. }
  2875.  
  2876. function createPopupDOM() {
  2877. var popup = document.createElement('div');
  2878. popup.className = 'tagItemsPopup popupMenu';
  2879. popup.addEventListener('click', createPopupOnClick(), false);
  2880. return popup;
  2881. }
  2882.  
  2883. function createPopupOnClick() {
  2884. return function(e) {
  2885. if (e.button !== 0 || e.shiftKey || e.ctrlKey || e.altKey || e.target.className === 'icon' || e.target.tagName === 'A') {
  2886. return;
  2887. }
  2888. this.style.display = 'none';
  2889. e.preventDefault();
  2890. e.stopPropagation();
  2891. };
  2892. }
  2893.  
  2894. function appendTagHistory(dom, text, dic) {
  2895. var $ = w.$;
  2896. if (uniq === null) {
  2897. uniq = {};
  2898. $history = $('<div class="tagSearchHistory"><h3 class="title">タグ検索履歴</h3></div>');
  2899. $history.css({width: $('.videoExplorerMenu').width() - 8, maxHeight: '300px', overflowY: 'auto'});
  2900. $('.videoExplorerMenu').append($history);
  2901. }
  2902. if (!uniq[text]) {
  2903. var a = $(dom).clone().css({marginRight: '8px', fontSize: '80%'}).click(Util.Closure.openNicoSearch(text));
  2904. dic.style.marginRight = '0';
  2905. $history.find('.title').after(a).after(dic);
  2906. }
  2907. uniq[text] = 1;
  2908. }
  2909.  
  2910. function createItemDOM(tag) {
  2911. var text = tag.tag;
  2912. var li = document.createElement('li');
  2913. li.className = 'popupTagItem';
  2914.  
  2915. // 大百科アイコン
  2916. var dic = createDicIconDOM(tag, text);
  2917. li.appendChild(dic);
  2918.  
  2919. // 新検索(search.nicovideo.jp)へのリンク
  2920. var newSearchIcon = createNewSearchIconDOM(tag, text);
  2921. li.appendChild(newSearchIcon);
  2922.  
  2923. // 本文リンク
  2924. var a = document.createElement('a');
  2925. a.appendChild(document.createTextNode(text));
  2926.  
  2927. var href = text;
  2928. if (conf.defaultSearchOption && conf.defaultSearchOption !== '' && !text.match(/(sm|nm|so)\d+/)) {
  2929. href += ' ' + conf.defaultSearchOption;
  2930. }
  2931. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  2932. a.href = 'http://' + host + '/tag/' + encodeURIComponent(href) + sortOrder;
  2933. a.addEventListener('click', createItemOnClick(text, dic), false);
  2934. li.appendChild(a);
  2935.  
  2936. return li;
  2937. }
  2938.  
  2939. function createItemOnClick(text, dic) {
  2940. return function(e) {
  2941. if (e.button !== 0 || e.metaKey) return;
  2942. if (w.WatchApp) {
  2943. WatchController.nicoSearch(text, 'tag');
  2944. e.preventDefault();
  2945. appendTagHistory(this, text, dic);
  2946. }
  2947. return false;
  2948. };
  2949. }
  2950.  
  2951. function createNewSearchIconDOM(tag, text) {
  2952. var link = document.createElement('a');
  2953. link.className = 'newsearch';
  2954. link.title = 'niconico新検索で開く';
  2955.  
  2956. // TODO: パラメータの対応表作ってあわせる
  2957. var newSortOrder = '';
  2958. link.href = 'http://search.nicovideo.jp/video/search/' + encodeURIComponent(text) + newSortOrder;
  2959. if (location.host !== 'search.nicovdieo.jp') {
  2960. link.target = '_blank';
  2961. }
  2962.  
  2963. var icon = document.createElement('img');
  2964. icon.className = 'icon';
  2965. icon.src = 'http://uni.res.nimg.jp/img/favicon.ico';
  2966. link.appendChild(icon);
  2967.  
  2968. return link;
  2969. }
  2970. function createDicIconDOM(tag, text) {
  2971. var dic = document.createElement('a');
  2972. dic.className = 'nicodic';
  2973. dic.href = 'http://dic.nicovideo.jp/a/' + encodeURIComponent(text);
  2974. dic.target = '_blank';
  2975. var icon = document.createElement('img');
  2976. icon.className = 'icon';
  2977. icon.src = tag.dic ? 'http://live.nicovideo.jp/img/2012/watch/tag_icon002.png' : 'http://live.nicovideo.jp/img/2012/watch/tag_icon003.png';
  2978. dic.appendChild(icon);
  2979. return dic;
  2980. }
  2981. };
  2982. popupContainer = document.createElement('div');
  2983. popupContainer.id = 'videoTagPopupContainer';
  2984. document.body.appendChild(popupContainer);
  2985.  
  2986. return pt;
  2987. })(conf, w);
  2988.  
  2989.  
  2990.  
  2991.  
  2992.  
  2993.  
  2994.  
  2995.  
  2996.  
  2997. /**
  2998. * マイリスト登録API
  2999. *
  3000. * (9)の頃は、iframeを作ってその中にマイリスト登録のポップアップウィンドウを開くという手抜きを行っていたが、
  3001. * ポップアップウィンドウは評判が悪いし、そのうち廃止されるだろうなと思うので、
  3002. * 真面目にAPIを叩くようにした。 (マイリストの新規作成機能は省略)
  3003. *
  3004. * …と思っていたのだが、(9)からQになった今でもポップアップウィンドウは廃止されないようだ。
  3005. */
  3006. var Mylist = window.WatchItLater.mylist = (function(){
  3007. var mylistlist = [];
  3008. var initialized = false;
  3009. var defListItems = [], mylistItems = {};
  3010. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  3011. var token = '';//
  3012.  
  3013. function Mylist() {
  3014. this.initialize();
  3015. }
  3016.  
  3017. function getToken() {
  3018. if (!isNativeGM && host !== location.host) return null; //
  3019.  
  3020. var _token = (w.NicoAPI) ? w.NicoAPI.token : '';
  3021. if (w.NicoAPI) {
  3022. return w.NicoAPI.token;
  3023. } else
  3024. if (w.WatchApp && w.WatchJsApi) {
  3025. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  3026. watchInfoModel.addEventListener('reset', function(watchInfoModel) {
  3027. token = watchInfoModel.csrfToken;
  3028. });
  3029. if (watchInfoModel.initialized) {
  3030. return watchInfoModel.csrfToken;
  3031. } else {
  3032. var dc = JSON.parse($("#watchAPIDataContainer").text());
  3033. return dc.flashvars.csrfToken;
  3034. }
  3035. } else
  3036. if (_token === null && w.FavMylist && w.FavMylist.csrf_token) {
  3037. _token = w.FavMylist.csrf_token;
  3038. }
  3039.  
  3040. if (_token !== '') {
  3041. return _token;
  3042. }
  3043. var url = 'http://' + host + '/mylist_add/video/sm9'; // マイリスト登録ウィンドウから強引にtoken取得
  3044. // var url = 'http://' + host + '/my/mylist'; // マイリスト登録ウィンドウから強引にtoken取得
  3045. GM_xmlhttpRequest({
  3046. url: url,
  3047. onload: function(resp) {
  3048. var result = resp.responseText;
  3049. if (result.match(/NicoAPI\.token = "([a-z0-9\-]+)";/)) {
  3050. token = RegExp.$1;
  3051. }
  3052. }
  3053. });
  3054. return _token;
  3055. }
  3056.  
  3057. var pt = Mylist.prototype, events = {defMylistUpdate: [], mylistUpdate: []};
  3058.  
  3059. function dispatchEvent(name) {
  3060. var e = events[name];
  3061. for (var i =0, len = e.length; i < len; i++) {
  3062. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  3063. }
  3064. }
  3065.  
  3066. pt.onDefMylistUpdate = function(callback) {
  3067. events.defMylistUpdate.push(callback);
  3068. };
  3069.  
  3070. pt.onMylistUpdate = function(callback) {
  3071. events.mylistUpdate.push(callback);
  3072. };
  3073.  
  3074. pt.getUserId = function() {
  3075. if (document.cookie.match(/user_session_(\d+)/)) {
  3076. return RegExp.$1;
  3077. } else {
  3078. return false;
  3079. }
  3080. };
  3081.  
  3082. var onInitialized = [];
  3083. pt.initialize = function() {
  3084. if (initialized) return;
  3085. var uid = this.getUserId();
  3086. if (!uid) {
  3087. return;
  3088. }
  3089. if (!isNativeGM && host !== location.host) {
  3090. initialized = true;
  3091. return;
  3092. }
  3093. token = getToken();
  3094. //var url = 'http://' + host + '/api/watch/uservideo?user_id=' + uid;
  3095. var url = 'http://' + host + '/api/mylistgroup/list';
  3096. GM_xmlhttpRequest({
  3097. url: url,
  3098. onload: function(resp) {
  3099. var result = JSON.parse(resp.responseText);
  3100. if (result.status === "ok" && result.mylistgroup) {
  3101. mylistlist = result.mylistgroup;
  3102. initialized = true;
  3103. for (var i = 0; i < onInitialized.length; i++) {
  3104. onInitialized[i](mylistlist.concat());
  3105. }
  3106. }
  3107. }
  3108. });
  3109. this.reloadDefList();
  3110. };
  3111.  
  3112. pt.loadMylistList = function(callback) {
  3113. if (initialized) {
  3114. setTimeout(function() { callback(mylistlist.concat()); }, 0);
  3115. } else {
  3116. onInitialized.push(callback);
  3117. }
  3118. };
  3119.  
  3120. pt.isMine = function(id) {
  3121. if (!initialized) { return false; }
  3122. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3123. if (mylistlist[i].id == id) { return true; }
  3124. }
  3125. return false;
  3126. };
  3127.  
  3128. pt.reloadDefList = function(callback) {
  3129. var url = 'http://' + host + '/api/deflist/list';
  3130. GM_xmlhttpRequest({
  3131. url: url,
  3132. onload: function(resp) {
  3133. try {
  3134. JSON.parse(resp.responseText);
  3135. } catch (e) {
  3136. window.console.log(e);
  3137. window.console.log(resp.responseText);
  3138. }
  3139. if (!resp.responseText) return;
  3140. var result = JSON.parse(resp.responseText);
  3141. if (result.status === "ok" && result.mylistitem) {
  3142. defListItems = result.mylistitem;
  3143. if (typeof callback === "function") callback(defListItems);
  3144. }
  3145. }
  3146. });
  3147. };
  3148.  
  3149. pt.loadMylist = function(groupId, callback) {
  3150. if (mylistItems[groupId]) {
  3151. setTimeout(function() {callback(mylistItems[groupId]); }, 0);
  3152. return;
  3153. }
  3154. var url = 'http://' + host + '/api/mylist/list?group_id=' + groupId;
  3155. GM_xmlhttpRequest({
  3156. url: url,
  3157. onload: function(resp) {
  3158. var result = JSON.parse(resp.responseText);
  3159. if (result.status === "ok" && result.mylistitem) {
  3160. mylistItems[groupId] = result.mylistitem;
  3161. if (typeof callback === "function") callback(result.mylistitem);
  3162. }
  3163. }
  3164. });
  3165. };
  3166.  
  3167. pt.clearMylistCache = function(groupId) {
  3168. delete mylistItems[groupId];
  3169. };
  3170.  
  3171. pt.reloadMylist = function(groupId, callback) {
  3172. this.clearMylistCache(groupId);
  3173. return this.loadMylist(groupId, callback);
  3174. };
  3175.  
  3176.  
  3177. pt.findDeflistByWatchId = function(watchId) {
  3178. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3179.  
  3180. for (var i = 0, len = defListItems.length; i < len; i++) {
  3181. var item = defListItems[i], wid = item.item_data.watch_id;
  3182. if (wid == watchId) return item;
  3183. }
  3184. return null;
  3185. };
  3186.  
  3187. pt.findMylistByWatchId = function(watchId, groupId) {
  3188. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3189. var items = mylistItems[groupId];
  3190. if (!items) { return null; }
  3191. for (var i = 0, len = items.length; i < len; i++) {
  3192. var item = items[i], wid = item.item_data.watch_id;
  3193. if (wid == watchId) return item;
  3194. }
  3195. return null;
  3196. };
  3197.  
  3198. // おもに参考にしたページ
  3199. // http://uni.res.nimg.jp/js/nicoapi.js
  3200. // http://d.hatena.ne.jp/lolloo-htn/20110115/1295105845
  3201. // http://d.hatena.ne.jp/aTaGo/20100811/1281552243
  3202. pt.deleteDefListItem = function(watchId, callback) {
  3203. var item = this.findDeflistByWatchId(watchId);
  3204. if (!item) return false;
  3205. var item_id = item.item_id;
  3206. var url = 'http://' + host + '/api/deflist/delete';
  3207. var data = 'id_list[0][]=' + item_id + '&token=' + token;
  3208. var req = {
  3209. method: 'POST',
  3210. data: data,
  3211. headers: {'Content-Type': 'application/x-www-form-urlencoded'}, // これを忘れて小一時間はまった
  3212. url: url,
  3213. onload: function(resp) {
  3214. var result = JSON.parse(resp.responseText);
  3215. if (typeof callback === "function") callback(result.status, result);
  3216. if (window.jQuery) {
  3217. defListItems = window.jQuery.grep(defListItems, function(item) {
  3218. return item.item_data.watch_id !== watchId;
  3219. });
  3220. }
  3221. dispatchEvent('defMylistUpdate');
  3222. }
  3223. };
  3224. GM_xmlhttpRequest(req);
  3225. return true;
  3226. };
  3227.  
  3228. pt.addDefListItem = function(watchId, callback, description) {
  3229. var url = 'http://' + host + '/api/deflist/add';
  3230.  
  3231. // 例えば、とりマイの300番目に登録済みだった場合に「登録済みです」と言われても探すのがダルいし、
  3232. // 他の動画を追加していけば、そのうち押し出されて消えてしまう。
  3233. // なので、重複時にエラーを出すのではなく、「消してから追加」することによって先頭に持ってくる。
  3234. // 「重複してたら先頭に持ってきて欲しいな~」って要望掲示板にこっそり書いたりしたけど相手にされないので自分で実装した。
  3235. var data = "item_id=" + watchId + "&token=" + token, replaced = true;
  3236. if (description) {
  3237. data += '&description='+ encodeURIComponent(description);
  3238. }
  3239.  
  3240. var _add = function(status, resp) {
  3241. var req = {
  3242. method: 'POST',
  3243. data: data,
  3244. url: url,
  3245. headers: {'Content-Type': 'application/x-www-form-urlencoded' }, // これを忘れて小一時間はまった
  3246. onload: function(resp) {
  3247. var result = JSON.parse(resp.responseText);
  3248. if (typeof callback === "function") callback(result.status, result, replaced);
  3249. }
  3250. };
  3251. GM_xmlhttpRequest(req);
  3252. };
  3253. // とりあえずマイリストにある場合はdeleteDefListItem()のcallbackで追加、ない場合は即時追加
  3254. if (!this.deleteDefListItem(watchId, _add)) {
  3255. replaced = false;
  3256. _add();
  3257. dispatchEvent('defMylistUpdate');
  3258. }
  3259. };
  3260.  
  3261. pt.addMylistItem = function(watchId, groupId, callback, description) {
  3262. var self = this;
  3263. var url = 'http://' + host + '/api/mylist/add';
  3264. var data = ['item_id=', watchId,
  3265. '&group_id=', groupId,
  3266. '&item_type=', 0, // video=0 seiga=5
  3267. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3268. '&token=', token
  3269. ].join('');
  3270. // 普通のマイリストのほうは重複しても「消してから追加」という処理を行っていない。
  3271. // とりあえずマイリストと違って登録の順番に意味があるのと、
  3272. // 古いのが押し出される心配がないため。
  3273. var _add = function() {
  3274. var req = {
  3275. method: 'POST',
  3276. data: data,
  3277. url: url,
  3278. headers: {'Content-Type': 'application/x-www-form-urlencoded' },
  3279. onload: function(resp) {
  3280. var result = JSON.parse(resp.responseText);
  3281. if (typeof callback === "function") callback(result.status, result);
  3282. if (result.status === 'ok') {
  3283. dispatchEvent('mylistUpdate', {action: 'add', groupId: groupId, watchId: watchId});
  3284. EventDispatcher.dispatch('onMylistItemAdded', groupId, watchId);
  3285. self.clearMylistCache(groupId);
  3286. }
  3287. },
  3288. error: function() {
  3289. Popup.alert('ネットワークエラー');
  3290. }
  3291. };
  3292. GM_xmlhttpRequest(req);
  3293. };
  3294. // 普通のマイリストに入れたら、とりあえずマイリストからは削除(≒移動)
  3295. if (!this.deleteDefListItem(watchId, _add)) _add();
  3296. };
  3297.  
  3298. pt.updateMylistItem = function(watchId, groupId, callback, description) {
  3299. var self = this;
  3300. this.loadMylist(groupId, function() {
  3301. var item = self.findMylistByWatchId(watchId, groupId);
  3302. if (!item) {
  3303. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3304. return;
  3305. }
  3306. var
  3307. itemId = item.item_id,
  3308. url = 'http://' + host + '/api/mylist/update',
  3309. data = ['item_id=', itemId,
  3310. '&group_id=', groupId,
  3311. '&item_type=', 0, // video=0 seiga=5
  3312. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3313. '&token=', token
  3314. ].join(''),
  3315. req = {
  3316. method: 'POST',
  3317. data: data,
  3318. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3319. url: url,
  3320. onload: function(resp) {
  3321. var result = JSON.parse(resp.responseText);
  3322. if (result.status === 'ok') {
  3323. if (typeof callback === "function") callback(result.status, result);
  3324. dispatchEvent('mylistUpdate', {action: 'update', groupId: groupId, watchId: watchId});
  3325. EventDispatcher.dispatch('onMylistItemUpdated', groupId, watchId);
  3326. }
  3327. },
  3328. error: function() {
  3329. Popup.alert('ネットワークエラー');
  3330. }
  3331. };
  3332.  
  3333. GM_xmlhttpRequest(req);
  3334. });
  3335. };
  3336.  
  3337.  
  3338. pt.deleteMylistItem = function(watchId, groupId, callback) {
  3339. var self = this;
  3340. this.loadMylist(groupId, function() {
  3341. var item = self.findMylistByWatchId(watchId, groupId);
  3342. if (!item) {
  3343. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3344. return;
  3345. }
  3346. var
  3347. item_id = item.item_id,
  3348. url = 'http://' + host + '/api/mylist/delete',
  3349. data = [
  3350. 'id_list[0][]=', item_id,
  3351. '&group_id=', groupId,
  3352. '&token=', token
  3353. ].join(''),
  3354. req = {
  3355. method: 'POST',
  3356. data: data,
  3357. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3358. url: url,
  3359. onload: function(resp) {
  3360. var result = JSON.parse(resp.responseText);
  3361. if (result.status === 'ok') {
  3362. if (typeof callback === "function") callback(result.status, result);
  3363. dispatchEvent('mylistUpdate', {action: 'delete', groupId: groupId, watchId: watchId});
  3364. EventDispatcher.dispatch('onMylistItemDeleted', groupId, watchId);
  3365. }
  3366. },
  3367. error: function() {
  3368. Popup.alert('ネットワークエラー');
  3369. }
  3370. };
  3371.  
  3372. GM_xmlhttpRequest(req);
  3373. });
  3374. };
  3375.  
  3376.  
  3377. /**
  3378. * マイリスト登録パネルを返す
  3379. */
  3380. pt.getPanel = function(watchId, videoId) {
  3381. if (isNativeGM || host === location.host) {
  3382. return this.getNativePanel(watchId, videoId);
  3383. } else {
  3384. return this.getIframePanel(watchId, videoId);
  3385. }
  3386. };
  3387.  
  3388. pt.getNativePanel = function(watchId, videoId) {
  3389. var self = this;
  3390. var _watchId = watchId, _videoId = videoId || watchId;
  3391. var body = document.createElement('div');
  3392. var mylistListPopup = null;
  3393. body.className = 'mylistPopupPanel deflistSelected';
  3394. var nobr = document.createElement('nobr');
  3395. body.appendChild(nobr);
  3396.  
  3397. var extArea = document.createElement('span');
  3398.  
  3399.  
  3400. var isWatchPage = (window.PlayerApp) ? true : false;
  3401.  
  3402. var addDeflist = function(watchId, description) {
  3403. self.addDefListItem(watchId, function(status, result, replaced) {
  3404. self.reloadDefList();
  3405. if (status !== 'ok') {
  3406. Popup.alert('とりあえずマイリストへの登録に失敗: ' + result.error.description);
  3407. } else {
  3408. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  3409. Popup.show(
  3410. torimai +
  3411. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  3412. );
  3413. }
  3414. }, description);
  3415. };
  3416. var addMylist = function(watchId, mylistId, mylistName, description) {
  3417. self.addMylistItem(watchId, mylistId, function(status, result) {
  3418. self.reloadDefList();
  3419. if (status === 'ok') {
  3420. Popup.show( '<a href="/my/mylist/#/' + mylistId + '">' + mylistName + '</a>に登録しました');
  3421. } else {
  3422. Popup.alert(mylistName + 'への登録に失敗: ' + result.error.description);
  3423. }
  3424. }, description);
  3425. };
  3426. var setButtonStyleUpdating = function(btn) {
  3427. btn.style.opacity = 0.5;
  3428. btn.style.cursor = 'pointer';
  3429. btn.disabled = true;
  3430.  
  3431. window.setTimeout(function() {
  3432. btn.disabled = false;
  3433. btn.style.opacity = 1;
  3434. btn.style.cursor = 'pointer';
  3435. btn = null;
  3436. }, 1000);
  3437. };
  3438. var onMylistListClick = function(mylistId, mylistName, type) {
  3439. if (type === 'icon') {
  3440. if (window.WatchApp) {
  3441. if (mylistId === 'default') {
  3442. WatchController.showDeflist();
  3443. } else {
  3444. WatchController.showMylist(mylistId);
  3445. }
  3446. } else {
  3447. location.href = 'http://' + host + '/my/mylist/#/' + mylistId.replace('default','home');
  3448. }
  3449. return;
  3450. }
  3451. if (mylistId === 'default') {
  3452. addDeflist(_watchId);
  3453. } else {
  3454. addMylist(_watchId, mylistId, mylistName);
  3455. }
  3456. };
  3457.  
  3458. body.watchId = function(w, v) {
  3459. if (w) {
  3460. _watchId = w;
  3461. _videoId = v || w;
  3462. var isThreadId = (/^[0-9]+$/.test(w));
  3463.  
  3464. deleteDef.disabled = false;
  3465. if (self.findDeflistByWatchId(w)) {
  3466. deleteDef.style.display = '';
  3467. } else {
  3468. deleteDef.style.display = 'none';
  3469. }
  3470. if (!isWatchPage && isThreadId) {
  3471. tagBtn.style.display = 'none'; // スレッドIDから動画IDを取る手段がないためタグ取得が難しい
  3472. } else {
  3473. tagBtn.style.display = '';
  3474. }
  3475. if (newTabLink) {
  3476. newTabLink.href = 'http://nico.ms/' + _watchId; // QWatchに乗っ取られないようにnico.msをかます(せこい)
  3477. }
  3478. if (mylistListPopup) {
  3479. mylistListPopup.hide();
  3480. }
  3481. return body;
  3482. }
  3483. return _watchId;
  3484. };
  3485.  
  3486. body.show = function() {
  3487. body.style.display = '';
  3488. if (mylistListPopup) {
  3489. mylistListPopup.hide();
  3490. }
  3491. };
  3492. body.hide = function() {
  3493. body.style.display = 'none';
  3494. if (mylistListPopup) {
  3495. mylistListPopup.hide();
  3496. }
  3497. };
  3498.  
  3499. function createSelector() {
  3500. var sel = document.createElement('select');
  3501. var lastSelect = 0;
  3502.  
  3503. sel.className = 'mylistSelect';
  3504. var appendO = function(sel, text, value) {
  3505. var opt = document.createElement('option');
  3506. opt.appendChild(document.createTextNode(text));
  3507. opt.value = value;
  3508. sel.appendChild(opt);
  3509. return opt;
  3510. },
  3511. createOptions = function() {
  3512. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3513. var mylist = mylistlist[i];
  3514. appendO(sel, (i + 1).toString(36) + ':' + mylist.name, mylist.id);
  3515. }
  3516. },
  3517. onSelect = function() {
  3518. // jQueryは全てのページにあるわけではないので気をつける。忘れると原宿が死ぬ
  3519. if (sel.selectedIndex === 0) {
  3520. body.className = body.className.replace('mylistSelected', 'deflistSelected');
  3521. } else {
  3522. lastSelect = sel.selectedIndex;
  3523. body.className = body.className.replace('deflistSelected', 'mylistSelected');
  3524. }
  3525. },
  3526. selectDeflist = function() {
  3527. sel.selectedIndex = 0;
  3528. onSelect();
  3529. },
  3530. onContextMenu = function(e) {
  3531. e.preventDefault();
  3532. e.stopPropagation();
  3533.  
  3534. if (lastSelect === 0) return;
  3535. if (sel.selectedIndex === 0) {
  3536. sel.selectedIndex = lastSelect;
  3537. } else {
  3538. sel.selectedIndex = 0;
  3539. }
  3540. onSelect();
  3541. };
  3542.  
  3543. appendO(sel, '0:とりマイ', 'default');
  3544. sel.selectedIndex = 0;
  3545. window.setTimeout(createOptions, initialized ? 0 : 3000);
  3546.  
  3547. sel.addEventListener('change', onSelect, false);
  3548. sel.addEventListener('contextmenu', onContextMenu, false);
  3549.  
  3550.  
  3551. body.addEventListener('dblclick', selectDeflist, false);
  3552. return sel;
  3553. }
  3554.  
  3555. function createSubmitButton() {
  3556. var btn = document.createElement('button');
  3557. btn.appendChild(document.createTextNode('my'));
  3558. btn.className = 'mylistAdd';
  3559. btn.title = 'マイリストに追加\n(ボタンを右クリックで詳細メニュー)';
  3560.  
  3561. var callMylistListPopup = function() {
  3562. if (!mylistListPopup) {
  3563. mylistListPopup = new MylistListPopup(mylistlist, onMylistListClick);
  3564. }
  3565. mylistListPopup.toggle(btn, _watchId);
  3566. };
  3567.  
  3568. btn.addEventListener('contextmenu', function(e) {
  3569. if (window.jQuery) {
  3570. e.preventDefault();
  3571. e.stopPropagation();
  3572. callMylistListPopup();
  3573. }
  3574. });
  3575.  
  3576. btn.addEventListener('click', function(e) {
  3577. var description = null;
  3578. if (e.shiftKey) {
  3579. description = prompt('マイリストコメントの入力');
  3580. if (!description) return;
  3581. }
  3582. setButtonStyleUpdating(btn);
  3583.  
  3584. var mylistId = sel.value, name = sel.options[sel.selectedIndex].textContent;
  3585. if (mylistId === 'default') {
  3586. addDeflist(_watchId, description);
  3587. } else {
  3588. addMylist(_watchId, mylistId, name, description);
  3589. }
  3590. } ,false);
  3591. return btn;
  3592. }
  3593.  
  3594. function createDeleteDeflistItemButton() {
  3595. var btn = document.createElement('button');
  3596. btn.appendChild(document.createTextNode('×'));
  3597. btn.className = 'deflistRemove';
  3598. btn.title = 'とりあえずマイリストから外す';
  3599.  
  3600. btn.addEventListener('click', function() {
  3601.  
  3602. setButtonStyleUpdating(btn);
  3603.  
  3604. self.deleteDefListItem(_watchId, function(status, result) {
  3605. self.reloadDefList();
  3606. btn.style.display = 'none';
  3607. if (status !== "ok") {
  3608. Popup.alert('とりあえずマイリストから削除に失敗: ' + result.error.description);
  3609. } else {
  3610. Popup.show('とりあえずマイリストから外しました');
  3611. }
  3612. });
  3613. } ,false);
  3614. return btn;
  3615. }
  3616.  
  3617. function createTagListButton() {
  3618. var btn = document.createElement('button');
  3619. btn.appendChild(document.createTextNode('tag'));
  3620. btn.className = 'tagGet';
  3621. btn.title = 'タグ取得';
  3622. btn.addEventListener('click', function(e) {
  3623. btn.disabled = true;
  3624.  
  3625. setButtonStyleUpdating(btn);
  3626.  
  3627. if (w.jQuery) {
  3628. var $btn = w.jQuery(btn), o = $btn.offset();
  3629. VideoTags.popupItems(_videoId, o.left, o.top + $btn.outerHeight());
  3630. } else {
  3631. VideoTags.popupItems(_videoId, e.pageX, e.pageY);
  3632. }
  3633. } ,false);
  3634. return btn;
  3635. }
  3636.  
  3637. function createNewTabLink() {
  3638. var a = document.createElement('a');
  3639. a.className = 'newTabLink';
  3640. a.target = '_blank';
  3641. a.title = 'この動画を新しいウィンドウで開く';
  3642. a.innerHTML = '▲';
  3643. return a;
  3644. }
  3645.  
  3646. var newTabLink = createNewTabLink();
  3647. if (w.WatchApp) {
  3648. nobr.appendChild(newTabLink);
  3649. }
  3650.  
  3651.  
  3652. var sel = createSelector();
  3653. var submit = createSubmitButton(sel);
  3654. nobr.appendChild(sel);
  3655. nobr.appendChild(submit);
  3656. if (w.jQuery) {
  3657. w.jQuery(sel).keydown(function(e) {
  3658. e.stopPropagation();
  3659. if (e.keyCode === 13) { // ENTER
  3660. submit.click();
  3661. }
  3662. });
  3663. }
  3664.  
  3665. var tagBtn = createTagListButton();
  3666. nobr.appendChild(tagBtn);
  3667.  
  3668. var deleteDef = createDeleteDeflistItemButton();
  3669. nobr.appendChild(deleteDef);
  3670.  
  3671.  
  3672.  
  3673. nobr.appendChild(extArea);
  3674.  
  3675. body.watchId(_watchId, _videoId);
  3676. return body;
  3677. };
  3678.  
  3679. // XHRでクロスドメインを超えられない場合はこちら
  3680. // 将来マイリストのポップアップウィンドウが廃止されたら使えない
  3681. // (マイページから強引に生成するか?)
  3682. pt.getIframePanel = function(watchId) {
  3683. var _watchId = watchId;
  3684. var body = document.createElement('iframe');
  3685. body.name = 'nicomylistaddDummy';
  3686. body.className = 'mylistPopupPanel';
  3687. body.setAttribute('style', 'width: 130px; height: 24px; z-index: 10000; border: 1px solid silver; padding: 0; margin: 0; overflow: hidden');
  3688.  
  3689. body.watchId = function(w) {
  3690. if (w) {
  3691. _watchId = w;
  3692. body.contentWindow.location.replace("http:/" + "/www.nicovideo.jp/mylist_add/video/" + w);
  3693. return body;
  3694. }
  3695. return _watchId;
  3696. };
  3697. if (watchId !== '') {
  3698. body.src = "http:/" + "/www.nicovideo.jp/mylist_add/video/" + _watchId;
  3699. }
  3700.  
  3701. // ダミーメソッド
  3702. body.show = function() {
  3703. body.style.display = '';
  3704. };
  3705. body.hide = function() {
  3706. body.style.display = 'none';
  3707. };
  3708.  
  3709.  
  3710. return body;
  3711. };
  3712.  
  3713. return new Mylist();
  3714. })();
  3715.  
  3716. var MylistListPopup = function() { this.initialize.apply(this, arguments); };
  3717. MylistListPopup.prototype = {
  3718. initialize: function(mylistList, onItemClick) {
  3719. this._mylistList = mylistList.concat();
  3720. this.initializeView(mylistList);
  3721. this._onItemClick = onItemClick;
  3722. },
  3723. initializeView: function() {
  3724. var $ = window.jQuery;
  3725. this._$view = $([
  3726. '<div class="mylistListPopup popupMenu">',
  3727. '<div class="listInner">',
  3728. '<ul></ul>',
  3729. '</div>',
  3730. '</div>',
  3731. ''].join(''));
  3732. this._$list = this._$view.find('ul');
  3733. this._$inner = this._$view.find('.listInner');
  3734.  
  3735.  
  3736. $('body').append(this._$view);
  3737.  
  3738. this.refresh();
  3739.  
  3740. this.initializeEvent(this._$view);
  3741. },
  3742. initializeEvent: function($view) {
  3743. $view.on('click', window.jQuery.proxy(function(e) {
  3744. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  3745. var target = e.target, $target = window.jQuery(target);
  3746. this.hide();
  3747. e.preventDefault();
  3748.  
  3749. var mylistId = $target.attr('data-mylist-id');
  3750. var mylistName = $target.attr('data-mylist-name');
  3751. if (!mylistId) { return; }
  3752. var type = target.className;
  3753.  
  3754. if (typeof this._onItemClick === 'function') {
  3755. this._onItemClick(mylistId, mylistName, type);
  3756. }
  3757. }, this));
  3758. var self = this, closeTimer = null;
  3759. $view.hover(
  3760. function() {
  3761. if (closeTimer) { window.clearTimeout(closeTimer); }
  3762. },
  3763. function() {
  3764. closeTimer = window.setTimeout(function() { self.hide(); }, 1000);
  3765. });
  3766.  
  3767. $view = null;
  3768. },
  3769. adjustColumnCount: function() {
  3770. this._$inner.css({
  3771. 'column-count': '',
  3772. 'max-height': ''
  3773. });
  3774. var height = this._$view.outerHeight(),
  3775. clientHeight = window.jQuery(window).innerHeight(),
  3776. threshold = clientHeight * 0.4;
  3777. if (threshold < height) {
  3778. var columns = parseInt( height / threshold, 10) + 1;
  3779. this._$inner.css({
  3780. 'column-count': columns,
  3781. 'max-height': clientHeight * 0.8
  3782. });
  3783. }
  3784. },
  3785. updateList: function(mylistList) {
  3786. this._mylistList = mylistList.concat();
  3787. this.refresh();
  3788. },
  3789. refresh: function() {
  3790. var mylistList = this._mylistList;
  3791. this._$list.empty();
  3792. for (var i = 0, len = mylistList.length; i < len; i++) {
  3793. var mylist = mylistList[i];
  3794. this.appendItem(mylist.id, mylist.name, mylist.icon_id);
  3795. }
  3796. this.appendItem('default', 'とりあえずマイリスト');
  3797.  
  3798. this.adjustColumnCount();
  3799. },
  3800. appendItem: function(id, name, icon_id) {
  3801. var $mylist = window.jQuery([
  3802. '<li class="folder', icon_id, '">',
  3803. '<span class="icon"></span>',
  3804. '<a href="my/mylist/#/', id.replace('default', 'home'), '" class="name">',
  3805. name,
  3806. '</a>',
  3807. '</li>',
  3808. ''].join(''));
  3809. $mylist.find('.icon, .name').attr({
  3810. 'data-mylist-id': id,
  3811. 'data-mylist-name': name
  3812. });
  3813.  
  3814. if (id === 'default') {
  3815. $mylist.addClass('deflist');
  3816. } else
  3817. if (id.indexOf('ext') === 0) {
  3818. $mylist.addClass('ext');
  3819. }
  3820. this._$list.append($mylist);
  3821. },
  3822. updateExist: function(watchId) {
  3823. if (!watchId) {
  3824. return;
  3825. }
  3826. this._$view.find('.exist').removeClass('exist');
  3827. this._$view.find('.name').each(function() {
  3828. var $this = window.jQuery(this), mylistId = $this.attr('data-mylist-id');
  3829. if (mylistId === 'default') { return; }
  3830. $this
  3831. .toggleClass('exist', window.WatchItLater.mylist.cache.hasItem(mylistId, watchId));
  3832. });
  3833.  
  3834. this._$view.find('.deflist .name')
  3835. .toggleClass('exist', window.WatchItLater.mylist.findDeflistByWatchId(watchId) !== null);
  3836. },
  3837. show: function(elm, watchId) {
  3838. this.adjustColumnCount();
  3839. this._$view.addClass('show active');
  3840.  
  3841. if (!elm) { return; }
  3842. var
  3843. $ = window.jQuery,
  3844. $elm = $(elm),
  3845. o = $elm.offset(),
  3846. $view = this._$view,
  3847. $window = $(window),
  3848. scrollLeft = $window.scrollLeft(),
  3849. scrollTop = $window.scrollTop();
  3850.  
  3851. $view.css({
  3852. top: Math.max(o.top - $view.outerHeight(), 0, scrollTop),
  3853. left: o.left
  3854. });
  3855. if ($view.offset().left + $view.outerWidth() > $window.innerWidth() + scrollLeft) {
  3856. $view.css({
  3857. left: Math.max(0, $window.innerWidth() + scrollLeft - $view.outerWidth())
  3858. });
  3859. }
  3860.  
  3861. this.updateExist(watchId);
  3862. window.jQuery('body').on('click', $.proxy(this._onBodyClick, this));
  3863. },
  3864. hide: function() {
  3865. var $view = this._$view
  3866. .removeClass('show');
  3867. window.setTimeout(function() {
  3868. $view.css({top: '', left: '', right: ''}).removeClass('active');
  3869. }, 500);
  3870. window.jQuery('body').off('click', this._onBodyClick);
  3871. },
  3872. toggle: function(elm, watchId) {
  3873. if (this._$view.hasClass('avtive')) {
  3874. this.hide();
  3875. } else {
  3876. this.show(elm, watchId);
  3877. }
  3878. },
  3879. _onBodyClick: function() {
  3880. this.hide();
  3881. }
  3882. };
  3883.  
  3884. window.WatchItLater.mylist.cache = (function() {
  3885. var CacheList = function() { this.initialize.apply(this, arguments); };
  3886. CacheList.prototype = {
  3887. initialize: function() {
  3888. this.reset();
  3889. },
  3890. reset: function() {
  3891. this._cacheList = {};
  3892. },
  3893. setCache: function(mylistId, items) {
  3894. if (!this.hasCache(mylistId)) {
  3895. this._cacheList[mylistId] = new MylistCache();
  3896. }
  3897. this._cacheList[mylistId].update(items);
  3898. },
  3899. hasCache: function(mylistId) {
  3900. if (this._cacheList[mylistId]) {
  3901. return true;
  3902. }
  3903. return false;
  3904. },
  3905. hasItem: function(mylistId, watchId) {
  3906. if (!this.hasCache(mylistId)) {
  3907. return false;
  3908. }
  3909. return this._cacheList[mylistId].hasItem(watchId);
  3910. },
  3911. addItem: function(mylistId, watchId) {
  3912. if (!this.hasCache(mylistId)) {
  3913. return false;
  3914. }
  3915. this._cacheList[mylistId].addItem(watchId);
  3916. },
  3917. removeItem: function(mylistId, watchId) {
  3918. if (!this.hasCache(mylistId)) {
  3919. return false;
  3920. }
  3921. this._cacheList[mylistId].removeItem(watchId);
  3922. },
  3923. count: function(mylistId) {
  3924. if (!this.hasCache(mylistId)) {
  3925. return NaN;
  3926. }
  3927. this._cacheList[mylistId].count();
  3928. },
  3929. toJSON: function() {
  3930. var cacheList = this._cacheList;
  3931. return this._cacheList;
  3932. },
  3933. parse: function(jsonString) {
  3934. var data;
  3935. try {
  3936. data = JSON.parse(jsonString);
  3937. } catch (e) {
  3938. data = {};
  3939. }
  3940. this.reset();
  3941. for (var mylistId in data) {
  3942. var mylistCache = data[mylistId];
  3943. this._cacheList[mylistId] = new MylistCache(mylistCache);
  3944. }
  3945. }
  3946. };
  3947.  
  3948. var MylistCache = function() { this.initialize.apply(this, arguments); };
  3949. MylistCache.prototype = {
  3950. initialize: function(mylistData) {
  3951. this._name = '';
  3952. if (mylistData) {
  3953. this.update(mylistData);
  3954. }
  3955. },
  3956. update: function(mylistData) {
  3957. this._cache = [];
  3958. this._hash = {};
  3959. var items = mylistData.items ? mylistData.items : mylistData;
  3960. for (var i = 0, len = items.length; i < len; i++) {
  3961. var
  3962. item = items[i],
  3963. watchId = typeof item.getId === 'function' ? item.getId() : item.id;
  3964. this._cache.push({id: watchId});
  3965. this._hash[watchId] = true;
  3966. }
  3967. if (mylistData.name) {
  3968. this._name = mylistData.name;
  3969. }
  3970. },
  3971. hasItem: function(watchId) {
  3972. return this._hash[watchId] === true;
  3973. },
  3974. addItem: function(watchId) {
  3975. if (this.hasItem(watchId)) {
  3976. return;
  3977. }
  3978. this._hash[watchId] = true;
  3979. this._cache.push({id: watchId});
  3980. },
  3981. removeItem: function(watchId) {
  3982. if (!this.hasItem(watchId)) {
  3983. return;
  3984. }
  3985. delete this._hash[watchId];
  3986. this._cache = $.grep(this._cache, function(item) {
  3987. return item.id !== watchId;
  3988. });
  3989. },
  3990. count: function() {
  3991. return this._cache.length;
  3992. },
  3993. toJSON: function() {
  3994. return {
  3995. name: this._name,
  3996. items: this._cache
  3997. };
  3998. },
  3999. parse: function(jsonString) {
  4000. var items;
  4001. try {
  4002. items = JSON.parse(jsonString);
  4003. } catch (e) {
  4004. items = [];
  4005. }
  4006. this.update(items);
  4007. }
  4008. };
  4009.  
  4010. var cacheList, noop = function() {};
  4011. var initialize = function() {
  4012. initialize = noop;
  4013. console.log('%cinitialize mylistCache', 'background: lightgreen;');
  4014. cacheList = new CacheList();
  4015.  
  4016. if (conf.enableLocalMylistCache) {
  4017. if (window.PlayerApp) {
  4018. $(window).on('beforeunload.watchItLater', function(e) {
  4019. window.localStorage.setItem('watchItLater_mylistCache', serialize());
  4020. });
  4021. }
  4022. var cacheData = window.localStorage.getItem('watchItLater_mylistCache');
  4023. if (cacheData) {
  4024. cacheList.parse(cacheData);
  4025. }
  4026. }
  4027. };
  4028. var hasCache = function(mylistId, watchId) {
  4029. initialize();
  4030. return cacheList.hasItem(mylistId, watchId);
  4031. };
  4032. var hasItem = function(mylistId, watchId) {
  4033. initialize();
  4034. return cacheList.hasItem(mylistId, watchId);
  4035. };
  4036. var addItem = function(mylistId, watchId) {
  4037. initialize();
  4038. cacheList.addItem(mylistId, watchId);
  4039. };
  4040. var removeItem = function(mylistId, watchId) {
  4041. initialize();
  4042. cacheList.removeItem(mylistId, watchId);
  4043. };
  4044. var setCache = function(mylistId, items) {
  4045. initialize();
  4046. cacheList.setCache(mylistId, items);
  4047. };
  4048. var serialize = function() {
  4049. initialize();
  4050. return JSON.stringify(cacheList);
  4051. };
  4052. var unserialize = function(json) {
  4053. initialize();
  4054. cacheList.parse(json);
  4055. };
  4056. var clearCache = function() {
  4057. window.localStorage.removeItem('watchItLater_mylistCache');
  4058. };
  4059.  
  4060.  
  4061. EventDispatcher.addEventListener('onMyMylistLoad', function(mylistId, list) {
  4062. setCache(mylistId, list || []);
  4063. });
  4064. EventDispatcher.addEventListener('onMylistItemAdded', function(mylistId, watchId) {
  4065. initialize();
  4066. cacheList.addItem(mylistId, watchId);
  4067. });
  4068. EventDispatcher.addEventListener('onMylistItemDeleted', function(mylistId, watchId) {
  4069. initialize();
  4070. cacheList.removeItem(mylistId, watchId);
  4071. });
  4072.  
  4073.  
  4074. return {
  4075. initialize: initialize,
  4076. hasItem: hasItem,
  4077. addItem: addItem,
  4078. setCache: setCache,
  4079. serialize: serialize,
  4080. unserialize: unserialize,
  4081. clearCache: clearCache
  4082. };
  4083. })();
  4084.  
  4085.  
  4086. var LocationHashParser = (function(conf, w) {
  4087. var self, dat = {};
  4088.  
  4089. function initialize() {
  4090. var hash = w.location.hash.toString();
  4091. var redirectedHash = window.sessionStorage.getItem('watchItLater_redirectedHash');
  4092. if (redirectedHash) {
  4093. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  4094. hash = redirectedHash;
  4095. window.sessionStorage.removeItem('watchItLater_redirectedHash');
  4096. }
  4097. try {
  4098. if (hash.indexOf('#json={') === 0) {
  4099. dat = JSON.parse(hash.substr(6));
  4100. }
  4101. } catch (e) {
  4102. try {
  4103. dat = JSON.parse(decodeURIComponent(hash.substr(6)));
  4104. } catch(ex) {
  4105. console.log(ex);
  4106. }
  4107. console.log(e);
  4108. }
  4109. }
  4110. function setValue(key, value) {
  4111. dat[key] = value;
  4112. }
  4113. function getValue(key) {
  4114. return dat[key];
  4115. }
  4116. function deleteValue(key) {
  4117. delete dat[key];
  4118. }
  4119. function updateHash() {
  4120. var loc = window.location.href.split('#')[0];
  4121. location.replace(loc + getHash());
  4122. }
  4123. function removeHash() {
  4124. if (location.hash.length <= 1) { return; }
  4125. var scrollTop = $(window).scrollTop();
  4126. var loc = window.location.href.split('#')[0];
  4127. location.replace(loc + '#');
  4128. $(window).scrollTop(scrollTop);
  4129. }
  4130. function getHash() {
  4131. var json = JSON.stringify(dat);
  4132. if (json === '{}') { return ''; }
  4133. return '#json=' + json;
  4134. }
  4135. function getUrl() {
  4136. var loc = window.location.href.split('#')[0];
  4137. return loc + getHash();
  4138. }
  4139. function clear() {
  4140. dat = {};
  4141. removeHash();
  4142. }
  4143.  
  4144. self = {
  4145. initialize: initialize,
  4146. setValue: setValue,
  4147. getValue: getValue,
  4148. deleteValue: deleteValue,
  4149. updateHash: updateHash,
  4150. removeHash: removeHash,
  4151. getHash: getHash,
  4152. getUrl: getUrl,
  4153. clear: clear
  4154. };
  4155. return self;
  4156. })(conf, w);
  4157.  
  4158. window.WatchItLater.loader.favMylists = (function() {
  4159. var lastUpdate = 0;
  4160. var favMylistList = [];
  4161. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4162. var $ = w.$;
  4163. /**
  4164. * お気に入りマイリストの取得。 jQueryのあるページでしか使えない
  4165. * マイページを無理矢理パースしてるので突然使えなくなるかも
  4166. */
  4167. var self = {
  4168. load: function(callback) {
  4169. if (!w.jQuery) return; //
  4170.  
  4171. function request(page) {
  4172. url = baseUrl + '?page=' + page;
  4173. GM_xmlhttpRequest({
  4174. url: url,
  4175. onload: function(resp) {
  4176. var $result = $(resp.responseText).find('#favMylist');
  4177.  
  4178. if ($result.length >= 1) {
  4179. updateMaxPage($result);
  4180.  
  4181. if (page === 1) { favMylistList = []; }
  4182.  
  4183. $result.find('.outer').each(function() {
  4184. favMylistList.push(readBlock(this));
  4185. });
  4186. }
  4187.  
  4188. if (page < maxPage) {
  4189. setTimeout(function() {
  4190. page++;
  4191. request(page);
  4192. }, 500);
  4193. } else {
  4194. sort();
  4195. do_callback();
  4196. }
  4197. }
  4198. });
  4199. }
  4200. function readBlock(elm) {
  4201. var
  4202. $elm = $(elm),
  4203. $a = $elm.find('h5 a'), $desc = $elm.find('.mylistDescription'),
  4204. iconType = $elm.find('.folderIcon').attr('class').split(' ')[1],
  4205. id = ($a.attr('href').split('/').reverse())[0],
  4206. $postTime = $elm.find('.postTime span'),
  4207. postTime = $.trim($postTime.text()),
  4208. postTimeData = $postTime.data(),
  4209. $videoLink = $elm.find('.videoTitle a'),
  4210. videoTitle = $videoLink.text(),
  4211. videoHref = $videoLink.attr('href'),
  4212. videoId = videoHref ? (videoHref.split('/').reverse()[0]) : '';
  4213. return {
  4214. id: id,
  4215. name: $a.text(),
  4216. description: $desc.text(),
  4217. iconType: iconType,
  4218. lastVideo: {
  4219. title: videoTitle,
  4220. videoId: videoId,
  4221. postedAt: postTime,
  4222. postTimeData: postTimeData
  4223. }
  4224. };
  4225. }
  4226.  
  4227. function updateMaxPage($result) {
  4228. var $paging = $result.find('.pagerWrap:first .pager:first a');
  4229. maxPage = Math.min(Math.max($paging.length, 1), 3);
  4230. }
  4231. function sort() {
  4232. favMylistList.sort(function(a, b) {
  4233. return (a.lastVideo.postedAt < b.lastVideo.postedAt) ? 1 : -1;
  4234. });
  4235. }
  4236. function do_callback() {
  4237. if (typeof callback === 'function') { callback(favMylistList); }
  4238. }
  4239.  
  4240. var now = Date.now();
  4241. if (now - lastUpdate < 3 * 60 * 1000) {
  4242. do_callback();
  4243. return;
  4244. }
  4245. lastUpdate = now;
  4246.  
  4247. var
  4248. baseUrl = 'http://' + host + '/my/fav/mylist',
  4249. url = baseUrl,
  4250. maxPage = 1;
  4251.  
  4252. request(1);
  4253.  
  4254. }
  4255. };
  4256. return self;
  4257. })();
  4258.  
  4259.  
  4260. window.WatchItLater.loader.favTags = (function(w) {
  4261. var lastUpdate = 0;
  4262. var favTagList = [], favTagTextList = [];
  4263. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4264. var $ = w.$;
  4265. var pt = function(){};
  4266.  
  4267. var load = function(callback) {
  4268. if (!w.jQuery) return; //
  4269. var now = Date.now();
  4270. if (now - lastUpdate < 60 * 1000) {
  4271. if (typeof callback === 'function') { callback(favTagList); }
  4272. return;
  4273. }
  4274. lastUpdate = now;
  4275. var api = 'http://' + host + '/api/favtag/list?t=' + now;
  4276. $.ajax({
  4277. url: api,
  4278. timeout: 30000,
  4279. complete: function(result) {
  4280. if (result.status !== 200) {
  4281. return;
  4282. }
  4283. try {
  4284. var json = JSON.parse(result.responseText), items = json.favtag_items;
  4285. for (var i = 0, len = items.length; i < len; i++) {
  4286. var text = items[i]['tag'];
  4287. favTagList.push({href: '/tag/' + encodeURIComponent(text), name: items[i]['tag']});
  4288. favTagTextList.push(text);
  4289. }
  4290. EventDispatcher.dispatch('onFavTagsLoad', favTagTextList.concat());
  4291. if (typeof callback === 'function') { callback(favTagList); }
  4292. } catch (e) {
  4293. console.log('tag parse error!', e);
  4294. }
  4295. }
  4296. });
  4297. };
  4298.  
  4299. pt.load = load;
  4300. return pt;
  4301. })(w);
  4302.  
  4303.  
  4304. /**
  4305. * 左下に出るポップアップメッセージ
  4306. *
  4307. */
  4308. var Popup = (function(){
  4309. function Popup() {}
  4310.  
  4311. Popup.show = function(text) {
  4312. console.log('%c' + text, 'background: cyan;');
  4313. if (w.WatchApp) {
  4314. text = text.replace(/[\n]/, '<br />');
  4315. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4316. // Firefoxではflashの上に半透明要素を重ねられないのでとりあえず黒で塗りつぶす
  4317. '<span style="background: black;">' + text + '</span>'
  4318. );
  4319. }
  4320. };
  4321.  
  4322. Popup.alert = function(text) {
  4323. console.log('%c' + text, 'background: yellow;');
  4324. if (w.WatchApp) {
  4325. text = text.replace(/[\n]/, '<br />');
  4326. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4327. '<span style="background: black; color: red;">' + text + '</span>'
  4328. );
  4329. } else {
  4330. w.alert(text);
  4331. }
  4332. };
  4333.  
  4334. Popup.hide = function() {
  4335. if (w.WatchApp) {
  4336. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.stop();
  4337. }
  4338. };
  4339. return Popup;
  4340. })();
  4341.  
  4342.  
  4343. var KeyMatch = (function() {
  4344. var self;
  4345.  
  4346. function create(def) {
  4347. var ch = def.char[0].toUpperCase();
  4348. return {
  4349. prop: {
  4350. char: ch,
  4351. code: typeof def.code === 'number' ? def.code : ch.charCodeAt(0),
  4352. shift: !!def.shift,
  4353. ctrl: !!def.ctrl,
  4354. alt: !!def.alt,
  4355. enable: !!def.enable
  4356. },
  4357. test: function(event) {
  4358. if (
  4359. this.prop.enable === true &&
  4360. this.prop.shift === event.shiftKey &&
  4361. this.prop.ctrl === event.ctrlKey &&
  4362. this.prop.alt === event.altKey &&
  4363. this.prop.code === event.which
  4364. ) {
  4365. event.preventDefault();
  4366. return true;
  4367. }
  4368. return false;
  4369. },
  4370. json: function() {
  4371. return JSON.stringify(this.prop);
  4372. }
  4373. };
  4374. }
  4375.  
  4376. self = {
  4377. create: create
  4378. };
  4379. return self;
  4380. })();
  4381.  
  4382. var TouchEventDispatcher = (function(target) {
  4383. var
  4384. self,
  4385. touchStartEvent = null,
  4386. touchEndEvent = null,
  4387. events = {
  4388. onflick: []
  4389. };
  4390. function dispatchEvent(name) {
  4391. var e = events[name];
  4392. for (var i =0, len = e.length; i < len; i++) {
  4393. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  4394. }
  4395. }
  4396.  
  4397. target.addEventListener('touchstart', function(e) {
  4398. touchStartEvent = e;
  4399. }, false);
  4400. target.addEventListener('touchcancel', function(e) {
  4401. touchStartEvent = null;
  4402. }, false);
  4403. target.addEventListener('touchend', function(e) {
  4404. touchEndEvent = e;
  4405. if (touchStartEvent !== null) {
  4406. var
  4407. sx = touchStartEvent.changedTouches[0].pageX, sy = touchStartEvent.changedTouches[0].pageY,
  4408. ex = touchEndEvent.changedTouches[0].pageX, ey = touchEndEvent.changedTouches[0].pageY,
  4409. dx = (sx - ex), dy = (sy - ey), len = Math.sqrt(dx * dx + dy * dy), s;
  4410. if (len > 150) {
  4411. s = dy / len;
  4412. var a = Math.abs(s), ss = Math.round(s);
  4413. if (a <= 0.3 || a >= 0.7) {
  4414. var d;
  4415. if (ss < 0) { d = 'down'; } else if (ss > 0) { d = 'up'; }
  4416. else if (dx < 0) { d = 'right';} else { d = 'left'; }
  4417. dispatchEvent('onflick', {
  4418. direction: d,
  4419. distance: len,
  4420. x: dx, y: dy,
  4421. startEvent: touchStartEvent,
  4422. endEvent: touchEndEvent
  4423. });
  4424. }
  4425. }
  4426. }
  4427. touchStartEvent = touchEndEvent = null;
  4428. }, false);
  4429.  
  4430. function onflick(callback) {
  4431. events.onflick.push(callback);
  4432. }
  4433.  
  4434. self = {
  4435. onflick: onflick
  4436. };
  4437. return self;
  4438. })(w.document);
  4439.  
  4440.  
  4441.  
  4442. /**
  4443. * リンクのマウスオーバーに仕込む処理
  4444. * ここの表示は再考の余地あり
  4445. */
  4446. var AnchorHoverPopup = (function(w, conf,EventDispatcher) {
  4447. var mylistPanel = Mylist.getPanel(''), hoverMenuDelay = conf.hoverMenuDelay * 1000;
  4448. mylistPanel.className += ' popup';
  4449. mylistPanel.style.display = 'none';
  4450. document.body.appendChild(mylistPanel);
  4451.  
  4452. EventDispatcher.addEventListener('on.config.hoverMenuDelay', function(delay) {
  4453. delay = parseFloat(delay, 10);
  4454. if (isNaN(delay)) { return; }
  4455. hoverMenuDelay = Math.abs(delay * 1000);
  4456. });
  4457.  
  4458. function showPanel(watchId, baseX, baseY, w_touch) {
  4459.  
  4460. var cn = mylistPanel.className.toString();
  4461. if (w_touch === true) {
  4462. cn = cn.replace(' w_touch', '') + ' w_touch';
  4463. } else {
  4464. if (cn.indexOf('w_touch') >= 0 && mylistPanel.style.display !== 'none') {
  4465. // フリック操作で表示したパネルが出ている間はそちらを優先し、なにもしない
  4466. return;
  4467. }
  4468. cn = cn.replace(' w_touch', '');
  4469.  
  4470. }
  4471. VideoTags.hidePopup();
  4472. if (mylistPanel.className !== cn) mylistPanel.className = cn;
  4473.  
  4474. mylistPanel.style.display = '';
  4475. mylistPanel.watchId(watchId);
  4476. mylistPanel.style.right = null;
  4477. mylistPanel.style.left = (baseX) + 'px';
  4478. mylistPanel.style.top = Math.max(baseY - mylistPanel.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  4479.  
  4480. if (mylistPanel.offsetLeft + mylistPanel.offsetWidth > document.body.clientWidth) {
  4481. mylistPanel.style.left = null;
  4482. mylistPanel.style.right = 0;
  4483. }
  4484.  
  4485. }
  4486.  
  4487.  
  4488. var videoReg = /(\?cc_video_id=|\?cc_id=|watch\/)([a-z0-9]+)/;
  4489. var excludeReg = /(news|live|seiga)\..*?nicovideo\.jp/;
  4490.  
  4491. function each(w, watchId) {
  4492.  
  4493. this.w_eventInit = false;
  4494.  
  4495. this.addEventListener('mouseover', function() {
  4496. var mx = 0, my = 0, self = this;
  4497. if (this.href) this.setAttribute('href', this.href.split('?')[0]);
  4498.  
  4499. self.w_mouse_in = true;
  4500. self.w_mouse_timer = null;
  4501. self.w_mouse_timer = setTimeout(function() {
  4502. self.w_mouse_timer = null;
  4503. if (!self.w_mouse_in) {
  4504. return;
  4505. }
  4506. var o;
  4507. if (w.jQuery) {
  4508. var $e = w.jQuery(self);
  4509. var t = $e.text();
  4510. o = t !== "" ? $e.offset() : $e.find('*').offset();
  4511. showPanel(watchId, o.left, o.top);
  4512. } else
  4513. if (self.getBoundingClientRect) {
  4514. o = (self.firstChild && self.firstChild.tagName === 'IMG') ? self.firstChild.getBoundingClientRect() : self.getBoundingClientRect();
  4515. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4516. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4517. showPanel(watchId, left + o.left, top + o.top);
  4518. } else {
  4519. showPanel(watchId, mx + 8, my + 8);
  4520. }
  4521. EventDispatcher.dispatch('mylistPanelOpen', mylistPanel, self, watchId);
  4522. }, hoverMenuDelay);
  4523.  
  4524. if (!this.w_eventInit) {
  4525. this.addEventListener('mouseout', function() {
  4526. self.w_mouse_in = false;
  4527. if (self.w_mouse_timer) {
  4528. clearTimeout(self.w_mouse_timer);
  4529. self.w_mouse_timer = null;
  4530. }
  4531. }, false);
  4532. if (!w.jQuery) {
  4533. this.addEventListener('mousemove', function(ev) {
  4534. mx = ev.pageX;
  4535. my = ev.pageY;
  4536. }, false);
  4537. }
  4538. this.w_eventInit = true;
  4539. }
  4540. }, false);
  4541. this.added = 1;
  4542. }
  4543.  
  4544. function bind(force, target) {
  4545. if (!conf.enableHoverPopup) { return; }
  4546.  
  4547. var a = Array.prototype.slice.apply(document.links), vreg = videoReg, ereg = excludeReg;
  4548. for (var i = 0, len = a.length; i < len; i++) {
  4549. var e = a[i];
  4550. var m, href= e.href;
  4551. if (
  4552. href &&
  4553. !e.added &&
  4554. (m = vreg.exec(href)) !== null &&
  4555. !ereg.test(href) &&
  4556. // e.className !== "itemEcoLink" &&
  4557. e.className !== "playlistSaveLink"
  4558. ) {
  4559. each.apply(e, [w, m[2]]);
  4560. }
  4561. }
  4562. }
  4563. function bindTouch() {
  4564. TouchEventDispatcher.onflick(function(e) {
  4565. var se = e.startEvent;
  4566. if (e.direction === 'right' && (se.target.tagName === 'A' || se.target.parentElement.tagName === 'A')) {
  4567. var
  4568. a = (se.target.tagName === 'A') ? e.startEvent.target : e.startEvent.target.parentElement,
  4569. href = a.href, vreg = videoReg, ereg = excludeReg, m, watchId;
  4570. if (
  4571. href &&
  4572. (m = vreg.exec(href)) !== null &&
  4573. !ereg.test(href) &&
  4574. // e.className !== "itemEcoLink" &&
  4575. true
  4576. ) {
  4577. watchId = m[2];
  4578. var o;
  4579. if (w.jQuery) {
  4580. var $a = w.jQuery(a);
  4581. var t = $a.text();
  4582. o = t !== "" ? $a.offset() : $a.find('*').offset();
  4583. showPanel(watchId, o.left, o.top, true);
  4584. } else {
  4585. o = (a.firstChild && a.firstChild.tagName === 'IMG') ? a.firstChild.getBoundingClientRect() : a.getBoundingClientRect();
  4586. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4587. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4588. showPanel(watchId, left + o.left, top + o.top, true);
  4589. }
  4590. }
  4591. }
  4592. });
  4593. }
  4594.  
  4595. var lastUpdate = 0, linksCount = document.links.length,
  4596. bindLoop = function(nextTime) {
  4597. var now = Date.now();
  4598. var updateInterval = w.document.hasFocus() ? 3000 : 15000;
  4599. if (now - lastUpdate < updateInterval) {
  4600. var len = document.links.length;
  4601. if (linksCount === len) {
  4602. return;
  4603. }
  4604. linksCount = len;
  4605. }
  4606. bind();
  4607. lastUpdate = now;
  4608. };
  4609.  
  4610. var self = {
  4611. hidePopup: function() {
  4612. VideoTags.hidePopup();
  4613. mylistPanel.hide();
  4614. return this;
  4615. },
  4616. updateNow: function() {
  4617. bind();
  4618. lastUpdate -= 1500;
  4619. return this;
  4620. }
  4621. };
  4622.  
  4623.  
  4624. if (location.host === "ext.nicovideo.jp") {
  4625. bind();
  4626. } else {
  4627. var thumbnailReg = /\.smilevideo\.jp\/smile\?i=(\d+)/;
  4628. if (location.host === 'ch.nicovideo.jp' && w.jQuery) {
  4629. w.jQuery('.lazyimage, .thumb_video.thumb_114.wide img, .itemset li .image a .item').each(function() {
  4630. var $e = w.jQuery(this).text(' ');
  4631. var src = $e.attr('data-original') || $e.attr('src');
  4632. if (typeof src === 'string' && thumbnailReg.test(src)) {
  4633. each.apply(this, [w, 'so' + RegExp.$1]);
  4634. }
  4635. });
  4636. }
  4637. bindTouch();
  4638. bind();
  4639. setInterval(bindLoop, 500);
  4640. }
  4641. return self;
  4642. })(w, conf, EventDispatcher);
  4643. window.WatchItLater.popup = AnchorHoverPopup;
  4644.  
  4645.  
  4646. //===================================================
  4647. //===================================================
  4648. //===================================================
  4649.  
  4650.  
  4651. /**
  4652. * マイリスト登録のポップアップウィンドウを乗っ取る処理
  4653. *
  4654. * iframeの子ウィンドウ内に開かれた時に実行される
  4655. * クロスドメインを越えられない環境ではこっちを使うしかない
  4656. */
  4657. (function(){ // mylist window
  4658. if (w.location.href.indexOf('/mylist_add/') < 0 || w.name === 'nicomylistadd') return;
  4659.  
  4660. var $ = w.jQuery;
  4661. $('body,table,img,td').css({border:0, margin:0, padding:0, background: "transparent", overflow: 'hidden'});
  4662. $('#main_frm').css({background: '#fff', paddig: 0, borderRadius: 0}).addClass('mylistPopupPanel');
  4663.  
  4664. if ($('#edit_description').length < 1) {
  4665. $('#main_frm .font12:first').css({position: 'absolute', margin: 0, top: 0, left: 0, padding: 0, color: 'red', fontSize: '8pt'});
  4666. // ログインしてないぽい
  4667. return;
  4668. }
  4669.  
  4670. $('#box_success').css({position: 'absolute', top: 0, left: 0});
  4671. $('#box_success h1').css({color: 'black', fontSize: '8pt', padding: 0});
  4672. $('td').css({padding: 0});
  4673.  
  4674. // 「マイリストに登録しました」
  4675. // $('.mb8p4:last').show();
  4676. // $('.mb8p4:last h1').css({fontSize : "8pt"});
  4677.  
  4678. $('table:first').css({width: '200px'});
  4679. $('table:first td.main_frm_bg').css({height: '20px'});
  4680. $('table:first table:first').hide();
  4681.  
  4682. $('select')
  4683. .css({width: '64px',position: 'absolute', top:0, left:0, margin: 0})
  4684. .addClass('mylistSelect');
  4685. $('select')[0].selectedIndex = $('select')[0].options.length - 1;
  4686. $('#select_group option:last')[0].innerHTML = 'とりマイ';
  4687.  
  4688. // var submit = document.createElement("input");
  4689. // submit.className = 'mylistAdd';
  4690. // submit.type = "submit";
  4691. // submit.value = "マ";
  4692. // $(submit).css({position: 'absolute', top: 0, left: '64px'});
  4693. // $('select')[0].parentNode.appendChild(submit);
  4694.  
  4695.  
  4696. $('#edit_description').hide();
  4697.  
  4698. w.document.documentElement.scrollTop = 0;
  4699. w.document.documentElement.scrollLeft = 0;
  4700.  
  4701.  
  4702. $($.browser.safari ? 'body' : 'html').scrollTop(0);
  4703.  
  4704. w.window.close = function()
  4705. {
  4706. return;
  4707. };
  4708. w.window.alert = function()
  4709. {
  4710. w.document.write('<span style="position:absolute;top:0;left:0;font-size:8pt;color:red;">' +
  4711. arguments[0] + '</span>');
  4712. };
  4713. })();
  4714.  
  4715.  
  4716. //===================================================
  4717. //===================================================
  4718. //===================================================
  4719.  
  4720. window.WatchItLater.loader.videoArrayAPILoader = (function() {
  4721. var sessionId = 0;
  4722. var deferredList = {};
  4723. var cacheData = {};
  4724. var currentRequestIds = '', currentPromise;
  4725. var loaderFrame, loaderWindow;
  4726. var BASE_URL = 'http://i.nicovideo.jp/v3/video.array?v=';
  4727.  
  4728. //WatchItLater.loader.videoArrayAPILoader.load('sm9').then(function(info) { console.log(info); });
  4729.  
  4730. var load = function(watchId) {
  4731. var ids = [], result = {}, def = new $.Deferred(), timeoutTimer = null;
  4732.  
  4733. initialize();
  4734.  
  4735. $(typeof watchId !== 'string' ? watchId : [watchId]).each(function(i, id) {
  4736. if (cacheData[id]) {
  4737. result[id] = cacheData[id];
  4738. } else {
  4739. ids.push(id);
  4740. }
  4741. });
  4742. ids = window._.unique(ids);
  4743. if (ids.length < 1) {
  4744. window.setTimeout(function() { def.resolve(result); }, 0);
  4745. return def;
  4746. }
  4747. var _ids = JSON.stringify(ids);
  4748. var onSuccess = function(infoList) {
  4749. $(ids).each(function(i, id) {
  4750. result[id] = result[id] || infoList[id] || cacheData[id];
  4751. });
  4752.  
  4753. window.clearTimeout(timeoutTimer);
  4754. currentRequestIds = ''; currentPromise = null;
  4755. def.resolve(result);
  4756. def = null;
  4757. };
  4758. var onFail = function() {
  4759. window.clearTimeout(timeoutTimer);
  4760. console.log('%cVideoArrayAPILoader.onFail', 'color: red;');
  4761. currentRequestIds = ''; currentPromise = null;
  4762. if (def) {
  4763. def.reject();
  4764. }
  4765. def = null;
  4766. };
  4767.  
  4768. sessionId++;
  4769.  
  4770. timeoutTimer = window.setTimeout(onFail, 30 * 1000);
  4771.  
  4772. if (_ids === currentRequestIds) {
  4773. currentPromise.then(onSuccess, onFail);
  4774. return def;
  4775. }
  4776.  
  4777. currentRequestIds = _ids;
  4778. sendRequest(ids, sessionId).then(onSuccess, onFail);
  4779.  
  4780. return def.promise();
  4781. };
  4782.  
  4783. var sendRequest = function(ids, sessionId) {
  4784. var def = new $.Deferred();
  4785. currentPromise = def;
  4786. deferredList[sessionId] = def;
  4787. loaderWindow.location.replace(BASE_URL + ids.join(',') + '#' + sessionId);
  4788. return def.promise();
  4789. };
  4790.  
  4791. var parseVideoArray = function(xml) {
  4792. var $xml = $(xml), infoList = {};
  4793. $xml.find('video_info').each(function() {
  4794. var info = new parseVideoInfo($(this));
  4795. infoList[info.id] = cacheData[info.id] = cacheData[info.default_thread] = info;
  4796. if (info.threadIds && info.threadIds.length > 1) {
  4797. $(info.threadIds).each(function(i, threadId) {
  4798. infoList[threadId] = cacheData[threadId] = info;
  4799. });
  4800. }
  4801. });
  4802. return infoList;
  4803. };
  4804.  
  4805. var parseVideoInfo = function($xml) {
  4806. var info = {threadIds: []};
  4807. var elements = [
  4808. 'id', 'user_id', 'title', 'description', 'length_in_seconds',
  4809. 'thumbnail_url', 'first_retrieve', 'default_thread',
  4810. 'view_counter', 'mylist_counter'];
  4811.  
  4812. $(elements).each(function(i, elm) {
  4813. info[elm] = $xml.find(elm + ':first').text();
  4814. });
  4815.  
  4816. info['num_res'] = $xml.find('thread:first num_res').text();
  4817.  
  4818. var duration = parseInt(info['length_in_seconds'], 10);
  4819. info['length'] = parseInt(duration / 60, 10) + ':' + (100 + (duration % 60)).toString().substr(1);
  4820.  
  4821. info['first_retrieve_t'] = info['first_retrieve'];
  4822. info['first_retrieve'] =
  4823. window.WatchApp.ns.util.DateFormat.strftime(
  4824. '%Y-%m-%d %H:%M:%S',
  4825. new Date(info['first_retrieve'])
  4826. );
  4827.  
  4828. $xml.find('thread id, channel_thread id').each(function() {
  4829. info.threadIds.push(this.innerHTML);
  4830. });
  4831. return info;
  4832. };
  4833.  
  4834.  
  4835. var initialize = function() {
  4836. initialize = window._.noop;
  4837.  
  4838. loaderFrame = document.createElement('iframe');
  4839. loaderFrame.name = 'watchItLaterAPILoader';
  4840. loaderFrame.className = 'watchItLaterAPILoaderFrame';
  4841. document.body.appendChild(loaderFrame);
  4842.  
  4843. loaderWindow = loaderFrame.contentWindow;
  4844.  
  4845. EventDispatcher.addEventListener('onMessage', function(data, type) {
  4846. if (type !== 'VideoArrayAPILoader') { return; }
  4847. try {
  4848. var session = data.session, xml = data.xml;
  4849. //console.log('VideoArrayAPILoader.onMessage', data.session, data.xml);
  4850. if (deferredList[session]) {
  4851. deferredList[session].resolve(parseVideoArray(xml));
  4852. delete deferredList[session];
  4853. currentPromise = null;
  4854. }
  4855. } catch (e) {
  4856. console.log('message parse error', e);
  4857. if (deferredList[session]) {
  4858. deferredList[session].reject();
  4859. delete deferredList[session];
  4860. currentPromise = null;
  4861. }
  4862. }
  4863. });
  4864.  
  4865. };
  4866.  
  4867. // sample URL
  4868. // http://i.nicovideo.jp/v3/video.array?v=sm9,sm3393520,sm5909863,so23023492,1394173596
  4869. //initialize();
  4870. return {
  4871. load: load
  4872. };
  4873. })();
  4874.  
  4875.  
  4876.  
  4877.  
  4878. //===================================================
  4879. //===================================================
  4880. //===================================================
  4881.  
  4882. var _watchController = function(w) {
  4883. var WatchApp = w.WatchApp, _false = function() { return false; };
  4884. var
  4885. watch = (WatchApp && WatchApp.ns.init) || {},
  4886. watchInfoModel = (watch.CommonModelInitializer && WatchApp.ns.model.WatchInfoModel.getInstance()) || {},
  4887. nicoPlayer = (watch.PlayerInitializer && watch.PlayerInitializer.nicoPlayerConnector) || {},
  4888. videoExplorerController = watch.VideoExplorerInitializer.videoExplorerController,
  4889. videoExplorer = videoExplorerController.getVideoExplorer(),
  4890. videoExplorerContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  4891. $ = w.$, WatchJsApi = w.WatchJsApi;
  4892. return {
  4893. isZeroWatch: function() {
  4894. return (window.PlayerApp) ? true : false;
  4895. },
  4896. getWatchInfoModel: function() {
  4897. return watchInfoModel;
  4898. },
  4899. nicoSearch: function(word, search_type) {
  4900. if (!search_type) {
  4901. try {
  4902. var type = videoExplorerContentType.SEARCH;
  4903. search_type = videoExplorer.getContentList().getContent(type).getSearchType();
  4904. } catch(e) {
  4905. search_type = search_type || 'tag';
  4906. }
  4907. }
  4908. videoExplorerController.searchVideo(word, search_type);
  4909. AnchorHoverPopup.hidePopup();
  4910. },
  4911. showMylist: function(mylistId) {
  4912. videoExplorerController.showMylist(mylistId.toString());
  4913. },
  4914. clearDeflistCache: function() {
  4915. watch.VideoExplorerInitializer.deflistVideoAPILoader._cache.clear();
  4916. },
  4917. clearMylistCache: function(id) {
  4918. if (id) {
  4919. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.deleteElement(
  4920. 'http://riapi.nicovideo.jp/api/watch/mylistvideo?id=' + id.toString()
  4921. );
  4922. } else {
  4923. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.clear();
  4924. }
  4925. },
  4926. showDeflist: function() {
  4927. videoExplorerController.showDeflist();
  4928. },
  4929. changeScreenMode: function(mode) {
  4930. WatchJsApi.player.changePlayerScreenMode(mode);
  4931. setTimeout(function(){$(window).resize();}, 3000);
  4932. },
  4933. isFixedHeader: function() {
  4934. return !$('body').hasClass('nofix');
  4935. },
  4936. // ヘッダー追従かどうかを考慮したscrollTop
  4937. scrollTop: function(top, dur) {
  4938. var header = (this.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  4939.  
  4940. if (top !== undefined) {
  4941. return $(window).scrollTop(top - header, dur);
  4942. } else {
  4943. return $(window).scrollTop() + header;
  4944. }
  4945. },
  4946. scrollToVideoPlayer: function(force) {
  4947. // 縦解像度がタグ+プレイヤーより大きいならタグの開始位置、そうでないならプレイヤーの位置にスクロール
  4948. // ただし、該当部分が画面内に納まっている場合は、勝手にスクロールするとかえってうざいのでなにもしない
  4949. var $body = $('body'), isContentFix = $body.hasClass('content-fix');
  4950. $body.addClass('w_noHover').removeClass('content-fix');
  4951. var h = $('#playerContainer').outerHeight() + $('#videoTagContainer').outerHeight();
  4952. var top = $(window).height() >= h ? '#videoTagContainer, #playerContainer' : '#playerContainer';
  4953.  
  4954.  
  4955. if (force) {
  4956. // 要素が画面内に納まっている場合でも、その要素の位置までスクロール
  4957. WatchApp.ns.util.WindowUtil.scrollFit(top, 600);
  4958. } else {
  4959. // 要素が画面内に収まっている場合はスクロールしない
  4960. WatchApp.ns.util.WindowUtil.scrollFitMinimum(top, 600);
  4961. }
  4962. $(window).scrollLeft(0);
  4963. $body.toggleClass('content-fix', isContentFix);
  4964. setTimeout(function() {
  4965. $body.removeClass('w_noHover');
  4966. }, 800);
  4967. },
  4968. play: function() {
  4969. nicoPlayer.playVideo();
  4970. },
  4971. pause: function() {
  4972. nicoPlayer.stopVideo();
  4973. },
  4974. togglePlay: function() {
  4975. var status = $("#external_nicoplayer")[0].ext_getStatus();
  4976. if (status === 'playing') {
  4977. this.pause();
  4978. } else {
  4979. this.play();
  4980. }
  4981. },
  4982. isPlaying: function() {
  4983. var status = $("#external_nicoplayer")[0].ext_getStatus();
  4984. return status === 'playing';
  4985. },
  4986. nextVideo: function() {
  4987. return nicoPlayer.playNextVideo();
  4988. },
  4989. prevVideo: function() {
  4990. return nicoPlayer.playPreviousVideo();
  4991. },
  4992. vpos: function(v) {
  4993. if (typeof v === 'number') {
  4994. return nicoPlayer.seekVideo(v);
  4995. } else {
  4996. return nicoPlayer.getVpos();
  4997. }
  4998. },
  4999. openSearch: function() {
  5000. WatchApp.ns.init.VideoExplorerInitializer.expandButtonView.open();
  5001. // videoExplorer.openByCurrentCondition();
  5002. // videoExplorer.changeState(true);
  5003. },
  5004. closeSearch: function() {
  5005. videoExplorer.changeState(false);
  5006. videoExplorer.close();
  5007. },
  5008. openVideoOwnersVideo: function() {
  5009. if (this.isChannelVideo()) {
  5010. this.openChannelOwnersVideo();
  5011. } else {
  5012. this.openUpNushiVideo();
  5013. }
  5014. },
  5015. openUpNushiVideo: function() {
  5016. videoExplorerController.showOwnerVideo();
  5017. },
  5018. openChannelOwnersVideo: function() {
  5019. videoExplorerController.showMylist('-3');
  5020. },
  5021. openUserVideo: function(userId, userNick) {
  5022. videoExplorerController.showOtherUserVideos(userId, userNick);
  5023. },
  5024. openRecommend: function() {
  5025. var
  5026. type = videoExplorerContentType.RELATED_VIDEO,
  5027. open = function() {
  5028. var rel = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer._menu.getItemByContentType(type);
  5029. rel.select();
  5030. };
  5031. if (videoExplorer.isOpen()) {
  5032. open();
  5033. } else {
  5034. this.openSearch();
  5035. setTimeout(open, 500);
  5036. }
  5037. },
  5038. getVideoExplorerCurrentItems: function(format) {
  5039. var ac = videoExplorer._contentList.getActiveContent();
  5040. if (!ac || !ac.getItems) return [];
  5041. var items = ac.getItems();
  5042.  
  5043. if (!format) {
  5044. return items;
  5045. } else
  5046. if (format === 'playlist') {
  5047. var result = [];
  5048. for (var i = items.length - 1; i >= 0; i--) {
  5049. result.unshift(
  5050. videoExplorerController._item2playlistItem(items[i])
  5051. );
  5052. }
  5053. return result;
  5054. }
  5055. },
  5056. getWatchId: function() {// スレッドIDだったりsmXXXXだったり
  5057. return watchInfoModel.v;
  5058. },
  5059. getVideoId: function() {// smXXXXXX, soXXXXX など
  5060. return watchInfoModel.id;
  5061. },
  5062. getMyNick: function() {
  5063. return watch.CommonModelInitializer.viewerInfoModel.nickname;
  5064. },
  5065. getMyUserId: function() {
  5066. return watch.CommonModelInitializer.viewerInfoModel.userId;
  5067. },
  5068. getPlaylistItems: function() {
  5069. return watch.PlaylistInitializer.playlist.items || watch.PlaylistInitializer.playlist.currentItems;
  5070. },
  5071. setPlaylistItems: function(items, currentItem) {
  5072. var playlist = watch.PlaylistInitializer.playlist;
  5073. // var isAutoPlay = playlist.isContinuous();//.isAutoPlay();
  5074. playlist.reset(
  5075. items,
  5076. 'WatchItLater',
  5077. playlist.type,
  5078. playlist.option
  5079. );
  5080. if (currentItem) { playlist.playingItem = currentItem; }
  5081. else { playlist.playingItem = items[0]; }
  5082. // if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  5083. // playlist.disableContinuous();
  5084. // }
  5085. },
  5086. shufflePlaylist: function(target) {
  5087. var x = this.getPlaylistItems(), items = [], i, currentIndex = -1, currentItem = null;
  5088. if (target === 'right') {
  5089. for (i = 0; i < x.length;) {
  5090. if (x[0]._isPlaying) {
  5091. currentIndex = i;
  5092. currentItem = x.shift();
  5093. items.push(currentItem);
  5094. break;
  5095. } else {
  5096. items.push(x.shift());
  5097. }
  5098. }
  5099. }
  5100.  
  5101. x = x.map(function(a){return {weight:Math.random(), value:a};})
  5102. .sort(function(a, b){return a.weight - b.weight;})
  5103. .map(function(a){return a.value;});
  5104. for (i = 0; i < x.length; i++) {
  5105. if (x[i]._isPlaying) {
  5106. items.unshift(x[i]);
  5107. } else {
  5108. items.push(x[i]);
  5109. }
  5110. }
  5111. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView;
  5112. var left = pm.getLeftSideIndex();
  5113. this.setPlaylistItems(items, currentItem);
  5114. pv.scroll(left);
  5115. },
  5116. clearPlaylist: function(target) {
  5117. var x = this.getPlaylistItems(), items = [], i, currentItem = null;
  5118. if (target === 'left') {
  5119. for (i = x.length - 1; i >= 0; i--) {
  5120. items.unshift(x[i]);
  5121. if (x[i]._isPlaying) {
  5122. currentItem = x[i];
  5123. break;
  5124. }
  5125. }
  5126. } else
  5127. if (target === 'right') {
  5128. for (i = 0; i < x.length ; i++) {
  5129. items.push(x[i]);
  5130. if (x[i]._isPlaying) {
  5131. currentItem = x[i];
  5132. break;
  5133. }
  5134. }
  5135. }
  5136. else {
  5137. for (i = 0; i < x.length; i++) {
  5138. if (x[i]._isPlaying) {
  5139. currentItem = x[i];
  5140. items.unshift(x[i]);
  5141. }
  5142. }
  5143. }
  5144. this.setPlaylistItems(items, currentItem);
  5145. },
  5146. appendSearchResultToPlaylist: function(mode) {
  5147. var
  5148. items = this.getPlaylistItems(),
  5149. searchItems = this.getVideoExplorerCurrentItems('playlist'),
  5150. uniq = {}, i, playingIndex = 0, c, len, currentItem = null;
  5151. if (!searchItems || searchItems.length < 1) {
  5152. return;
  5153. }
  5154. for (i = 0, len = items.length; i < len; i++) {
  5155. uniq[items[i].id] = true;
  5156. if (items[i]._isPlaying) { playingIndex = i; currentItem = items[i]; }
  5157. }
  5158. if (mode === 'next') {
  5159. for (i = searchItems.length - 1; i >= 0; i--) {
  5160. c = searchItems[i];
  5161. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === undefined && items.splice(playingIndex + 1, 0, c);
  5162. }
  5163. } else {
  5164. for (i = 0, len = searchItems.length; i < len; i++) {
  5165. c = searchItems[i];
  5166. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === undefined && items.push(c);
  5167. }
  5168. }
  5169. this.setPlaylistItems(items, currentItem);
  5170. },
  5171. insertVideoToPlaylist: function(id) {
  5172. WatchItLater.VideoInfoLoader.load(id).then(function(info) {
  5173. var item = new WatchApp.ns.model.playlist.PlaylistItem(info);
  5174. watch.PlaylistInitializer.playlist.insertNextPlayingItem(item);
  5175. }, function(err) {
  5176. Popup.alert(err.message);
  5177. });
  5178. },
  5179. addDefMylist: function(description) {
  5180. var watchId = watchInfoModel.id;
  5181. setTimeout(function() {
  5182. Mylist.addDefListItem(watchId, function(status, result, replaced) {
  5183. Mylist.reloadDefList();
  5184. if (status !== "ok") {
  5185. Popup.alert('とりあえずマイリストの登録に失敗: ' + result.error.description);
  5186. } else {
  5187. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  5188. Popup.show(
  5189. torimai +
  5190. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  5191. );
  5192. }
  5193. }, description);
  5194. }, 0);
  5195. },
  5196. commentVisibility: function(v) {
  5197. if (v === 'toggle') {
  5198. return this.commentVisibility(!this.commentVisibility());
  5199. } else
  5200. if (typeof v === 'boolean') {
  5201. nicoPlayer.playerConfig.set({commentVisible: v});
  5202. return this;
  5203. } else {
  5204. var pc = nicoPlayer.playerConfig.get();
  5205. return pc.commentVisible;
  5206. }
  5207. },
  5208. deepenedComment: function(v) {
  5209. if (v === 'toggle') {
  5210. return this.deepenedComment(!this.deepenedComment());
  5211. } else
  5212. if (typeof v === 'boolean') {
  5213. nicoPlayer.playerConfig.set({deepenedComment: v});
  5214. return this;
  5215. } else {
  5216. var pc = nicoPlayer.playerConfig.get();
  5217. return pc.deepenedComment;
  5218. }
  5219. },
  5220. allowStageVideo: function(v) {
  5221. if (v === 'toggle') {
  5222. return this.allowStageVideo(!this.allowStageVideo());
  5223. } else
  5224. if (typeof v === 'boolean') {
  5225. nicoPlayer.playerConfig.set({allowStageVideo: v});
  5226. return this;
  5227. } else {
  5228. var pc = nicoPlayer.playerConfig.get();
  5229. return pc.allowStageVideo;
  5230. }
  5231. },
  5232. isStageVideoSupported: function() {
  5233. try {
  5234. var exp = w.document.getElementById('external_nicoplayer');
  5235. return exp.isStageVideoSupported();
  5236. } catch(e) {
  5237. console.log(e);
  5238. return false;
  5239. }
  5240. },
  5241. isStageVideoAvailable: function() {
  5242. try {
  5243. var exp = w.document.getElementById('external_nicoplayer');
  5244. return exp.isStageVideoAvailable();
  5245. } catch(e) {
  5246. console.log(e);
  5247. return false;
  5248. }
  5249. },
  5250. toggleStageVideo: function() {
  5251. if (!this.isStageVideoSupported()) {
  5252. Popup.alert('ハードウェアアクセラレーションを使用できない状態か、未対応の環境です');
  5253. return;
  5254. }
  5255. var isAllowed = this.allowStageVideo(), exp = $('#external_nicoplayer')[0];
  5256. exp.setIsForceUsingStageVideo(!isAllowed && conf.forceEnableStageVideo);
  5257. this.allowStageVideo(!isAllowed);
  5258. setTimeout($.proxy(function() {
  5259. isAllowed = this.allowStageVideo();
  5260. var isAvailable = this.isStageVideoAvailable();
  5261. Popup.show('ハードウェアアクセラレーション:' +
  5262. (isAllowed ? '設定ON' : '設定OFF') + ' / ' +
  5263. (isAvailable ? '使用可能' : '使用不能')
  5264. );
  5265. }, this), 100);
  5266. },
  5267. mute: function(v) {
  5268. var exp = w.document.getElementById('external_nicoplayer');
  5269.  
  5270. if (v === 'toggle') {
  5271. return this.mute(!this.mute());
  5272. } else
  5273. if (typeof v === 'boolean') {
  5274. exp.ext_setMute(v);
  5275. return this;
  5276. } else {
  5277. return exp.ext_isMute();
  5278. }
  5279. },
  5280. volume: function(v) {
  5281. var exp = w.document.getElementById('external_nicoplayer');
  5282. if (typeof v === 'string' && v.match(/^[+-]\d+$/)) {
  5283. this.volume(this.volume() + v * 1);
  5284. } else
  5285. if (typeof v === 'number' || (typeof v === 'string' && v.match(/^\d+$/))) {
  5286. exp.ext_setVolume(Math.max(0, Math.min(v * 1, 100)));
  5287. }
  5288. return exp.ext_getVolume();
  5289. },
  5290. isWide: function() {
  5291. var exp = w.document.getElementById('external_nicoplayer');
  5292. return exp.ext_isWide();
  5293. },
  5294. isPlaylistActive: function() {
  5295. return watch.PlaylistInitializer.playlist.getPlaybackMode() !== 'normal';
  5296. },
  5297. isPlaylistRandom: function() {
  5298. return watch.PlaylistInitializer.playlist.isShuffle();
  5299. },
  5300. isPlaylistContinuous: function() {
  5301. return watch.PlaylistInitializer.playlist.getPlaybackMode() === 'continuous';
  5302. },
  5303. getOwnerIcon: function() {
  5304. try {
  5305. return this.isChannelVideo() ? watchInfoModel.channelInfo.iconUrl : watchInfoModel.uploaderInfo.iconUrl;
  5306. } catch (e) {
  5307. return 'http://uni.res.nimg.jp/img/user/thumb/blank_s.jpg';
  5308. }
  5309. },
  5310. getOwnerName: function() {
  5311. try {
  5312. return this.isChannelVideo() ? watchInfoModel.channelInfo.name : watchInfoModel.uploaderInfo.nickname;
  5313. } catch (e) {
  5314. return '';
  5315. }
  5316. },
  5317. getOwnerId: function() {
  5318. try {
  5319. return this.isChannelVideo() ? watchInfoModel.channelInfo.id : watchInfoModel.uploaderInfo.id;
  5320. } catch (e) {
  5321. return '0';
  5322. }
  5323. },
  5324. getOwnerType: function() {
  5325. try {
  5326. return this.isChannelVideo() ? 'channel' : 'user';
  5327. } catch (e) {
  5328. return 'channel';
  5329. }
  5330. },
  5331. getOwnerPage: function() {
  5332. try {
  5333. if (this.isChannelVideo()) {
  5334. return $('#ch_prof').find('.symbol').attr('href');
  5335. } else {
  5336. return '/user/' + this.getOwnerId();
  5337. }
  5338. } catch (e) {
  5339. return '';
  5340. }
  5341. },
  5342. isFavoriteOwner: function() {
  5343. try {
  5344. return this.isChannelVideo() ?
  5345. !!(watchInfoModel.channelInfo && watchInfoModel.channelInfo.isFavorited) :
  5346. watchInfoModel.uploaderInfo.isFavorited;
  5347. } catch (e) {
  5348. return false;
  5349. }
  5350. },
  5351. isVideoPublic: function() { // 投稿動画一覧を公開しているか? 公開マイリストがあるかどうかとは別なのでややこしい
  5352. return this.isChannelVideo() ? true : watchInfoModel.uploaderInfo.isUserVideoPublic;
  5353. },
  5354. isChannelVideo: function() {
  5355. return watchInfoModel.isChannelVideo();
  5356. },
  5357. getOwnerInfo: function() {
  5358. return {
  5359. type: this.getOwnerType(),
  5360. name: this.getOwnerName(),
  5361. icon: this.getOwnerIcon(),
  5362. id: this.getOwnerId(),
  5363. page: this.getOwnerPage(),
  5364. isFavorite: this.isFavoriteOwner(),
  5365. isVideoPublic: this.isVideoPublic()
  5366. };
  5367. },
  5368. isSearchMode: function() {
  5369. return videoExplorer.isOpen(); ////return $('body').hasClass('videoExplorer');
  5370. },
  5371. isFullScreen: function() {
  5372. return $('body').hasClass('full_with_browser');
  5373. },
  5374. // フルスクリーンの時にタグとかプレイリストを表示する設定かどうか
  5375. isFullScreenContentAll: function() {
  5376. try {
  5377. var content = localStorage.BROWSER_FULL_OPTIONS;
  5378. if (typeof content !== 'string') return false;
  5379. var isAll = JSON.parse(content).content === 'all';
  5380. return isAll;
  5381. } catch(e) {
  5382. console.log('%cexception', 'background: red; color: white;', e);
  5383. return false;
  5384. }
  5385. },
  5386. isIchibaEmpty: function() {
  5387. return $('#ichibaMain') .find('.ichiba_mainitem').length < 1;
  5388. },
  5389. postComment: function(comment, command) {
  5390. comment = $.trim(comment);
  5391. if (comment.length <= 0) { return; }
  5392. if (!command) { command = ''; }
  5393.  
  5394. setTimeout(function() {
  5395. try {
  5396. var exp = w.document.getElementById('external_nicoplayer');
  5397. console.log('postComment: ', [comment, command]);
  5398. if (!exp.externalPostChat(comment, command)) {
  5399. Popup.alert('コメント投稿に失敗しました');
  5400. }
  5401. } catch(e) {
  5402. Popup.alert('コメント投稿に失敗しました');
  5403. }
  5404. }, 0);
  5405. },
  5406. // スレッドIDから動画IDに変換。出来なかった時はそのまま返す
  5407. getTid2Vid: function(watchId, callback) {
  5408. if (!watchId.match(/^[0-9]+$/)) {
  5409. return callback(watchId);
  5410. }
  5411. WatchItLater.VideoInfoLoader.load(watchId).then(function(info) {
  5412. callback(info.id);
  5413. },
  5414. function() {
  5415. callback(watchId);
  5416. });
  5417. }
  5418. };
  5419. }; // end _watchController
  5420.  
  5421. (function() {
  5422. window.WatchItLater.WatchController =
  5423. window.WatchController = {
  5424. isZeroWatch: function() { return window.PlayerApp ? true : false; },
  5425. isFullScreen: function() { return false; },
  5426. isSearchMode: function() { return false; },
  5427. getTid2Vid: function(threadId, callback) { return callback(threadId);}
  5428. };
  5429. })();
  5430.  
  5431.  
  5432.  
  5433. var Util = (function() {
  5434. var Cache = {
  5435. storage: {},
  5436. get: function(key) {
  5437. if (!this.storage[key]) {
  5438. console.log('no cache');
  5439. return false;
  5440. } else
  5441. if (this.storage[key].cachedUntil <= Date.now()){
  5442. console.log('cache timeout');
  5443. delete this.storage[key];
  5444. return false;
  5445. } else {
  5446. console.log('cache exist');
  5447. return this.storage[key].data;
  5448. }
  5449. },
  5450. set: function(key, data, cacheTimeMs) {
  5451. cacheTimeMs = cacheTimeMs || 1000 * 60 * 10;
  5452. console.log('set cache', key, cacheTimeMs);
  5453. this.storage[key] = {
  5454. data: data,
  5455. cachedUntil: Date.now() + cacheTimeMs
  5456. };
  5457. return data;
  5458. }
  5459. };
  5460. var Browser = {
  5461. isWebkit: function() {
  5462. return navigator.userAgent.toLowerCase().indexOf('webkit') >= 0;
  5463. },
  5464. isFx: function() {
  5465. return navigator.userAgent.toLowerCase().indexOf('firefox') >= 0;
  5466. }
  5467. };
  5468. var
  5469. isMetaKey = function(e) {
  5470. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return true; }
  5471. return false;
  5472. },
  5473. prevent = function(e) {
  5474. e.preventDefault(); e.stopPropagation();
  5475. },
  5476. scrollToVideoExplorer = function() {
  5477. if (!WatchController.isSearchMode()) { return; }
  5478. window.setTimeout(function() {
  5479. window.WatchApp.ns.util.WindowUtil.scrollFit($('#videoExplorer'));
  5480. }, 100);
  5481. };
  5482.  
  5483. var Closure = {
  5484. outScope: function(func) {
  5485. return new Function([
  5486. '(' + func.toString() + ').apply(this, arguments);'
  5487. ].join(''));
  5488. },
  5489. openVideoOwnersVideo: function() {
  5490. return function(e) {
  5491. if (isMetaKey(e)) { return; }
  5492. prevent(e);
  5493. WatchController.openVideoOwnersVideo();
  5494. scrollToVideoExplorer();
  5495. };
  5496. },
  5497. openVideoOwnersNicorepo: function() {
  5498. return function(e) {
  5499. if (isMetaKey(e)) { return; }
  5500. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  5501. return;
  5502. }
  5503. prevent(e);
  5504. //WatchController.showMylist(NicorepoVideo.REPO_OWNER);
  5505. WatchController.showMylist('repo-owner-' + WatchController.getOwnerId());
  5506. scrollToVideoExplorer();
  5507. };
  5508. },
  5509. openDefMylist: function() {
  5510. return function(e) {
  5511. if (isMetaKey(e)) { return; }
  5512. prevent(e);
  5513. WatchController.showDeflist();
  5514. scrollToVideoExplorer();
  5515. };
  5516. },
  5517. openMylist: function(id) {
  5518. return function(e) {
  5519. if (isMetaKey(e)) { return; }
  5520. prevent(e);
  5521. WatchController.showMylist(id);
  5522. scrollToVideoExplorer();
  5523. };
  5524. },
  5525. openNicoSearch: function(word, type) {
  5526. return function(e) {
  5527. if (isMetaKey(e)) { return; }
  5528. if (WatchController.isZeroWatch()) {
  5529. prevent(e);
  5530. WatchController.nicoSearch(word, type);
  5531. scrollToVideoExplorer();
  5532. }
  5533. };
  5534. },
  5535. seekVideo: function(vpos) {
  5536. return function(e) {
  5537. if (isMetaKey(e)) { return; }
  5538. prevent(e);
  5539. WatchController.vpos(vpos);
  5540. };
  5541. },
  5542. showLargeThumbnail: function(url) {
  5543. return function() {
  5544. WatchController.showLargeThumbnail(url);
  5545. };
  5546. },
  5547. commentPanelContextMenu: function() {
  5548. return function(a) {
  5549. a.preventDefault(); a.stopPropagation();
  5550. var c = this.commentListModel.getComment(this.parseResNo(jQuery(a.currentTarget)));
  5551. var WatchApp = null, WatchController = null;
  5552. if (c) {
  5553. var
  5554. $d = this.CommentContextMenu.$contextMenuContainer,
  5555. $e = jQuery("#playerCommentPanel"),
  5556. left = this.$commentTableHeaderOuter.position().left,
  5557. top = a.pageY - $e.offset().top,
  5558. f = Math.min($e.offset().top + $e.outerHeight(), jQuery(window).scrollTop() + jQuery(window).outerHeight());
  5559. if (f < a.pageY + $d.outerHeight()) top -= a.pageY + $d.outerHeight() - f;
  5560. this.CommentContextMenu.show(c, left, top);
  5561. }
  5562. };
  5563. }
  5564. };
  5565. var Deferred = {
  5566. wait: function(msec) {
  5567. return function() {
  5568. var args = Array.prototype.slice.call(arguments, 0);
  5569. var d = new $.Deferred();
  5570. setTimeout(function() {
  5571. d.resolve.apply(d, args);
  5572. }, msec);
  5573. return d.promise();
  5574. };
  5575. }
  5576. };
  5577.  
  5578.  
  5579. var self = {
  5580. Cache: Cache,
  5581. Closure: Closure,
  5582. Deferred: Deferred,
  5583. Browser: Browser,
  5584. here: function(func) { // えせヒアドキュメント
  5585. return func.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  5586. }
  5587. };
  5588. return self;
  5589. })();
  5590. window.WatchItLater.util = Util;
  5591.  
  5592. var NicoNews = (function() {
  5593. var WatchApp = null, watch = null, $ = null, WatchJsApi = null, initialized = false;
  5594. var $button = null, $history = null, $ul = null, deteru = {}, $textMarquee, $textMarqueeInner;
  5595. var isHover = false;
  5596.  
  5597. function onNewsUpdate(news) {
  5598. var type = news.data.type, $current = null,
  5599. newsText = $textMarqueeInner.find('.categoryOuter:last').text() +
  5600. $textMarqueeInner.find('.item .title, .item .header, .item .bannertext, .item .text').text(),
  5601. newsHref = $textMarqueeInner.find('a').attr('href');
  5602. if (deteru[newsHref]) {
  5603. $current = deteru[newsHref].remove();
  5604. } else {
  5605. $current = deteru[newsHref] = makeTopic(newsText, newsHref, type);
  5606. }
  5607. $ul.append($current);
  5608. $current.show(200, scrollToBottom);
  5609. }
  5610. function makeTopic(title, url, type) {
  5611. return $([
  5612. '<li style="display: none;">',
  5613. '<a href="', url , '" target="_blank" class="', type, ' title="', escape(title),'">', title, '</a>',
  5614. '</li>',
  5615. ''].join(''));
  5616. }
  5617. function scrollToBottom() {
  5618. if (!isHover) {
  5619. $history.animate({scrollTop: $('.newsHistory ul').innerHeight()}, 200);
  5620. }
  5621. }
  5622.  
  5623. var self = {
  5624. initialize: function(w) {
  5625. WatchApp = w.WatchApp;
  5626. if (!WatchApp || initialized) { return; }
  5627. watch = WatchApp.ns.init;
  5628. $ = w.$;
  5629. WatchJsApi = w.WatchJsApi;
  5630. $textMarquee = $('#textMarquee');
  5631. $textMarqueeInner = $textMarquee.find('.textMarqueeInner');
  5632.  
  5633. watch.TextMarqueeInitializer.textMarqueeViewController.scheduler.addEventListener(
  5634. 'schedule',
  5635. onNewsUpdate);
  5636.  
  5637. $button = $('<button class="openNewsHistory" title="ニコニコニュースの履歴を開く">▲</button>');
  5638. $history = $('<div class="newsHistory" style="display: none;"><ul></ul></div>');
  5639. $history.hover(
  5640. function() { isHover = true; },
  5641. function() { isHover = false; }
  5642. );
  5643. $ul = $history.find('ul');
  5644. $button.click(function() { self.toggle(); });
  5645.  
  5646. $textMarquee.append($button).append($history);
  5647. initialized = true;
  5648. },
  5649. open: function() {
  5650. $history.show(200, function() {
  5651. scrollToBottom();
  5652. WatchApp.ns.util.WindowUtil.scrollFitMinimum('.newsHistory', 200);
  5653. });
  5654. },
  5655. close: function() {
  5656. $history.hide(200);
  5657. isHover = false;
  5658. },
  5659. toggle: function() {
  5660. if ($history.is(':visible')) {
  5661. $button.text('▲');
  5662. this.close();
  5663. } else {
  5664. $button.text('▼');
  5665. this.open();
  5666. }
  5667. }
  5668. };
  5669. return self;
  5670. })();
  5671.  
  5672.  
  5673.  
  5674. /**
  5675. * マイリストや検索API互換形式のデータを返すやつ
  5676. */
  5677. var DummyMylist = function() { this.initialize.apply(this, arguments); };
  5678. DummyMylist.prototype = {
  5679. banner: '',
  5680. id: '-100',
  5681. sort: '4',
  5682. isDeflist: -1,
  5683. isWatchngCountFull: false,
  5684. isWatchngThisMylist: false,
  5685. itemCount: 0,
  5686. items: [],
  5687. rawData: {},
  5688. page: 1,
  5689. perPage: 32,
  5690. type: 2, // 2: MYLIST_VIDEO
  5691. //
  5692. // ver130726より新規追加 defineGetterのほうがいいかも
  5693.  
  5694. status: 'ok',
  5695. name: '',
  5696. description: '',
  5697. user_id: '',
  5698. user_nickname: 'ニコニコ動画',
  5699. default_sort: '1',
  5700. is_watching_this_mylist: false,
  5701. is_watching_count_full: false,
  5702. list: [],
  5703. // ここまで
  5704. initialize: function(param) {
  5705. this.rawData = {
  5706. status: 'ok',
  5707. list: [],
  5708. name: '総合ランキング',
  5709. description: '',
  5710. is_watching_count_full: false,
  5711. is_watching_this_mylist: false,
  5712. user_nickname: '',
  5713. user_id: '',
  5714. sort: '1'
  5715. };
  5716. this._baseCreateTime = Date.now();//new Date();
  5717. this.rawData.user_nickname = param.user_nickname || WatchController.getMyNick();
  5718. this.rawData.user_id = param.user_id || WatchController.getMyUserId();
  5719. this.rawData.name = param.name || this.rawData.name;
  5720. this.rawData.description = param.description || '';
  5721.  
  5722. this.type = param.type || WatchApp.ns.components.videoexplorer.model.ContentType.MYLIST_VIDEO;
  5723. this.sort = this.rawData.sort = param.sort || this.sort;
  5724. this.id = param.id || '-100';
  5725.  
  5726. this.status = this.rawData.status;
  5727. this.list = this.rawData.list;
  5728. this.name = this.rawData.name;
  5729. this.description = this.rawData.description;
  5730. this.default_sort = this.rawData.sort || this.sort;
  5731. this.user_nickname = this.rawData.user_nickname || this.user_nickname;
  5732. this.user_id = this.rawData.user_id;
  5733.  
  5734.  
  5735. },
  5736. setName: function(name) {
  5737. this.rawData.name = name;
  5738. },
  5739. getName: function() {
  5740. return this.rawData.name || '';
  5741. },
  5742. setPage: function(page) {
  5743. this.page = page;
  5744. this.items = this.rawData.list.slice(page * this.perPage - this.perPage, page * this.perPage);
  5745. },
  5746. push: function(item) {
  5747. if (!item.create_time) {
  5748. var tm = this._baseCreateTime - 60000 * this.itemCount;
  5749. item.create_time = tm;
  5750. }
  5751. this.rawData.list.push(item);
  5752. this.itemCount = this.rawData.list.length;
  5753. this.setPage(this.page);
  5754. },
  5755. unshift: function(item) {
  5756. if (!item.create_time) {
  5757. var tm = this._baseCreateTime + 60000 * this.itemCount;
  5758. item.create_time = tm;
  5759. }
  5760. this.rawData.list.unshift(item);
  5761. this.itemCount = this.rawData.list.length;
  5762. this.setPage(this.page);
  5763. },
  5764. slice: function(b, e) {
  5765. this.rawData.list = this.rawData.list.slice(b, e);
  5766. this.itemCount = this.rawData.list.length;
  5767. this.setPage(this.page);
  5768. },
  5769. sortItem: function(sortId, force) {
  5770. sortId = parseInt(sortId, 10);
  5771. if (!!!force && (sortId < 0 || sortId === parseInt(this.sort, 10)) ) { return; }
  5772. var sortKey = ([
  5773. 'create_time', 'create_time',
  5774. 'mylist_comment', 'mylist_comment',
  5775. 'title', 'title',
  5776. 'first_retrieve', 'first_retrieve',
  5777. 'view_counter', 'view_counter',
  5778. 'thread_update_time', 'thread_update_time',
  5779. 'num_res', 'num_res',
  5780. 'mylist_counter', 'mylist_counter',
  5781. 'length_seconds', 'length_seconds'
  5782. ])[sortId],
  5783. order = (sortId % 2 === 0) ? 'asc' : 'desc';
  5784.  
  5785. if (!sortKey) { return; }
  5786. var compare= {
  5787. asc: function(a, b) { return (a[sortKey] > b[sortKey] ) ? 1 : -1; },
  5788. desc: function(a, b) { return (a[sortKey] < b[sortKey] ) ? 1 : -1; },
  5789. iasc: function(a, b) { return (a[sortKey]*1 > b[sortKey]*1) ? 1 : -1; },
  5790. idesc: function(a, b) { return (a[sortKey]*1 < b[sortKey]*1) ? 1 : -1; }
  5791. };
  5792. // 偶数がascで奇数がdescかと思ったら特に統一されてなかった
  5793. if (
  5794. sortKey === 'first_retrieve' ||
  5795. sortKey === 'thread_update_time'
  5796. ) {
  5797. order = (sortId % 2 === 1) ? 'asc' : 'desc';
  5798. } else
  5799. // 数値系は偶数がdesc
  5800. if (sortKey === 'view_counter' ||
  5801. sortKey === 'num_res' ||
  5802. sortKey === 'mylist_counter' ||
  5803. sortKey === 'length_seconds'
  5804. ) {
  5805. order = (sortId % 2 === 1) ? 'iasc' : 'idesc';
  5806. }
  5807. this.sort = this.rawData.sort = sortId.toString();
  5808. this.rawData.list.sort(compare[order]);
  5809. this.items = this.rawData.list.slice(0, 32);
  5810. this.list = this.rawData.list.slice(0);
  5811. }
  5812. };
  5813.  
  5814. var DummyMylistVideo = function() { this.initialize.apply(this, arguments); };
  5815. DummyMylistVideo.prototype = {
  5816. id: 0,
  5817. title: '',
  5818. length: 0,
  5819. view_counter: 0,
  5820. num_res: 0,
  5821. mylist_counter: 0,
  5822. description_short: '',
  5823. first_retrieve: null,
  5824. thumbnail_url: null,
  5825. mylist_comment: '',
  5826. create_time: null,
  5827. type: 0, //'video',
  5828. _info: {},
  5829.  
  5830.  
  5831. initialize: function(info) {
  5832. this._info = info._info || this;
  5833. this.id = info.id;
  5834. this.length = info.length;
  5835. this.mylist_counter = info.mylist_counter || 0;
  5836. this.view_counter = info.view_counter || 0;
  5837. this.num_res = info.num_res || 0;
  5838. this.first_retrieve = info.first_retrieve || '2000-01-01 00:00:00';
  5839. this.create_time = info.create_time || null;
  5840. this.thumbnail_url = info.thumbnail_url || 'http://res.nimg.jp/img/common/video_deleted_ja-jp.jpg' /* 「視聴できません」 */;
  5841. this.title = info.title || '';
  5842. this.type = info.type || 'video';
  5843. this.description_short = info.description_short;
  5844. this.length = info.length || '00:00';
  5845. this.length_seconds = parseInt(info.length_seconds || 0, 10);
  5846. this.mylist_comment = info.mylist_comment || '';
  5847. this.type = info.type || WatchApp.ns.components.videoexplorer.model.ContentItemType.VIDEO;
  5848.  
  5849. if (this.length_seconds === 0 && this.length && this.length.indexOf(':') >= 0) {
  5850. var sp = this.length.split(':');
  5851. this.length_seconds = sp[0] * 60 + sp[1] * 1;
  5852. } else
  5853. if (this.length === '00:00' && this.length_seconds > 0) {
  5854. this.length = parseInt(this.length_seconds / 60, 10) + ':' + (this.length_seconds % 60);
  5855. }
  5856.  
  5857. if (typeof info.is_middle_thumbnail !== 'boolean') {
  5858. if (this.thumbnail_url.indexOf('.M') >= 0) {
  5859. this.thumbnail_url = this.thumbnail_url.replace(/\.M$/, '');
  5860. this.is_middle_thumbnail = true;
  5861. } else
  5862. if (this.thumbnail_url.indexOf('.M') < 0 &&
  5863. this.id.indexOf('sm') === 0) {
  5864. var threshold = 23608629, // .Mのついた最小ID?
  5865. _id = _.parseInt(this.id.substr(2));
  5866. if (_id >= threshold) {
  5867. this.is_middle_thumbnail = true;
  5868. }
  5869. }
  5870. }
  5871. },
  5872. getType: function() { return this.type; },
  5873. getInfo: function() { return this; }, // 手抜き
  5874. getName: function() { return this.title; },
  5875. getId: function() { return this.id; },
  5876. getDescription: function() { return this.description_short; },
  5877.  
  5878.  
  5879. length_seconds: 0, // TODO:
  5880. thread_update_time: '2000-01-01 00:00:00' // TODO: 「コメントが新しい順でソート」に必要?
  5881. };
  5882.  
  5883. // 参考:
  5884. // http://looooooooop.blog35.fc2.com/blog-entry-1146.html
  5885. // http://toxy.hatenablog.jp/entry/2013/07/25/200645
  5886. // http://ch.nicovideo.jp/pita/blomaga/ar297860
  5887. // http://search.nicovideo.jp/docs/api/ma9.html
  5888. var NewNicoSearch = function() { this.initialize.apply(this, arguments); };
  5889. NewNicoSearch.API_BASE_URL = 'http://api.search.nicovideo.jp/api/';
  5890. NewNicoSearch.PAGE_BASE_URL = 'http://search.nicovideo.jp/video/';
  5891. NewNicoSearch.prototype = {
  5892. _u: '', // 24h, 1w, 1m, ft 期間指定
  5893. _ftfrom: '', // YYYY-MM-DD
  5894. _ftto: '', // YYYY-MM-DD
  5895. _l: '', // short long
  5896. _m: false, // true=音楽ダウンロード
  5897. _sort: '', // last_comment_time, last_comment_time_asc,
  5898. // view_counter, view_counter_asc,
  5899. // comment_counter, comment_counter_asc,
  5900. // mylist_counter, mylist_counter_asc,
  5901. // upload_time, upload_time_asc,
  5902. // length_seconds, length_seconds_asc
  5903. _size: 32, // 一ページの件数 maxは100
  5904. _issuer: 'watch-it-later',
  5905. _base_url: NewNicoSearch.API_BASE_URL,
  5906. initialize: function(params) {
  5907.  
  5908. },
  5909. load: function(params, callback) {
  5910. var url = this._base_url;
  5911. var data = {};
  5912. data.query = params.query || 'Qwatch';
  5913. data.service = params.service || ['video']; // video video_tag
  5914. data.search = params.search || ['title', 'tags', 'description'];
  5915. data.join = params.join || [
  5916. // TODO:投稿者IDを取得する方法がないか?
  5917. 'cmsid', 'title', 'description', 'thumbnail_url', 'start_time',
  5918. 'view_counter', 'comment_counter', 'mylist_counter', 'length_seconds', 'last_res_body'
  5919. // 'user_id', 'channel_id', 'main_community_id', 'ss_adlut'
  5920. ];
  5921. data.filters = params.filters || [{}];
  5922. data.sort_by = params.sort_by || 'start_time';
  5923. data.order = params.order || 'desc';
  5924. data.timeout = params.timeout || 10000;
  5925. data.issuer = params.issuer || 'watch-it-later';
  5926. data.reason = params.reason || 'video-explorer'; // 'watchItLater';
  5927. data.size = params.size || 32;
  5928. data.from = params.from || 0;
  5929.  
  5930. if (params.sort_by === '_hot') { // 人気順ソートのパラメータ
  5931. data.hot_field = params.hot_field;
  5932. data.hot_from = params.hot_from;
  5933. data.hot_to = params.hot_to;
  5934. }
  5935.  
  5936. var cache_key = JSON.stringify({url: url, data: data}), cache = Util.Cache.get(cache_key);
  5937. if (cache) {
  5938. setTimeout(function() { callback(null, cache); }, 0);
  5939. return;
  5940. }
  5941.  
  5942. $.ajax({
  5943. url: url,
  5944. type: 'POST',
  5945. data: JSON.stringify(data),
  5946. timeout: 30000,
  5947. complete: function(result) {
  5948. console.log('result', result);
  5949. if (result.status !== 200) {
  5950. callback('fail', 'HTTP status:' + result.status);
  5951. return;
  5952. }
  5953. var data;
  5954. try {
  5955. var lines = result.responseText.split('\n'), head = JSON.parse(lines[0]);
  5956. if (head.values[0].total > 0) {
  5957. data = [head];
  5958. for (var i = 1, len = lines.length; i < len - 1; i++) {
  5959. data.push(JSON.parse(lines[i]));
  5960. }
  5961. } else {
  5962. data = [head, JSON.parse(lines[1]), {type: 'hits', values: []}, JSON.parse(lines[2])];
  5963. }
  5964. Util.Cache.set(cache_key, data);
  5965. } catch(e) {
  5966. console.log('Exception: ', e, result);
  5967. callback('fail', 'JSON syntax');
  5968. return;
  5969. }
  5970. callback(null, data);
  5971. },
  5972. error: function(req, status, thrown) {
  5973. if (status === 'parsererror') {
  5974. return;
  5975. }
  5976. console.log('%c ajax error: ' + status, 'background: red', arguments);
  5977. callback('fail', status);
  5978. }
  5979. });
  5980. }
  5981. };
  5982.  
  5983.  
  5984.  
  5985. /**
  5986. * niconico新検索の検索結果を既存の検索API互換形式に変換して返すやつ
  5987. */
  5988. var NewNicoSearchWrapper = function() { this.initialize.apply(this, arguments); };
  5989. NewNicoSearchWrapper.prototype = {
  5990. _search: null,
  5991. sortTable: {f: 'start_time', v: 'view_counter', r: 'comment_counter', m: 'mylist_counter', l: 'length_seconds',
  5992. '_hot': '_hot', // 人気が高い順
  5993. '_explore': '_explore', // 新着優先
  5994. '_popular': '_popular' // 並び順指定なし
  5995. },
  5996. initialize: function(params) {
  5997. this._search = params.search;
  5998. },
  5999. _buildSearchQuery: function(params) {
  6000. var query = {filters: []};
  6001. var sortTable = this.sortTable;
  6002. query.query = params.searchWord;
  6003. query.search = params.searchType === 'tag' ? ['tags'] : ['tags', 'title', 'description'];
  6004. query.sort_by = params.sort && sortTable[params.sort] ? sortTable[params.sort] : 'last_comment_time';
  6005. query.order = params.order === 'd' ? 'desc' : 'asc';
  6006. query.size = params.size || 32;
  6007. query.from = params.page ? Math.max(parseInt(params.page, 10) - 1, 0) * query.size : 0;
  6008.  
  6009. var n = new Date();
  6010. var now = n.getTime();
  6011. switch (params.u) {
  6012. case '1h':
  6013. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 1 * 60 * 60 * 1000)));
  6014. break;
  6015. case '24h': case '1d':
  6016. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 24 * 60 * 60 * 1000)));
  6017. break;
  6018. case '1w': case '7d':
  6019. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 7 * 24 * 60 * 60 * 1000)));
  6020. break;
  6021. case '1m':
  6022. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 30 * 24 * 60 * 60 * 1000)));
  6023. break;
  6024. case '3m':
  6025. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 90 * 24 * 60 * 60 * 1000)));
  6026. break;
  6027. case '6m':
  6028. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 180 * 24 * 60 * 60 * 1000)));
  6029. break;
  6030. default:
  6031. break;
  6032. }
  6033.  
  6034. if (query.sort_by === '_hot') {
  6035. // 人気が高い順ソート
  6036. (function() {
  6037. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6038. query.hot_field = 'mylist_counter';
  6039. query.hot_from = format(new Date(now - 1 * 24 * 60 * 60 * 1000));
  6040. query.hot_to = format(n);
  6041.  
  6042. query.order = 'desc';
  6043. })();
  6044. }
  6045.  
  6046. if (typeof params.userId === 'string' && params.userId.match(/^\d+$/)) {
  6047. query.filters.push({type: 'equal', field: 'user_id', value: params.userId});
  6048. }
  6049. if (typeof params.channelId === 'string' && params.channelId.match(/^\d+$/)) {
  6050. query.filters.push({type: 'equal', field: 'channel_id', value: params.channelId});
  6051. }
  6052.  
  6053. if (params.l === 'short') { // 5分以内
  6054. query.filters.push(this._buildLengthSecondsRangeFilter(0, 60 * 5));
  6055. } else
  6056. if (params.l === 'long' ) { // 20分以上
  6057. query.filters.push(this._buildLengthSecondsRangeFilter(60 * 20));
  6058. }
  6059.  
  6060. if (params.m === true) { // 音楽ダウンロード
  6061. query.filters.push({type: 'equal', field: 'music_download', value: true});
  6062. }
  6063.  
  6064. // TODO: これの調査 → {field: 'ss_adult', type: 'equal', value: false}
  6065.  
  6066. return query;
  6067. },
  6068. _buildStartTimeRangeFilter: function(from, to) {
  6069. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6070. var range = {field: 'start_time', type: 'range', include_lower: true, };
  6071. range.from = format(from);
  6072. if (to) range.to = format(to);
  6073. return range;
  6074. },
  6075. _buildLengthSecondsRangeFilter: function(from, to) {
  6076. var range = {field: 'length_seconds', type: 'range'};
  6077. if (to) { // xxx ~ xxx
  6078. range.from = Math.min(from, to);
  6079. range.to = Math.max(from, to);
  6080. range.include_lower = range.include_upper = true;
  6081. } else { // xxx以上
  6082. range.from = from;
  6083. range.include_lower = true;
  6084. }
  6085. return range;
  6086. },
  6087. load: function(params, callback) {
  6088. var query = this._buildSearchQuery(params);
  6089. this._search.load(query, $.proxy(function(err, result) {
  6090. this.onLoad(err, result, params, query, callback);
  6091. }, this));
  6092. },
  6093. onLoad: function(err, result, params, query, callback) {
  6094. if (err) {
  6095. console.log('load fail', err, result);
  6096. callback('fail', {message: '通信に失敗しました1'});
  6097. return;
  6098. }
  6099. var searchResult;
  6100. searchResult = {
  6101. status: 'ok',
  6102. count: result[0].values[0].total,
  6103. page: params.page,
  6104. list: []
  6105. };
  6106. var pushItems = function(items) {
  6107. var len = items.length;
  6108. for (var i = 0; i < len; i++) {
  6109. var item = items[i], description = item.description ? item.description.replace(/<.*?>/g, '') : '';
  6110.  
  6111. item.id = item.cmsid;
  6112. if (item.thumbnail_url.indexOf('.M') >= 0) {
  6113. item.thumbnail_url = item.thumbnail_url.replace(/\.M$/, '');
  6114. item.is_middle_thumbnail = true;
  6115. } else
  6116. if (item.thumbnail_url.indexOf('.M') < 0 &&
  6117. item.id.indexOf('sm') === 0) {
  6118. var threshold = 23608629, // .Mのついた最小ID?
  6119. _id = _.parseInt(item.id.substr(2));
  6120. if (_id >= threshold) {
  6121. item.is_middle_thumbnail = true;
  6122. }
  6123. }
  6124.  
  6125. searchResult.list.push({
  6126. id: item.cmsid,
  6127. type: 0, // 0 = VIDEO,
  6128. length: item.length_seconds ?
  6129. Math.floor(item.length_seconds / 60) + ':' + (item.length_seconds % 60 + 100).toString().substr(1) : '',
  6130. mylist_counter: item.mylist_counter,
  6131. view_counter: item.view_counter,
  6132. num_res: item.comment_counter,
  6133. first_retrieve: item.start_time,
  6134. create_time: item.start_time,
  6135. thumbnail_url: item.thumbnail_url,
  6136. title: item.title,
  6137. description_short: description.substr(0, 150),
  6138. description_full: description,
  6139. length_seconds: item.length_seconds,
  6140. last_res_body: item.last_res_body,
  6141. is_middle_thumbnail: item.is_middle_thumbnail
  6142. // channel_id: item.channel_id,
  6143. // main_community_id: item.main_community_id
  6144. });
  6145. }
  6146. };
  6147. for (var i = 1; i < result.length; i++) {
  6148. if (result[i].type === 'hits' && result[i].endofstream) { break; }
  6149. if (result[i].type === 'hits' && result[i].values) {
  6150. pushItems(result[i].values);
  6151. }
  6152. }
  6153. callback(null, searchResult);
  6154. }
  6155. };
  6156.  
  6157. // sug.search.nicovideo.jpはリアルタイムの入力補完用? で、関連タグはhttp://api.search.nicovideo.jp/api/tag/ っぽい
  6158. var NicoSearchSuggest = function() { this.initialize.apply(this, arguments); };
  6159. NicoSearchSuggest.API_BASE_URL = 'http://sug.search.nicovideo.jp/'; //'/suggestion/complete';
  6160. NicoSearchSuggest.prototype = {
  6161. _base_url: NicoSearchSuggest.API_BASE_URL,
  6162. initialize: function(params) {
  6163. },
  6164. load: function(word, callback) {
  6165. if (typeof word !== 'string' || word.length <= 0) {
  6166. throw new Error('wordが設定されてない!');
  6167. }
  6168. var url = this._base_url + 'suggestion/complete',
  6169. cache_key = JSON.stringify({url: url, word: word}),
  6170. cache_time = 60 * 1000 * 1,
  6171. cache = Util.Cache.get(cache_key);
  6172. if (cache) {
  6173. setTimeout(function() { callback(null, cache); }, 0);
  6174. return;
  6175. }
  6176. $.ajax({
  6177. url: url,
  6178. type: 'POST',
  6179. data: word,
  6180. timeout: 30000,
  6181. complete: function(result) {
  6182. if (result.status !== 200) {
  6183. callback('fail', 'HTTP status:' + result.status);
  6184. return;
  6185. }
  6186. var data;
  6187. try {
  6188. data = JSON.parse(result.responseText);
  6189. } catch(e) {
  6190. console.log('Exception: ', e, result);
  6191. callback('fail', 'JSON syntax');
  6192. }
  6193. Util.Cache.set(cache_key, data, cache_time);
  6194. callback(null, data);
  6195. },
  6196. error: function(req, status, thrown) {
  6197. if (status === 'parsererror') {
  6198. return;
  6199. }
  6200. callback('fail', status);
  6201. }
  6202. });
  6203. }
  6204. };
  6205.  
  6206. var NicoSearchRelatedTag = function() { this.initialize.apply(this, arguments); };
  6207. NicoSearchRelatedTag.API_BASE_URL = 'http://api.search.nicovideo.jp/';
  6208. NicoSearchRelatedTag.prototype = {
  6209. _base_url: NicoSearchRelatedTag.API_BASE_URL,
  6210. initialize: function(params) {
  6211. },
  6212. load: function(word, callback) {
  6213. var url = this._base_url + 'api/tag/',
  6214. cache_key = JSON.stringify({url: url, word: word}),
  6215. cache_time = 60 * 1000 * 10,
  6216. cache = Util.Cache.get(cache_key);
  6217. if (cache) {
  6218. setTimeout(function() { callback(null, cache); }, 0);
  6219. return;
  6220. }
  6221. var query = {query: word, service: ['tag_video'], from: 0, size: 100, timeout: 10000, reason: 'user'};
  6222. $.ajax({
  6223. url: url,
  6224. type: 'POST',
  6225. data: JSON.stringify(query),
  6226. timeout: 30000,
  6227. complete: function(result) {
  6228. if (result.status !== 200) {
  6229. callback('fail', 'HTTP status:' + result.status);
  6230. return;
  6231. }
  6232. var data;
  6233. try {
  6234. var lines = result.responseText.split('\n');
  6235. data = JSON.parse(lines[0]);
  6236. } catch(e) {
  6237. console.log('Exception: ', e, result);
  6238. callback('fail', 'JSON syntax');
  6239. return;
  6240. }
  6241. Util.Cache.set(cache_key, data, cache_time);
  6242. callback(null, data);
  6243. },
  6244. error: function(req, status, thrown) {
  6245. if (status === 'parsererror') {
  6246. return;
  6247. }
  6248. callback('fail', status);
  6249. }
  6250. });
  6251. }
  6252. };
  6253.  
  6254.  
  6255. var VideoInfoLoader = function() { this.initialize.apply(this, arguments); };
  6256. VideoInfoLoader.BASE_URL = "http://riapi.nicovideo.jp/api/search/tag";
  6257. VideoInfoLoader.prototype = {
  6258. initialize: function(params) {
  6259. },
  6260. load: function(id, callback) {
  6261. var def = new $.Deferred;
  6262.  
  6263. var cache_key = JSON.stringify({'VideoInfoLoaderCache': id}), cacheData = Util.Cache.get(cache_key);
  6264. if (cacheData) {
  6265. return def.resolve(cacheData);
  6266. }
  6267.  
  6268. if (id.toString().match(/^\d+$/)) { // watchId
  6269. WatchApp.ns.init.PlaylistInitializer.videoInfoAPILoader.load(
  6270. [id],
  6271. function(err, resp) {
  6272. if (err !== null) {
  6273. return def.reject({message: '通信に失敗しました(1)', status: 'fail'});
  6274. }
  6275. if (resp.items && resp.items[id] && resp.items[id].id) {
  6276. if (typeof callback === 'function') { callback(null, resp.items[id]); }
  6277. return def.resolve(Util.Cache.set(cache_key, resp.items[id]));
  6278. }
  6279. var err = {message: '動画が見つかりませんでした(1): ' + id, status: 'fail'};
  6280. if (typeof callback === 'function') { callback(err, null); }
  6281. return def.reject(err);
  6282. }
  6283. );
  6284. return def.promise();
  6285. }
  6286.  
  6287. // タグ検索APIの「もしかして: xxx」を使って動画情報を取得する
  6288. WatchApp.ns.util.HTTPUtil.loadXDomainAPI({ // videoId
  6289. url: VideoInfoLoader.BASE_URL,
  6290. type: 'GET',
  6291. dataType: 'json',
  6292. xhrFields: {
  6293. withCredentials: true
  6294. },
  6295. data: {
  6296. words: 'watch/' + id,
  6297. sort: 'f',
  6298. order: 'd',
  6299. page: '1',
  6300. mode: 'watch'
  6301. },
  6302. success: function(result) {
  6303. if (result.suggest_video && result.suggest_video.id) {
  6304. if (typeof callback === 'function') { callback(null, result.suggest_video); }
  6305. def.resolve(Util.Cache.set(cache_key, result.suggest_video));
  6306. } else {
  6307. var err = {message: '動画が見つかりませんでした(2): ' + id, status: 'fail'};
  6308. if (typeof callback === 'function') { callback(err, null); }
  6309. def.reject(err);
  6310. }
  6311. },
  6312. error: function(resp) {
  6313. var err = {message: '通信に失敗しました(2)', status: 'fail'};
  6314. if (typeof callback === 'function') { callback(err, null); }
  6315. def.reject(err);
  6316. }
  6317. });
  6318. return def.promise();
  6319. }
  6320. };
  6321. WatchItLater.VideoInfoLoader = new VideoInfoLoader({});
  6322.  
  6323. var RelatedVideo = function() { this.initialize.apply(this, arguments); }
  6324. RelatedVideo.prototype = {
  6325. initialize: function(params) {
  6326. },
  6327. load: function(watchId) {
  6328. var def = new $.Deferred;
  6329. window.WatchApp.ns.init.VideoExplorerInitializer.relatedVideoAPILoader.load(
  6330. {'video_id': watchId},
  6331. function(err, result) {
  6332. if (err !== null) {
  6333. return def.reject({message: '通信に失敗しました(1)', status: 'fail', err: err});
  6334. }
  6335. return def.resolve(result);
  6336. }
  6337. );
  6338. return def.promise();
  6339. }
  6340. };
  6341. WatchItLater.RelatedVideo = new RelatedVideo({});
  6342.  
  6343. /**
  6344. * 動画視聴履歴をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  6345. */
  6346. var VideoWatchHistory = (function(w, Util){
  6347. function load(callback) {
  6348. var watch, $, myNick, myId, url;
  6349. try{
  6350. watch = w.WatchApp.ns.init;
  6351. $ = w.$; url = '/my/history';
  6352. myNick = WatchController.getMyNick(); myId = WatchController.getMyUserId();
  6353. } catch (e) {
  6354. console.log(e);
  6355. throw { message: 'エラーが発生しました', status: 'fail'};
  6356. }
  6357.  
  6358. var CACHE_KEY = 'videohistory', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6359. if (cacheData) {
  6360. setTimeout(function() {callback(cacheData);}, 0);
  6361. return;
  6362. }
  6363.  
  6364. var result = new DummyMylist({
  6365. id: '-1',
  6366. sort: '1',
  6367. name: myNick + 'の視聴履歴',
  6368. user_id: myId,
  6369. user_name: 'ニコニコ動画'
  6370. });
  6371. GM_xmlhttpRequest({
  6372. url: url,
  6373. onload: function(resp) {
  6374. var $dom = $(resp.responseText), $list = $dom.find('#historyList');
  6375. $list.find('.outer').each(function() {
  6376. var
  6377. $item = $(this), $meta = $item.find('.metadata'), $title = $item.find('.section h5 a'),
  6378. id = $title.attr('href').split('/').reverse()[0], title = $title.text(),
  6379. duration = $item.find('.videoTime').text(),
  6380. viewCnt = $meta.find('.play') .text().split(':')[1].replace(/,/g, ''),
  6381. resCnt = $meta.find('.comment').text().split(':')[1].replace(/,/g, ''),
  6382. mylistCnt = $meta.find('.mylist') .text().split(':')[1].replace(/,/g, ''),
  6383. postedAt = '20' + $meta.find('.posttime').text().replace(/(年|月)/g, '-').replace(/(日| *投稿)/g, ''),
  6384. thumbnail = $item.find('.thumbContainer a .video').attr('src');
  6385.  
  6386. var item = new DummyMylistVideo({
  6387. id: id,
  6388. length: duration,
  6389. mylist_counter: mylistCnt,
  6390. view_counter: viewCnt,
  6391. num_res: resCnt,
  6392. first_retrieve: postedAt,
  6393. thumbnail_url: thumbnail,
  6394. title: title,
  6395. _info: {first_retrieve: postedAt},
  6396. description_short: $item.find('.section .posttime span').text()
  6397. });
  6398. result.push(item);
  6399. });
  6400. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6401. },
  6402. onerror: function() {
  6403. Popup.alert('視聴履歴の取得に失敗しました');
  6404. }
  6405. });
  6406.  
  6407. }
  6408. var self = {
  6409. load : load
  6410. };
  6411. return self;
  6412. })(w, Util);
  6413.  
  6414.  
  6415.  
  6416. var VideoRecommendations = (function(w, Util){
  6417. var histories = {};
  6418. function request(callback) {
  6419. var watch, $, url, myNick, myId;
  6420. try{
  6421. watch = w.WatchApp.ns.init;
  6422. $ = w.$;
  6423. url = '/recommendations';
  6424. myNick = WatchController.getMyNick();
  6425. myId = WatchController.getMyUserId();
  6426. } catch (e) {
  6427. console.log(e);
  6428. throw { message: 'エラーが発生しました', status: 'fail'};
  6429. }
  6430. var CACHE_KEY = 'recommend', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6431. if (cacheData) {
  6432. setTimeout(function() {callback(cacheData); }, 0);
  6433. return;
  6434. }
  6435.  
  6436. var result = new DummyMylist({
  6437. id: '-2',
  6438. sort: '1',
  6439. name: 'あなたにオススメの動画'
  6440. });
  6441. GM_xmlhttpRequest({
  6442. url: url,
  6443. onload: function(resp) {
  6444. var text = resp.responseText, lines = text.split(/[\r\n]/), found = false, data, i, len;
  6445. for (i = 0, len = lines.length; i < len; i++) {
  6446. var line = lines[i];
  6447. if (line.indexOf('var Nico_RecommendationsParams') >= 0 &&
  6448. lines[i + 5] && lines[i + 5].indexOf('first_data') >= 0) {
  6449. data = JSON.parse(lines[i + 5].replace(/^.*?:/, ''));
  6450. if (data && data.videos) {
  6451. found = true;
  6452. break;
  6453. }
  6454. }
  6455. }
  6456. if (!found) {
  6457. throw { message: '取得に失敗しました', status: 'fail'};
  6458. }
  6459.  
  6460. for (i = 0, len = data.videos.length; i < len; i++) {
  6461. var video = data.videos[i];
  6462. if (histories[video.id]) {
  6463. delete histories[video.id];
  6464. }
  6465. var item = new DummyMylistVideo({
  6466. id: video.id,
  6467. length: video.length,
  6468. mylist_counter: video.mylist_counter,
  6469. view_counter: video.view_counter,
  6470. num_res: video.num_res,
  6471. first_retrieve: video.first_retrieve,
  6472. thumbnail_url: video.thumbnail_url,
  6473. title: video.title_short,
  6474. _info: video,
  6475. description_short: '関連タグ: ' + video.recommend_tag
  6476. });
  6477. histories[video.id] = item;
  6478. }
  6479. for (var v in histories) {
  6480. result.unshift(histories[v]);
  6481. }
  6482. result.slice(0, 128);
  6483. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6484. },
  6485. onerror: function() {
  6486. throw { message: '取得に失敗しました', status: 'fail'};
  6487. }
  6488. });
  6489.  
  6490. }
  6491. function load(callback, param) {
  6492. request(function(result) {
  6493. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6494. result.setPage(viewPage);
  6495. callback(result);
  6496. });
  6497. }
  6498. var self = {
  6499. load : load
  6500. };
  6501. return self;
  6502. })(w, Util);
  6503.  
  6504.  
  6505. var NicorepoVideo = (function(w, Util) {
  6506. if (!window.PlayerApp) return {};
  6507.  
  6508. var CACHE_TIME = 1000 * 60 * 10;
  6509. var WatchApp = w.WatchApp;
  6510.  
  6511. var getNicorepoTitle = function(type, param) {
  6512. var base = '【ニコレポ】';
  6513. if (type === 'all') {
  6514. return base + 'すべての動画';
  6515. } else
  6516. if (type === 'chcom') {
  6517. return base + 'お気に入りチャンネル&コミュニティの動画';
  6518. } else
  6519. if (type === 'mylist') {
  6520. return base + 'お気に入りマイリストの動画';
  6521. } else
  6522. if (type === 'owner') {
  6523. return WatchController.getOwnerName() + 'のニコレポ';
  6524. }
  6525. return base + 'お気に入りユーザーの動画';
  6526. };
  6527.  
  6528. var parseItemList = function($dom) {
  6529. var $list = $dom.find('.timeline');
  6530. return $list.find([
  6531. '.log-user-mylist-add',
  6532. '.log-user-uad-advertise',
  6533. '.log-user-video-upload',
  6534. '.log-user-video-review',
  6535. '.log-mylist-added-video',
  6536. '.log-community-video-upload',
  6537. '.log-user-video-round-number-of-view-counter',
  6538. '.log-user-video-round-number-of-mylist-counter'
  6539. ].join(', '));
  6540. };
  6541.  
  6542. var ownerReg = /\/(community|user|channel)\/((co|ch)?\d+)\??/;
  6543. var parseNicorepoItem = function(src) {
  6544. var
  6545. $item = $(src), $title = $item.find('.log-content .log-target-info a'),
  6546. id = $title.attr('href').split('/').reverse()[0].replace(/\?.*$/, ''), title = $title.text(),
  6547. duration = '--:--',
  6548. viewCnt = '-',
  6549. resCnt = '-',
  6550. mylistCnt = '-',
  6551. postedAt = WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', new Date($item.find('.log-footer-date time').attr('datetime'))),
  6552. thumbnail = $item.find('.log-target-thumbnail .video').attr('data-src'),
  6553. description_short = $.trim($item.find('.log-body').text()).replace(/(しました|されました)。/g, ''),
  6554. $owner = $item.find('.author-user, .author-community'),
  6555. ownerPage = $owner.attr('href'),
  6556. ownerMatch = ownerReg.exec(ownerPage),
  6557. ownerName = $owner.text(),
  6558. ownerId = (ownerMatch !== null && ownerMatch.length >= 3) ? ownerMatch[2] : null,
  6559. ownerIcon = $item.find('.log-author img').attr('data-src'),
  6560. mylistComment = $item.find('.log-content .log-subdetails').text()
  6561. ;
  6562.  
  6563. $item.removeClass('log').removeClass('passive').removeClass('first');
  6564. if (src.className === 'log-mylist-added-video') {
  6565. ownerName = $item.find('.log-body a:first').text();
  6566. ownerPage = $item.find('.log-body a:last').attr('href');
  6567. }
  6568.  
  6569. var item = new DummyMylistVideo({
  6570. id: id,
  6571. length: duration,
  6572. mylist_counter: mylistCnt,
  6573. view_counter: viewCnt,
  6574. num_res: resCnt,
  6575. first_retrieve: postedAt,
  6576. thumbnail_url: thumbnail,
  6577. mylist_comment: mylistComment,
  6578. title: title,
  6579. _info: {
  6580. first_retrieve: postedAt,
  6581. nicorepo_className: src.className,
  6582. nicorepo_log: [window._.escape(description_short)],
  6583. nicorepo_owner: {
  6584. id: ownerId,
  6585. icon: ownerIcon,
  6586. page: ownerPage,
  6587. name: ownerName
  6588. }
  6589. },
  6590. description_short: description_short
  6591. });
  6592. return item;
  6593. };
  6594.  
  6595. var loadPage = function(baseUrl, result, nextLink, type) {
  6596. var def = new $.Deferred();
  6597. if (nextLink === null) {
  6598. return def.resolve(baseUrl, result, null, null);
  6599. }
  6600. var url = baseUrl;
  6601. if (type === 'offset') {
  6602. url += nextLink ? ('&offset=' + nextLink) : '';
  6603. } else {
  6604. url += nextLink ? ('&last_timeline=' + nextLink) : '';
  6605. }
  6606. console.log('load Url=', url);
  6607.  
  6608. $.ajax({
  6609. url: url,
  6610. timeout: 30000
  6611. }).then(
  6612. function(resp) {
  6613. var $dom = $(resp),
  6614. $nextPageLink = $dom.find('.next-page-link'),
  6615. hasNextPage = $nextPageLink.length > 0;
  6616.  
  6617. parseItemList($dom).each(function() {
  6618. result.push(parseNicorepoItem(this));
  6619. });
  6620.  
  6621. var nextLinkReg = /(last_timeline|offset)=(\d+)/;
  6622. if (hasNextPage) {
  6623. var href = $nextPageLink.attr('href');
  6624. if (nextLinkReg.test(href)) {
  6625. def.resolve(baseUrl, result, RegExp.$2, RegExp.$1);
  6626. } else {
  6627. def.resolve(baseUrl, result, null, null);
  6628. }
  6629. } else {
  6630. def.resolve(baseUrl, result, null, null);
  6631. }
  6632. },
  6633. function() {
  6634. def.reject();
  6635. });
  6636.  
  6637. return def.promise();
  6638. };
  6639.  
  6640. var pipeRequest = function(baseUrl, result, maxPages, callback) {
  6641. var def = new $.Deferred(), p = def.promise();
  6642.  
  6643. for (var i = maxPages; i >= 0; i--) {
  6644. p = p.then(loadPage);
  6645. if (i > 0) p = p.then(Util.Deferred.wait(300));
  6646. }
  6647.  
  6648. p.then(
  6649. function() {
  6650. var uniq = {}, uniq_items = [];
  6651. for (var i = result.rawData.list.length - 1; i >= 0; i--) {
  6652. var item = result.rawData.list[i], id = item.id, mc = item.mylist_comment;
  6653. if (uniq[id + mc]) {
  6654. uniq[id + mc]._info.nicorepo_log.push(item.first_retrieve + ' ' + item._info.nicorepo_log[0].replace(/^.*?さん(の|が)動画(が|を) ?/, ''));
  6655. } else {
  6656. uniq[id + mc] = item;
  6657. }
  6658. }
  6659. for (var v in uniq) {
  6660. uniq_items.unshift(uniq[v]);
  6661. }
  6662. result.rawData.list = uniq_items;
  6663. callback(result);
  6664. }
  6665. );
  6666. def.resolve(baseUrl, result, '', '');
  6667.  
  6668. };
  6669.  
  6670. var request = function(param) {
  6671. var url, nickname, userId, type, baseUrl;
  6672. var def = new $.Deferred;
  6673. try {
  6674. url = '';
  6675. nickname = param.nickname || WatchController.getMyNick();
  6676. userId = param.userId || WatchController.getMyUserId();
  6677. type = param.type || 'user';
  6678. baseUrl = '/my/top/' + type + '?innerPage=1&mode=next_page';
  6679. if (param.userId) {
  6680. baseUrl = '/user/'+ param.userId +'/top?innerPage=1&mode=next_page';
  6681. }
  6682. } catch (e) {
  6683. console.log(e);
  6684. return def.reject({message: 'エラーが発生しました', status: 'fail'});
  6685. }
  6686.  
  6687. var cacheData = Util.Cache.get(baseUrl);
  6688. if (cacheData) {
  6689. return def.resolve(cacheData);
  6690. }
  6691.  
  6692. var
  6693. result = new DummyMylist({
  6694. id: '-10',
  6695. sort: '1',
  6696. default_sort: '1',
  6697. name: getNicorepoTitle(type, param),
  6698. user_id: type === 'owner' ? WatchController.getOwnerId() : userId,
  6699. user_nickname: type === 'owner' ? WatchController.getOwnerName() : nickname
  6700. });
  6701.  
  6702. pipeRequest(baseUrl, result, 2, function(result) {
  6703. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  6704. });
  6705.  
  6706. return def.promise();
  6707. };
  6708.  
  6709. var load = function(callback, param) {
  6710. return request(param)
  6711. .then(function(result) {
  6712. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6713. result.sortItem(param.sort || 1, true);
  6714. result.setPage(viewPage);
  6715. if (typeof result === 'function') { callback(result); }
  6716. return this.done(result);
  6717. }, function() {
  6718. return this.fail({message: 'ニコレポの取得に失敗しました', status: 'fail'});
  6719. });
  6720. };
  6721.  
  6722. var self = {
  6723. load: load,
  6724. REPO_ALL: -10,
  6725. REPO_USER: -11,
  6726. REPO_CHCOM: -12,
  6727. REPO_MYLIST: -13,
  6728. REPO_OWNER: -14,
  6729. loadAll: function(callback, p) {
  6730. p = p || {};
  6731. p.type = 'all';
  6732. return self.load(callback, p);
  6733. },
  6734. loadUser: function(callback, p) {
  6735. p = p || {};
  6736. p.type = 'user';
  6737. return self.load(callback, p);
  6738. },
  6739. loadChCom: function(callback, p) {
  6740. p = p || {};
  6741. p.type = 'chcom';
  6742. return self.load(callback, p);
  6743. },
  6744. loadMylist: function(callback, p) {
  6745. p = p || {};
  6746. p.type = 'mylist';
  6747. return self.load(callback, p);
  6748. },
  6749. loadOwner: function(callback, p) {
  6750. p = p || {};
  6751. p.type = 'owner';
  6752. p.userId = WatchController.getOwnerId();
  6753. return self.load(callback, p);
  6754. }
  6755. };
  6756. WatchItLater.NicorepoVideo = self;
  6757.  
  6758. return self;
  6759. })(w, Util);
  6760.  
  6761.  
  6762.  
  6763. /**
  6764. * ランキングのRSSをマイリストAPIと互換のある形式に変換することで、ダミーマイリストとして表示してしまう作戦
  6765. */
  6766. var VideoRanking = (function(w, Util) {
  6767. if (!window.PlayerApp) return {};
  6768. var $ = w.jQuery;
  6769.  
  6770. var
  6771. genreIdTable = {
  6772. all: -100,
  6773. g_ent2: -110,
  6774. ent: -111,
  6775. music: -112,
  6776. sing: -113,
  6777. play: -114,
  6778. dance: -115,
  6779. vocaloid: -116,
  6780. nicoindies: -117,
  6781. g_life2: -120,
  6782. animal: -121,
  6783. cooking: -122,
  6784. nature: -123,
  6785. travel: -124,
  6786. sport: -125,
  6787. lecture: -126,
  6788. drive: -127,
  6789. history: -128,
  6790. g_politics: -130,
  6791. g_tech: -140,
  6792. science: -141,
  6793. tech: -142,
  6794. handcraft: -143,
  6795. make: -144,
  6796. g_culture2: -150,
  6797. anime: -151,
  6798. game: -152,
  6799. toho: -153,
  6800. imas: -154,
  6801. radio: -155,
  6802. draw: -156,
  6803. g_other: -160,
  6804. are: -161,
  6805. diary: -162,
  6806. other: -163
  6807. // r18: -170
  6808. },
  6809. genreNameTable = {
  6810. all: 'カテゴリ合算',
  6811. g_ent2: 'エンタメ・音楽',
  6812. ent: 'エンターテイメント',
  6813. music: '音楽',
  6814. sing: '歌ってみた',
  6815. play: '演奏してみた',
  6816. dance: '踊ってみた',
  6817. vocaloid: 'VOCALOID',
  6818. nicoindies: 'ニコニコインディーズ',
  6819. g_life2: '生活・一般・スポ',
  6820. animal: '動物',
  6821. cooking: '料理',
  6822. nature: '自然',
  6823. travel: '旅行',
  6824. sport: 'スポーツ',
  6825. lecture: 'ニコニコ動画講座',
  6826. drive: '車載動画',
  6827. history: '歴史',
  6828. g_politics: '政治',
  6829. g_tech: '科学・技術',
  6830. science: '科学',
  6831. tech: 'ニコニコ技術部',
  6832. handcraft: 'ニコニコ手芸部',
  6833. make: '作ってみた',
  6834. g_culture2: 'アニメ・ゲーム・絵',
  6835. anime: 'アニメ',
  6836. game: 'ゲーム',
  6837. toho: '東方',
  6838. imas: 'アイドルマスター',
  6839. radio: 'ラジオ',
  6840. draw: '描いてみた',
  6841. g_other: 'その他',
  6842. are: '例のアレ',
  6843. diary: '日記',
  6844. other: 'その他',
  6845. r18: 'R-18'
  6846. },
  6847. termIdTable = {
  6848. 'hourly': 0,
  6849. 'daily': -1000,
  6850. 'weekly': -2000,
  6851. 'monthly': -3000,
  6852. 'total': -4000
  6853. },
  6854. termNameTable = {
  6855. 'hourly': '(毎時)',
  6856. 'daily': '(24時間)',
  6857. 'weekly': '(週間)',
  6858. 'monthly': '(月間)',
  6859. 'total': '(合計)'
  6860. },
  6861. idTermTable = {},
  6862. idGenreTable = {}
  6863. ;
  6864. if (conf.debugMode) { genreIdTable['r18'] = -170; }
  6865. for (var genre in genreIdTable) { idGenreTable[genreIdTable[genre]] = genre;}
  6866. for (var term in termIdTable ) { idTermTable [termIdTable [term ]] = term; }
  6867.  
  6868. /**
  6869. * ニコニコ動画ランキングのRSSをマイリストAPI互換のデータ形式に変換
  6870. */
  6871. var rss2mylist = function(xml) {
  6872. var
  6873. $x = $(xml),
  6874. title = $x.find('channel title:first').text(),
  6875. $items = $x.find('channel item'),
  6876. result = new DummyMylist({
  6877. name: title,
  6878. id: '-100'
  6879. });
  6880. $items.each(function() {
  6881. var video = parseRssItem($(this));
  6882. var item = new DummyMylistVideo({
  6883. id: video.id,
  6884. length: video.duration,
  6885. mylist_counter: video.mylistCnt,
  6886. view_counter: video.viewCnt,
  6887. num_res: video.resCnt,
  6888. first_retrieve: video.postedAt,
  6889. thumbnail_url: video.thumbnail,
  6890. title: video.title.replace(/^.*?第(\d+)位/, '第000$1位').replace(/^第\d+(\d{3})位/, '第$1位'),
  6891. _info: {first_retrieve: video.postedAt},
  6892. description_short: video.description.substring(0, 50)
  6893. });
  6894. result.push(item);
  6895. });
  6896. return result;
  6897. };
  6898.  
  6899. var parseRssItem = function($item) {
  6900. var
  6901. desc_cdata = $item.find('description').text(),
  6902. $desc = $('<div>' + desc_cdata + '</div>');
  6903. return {
  6904. title : $item.find('title') .text(),
  6905. id : $item.find('guid') .text().split('/').reverse()[0],
  6906. duration : $desc.find('.nico-info-length') .text(),
  6907. viewCnt : $desc.find('.nico-info-total-view') .text().replace(/,/g, ''),
  6908. resCnt : $desc.find('.nico-info-total-res') .text().replace(/,/g, ''),
  6909. mylistCnt : $desc.find('.nico-info-total-mylist').text().replace(/,/g, ''),
  6910. postedAt : $desc.find('.nico-info-date') .text()
  6911. .replace(/(年|月)/g, '-')
  6912. .replace(/:/g, ':')
  6913. .replace(/(日)/g, ''),
  6914. description : $desc.find('.nico-description') .text(),
  6915. thumbnail : $desc.find('.nico-thumbnail img').attr('src')
  6916. };
  6917. };
  6918.  
  6919. var pipeRequest = function(baseUrl, result, page, maxPage) {
  6920. var def = new $.Deferred(), p = def.promise();
  6921.  
  6922. var getPipe = function(result, url, page) {
  6923. return function() {
  6924. console.log('load RSS', url, page);
  6925. return $.ajax({
  6926. url: url,
  6927. timeout: 30000,
  6928. data: {rss: '2.0', lang: 'ja-jp', page: page},
  6929. beforeSend: function(xhr) {
  6930. xhr.setRequestHeader('X-Requested-With', {toString: function(){ return ''; }});
  6931. }
  6932. }).then(function(resp) {
  6933. var res = rss2mylist(resp);
  6934. for (var i = 0, len = res.rawData.list.length; i < len; i++) {
  6935. result.push(res.rawData.list[i]);
  6936. }
  6937. });
  6938. };
  6939. };
  6940.  
  6941. for (var i = page; i <= maxPage; i++) {
  6942. p = p.then(getPipe(result, baseUrl, i));
  6943. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  6944. }
  6945.  
  6946. def.resolve();
  6947.  
  6948. return p;
  6949. };
  6950.  
  6951. var CACHE_TIME = 1000 * 60 * 30;
  6952. var request = function(baseUrl, page, maxPage) {
  6953. var def = new $.Deferred();
  6954. var cacheData = Util.Cache.get(baseUrl);
  6955. if (cacheData) {
  6956. return def.resolve(cacheData);
  6957. }
  6958.  
  6959. var result = new DummyMylist({
  6960. name: '総合ランキング',
  6961. id: '-100'
  6962. });
  6963.  
  6964. pipeRequest(baseUrl, result, page, maxPage).then(
  6965. function() {
  6966. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  6967. },
  6968. function() {
  6969. def.reject();
  6970. });
  6971.  
  6972. return def.promise();
  6973. };
  6974.  
  6975. var parseParam = function(param) {
  6976. var
  6977. id = parseInt(param.id || -100, 10),
  6978. genreId = getGenreId(id),
  6979. termId = getTermId(id),
  6980. category = idGenreTable[genreId] || 'all', type = 'fav', term = 'daily', lang= 'ja-jp',
  6981. viewPage = (param && typeof param.page === 'number') ? param.page : 1,
  6982. genreName = genreNameTable[category] || genreNameTable['all'],
  6983. maxRssPage = 1, sort = param.sort || '4';
  6984.  
  6985. term = idTermTable[termId] || idTermTable[0];
  6986. maxRssPage = (category === 'all' && term !== 'hourly') ? 3 : 1;
  6987. return {
  6988. genreId: genreId,
  6989. genreName: genreName,
  6990. category: category,
  6991. type: type,
  6992. term: term,
  6993. lang: lang,
  6994. viewPage: viewPage,
  6995. sort: sort,
  6996. maxRssPage: maxRssPage,
  6997. baseUrl:
  6998. '/ranking/'+ type +'/'+ term + '/'+ category //+'?rss=2.0&lang=' + lang
  6999. };
  7000. };
  7001.  
  7002. var loadRanking = function(param) {
  7003. var p = parseParam(param);
  7004. return request(p.baseUrl, 1, p.maxRssPage)
  7005. .then(function(result) {
  7006. result.name = p.genreName;
  7007. result.setPage(p.viewPage);
  7008.  
  7009. this.done(result);
  7010. });
  7011. };
  7012.  
  7013. var load = function(onload, param) {
  7014. var p = parseParam(param);
  7015. return request(p.baseUrl, 1, p.maxRssPage)
  7016. .then(function(result) {
  7017. result.name = p.genreName;
  7018. result.setPage(p.viewPage);
  7019.  
  7020. if (typeof onload === 'function') {
  7021. onload(result);
  7022. }
  7023. return this.done(result);
  7024. }, function() {
  7025. return this.fail({message: 'ランキングの取得に失敗しました', status: 'fail'});
  7026. });
  7027. };
  7028.  
  7029. var getTermId = function(t) {
  7030. if (typeof t === 'string') {
  7031. return termIdTable[t] || 0;
  7032. } else
  7033. if (typeof t === 'number'){
  7034. return (t - (t % 1000)) % 10000;
  7035. }
  7036. return 0;
  7037. };
  7038. var getGenreId = function(g, term) {
  7039. if (typeof g === 'string') {
  7040. return (genreIdTable[g] || 0) + getTermId(term);
  7041. } else
  7042. if (typeof g === 'number'){
  7043. return g % 1000;
  7044. } else {
  7045. return genreIdTable;
  7046. }
  7047. };
  7048. var getGenreName = function(g) {
  7049. if (typeof g === 'number' || (typeof g === 'string' && g.match(/^-?[0-9]+$/))) {
  7050. g = g % 1000;
  7051. var genre = idGenreTable[g];
  7052. return genreNameTable[genre];
  7053. } else
  7054. if (typeof g === 'string') {
  7055. return genreNameTable[g];
  7056. } else {
  7057. return genreNameTable;
  7058. }
  7059. };
  7060. var getCategory = function(g) {
  7061. if (typeof g === 'number') {
  7062. g = g % 1000;
  7063. return idGenreTable[g - (g %10)];
  7064. } else
  7065. if (typeof g === 'string') {
  7066. g = genreIdTable[g];
  7067. return idGenreTable[g - (g %10)];
  7068. } else {
  7069. return 'all';
  7070. }
  7071. };
  7072.  
  7073. var self = {
  7074. load: load,
  7075. getTermId: getTermId,
  7076. getGenreId: getGenreId,
  7077. getGenreName: getGenreName,
  7078. getCategory: getCategory
  7079. };
  7080. WatchItLater.VideoRanking = self;
  7081. return self;
  7082. })(w, Util);
  7083.  
  7084.  
  7085.  
  7086. /**
  7087. * チャンネル動画一覧をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  7088. */
  7089. var ChannelVideoList = (function(w, Util){
  7090. if (!window.PlayerApp) return {};
  7091. var
  7092. CACHE_TIME = 1000 * 60 * 1, MAX_PAGE = 3,
  7093. getPipe = function(baseUrl, result, page) {
  7094. return function(hasPage) {
  7095. var def = new $.Deferred();
  7096. if (!hasPage) return def.resolve(hasPage);
  7097. var url = baseUrl + '?page=' + page;
  7098. console.log('load page', url);
  7099.  
  7100. $.ajax({url: url, timeout: 30000}).then(function(resp) {
  7101. var hasNextPage = parseItems(resp, result);
  7102. def.resolve(hasNextPage);
  7103. }, function(err) {
  7104. def.reject(err);
  7105. });
  7106. return def.promise();
  7107. };
  7108. },
  7109. pipeRequest = function(baseUrl, result) {
  7110. var def = new $.Deferred(), p = def.promise();
  7111.  
  7112. var maxPage = MAX_PAGE;
  7113. for (var i = 1; i <= maxPage; i++) {
  7114. p = p.then(getPipe(baseUrl, result, i));
  7115. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  7116. }
  7117.  
  7118. p.then(function() {
  7119. this.done(result);
  7120. });
  7121.  
  7122. def.resolve(true);
  7123. return p;
  7124. },
  7125. load = function(callback, params) {
  7126. var myId, url, id, ownerName, def = new $.Deferred();
  7127. try{
  7128. id = params.id.toString().replace(/^ch/, '');
  7129. ownerName = params.ownerName;
  7130. url = 'http://ch.nicovideo.jp/channel/ch'+ id + '/video';
  7131. myId = WatchController.getMyUserId();
  7132. } catch (e) {
  7133. console.log(e);
  7134. throw { message: 'エラーが発生しました', status: 'fail'};
  7135. }
  7136.  
  7137. var CACHE_KEY = 'ch-' + id, cacheData = Util.Cache.get(CACHE_KEY);
  7138. if (cacheData) {
  7139. if (typeof callback === 'function') {
  7140. setTimeout(function() { callback(cacheData); } , 0);
  7141. }
  7142. return def.resolve(cacheData);
  7143. }
  7144.  
  7145.  
  7146. var result = new DummyMylist({
  7147. id: 'ch' + id,
  7148. sort: '1',
  7149. name: ownerName + 'の動画',
  7150. user_id: myId,
  7151. user_name: 'ニコニコ動画'
  7152. });
  7153.  
  7154. pipeRequest(url, result).then(function() {
  7155. Util.Cache.set(CACHE_KEY, result, CACHE_TIME);
  7156. if (typeof callback === 'function') callback(result);
  7157. def.resolve(result);
  7158. });
  7159.  
  7160. return def.promise();
  7161. },
  7162. parseItems = function(html, result) {
  7163. var $html = $(html), $list = $html.find('.contents_list .item');
  7164. var hasNextPage = false;
  7165. $list.each(function() {
  7166. var $item = $(this);
  7167. var id = $item.find('.title a').attr('href').split('/').reverse()[0];
  7168. var $counts = $item.find('.counts'), first_retrieve = $item.find('.time var').attr('title');
  7169. w.$item = $item;
  7170. result.push(new DummyMylistVideo({
  7171. id: id,
  7172. length: $item.find('.length').text(),
  7173. mylist_counter: $counts.find('.mylist var').text().split(',').join(''),
  7174. view_counter: $counts.find('.view var').text().split(',').join(''),
  7175. num_res: $counts.find('.comment var').text().split(',').join(''),
  7176. first_retrieve: first_retrieve,
  7177. thumbnail_url: $item.find('.lazyimage').data('original'),
  7178. title: $item.find('.title').text().trim(),
  7179. _info: {first_retrieve: first_retrieve, is_channel: true},
  7180. description_short: $item.find('.description').text().trim()
  7181. }));
  7182. });
  7183. if ($html.find('.pager .next:not(.disabled)').length > 0) {
  7184. hasNextPage = true;
  7185. }
  7186. return hasNextPage;
  7187. },
  7188. loadOwnerVideo = function(callback) {
  7189. if (!WatchController.isChannelVideo()) {
  7190. throw {message: 'チャンネル情報の取得に失敗しました', status: 'fail'};
  7191. }
  7192. var params = {
  7193. id: WatchController.getOwnerId(),
  7194. ownerName: WatchController.getOwnerName()
  7195. };
  7196. var def = new $.Deferred();
  7197. load(callback, params).then(function(result) {
  7198. if (typeof callback === 'function') callback(result);
  7199. def.resolve(result);
  7200. }, function() {
  7201. def.reject({message: 'チャンネル動画の取得に失敗しました', status: 'fail'});
  7202. });
  7203. return def.promise();
  7204. };
  7205.  
  7206. var self = {
  7207. load: load,
  7208. loadOwnerVideo: loadOwnerVideo
  7209. };
  7210. WatchItLater.ChannelVideo = self;
  7211. return self;
  7212. })(w, Util);
  7213.  
  7214.  
  7215.  
  7216. var niconicodoRedirect = function() {
  7217. // www.nicovideo.jp/stampを watchにパラメータを中継するためのクッションページとして使う。
  7218. // watchと同じドメインならどこでもいいけど、ここはDBアクセスもなさそうな静的ページため採用
  7219. var hash = location.hash.toString();
  7220. if (hash.indexOf('#json={') !== 0) {
  7221. return;
  7222. }
  7223. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  7224.  
  7225. LocationHashParser.initialize();
  7226. var blankWatchId = 'sm20353707';
  7227. var redirectWatchId = LocationHashParser.getValue('redirectWatchId');
  7228. // 見た目が残念なので消す
  7229. document.body.innerHTML = '';
  7230.  
  7231. window.sessionStorage.setItem('watchItLater_redirectedHash', location.hash);
  7232. var redirectTo = '/watch/' + (redirectWatchId ? redirectWatchId : blankWatchId);
  7233. location.replace(redirectTo);
  7234. };
  7235.  
  7236.  
  7237. /**
  7238. * GINZAwatch上でのあれこれ
  7239. * 無計画に増築中
  7240. *
  7241. * watch.jsを解析すればわかる
  7242. *
  7243. */
  7244. var ZeroFunc = function(w) { // Zero Watch
  7245. var
  7246. video_id = '', watch_id = '',
  7247. // WatchApp = w.WatchApp, WatchJsApi = w.WatchJsApi,
  7248. isTouchActive = false,
  7249. console = conf.debugMode ? window.console : {log: _.noop, trace: _.noop, time: _.noop, timeEnd: _.noop},
  7250. watch = WatchApp.ns.init,
  7251. watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  7252. if (!WatchApp.mixin) {
  7253. WatchApp.mixin = _.mixin;
  7254. }
  7255.  
  7256. console.log('%cGinza', 'background: lightgreen;');
  7257.  
  7258. /**
  7259. * ゆっくり再生(スロー再生)メニュー
  7260. */
  7261. var Yukkuri = (function($, conf, w) {
  7262. var self, $content = null, $button = null, timer = null, cnt = 0, isActive = false;
  7263.  
  7264. function createDom() {
  7265. $content = $('<div id="yukkuriPanel" />');
  7266. $button = $('<button>yu</button>').addClass('yukkuriButton').attr({title: 'ゆっくり(スロー再生)'});
  7267. $button.click(function() {
  7268. toggleActive();
  7269. });
  7270. $content.append($button);
  7271.  
  7272. $('body').append($content);
  7273. }
  7274.  
  7275. function show() {
  7276. if ($content === null) {
  7277. createDom();
  7278. }
  7279. updateView();
  7280. $content.show();
  7281. }
  7282. function hide() {
  7283. $content.hide();
  7284. }
  7285. function updateView() {
  7286. $button.toggleClass('active', isActive);
  7287. }
  7288.  
  7289. function start() {
  7290. if (timer !== null) {
  7291. clearInterval(timer);
  7292. }
  7293. isActive = true;
  7294. updateView();
  7295. timer = setInterval(function() {
  7296. var v = cnt++ % 4;
  7297. if (v === 0) {
  7298. WatchController.play();
  7299. } else
  7300. if (v === 1) {
  7301. WatchController.pause();
  7302. }
  7303. }, 20);
  7304. }
  7305. function stop() {
  7306. if (timer !== null) {
  7307. clearInterval(timer);
  7308. timer = null;
  7309. }
  7310. isActive = false;
  7311. updateView();
  7312. WatchController.pause();
  7313. }
  7314.  
  7315. function toggleActive() {
  7316. if (isActive) {
  7317. stop();
  7318. } else {
  7319. start();
  7320. }
  7321. return isActive;
  7322. }
  7323.  
  7324. self = {
  7325. show: show,
  7326. hide: hide,
  7327. start: start,
  7328. stop: stop
  7329. };
  7330. return self;
  7331. })($, conf, w);
  7332.  
  7333. function onWindowResizeEnd() {
  7334. setTimeout(function() {
  7335. EventDispatcher.dispatch('onWindowResizeEnd');
  7336. }, 1000);
  7337. }
  7338.  
  7339. /**
  7340. * デフォルトの市場貼付ボタンはなぜかページの一番上までスクロールするという意地悪な仕様だが、
  7341. * こっちはがんばって見やすい位置に調整して開く
  7342. */
  7343. function ichibaSearch(word, shopCode) {
  7344. var wait = 10, opened = false;
  7345. //shopCode = shopCode || 'az'; // az = amazon
  7346. var search = function() {
  7347. if ($('#ichibaConsole').is(':visible')) {
  7348. setTimeout(function() {
  7349. w.WatchApp.ns.util.WindowUtil.scrollFitMinimum('#ichibaConsole', 300);
  7350. }, 1000);
  7351. if (!word) {
  7352. return;
  7353. }
  7354. if ($('#ichiba_search_form_query').is(':visible')) {
  7355. $('#ichiba_search_form_query').val(word);
  7356. w.ichiba.search(shopCode, 0, 'all');
  7357. setTimeout(function() {$('#ichiba_search_form_query').focus();}, 1000);
  7358. } else {
  7359. if (!opened) {
  7360. if(shopCode) { w.ichiba.showRelatedTagItems(shopCode, 0, 'all'); }
  7361. opened = true;
  7362. }
  7363. if (wait-- > 0) setTimeout(search, 1000);
  7364. }
  7365. } else {
  7366. if (wait-- > 0) setTimeout(search, 1000);
  7367. }
  7368. };
  7369. search();
  7370. w.ichiba.showConsole();
  7371. }
  7372. WatchController.ichibaSearch = ichibaSearch;
  7373.  
  7374. function initVideoCounter() {
  7375. var
  7376. playerAreaConnector = watch.PlayerInitializer.playerAreaConnector,
  7377. counter = {mylistCount: 0, viewCount: 0, commentCount: 0},
  7378. blinkItem = function($elm) {
  7379. $elm.removeClass('animateBlink').addClass('blink');
  7380. setTimeout(function() {
  7381. $elm.addClass('animateBlink').removeClass('blink');
  7382. $elm = null;
  7383. }, 500);
  7384. };
  7385. var setVideoCounter = function(watchId, title) {
  7386. var $tpl = $(
  7387. '<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>'
  7388. );
  7389. assignVideoCountToDom($tpl, counter);
  7390.  
  7391. if ((conf.popupViewCounter === 'always') ||
  7392. (conf.popupViewCounter === 'full' && $('body').hasClass('full_with_browser'))
  7393. ) {
  7394. Popup.show(
  7395. $('<div/>')
  7396. .append(
  7397. $('<a/>')
  7398. .text(window._.unescape(title))
  7399. .attr('href', 'http://nico.ms/' + watchId)
  7400. )
  7401. .html() +
  7402. '<br/><span style="margin-left:10px; font-size: 90%;">'+ $tpl.html() + '</span>'
  7403. );
  7404. }
  7405. $('#trueBrowserFullShield').html([
  7406. '<img class="ownerIcon" src="', WatchController.getOwnerIcon(), '">',
  7407. '<div class="title">', title, '</div>',
  7408. '<p class="postedAt">',$('.videoPostedAt:last').text(), '</p>',
  7409. '<p class="videoCounter">', $tpl.html(), '</p>',
  7410. ''].join(''))
  7411. .toggleClass('favorite', WatchController.isFavoriteOwner())
  7412. .find('img').attr('title', WatchController.getOwnerName());
  7413.  
  7414. if (conf.headerViewCounter) {
  7415. var vc = $('#videoCounter');
  7416. if (vc.length < 1) {
  7417. var li = $('<li></li>')[0];
  7418. li.id = 'videoCounter';
  7419. $('#siteHeaderLeftMenu').after(li);
  7420. vc = $('#videoCounter');
  7421. }
  7422. vc.empty().append($tpl);
  7423. }
  7424. };
  7425.  
  7426. playerAreaConnector.addEventListener('onWatchCountUpdated', function(c) {
  7427. var diff = c - counter.viewCount;
  7428. if (diff === 0) return;
  7429. counter.viewCount = c;
  7430. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'viewCount', diff);
  7431. });
  7432. playerAreaConnector.addEventListener('onCommentCountUpdated', function(c) {
  7433. var diff = c - counter.commentCount;
  7434. if (diff === 0) return;
  7435. counter.commentCount = c;
  7436. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'commentCount', diff);
  7437. });
  7438. playerAreaConnector.addEventListener('onMylistCountUpdated', function(c) {
  7439. var diff = c - counter.mylistCount;
  7440. if (diff === 0) return;
  7441. counter.mylistCount = c;
  7442. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'mylistCount', diff);
  7443. });
  7444.  
  7445. EventDispatcher.addEventListener('onWatchInfoReset', function(watchInfoModel){
  7446. counter.mylistCount = watchInfoModel.mylistCount;
  7447. counter.viewCount = watchInfoModel.viewCount;
  7448. counter.commentCount = watchInfoModel.commentCount;
  7449.  
  7450. setVideoCounter(watchInfoModel.v, watchInfoModel.title);
  7451. });
  7452. EventDispatcher.addEventListener('onVideoCountUpdated', function(c, type, diff) {
  7453. var $target = $('.sidePanel .videoInfo, #trueBrowserFullShield, #videoCounter');
  7454. assignVideoCountToDom($target, c);
  7455. $target.find('.' + type + 'Diff').text(diff).toggleClass('down', diff < 0);
  7456. blinkItem($target.find('.' + type + ', .' + type + 'Diff'));
  7457. });
  7458.  
  7459. } //
  7460.  
  7461. var isFirst = true;
  7462. function onVideoInitialized() {
  7463. watch = WatchApp.ns.init;
  7464. AnchorHoverPopup.hidePopup().updateNow();
  7465. tagv = watch.TagInitializer.tagViewController;
  7466. WatchCounter.add();
  7467.  
  7468. if (isFirst) {
  7469. if (conf.autoPlayIfWindowActive === 'yes' && w.document.hasFocus()) {
  7470. // ウィンドウがアクティブの時だけ自動再生する。 複数タブ開いてるときは便利
  7471. setTimeout(function() { WatchController.play(); }, 2000);
  7472. }
  7473.  
  7474. if (isFirst && conf.commentVisibility !== 'visible') {
  7475. if (conf.commentVisibility === 'hidden') {
  7476. console.log('comment off');
  7477. WatchController.commentVisibility(false);
  7478. } else {
  7479. console.log('last state', conf.lastCommentVisibility);
  7480. WatchController.commentVisibility(conf.lastCommentVisibility === 'visible');
  7481. }
  7482. }
  7483. EventDispatcher.dispatch('onFirstVideoInitialized');
  7484. }
  7485.  
  7486. EventDispatcher.dispatch('onVideoInitialized', isFirst);
  7487. isFirst = false;
  7488. } //
  7489.  
  7490. function onVideoChangeStatusUpdated(isChanging) {
  7491. AnchorHoverPopup.hidePopup();
  7492. if (isChanging) {
  7493. $('.sidePanel .sideVideoInfo').removeClass('show');
  7494. }
  7495. if ((conf.enableAutoPlaybackContinue || conf.debugMode) && watch.PlayerInitializer.noUserOperationController.autoPlaybackModel._isAutoPlayback) {
  7496. watch.PlayerInitializer.noUserOperationController.autoPlaybackModel.setCount(0);
  7497. }
  7498. EventDispatcher.dispatch('onVideoChangeStatusUpdated', isChanging);
  7499. }
  7500.  
  7501. var $sideInfoPanelTemplate = $([
  7502. '<div class="sideVideoInfoInner">',
  7503.  
  7504. '<div class="videoTitleContainer"><h3 class="videoTitle"></h3></div>',
  7505. '<div class="videoOwnerInfoContainer">',
  7506. '<div class="channelIconContainer"><a target="_blank" class="channelIconLink">',
  7507. '<img class="channelIcon"></a>',
  7508. '<span class="channelName">提供: ',
  7509. '<a class="showOtherVideos" target="_blank"><span class="channelNameInner"></span></a></span>',
  7510. '</span>',
  7511. '</div>',
  7512. '<div class="userIconContainer"><a target="_blank" class="userIconLink">',
  7513. '<img class="userIcon"></a>',
  7514. '<span class="userName">投稿者: ',
  7515. '<span class="userNameInner notPublic"></span>',
  7516. '<span class="isPublic"><a class="showOtherVideos"><span class="userNameInner"></span></a></span>',
  7517. '</span>',
  7518. '</div>',
  7519. '</div>',
  7520. '<div class="videoInfo">',
  7521. '<span class="videoPostedAt"></span>',
  7522. '<ul class="videoStats">',
  7523. '<li style="position: relative;">再生: <span class="viewCountDiff videoCountDiff"></span><span class="videoCount viewCount"></span></li>',
  7524. '<li style="position: relative;">コメント: <span class="commentCountDiff videoCountDiff"></span><span class="videoCount commentCount"></span></li>',
  7525. '<li style="position: relative;">マイリスト: <span class="mylistCountDiff videoCountDiff"></span><span class="videoCount mylistCount"></span></li>',
  7526. '</ul>',
  7527. '</div>',
  7528.  
  7529.  
  7530. '<div class="videoThumbnailContainer" style="display: none;">',
  7531. '<img class="videoThumbnailImage">',
  7532. '</div>',
  7533. '<div class="videoDetails">',
  7534. '<div class="videoDescription">',
  7535. '<div class="videoDescriptionInner">',
  7536. '</div>',
  7537. '</div>',
  7538. '</div>',
  7539. '</div>',
  7540. ''].join(''));
  7541.  
  7542.  
  7543. // - 左パネル乗っ取る
  7544. function initLeftPanel($, conf, w) {
  7545.  
  7546. var $tab = $([
  7547. '<ul id="leftPanelTabContainer">',
  7548. '<li class="tab ichiba" data-selection="ichiba" >市場</li>',
  7549. '<li class="tab videoInfo" data-selection="videoInfo">情報</li>',
  7550. '</ul>'].join(''));
  7551.  
  7552. var
  7553. $sidePanel = $('<div id="leftPanel" />').addClass('sidePanel'),
  7554. $infoPanel = $('<div/>').attr({'id': 'leftVideoInfo', 'class': 'sideVideoInfo sidePanelInner'}),
  7555. $ichibaPanel = $('<div/>').attr({'id': 'leftIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7556. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel);
  7557. $('#playerTabWrapper').after($sidePanel);
  7558.  
  7559. var
  7560. onTabSelect = function(e) {
  7561. e.preventDefault();
  7562. AnchorHoverPopup.hidePopup();
  7563. var selection = $(e.target).attr('data-selection');
  7564. if (typeof selection === 'string') {
  7565. conf.setValue('lastLeftTab', selection);
  7566. changeTab(selection);
  7567. }
  7568. },
  7569. changeTab = function(selection) {
  7570. $sidePanel.removeClass('videoInfo ichiba').addClass(selection);
  7571. if (selection === 'ichiba') {
  7572. resetIchiba(false);
  7573. }
  7574. },
  7575. lastIchibaVideoId = '',
  7576. resetIchiba = function(force) {
  7577. var videoId = watchInfoModel.id;
  7578. if (lastIchibaVideoId === videoId && !force) {
  7579. return;
  7580. }
  7581. lastIchibaVideoId = videoId;
  7582. resetSideIchibaPanel($ichibaPanel, true);
  7583. },
  7584. resetScroll = function() {
  7585. $(this).animate({scrollTop: 0}, 600);
  7586. };
  7587.  
  7588. $infoPanel .on('dblclick', resetScroll);
  7589. $ichibaPanel.on('dblclick', resetScroll);
  7590.  
  7591. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7592. changeTab(conf.lastLeftTab);
  7593.  
  7594. var refreshPanel = function(isFirst) {
  7595. if (isFirst) { return; }
  7596.  
  7597. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  7598. if ($ichibaPanel.is(':visible')) {
  7599. resetIchiba(true);
  7600. }
  7601. };
  7602. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  7603. refreshPanel();
  7604.  
  7605. } // end of initLeftPanel
  7606.  
  7607. function initRightPanel($, conf, w) {
  7608. var $rightPanel = $('#playerTabWrapper').addClass('sidePanel');
  7609. initRightPanelVerticalTab($rightPanel);
  7610. initRightPanelHorizontalTab($, conf, w);
  7611. var $playerTabWrapper = $rightPanel, wideCss = null;
  7612. var
  7613. createWideCommentPanelCss = function (targetWidth) {
  7614. var px = targetWidth - $rightPanel.outerWidth();
  7615. var elms = [
  7616. '#playerTabWrapper', //'#playerTabWrapper',
  7617. '#commentDefaultHeader',
  7618. '#playerCommentPanel .commentTable',
  7619. '#playerCommentPanel .commentTable .commentTableContainer'
  7620. ];
  7621. var css = [
  7622. 'body.videoExplorer #content.w_adjusted #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7623. 'body:not(.full_with_browser) .w_wide #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7624. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }\n', // 960 + 140
  7625. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }\n\n' // 1186 + 140
  7626. ];
  7627. for (var v in elms) {
  7628. var $e = $(elms[v]), newWidth = $e.width() + px;
  7629. css.push([
  7630. '.w_wide #playerTabWrapper ', elms[v],
  7631. ' , body.videoExplorer #content.w_adjusted ',
  7632. elms[v], '\n{ width: ', newWidth,'px !important; }\n\n'
  7633. ].join(''));
  7634. }
  7635. wideCss = addStyle(css.join(''), 'wideCommentPanelCss');
  7636. console.log(css.join(''));
  7637. },
  7638. toggleWide = function(v) {
  7639. $('#content').toggleClass('w_wide', v);
  7640. EventDispatcher.dispatch('onWindowResizeEnd');
  7641. };
  7642.  
  7643. var wideCommentPanelCss = Util.here(function() {/*
  7644. body.videoExplorer #content.w_adjusted #playerTabWrapper { width: 420px; }
  7645. body:not(.full_with_browser) .w_wide #playerTabWrapper { width: 420px; }
  7646.  
  7647. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }
  7648. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }
  7649.  
  7650. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerTabWrapper,
  7651. body.videoExplorer #content.w_adjusted #playerTabWrapper
  7652. { width: 420px !important; }
  7653.  
  7654. body:not(.full_with_browser) .w_wide #playerTabWrapper #commentDefaultHeader,
  7655. body.videoExplorer #content.w_adjusted #commentDefaultHeader
  7656. { width: 408px !important; }
  7657.  
  7658. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable,
  7659. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable
  7660. { width: 406px !important; }
  7661.  
  7662. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable .commentTableContainer,
  7663. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable .commentTableContainer
  7664. { width: 406px !important; }
  7665. */});
  7666. addStyle(wideCommentPanelCss, 'wideCommentPanelCss');
  7667.  
  7668. EventDispatcher.addEventListener('on.config.wideCommentPanel', toggleWide);
  7669. toggleWide(!!conf.wideCommentPanel);
  7670.  
  7671. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  7672.  
  7673. //EventDispatcher.dispatch('onWindowResizeEnd');
  7674. //createWideCommentPanelCss(420);
  7675.  
  7676. var $div = $([
  7677. '<div id="sharedNgSettingContainer" style="display: none;">NG共有: ',
  7678. '<select id="sharedNgSetting">',
  7679. '<option value="HIGH">高</option>',
  7680. '<option value="MIDDLE">中</option>',
  7681. '<option value="LOW">低</option>',
  7682. '<option value="NONE">無</option>',
  7683. '</select>',
  7684. '</div>',
  7685. ''].join('')), $ngs = $div.find('select');
  7686.  
  7687. $ngs
  7688. .val(watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get().ngScoringFilteringLevel)
  7689. .on('change', function() {
  7690. var val = this.value;
  7691. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({ngScoringFilteringLevel: this.value});
  7692. });
  7693. $('#commentDefaultHeader').append($div);
  7694.  
  7695. EventDispatcher.addEventListener('on.config.enableSharedNgSetting', function(newValue, oldValue) {
  7696. if (newValue) {
  7697. $div.show();
  7698. } else {
  7699. $div.hide();
  7700. }
  7701. });
  7702. if (conf.enableSharedNgSetting) { $div.show(); }
  7703. });
  7704.  
  7705. if (conf.removeCommentPanelHoverEvent) {
  7706. $("#commentDefault").find(".commentTableContainerInner") .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7707. $('#playerCommentPanel .section .commentTable .commentTableContainer') .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7708. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7709. .off('contextmenu')
  7710. .on('contextmenu', '.cell',
  7711. $.proxy(Util.Closure.commentPanelContextMenu(), watch.PlayerInitializer.commentPanelViewController.commentContent)
  7712. );
  7713. }
  7714. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function(isChanging) {
  7715. if (isChanging) {
  7716. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7717. .find('.cell').off();
  7718. }
  7719. });
  7720. } // end initRightPanel
  7721.  
  7722. function initRightPanelHorizontalTab($, conf, w) {
  7723. } //
  7724.  
  7725. function initRightPanelVerticalTab($sidePanel) {
  7726. if (!conf.rightPanelJack) { return; }
  7727.  
  7728. var $tab = $([
  7729. '<ul id="sidePanelTabContainer">',
  7730. '<li class="tab comment" data-selection="w_comment" >コメント</li>',
  7731. '<li class="tab videoInfo" data-selection="w_videoInfo">動画情報</li>',
  7732. '<li class="tab ichiba" data-selection="w_ichiba" >ニコニコ市場</li>',
  7733. '<li class="tab review" data-selection="w_review" >レビュー</li>',
  7734. '</ul>'].join(''));
  7735.  
  7736. var $infoPanel = $('<div/>').attr({'id': 'rightVideoInfo', 'class': 'sideVideoInfo sidePanelInner'});
  7737. var $ichibaPanel = $('<div/>').attr({'id': 'rightIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7738. var $reviewPanel = $('<div/>').attr({'id': 'rightReviewPanel', 'class': 'sideReviewPanel sidePanelInner'});
  7739. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel).append($reviewPanel);
  7740.  
  7741. var
  7742. onTabSelect = function(e) {
  7743. e.preventDefault();
  7744. AnchorHoverPopup.hidePopup();
  7745. var selection = $(e.target).attr('data-selection');
  7746. if (typeof selection === 'string') {
  7747. if (WatchController.isSearchMode()) {
  7748. conf.setValue('lastRightTabInExplorer', selection);
  7749. } else {
  7750. conf.setValue('lastRightTab', selection);
  7751. }
  7752. changeTab(selection);
  7753. }
  7754. },
  7755. $videoReview = $('#videoReview'),
  7756. toggleReview = function(f) {
  7757. if (f) {
  7758. $reviewPanel.append($videoReview);
  7759. } else {
  7760. $('#playerBottomAd').after($videoReview);
  7761. }
  7762. },
  7763. changeTab = function(selection) {
  7764. if ($sidePanel.hasClass('w_review') && selection !== 'w_review') {
  7765. toggleReview(false);
  7766. }
  7767. $sidePanel.removeClass('w_videoInfo w_comment w_ichiba w_review').addClass(selection);
  7768. if (selection === 'w_ichiba') {
  7769. resetIchiba(false);
  7770. } else
  7771. if (selection === 'w_review') {
  7772. toggleReview(true);
  7773. } else
  7774. if (selection === 'w_comment') {
  7775. setTimeout(function() {
  7776. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  7777. }, 500);
  7778. }
  7779. return changeTab;
  7780. },
  7781. lastIchibaVideoId = '', resetIchiba = function(force) {
  7782. var videoId = watchInfoModel.id;
  7783. if (lastIchibaVideoId === videoId && !force) {
  7784. return;
  7785. }
  7786. lastIchibaVideoId = videoId;
  7787. resetSideIchibaPanel($ichibaPanel, true);
  7788. },
  7789. resetScroll = function() {
  7790. $(this).animate({scrollTop: 0}, 600);
  7791. };
  7792.  
  7793. $infoPanel .on('dblclick', resetScroll);
  7794. $ichibaPanel.on('dblclick', resetScroll);
  7795. $reviewPanel.on('dblclick', resetScroll);
  7796.  
  7797. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7798. changeTab(conf.lastRightTab);
  7799.  
  7800. EventDispatcher.addEventListener('onVideoExplorerOpening', function() {
  7801. changeTab('w_comment');
  7802. });
  7803. EventDispatcher.addEventListener('onVideoExplorerClosing', function() {
  7804. changeTab(conf.lastRightTab);
  7805. });
  7806.  
  7807. var onOuterResize = function() {
  7808. var $body = $('body'), $right = $('#playerTabWrapper');
  7809. if (WatchController.isSearchMode() || $body.hasClass('full_with_browser')) { return; }
  7810. var w = $('#external_nicoplayer').outerWidth(), margin = 124;
  7811. w += $right.is(':visible') ? $right.outerWidth() : 0;
  7812. $('#sidePanelTabContainer').toggleClass('left', (window.innerWidth - w - margin < 0));
  7813. };
  7814. EventDispatcher.addEventListener('onWindowResizeEnd', onOuterResize);
  7815. EventDispatcher.addEventListener('onPlayerAlignmentAreaResize', onOuterResize);
  7816.  
  7817. var refreshPanel = function(isFirst) {
  7818.  
  7819. window.setTimeout(function() {
  7820. $sidePanel
  7821. .toggleClass('reviewEmpty', $('#videoReview').find('.stream').length < 1)
  7822. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  7823. }, 2000);
  7824.  
  7825. if (isFirst) { return; }
  7826.  
  7827. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  7828. if ($ichibaPanel.is(':visible')) {
  7829. resetIchiba(true);
  7830. }
  7831. };
  7832. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  7833.  
  7834. refreshPanel();
  7835.  
  7836. } // end of initRightPanelVerticalTab
  7837.  
  7838.  
  7839. function assignVideoCountToDom($tpl, count) {
  7840. var addComma = WatchApp.ns.util.StringUtil.addComma;
  7841. $tpl
  7842. .find('.viewCount' ).text(addComma(count.viewCount )).end()
  7843. .find('.commentCount').text(addComma(count.commentCount)).end()
  7844. .find('.mylistCount' ).text(addComma(count.mylistCount ));
  7845. return $tpl;
  7846. } //
  7847.  
  7848. function sidePanelRefresh($sideInfoPanel, $ichibaPanel, $sidePanel, $template) {
  7849. var isFavorite = WatchController.isFavoriteOwner();
  7850. //var h = $sideInfoPanel.innerHeight() - 100;
  7851.  
  7852. $template.find('.videoTitle').html(watchInfoModel.title);
  7853.  
  7854. assignVideoCountToDom($template, watchInfoModel);
  7855. $template.find('.videoPostedAt').text($('.videoPostedAt:last').text());
  7856.  
  7857. var $videoDescription = $template.find('.videoDescription');
  7858.  
  7859. $videoDescription.find('.videoDescriptionInner').append(create$videoDescription(watchInfoModel.description));
  7860.  
  7861. var $userIconContainer = $template.find('.userIconContainer');
  7862. var $channelIconContainer = $template.find('.channelIconContainer');
  7863.  
  7864. var info = WatchController.getOwnerInfo();
  7865. if (info.type === 'channel') {
  7866. if (info.id && info.id !== '0') {
  7867. $channelIconContainer
  7868. .find('.channelIcon')
  7869. .attr({'src': info.icon}).end()
  7870. .find('.channelIconLink')
  7871. .attr({'href': info.page})
  7872. .on('click', Util.Closure.openVideoOwnersVideo()).end()
  7873. .find('.channelNameInner')
  7874. .text(info.name).end()
  7875. .find('.showOtherVideos')
  7876. .attr({'href': info.page})
  7877. .on('click', Util.Closure.openVideoOwnersVideo());
  7878. }
  7879. $userIconContainer.remove();
  7880. } else {
  7881. if (info.id && info.id !== '0') { // ユーザーが退会してたりすると情報が無いのでチェックしてから
  7882. $userIconContainer
  7883. .find('.userIcon')
  7884. .attr({'src': info.icon}).end()
  7885. .find('.userIconLink')
  7886. .attr({'href': info.page})
  7887. .on('click', Util.Closure.openVideoOwnersNicorepo()).end()
  7888. .find('.userNameInner')
  7889. .text(info.name).end()
  7890. .find('.showOtherVideos')
  7891. .attr({'href': info.page + '/video'})
  7892. .on('click', Util.Closure.openVideoOwnersVideo() ).end()
  7893. .toggleClass('isUserVideoPublic', info.isVideoPublic);
  7894. $channelIconContainer.remove();
  7895. } else {
  7896. $userIconContainer.remove();
  7897. $channelIconContainer.remove();
  7898. }
  7899. }
  7900.  
  7901. $sideInfoPanel.find('*').unbind();
  7902.  
  7903. $sidePanel
  7904. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  7905.  
  7906. $sideInfoPanel
  7907. .empty()
  7908. .scrollTop(0)
  7909. .toggleClass('isFavorite', isFavorite)
  7910. .toggleClass('isChannel', WatchController.isChannelVideo())
  7911. .append($template);
  7912.  
  7913. window.setTimeout(function() {
  7914. $sideInfoPanel.addClass('show');
  7915. $sideInfoPanel = $ichibaPanel = $sidePanel = $template =
  7916. $videoDescription = $userIconContainer =
  7917. $channelIconContainer = null;
  7918. }, 100);
  7919.  
  7920. } // end of sidePanelRefresh
  7921.  
  7922. /**
  7923. * 説明文中の動画リンク類を加工
  7924. */
  7925. function decorateVideoDescriptionLink($description) {
  7926. var watchLinks = [], watchIds = [];
  7927. var videoReg = /\/watch\/((sm|nm|so|)\d+)$/;
  7928. var seigaReg = /seiga\/im(\d+)/;
  7929. $description.find('a').each(function() {
  7930. var url = this.href, text, $this = $(this);
  7931. if (videoReg.test(url)) {
  7932. var watchId = RegExp.$1;
  7933. var $nextButton = $([
  7934. '<div class="nextPlayButton" title="次に再生" onclick="WatchItLater.WatchController.insertVideoToPlaylist(\'', watchId, '\')">次に再生</div>',
  7935. ''].join(''));
  7936. $this.after($nextButton);
  7937.  
  7938. watchLinks.push({id: watchId, $target: $nextButton});
  7939. watchIds.push(watchId);
  7940. } else if (seigaReg.test(url)) {
  7941. var illustId = RegExp.$1;
  7942. var $thumbnail = $([
  7943. '<div class="descriptionThumbnail illust">',
  7944. '<img src="http://lohas.nicoseiga.jp/thumb/',
  7945. illustId,
  7946. 'z" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  7947. '</div>',
  7948. ''].join(''));
  7949. $this.after($thumbnail);
  7950. }
  7951. });
  7952.  
  7953. if (conf.enableDescriptionThumbnail && watchIds.length > 0) {
  7954. var ac = function(s) {
  7955. s = parseInt(s, 10);
  7956. s = s < 1 ? '-' : s;
  7957. return '<span class="count">' + WatchApp.ns.util.StringUtil.addComma(s) + '</span>';
  7958. };
  7959. var onWatchIdInfoReady = function(result) {
  7960. $(watchLinks).each(function(i, watchLink) {
  7961. var id = watchLink.id, $target = watchLink.$target;
  7962. if (result[id]) {
  7963. var info = result[id];
  7964. var $thmb = $([
  7965. '<div class="descriptionThumbnail video">',
  7966. '<img src="', info.thumbnail_url, '" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  7967. '<span class="uploadAt">', info.first_retrieve ,' 投稿</span>',
  7968. '<p>', info.title, ' (', info.length, ')</p>',
  7969. '<div class="counterContainer">',
  7970. '<span class="view">再生: ', ac(info.view_counter) ,'</span> ',
  7971. '<span class="comment">コメ: ', ac(info.num_res) ,'</span> ',
  7972. '<span class="mylist">マイ: ', ac(info.mylist_counter),'</span>',
  7973. '</div>',
  7974. '</div>'].join(''));
  7975. $target.after($thmb);
  7976. }
  7977. $target = watchLink = null;
  7978. });
  7979. watchIds = watchLinks = null;
  7980. };
  7981. var onWatchIdInfoFail = function() {
  7982. watchIds = watchLinks = null;
  7983. };
  7984. window.setTimeout(function() {
  7985. window.WatchItLater.loader.videoArrayAPILoader.load(watchIds).then(onWatchIdInfoReady, onWatchIdInfoFail);
  7986. }, 1000);
  7987. } else {
  7988. watchIds = watchLinks = null;
  7989. }
  7990. $description = null;
  7991. }
  7992.  
  7993. /**
  7994. * 動画説明文のクリックイベント類を割り当てる
  7995. */
  7996. function bindDescriptionEvents($description) {
  7997. $description.on('click', function(e) {
  7998. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  7999.  
  8000. var elm = e.target;
  8001. if (elm.tagName !== 'A') { return; }
  8002. if (elm.className === 'otherSite') return;
  8003.  
  8004. var $elm = $(elm);
  8005.  
  8006. if (elm.textContent.indexOf('mylist/') === 0) {
  8007. e.preventDefault(); e.stopPropagation();
  8008. var mylistId = elm.textContent.split('/').reverse()[0];
  8009.  
  8010. WatchController.showMylist(mylistId);
  8011. } else
  8012. if (elm.className === 'seekTime') {
  8013. e.preventDefault(); e.stopPropagation();
  8014. var data = $elm.attr('data-seekTime').split(":"),
  8015. vpos = (data[0] * 60 + parseInt(data[1], 10)) * 1000;
  8016. WatchController.vpos(vpos);
  8017. }
  8018. });
  8019. $description.find('.watch').unbind('click');
  8020. $description = null;
  8021. }
  8022.  
  8023. function create$videoDescription(html) {
  8024. var linkmatch = /<a.*?<\/a>/, links = [], n;
  8025. html = html.split('<br />').join(' <br /> ');
  8026. while ((n = linkmatch.exec(html)) !== null) {
  8027. links.push(n);
  8028. html = html.replace(n, ' <!----> ');
  8029. }
  8030.  
  8031. // (htttp://example.com) -> ( htttp://example.com ) にして、 閉じカッコがリンクされるのを抑止
  8032. html = html.replace(/\((https?:\/\/[\x21-\x3b\x3d-\x7e]+)\)/gi, '( $1 )');
  8033. html = html.replace(/(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi, '<a href="$1" target="_blank" class="otherSite">$1</a>');
  8034. for (var i = 0, len = links.length; i < len; i++) {
  8035. html = html.replace(' <!----> ', links[i]);
  8036. }
  8037. html = html.split(' <br /> ').join('<br />');
  8038. var $description = $('<p class="videoDescription description">' + html + '</p>');
  8039.  
  8040. bindDescriptionEvents($description);
  8041. decorateVideoDescriptionLink($description);
  8042. return $description;
  8043. } //
  8044.  
  8045. function resetSideIchibaPanel($ichibaPanel, force) {
  8046.  
  8047. $ichibaPanel.scrollTop(0).find('*').unbind().empty();
  8048. var $inner = $('<div class="ichibaPanelInner" />');
  8049. var $header = $('<div class="ichibaPanelHeader"><p class="logo">ニコニコ市場出張所</p></div>');
  8050. $inner.append($header);
  8051.  
  8052.  
  8053. var items = [];
  8054. $('#ichibaMain').find('.ichiba_mainitem>div').each(function() {
  8055. var $item = $(this).clone().attr('id', null);
  8056. var $dl = $('<dl class="ichiba_mainitem" />').append($item);
  8057. $item.find('.thumbnail span').css({fontSize: ''});
  8058. // 誤クリックしやすいのでサムネはリンクを外す
  8059. $item.find('.thumbnail a img, .blomagaThumbnail, .blomagaText')
  8060. .parent().attr('href', null).attr('style', null).css({'text-decoration': 'none'});
  8061. $item.find('a').attr('onclick', null);
  8062. items.push($dl);
  8063. $inner.append($dl);
  8064. });
  8065. if (items.length > 0) {
  8066. for (var i = items.length -1; i >= 0; i--) {
  8067. $inner.find('#watch' + i + '_mq').attr('id', null).addClass('ichibaMarquee');
  8068. }
  8069. }
  8070. $inner.find('.nicoru').remove();
  8071.  
  8072. var $footer = $('<div class="ichibaPanelFooter"></div>');
  8073.  
  8074. var $addIchiba = $('<button class="addIchiba">商品を貼る</button>');
  8075. $addIchiba.click(function() {
  8076. AnchorHoverPopup.hidePopup();
  8077. ichibaSearch();
  8078. });
  8079. $footer.append($addIchiba);
  8080.  
  8081. var $reloadIchiba = $('<button class="reloadIchiba">リロード</button>');
  8082. $reloadIchiba.click(function() {
  8083. resetSideIchibaPanel($ichibaPanel, true);
  8084. $ichibaPanel = null;
  8085. });
  8086. $footer.append($reloadIchiba);
  8087.  
  8088. $inner.append($footer);
  8089. $inner.hide();
  8090. $ichibaPanel.append($inner);
  8091. $inner.fadeIn();
  8092. $inner = $header = $footer = $addIchiba = $reloadIchiba = null;
  8093. } //
  8094.  
  8095. function initHidariue() {
  8096. // 再生終了時に勝手にメニューが開閉するのを止める
  8097. window.WatchApp.ns.init.PlayerInitializer.videoendViewController.videoHeaderViewController = {openVideoMenu: function(){}, closeVideoMenu: function() {}};
  8098. var hidariue = null;
  8099. var resetHidariue = function() {
  8100. // var dt = new Date();
  8101. // if (dt.getMonth() < 1 && dt.getDate() <=1) {
  8102. // $('#videoMenuTopList').append('<li style="font-size:50%"> \ │ /<br>  /‾\   /‾‾‾‾‾‾‾‾‾<br>─( ゜ ∀ ゜ )< しんねんしんねん!<br>  \_/   \_________<br> / │ \</li>');
  8103. // }
  8104. if (!conf.hidariue) { return; }
  8105. if (!hidariue) {
  8106. $('#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>');
  8107. hidariue = $('#hidariue')[0];
  8108. }
  8109. hidariue.src = 'http://res.nimg.jp/img/base/head/icon/nico/' +
  8110. (1000 + Math.floor(Math.random() * 1000)).toString().substr(1) + '.gif';
  8111. };
  8112. EventDispatcher.addEventListener('onVideoInitialized', resetHidariue);
  8113. } //
  8114.  
  8115.  
  8116. var VideoExplorerToggleMenu = function(title, titleLink) {
  8117. this.initializeBaseDom(title, titleLink);
  8118. };
  8119. WatchApp.mixin(VideoExplorerToggleMenu.prototype, {
  8120. initializeBaseDom: function(title, titleLink) {
  8121.  
  8122. this._$toggle = $('<li style="display:none;" class="toggleVideoExplorerMenu watchItLaterMenu"></li>');
  8123. this._$menu = $('<li class="slideMenu"><ul></ul></li>');
  8124. this._$list = this._$menu.find('ul');
  8125.  
  8126. var $a = $('<a/>').text(title).attr('href', titleLink);
  8127. this._$toggle.append($a);
  8128.  
  8129. this._initializeToggleEvent();
  8130. this._initializeItemEvent();
  8131. },
  8132. _initializeToggleEvent: function() {
  8133. this._$toggle.on('click', $.proxy(function(e) {
  8134. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8135. e.preventDefault(); e.stopPropagation();
  8136.  
  8137. var isVisible = this._$menu.hasClass('open');
  8138. this._$toggle.addClass('opening');
  8139. this._$menu.toggleClass('open', !isVisible);
  8140.  
  8141. window.setTimeout($.proxy(function() {
  8142. this._$toggle.toggleClass('open', !isVisible);
  8143. this._$toggle.removeClass('opening');
  8144. }, this), 500);
  8145. }, this));
  8146. },
  8147. _initializeItemEvent: function() {
  8148. this._$menu.on('click', function(e) {
  8149. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8150.  
  8151. var elm = e.target;
  8152. if (elm.tagName !== 'A') { return; }
  8153. var $elm = $(elm);
  8154. var type = $elm.attr('data-menu-type');
  8155.  
  8156. if (type === 'mylist') {
  8157. e.preventDefault(); e.stopPropagation();
  8158. var mylistId = $elm.attr('data-mylist-id');
  8159. WatchController.showMylist(mylistId);
  8160. } else
  8161. if (type === 'deflist') {
  8162. e.preventDefault(); e.stopPropagation();
  8163. WatchController.showDeflist();
  8164. } else
  8165. if (type === 'tag') {
  8166. e.preventDefault(); e.stopPropagation();
  8167. var tag = $elm.attr('data-search-tag');
  8168. WatchController.nicoSearch(tag, 'tag');
  8169. }
  8170. });
  8171. },
  8172. attach: function() {
  8173. $('.videoExplorerMenu').find('ul:first li:first').after(this._$menu).after(this._$toggle);
  8174. },
  8175. detach: function() {
  8176. this._$toggle.detach();
  8177. this._$menu.detach();
  8178. },
  8179. add$listItem: function($item) {
  8180. this._$list.append($item);
  8181. },
  8182. addItem: function(name, title, attr) {
  8183. var
  8184. $a = $('<a/>')
  8185. .attr(attr)
  8186. .text(title),
  8187. $li = $('<li/>').addClass(iconType);
  8188. $li.append($a);
  8189. this._$list.append($li);
  8190. return $a;
  8191. },
  8192. addMylistItem: function(name, mylistId, title, iconType) {
  8193. var
  8194. $a = $('<a/>')
  8195. .text(name)
  8196. .attr({
  8197. href: '/mylist/' + mylistId,
  8198. title: title,
  8199. 'data-menu-type': 'mylist',
  8200. 'data-mylist-id': mylistId,
  8201. }),
  8202. $li = $('<li/>').addClass(iconType || '');
  8203. $li.append($a);
  8204. this._$list.append($li);
  8205. return $a;
  8206. },
  8207. show: function() {
  8208. this._$toggle.fadeIn(500);
  8209. }
  8210. });
  8211.  
  8212. WatchItLater.videoExplorerMenu = {};
  8213. WatchItLater.videoExplorerMenu.favMylists = (function() {
  8214. var toggleMenu;
  8215.  
  8216. var initialize = function() {
  8217. initialize = window._.noop;
  8218. console.log('%cinitialize WatchItLater.videoExplorerMenu.favMylists', 'background: lightgreen;');
  8219.  
  8220. toggleMenu = new VideoExplorerToggleMenu('お気に入りマイリスト', '/my/fav/mylist');
  8221.  
  8222. window.WatchItLater.loader.favMylists.load(function(mylists) {
  8223. if (mylists.length < 1) {
  8224. return;
  8225. }
  8226. for (var i = 0, len = mylists.length; i < len; i++) {
  8227. var mylist = mylists[i], lastVideo = mylist.lastVideo, $li = $('<li/>'),
  8228. title = [
  8229. '/mylist/', mylist.id, '\n',
  8230. mylist.description, '\n',
  8231. '最新動画: ', lastVideo.title, '\n',
  8232. '投稿日時: ', lastVideo.postedAt, '\n',
  8233. ''].join('');
  8234. toggleMenu.addMylistItem(
  8235. mylist.name,
  8236. mylist.id,
  8237. title,
  8238. mylist.iconType
  8239. );
  8240. }
  8241. toggleMenu.show();
  8242. });
  8243. };
  8244.  
  8245. return {
  8246. attach: function() {
  8247. initialize();
  8248. toggleMenu.attach();
  8249. },
  8250. detach: function() {
  8251. if (!toggleMenu) {
  8252. return;
  8253. }
  8254. console.log(toggleMenu);
  8255. toggleMenu.detach();
  8256. }
  8257. };
  8258. })();
  8259.  
  8260. WatchItLater.videoExplorerMenu.favTags = (function() {
  8261. var toggleMenu;
  8262.  
  8263. var initialize = function() {
  8264. initialize = window._.noop;
  8265. console.log('%cinitialize WatchItLater.videoExplorerMenu.favTags', 'background: lightgreen;');
  8266.  
  8267. toggleMenu = new VideoExplorerToggleMenu('お気に入りタグ', '/my/fav/tag');
  8268.  
  8269. window.WatchItLater.loader.favTags.load(function(tags) {
  8270. if (tags.length < 1) {
  8271. $toggle.remove();
  8272. return;
  8273. }
  8274. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  8275. for (var i = 0, len = tags.length; i < len; i++) {
  8276. var tag = tags[i], $li = $('<li/>'),
  8277. $a = $('<a/>')
  8278. .attr({
  8279. href: '/tag/' + encodeURIComponent(tag.name + ' ' + conf.defaultSearchOption) + sortOrder,
  8280. 'data-menu-type': 'tag',
  8281. 'data-search-tag': tag.name
  8282. })
  8283. .text(tag.name);
  8284. toggleMenu.add$listItem($li.append($a));
  8285. }
  8286. toggleMenu.show();
  8287. });
  8288. };
  8289.  
  8290. return {
  8291. attach: function() {
  8292. initialize();
  8293. toggleMenu.attach();
  8294. },
  8295. detach: function() {
  8296. if (!toggleMenu) {
  8297. return;
  8298. }
  8299. toggleMenu.detach();
  8300. }
  8301. };
  8302. })();
  8303.  
  8304.  
  8305. WatchItLater.videoExplorerMenu.myShortcuts = (function() {
  8306. var toggleMenu;
  8307.  
  8308. var initialize = function() {
  8309. initialize = window._.noop;
  8310. console.log('%cinitialize WatchItLater.videoExplorerMenu.myShortcuts', 'background: lightgreen;');
  8311.  
  8312. toggleMenu = new VideoExplorerToggleMenu('マイショートカット', '/my/mylist');
  8313. toggleMenu.attach();
  8314.  
  8315. window.WatchItLater.mylist.loadMylistList(function(mylistList) {
  8316. toggleMenu.add$listItem(
  8317. $('<li/>').append(
  8318. $('<a/>')
  8319. .addClass('defMylist')
  8320. .attr({href: '/my/mylist', 'data-menu-type': 'deflist'})
  8321. .text('とりあえずマイリスト')
  8322. ));
  8323. var items = [
  8324. {id: -1, href: '/my/history', name: '視聴履歴'},
  8325. {id: -2, href: '/recommendations', name: 'あなたにオススメの動画'},
  8326. {id: NicorepoVideo.REPO_ALL, href: '/my/top/all', name: '【ニコレポ】すべての動画'},
  8327. {id: NicorepoVideo.REPO_USER, href: '/my/top/user', name: '【ニコレポ】お気に入りユーザー'},
  8328. {id: NicorepoVideo.REPO_CHCOM, href: '/my/top/chcom', name: '【ニコレポ】チャンネル&コミュニティ'},
  8329. {id: NicorepoVideo.REPO_MYLIST, href: '/my/top/mylist', name: '【ニコレポ】お気に入りマイリスト'}
  8330. ];
  8331. for (var v in items) {
  8332. var item = items[v];
  8333. toggleMenu
  8334. .addMylistItem(item.name, item.id)
  8335. .addClass('defMylist')
  8336. .attr({
  8337. href: item.href
  8338. });
  8339. }
  8340. for (var i = 0, len = mylistList.length; i < len; i++) {
  8341. var mylist = mylistList[i];
  8342. toggleMenu.addMylistItem(mylist.name, mylist.id, '', 'folder' + mylist.icon_id);
  8343. }
  8344.  
  8345. toggleMenu.show();
  8346. });
  8347. };
  8348.  
  8349. return {
  8350. attach: function() {
  8351. initialize();
  8352. toggleMenu.attach();
  8353. },
  8354. detach: function() {
  8355. if (!toggleMenu) {
  8356. return;
  8357. }
  8358. toggleMenu.detach();
  8359. }
  8360. };
  8361. })();
  8362.  
  8363. WatchItLater.videoExplorerMenu.videoRanking = (function() {
  8364. var toggleMenu;
  8365.  
  8366. var VideoRankingToggleMenu = function(title, titleLink) {
  8367. WatchApp.extend(this, VideoRankingToggleMenu, VideoExplorerToggleMenu, [title, titleLink]);
  8368.  
  8369. this._$menu.addClass('videoRankingList');
  8370. this.initializeCategoryToggleEvents();
  8371. };
  8372. WatchApp.mixin(VideoRankingToggleMenu.prototype, {
  8373. initializeCategoryToggleEvents: function() {
  8374. this._$menu.on('click', '.rankingCategoryToggle', function(e) {
  8375. e.preventDefault(); e.stopPropagation();
  8376.  
  8377. var $target = $(e.currentTarget), category = $target.attr('data-category');
  8378. var $popup = $target.closest('.slideMenu');
  8379. var isClose = $popup.find('li.' + category).toggleClass('categoryClose').hasClass('categoryClose');
  8380.  
  8381. conf.setValue('rankingCategory_' + category + '_Close', isClose);
  8382. });
  8383. },
  8384. addRankingItem: function($, genre, id, name, category, term) {
  8385. var $a =
  8386. $('<a/>')
  8387. .attr({
  8388. href: '/ranking/fav/' + term + '/' + genre,
  8389. 'data-menu-type': 'mylist',
  8390. 'data-mylist-id': id
  8391. })
  8392. .text(name)
  8393. .addClass(genre);
  8394. var $li = $('<li/>');
  8395.  
  8396. if (genre === category) {
  8397. $li.addClass('isCategory'); // nameと同じならカテゴリランキング、違うならジャンルランキング
  8398. if (genre !== 'all' && genre !== 'g_politics' && genre !== 'r18') {
  8399. var $button = $([
  8400. '<button class="rankingCategoryToggle">',
  8401. '<span class="open" title="サブカテゴリを開く">▼</span>',
  8402. '<span class="close" title="サブカテゴリを閉じる">▲</span>',
  8403. '</button>'
  8404. ].join(''));
  8405. $button.attr('data-category', category);
  8406. $li.append($button);
  8407. }
  8408. }
  8409. var isClose = conf.getValue('rankingCategory_' + category + '_Close');
  8410. $li
  8411. .toggleClass('categoryClose', isClose)
  8412. .attr({'data-genre': genre, 'data-category': category})
  8413. .addClass(category).addClass(genre)
  8414. .append($a);
  8415.  
  8416. this._$list.append($li);
  8417. return $a;
  8418. }
  8419. });
  8420.  
  8421.  
  8422. var initialize = function() {
  8423. initialize = window._.noop;
  8424. console.log('%cinitialize WatchItLater.videoExplorerMenu.videoRanking', 'background: lightgreen;');
  8425.  
  8426. toggleMenu = new VideoRankingToggleMenu('動画ランキング', '/ranking');
  8427. toggleMenu.attach();
  8428.  
  8429. // TODO: マジックナンバーを
  8430. toggleMenu.addRankingItem($, 'all', -100, 'カテゴリ合算(毎時)', 'all', 'hourly');
  8431. toggleMenu.addRankingItem($, 'all', -1100, 'カテゴリ合算(24時間)', 'all', 'daily');
  8432. // toggleMenu.addRankingItem($, 'all', -4100, 'カテゴリ合算(合計)', 'all', 'total');
  8433.  
  8434. var genreId = VideoRanking.getGenreId();
  8435. for (var genre in genreId) {
  8436. if (genre === 'all') { continue; }
  8437. var id = genreId[genre], name = VideoRanking.getGenreName(genre), category = VideoRanking.getCategory(id);
  8438. toggleMenu.addRankingItem($, genre, id, name, category, 'hourly');
  8439. }
  8440.  
  8441. window.setTimeout(function() { toggleMenu.show(); }, 100);
  8442. };
  8443.  
  8444. return {
  8445. attach: function() {
  8446. initialize();
  8447. toggleMenu.attach();
  8448. },
  8449. detach: function() {
  8450. if (!toggleMenu) {
  8451. return;
  8452. }
  8453. toggleMenu.detach();
  8454. }
  8455. };
  8456. })();
  8457.  
  8458.  
  8459. var WatchingVideoView = function() { this.initialize.apply(this, arguments); };
  8460. WatchingVideoView.prototype = {
  8461. _params: null,
  8462. _$view: null,
  8463. _watchInfoModel: null,
  8464. _type: null,
  8465. initialize: function(params) {
  8466. this._content = params.content;
  8467. this._watchInfoModel = params.watchInfoModel;
  8468. this._$view = params.$view;
  8469. this._mylistController = params.mylistController;
  8470. this._type = params.type;
  8471.  
  8472. this._$title = this._$view.find('.title');
  8473. this._$thumb = this._$view.find('.thumbnail');
  8474. this._$add = this._$view.find('.add');
  8475. this._$remove = this._$view.find('.remove');
  8476.  
  8477. this._$add .on('click', $.proxy(this._onAddClick, this));
  8478. this._$remove.on('click', $.proxy(this._onRemoveClick, this));
  8479.  
  8480. EventDispatcher.addEventListener('onWatchInfoReset', $.proxy(this.onVideoChange, this));
  8481. },
  8482. getView: function() {
  8483. return this._$view;
  8484. },
  8485. detach: function() {
  8486. this._$view.detach();
  8487. },
  8488. update: function() {
  8489. $('.videoExplorerBody').toggleClass('containsWatchingVideo', this._content.containsWatchId());
  8490. },
  8491. onVideoChange: function() {
  8492. this._$title.html(this._watchInfoModel.title);
  8493. this._$thumb
  8494. .attr('src', this._watchInfoModel.thumbnail)
  8495. .off('click').on('click', Util.Closure.showLargeThumbnail(this._watchInfoModel.thumbnail));
  8496. if (this._content.isActive()) {
  8497. this.update();
  8498. }
  8499. },
  8500. _setIsUpdating: function() {
  8501. this._$view.addClass('updating');
  8502. setTimeout($.proxy(this._clearIsUpdating, this), 3000);
  8503. },
  8504. _clearIsUpdating: function() {
  8505. this._$view.removeClass('updating');
  8506. },
  8507. _getIsUpdating: function() {
  8508. return this._$view.hasClass('updating');
  8509. },
  8510. _onAddClick: function() {
  8511. var watchId = WatchController.getWatchId();
  8512. this._setIsUpdating();
  8513. if (this._type === 'deflist') {
  8514. this._mylistController.addDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8515. } else {
  8516. var mylistId = this._content.getMylistId();
  8517. this._mylistController.addMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8518. }
  8519. },
  8520. _onRemoveClick: function() {
  8521. var watchId = WatchController.getWatchId();
  8522. this._setIsUpdating();
  8523. if (this._type === 'deflist') {
  8524. this._mylistController.deleteDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8525. } else {
  8526. var mylistId = this._content.getMylistId();
  8527. this._mylistController.deleteMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8528. }
  8529. },
  8530. _onMylistUpdate: function(status, result) {
  8531. if (status === 'ok') {
  8532. if (this._type === 'deflist') {
  8533. WatchController.clearDeflistCache();
  8534. }
  8535. } else {
  8536. Popup.alert('更新に失敗: ' + result.error.description);
  8537. }
  8538. this._content.setFilter(null);
  8539. setTimeout(
  8540. $.proxy(function() {
  8541. //this._content.changeState({page: 1});
  8542. this.contentRefresh();
  8543. this._clearIsUpdating();
  8544. }, this), 500);
  8545. },
  8546. contentRefresh: function() {
  8547. var params = this._content.getParams();
  8548. params.page = 1;
  8549. this._content.changeState(params);
  8550. this._content.refresh({page: 1});
  8551. }
  8552. }; // end WatchingVideoView.prototype
  8553.  
  8554.  
  8555. var GrepOptionView = function() { this.initialize.apply(this, arguments); };
  8556. GrepOptionView.prototype = {
  8557. _params: null,
  8558. _$view: null,
  8559. initialize: function(params) {
  8560. this._content = params.content;
  8561. this._$view = params.$view;
  8562. this._$form = this._$view.find('form');
  8563. this._$input = this._$view.find('.grepInput').attr('list', params.listName);
  8564. this._$community = this._$view.find('.community');
  8565. this._$alive = this._$view.find('.alive');
  8566. this._$invert = this._$view.find('.invert');
  8567. this._$checkboxes = this._$view.find('input[type=checkbox]');
  8568.  
  8569. this._not = false;
  8570.  
  8571. this._$view.toggleClass('debug', !!conf.debugMode);
  8572.  
  8573. this._$list = $('<datalist />').attr('id', params.listName);
  8574. $('body').append(this._$list);
  8575.  
  8576. this._$form.on('submit', $.proxy(this._onFormSubmit, this));
  8577. this._$checkboxes.on('click', $.proxy(this._onCheckClick, this));
  8578.  
  8579. this._$input.on('click', $.proxy(function(e) {
  8580. e.stopPropagation();
  8581. }, this)) .on('focus', $.proxy(function(e) {
  8582. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  8583. }, this));
  8584.  
  8585. this._$view .on('click', $.proxy(function(e) {
  8586. this._$input.focus();
  8587. }, this));
  8588. },
  8589. getView: function() {
  8590. return this._$view;
  8591. },
  8592. detach: function() {
  8593. this._$view.detach();
  8594. },
  8595. clear: function() {
  8596. this._$input.val('');
  8597. this._$checkboxes.prop('checked', false);
  8598. this._$view.removeClass('active');
  8599. },
  8600. update: function() {
  8601. var list = this._content.getRawList();
  8602. var tmp = [];
  8603. for (var i = list.length -1; i >= 0; i--) {
  8604. tmp.push('<option>');
  8605. tmp.push(list[i].title); // 既にエスケープされてる
  8606. tmp.push('</option>');
  8607. }
  8608. this._$list.html(tmp.join(''));
  8609.  
  8610. if (this._getWord().length > 0) {
  8611. window.setTimeout($.proxy(function() { this._$input.focus(); }, this), 100);
  8612. }
  8613. },
  8614. _isActive: function() {
  8615. return (this._$input.val().length > 0 ||
  8616. !!this._$community.prop('checked') ||
  8617. !!this._$alive .prop('checked'));
  8618. },
  8619. _getWord: function() {
  8620. return $.trim(this._$input.val());
  8621. },
  8622. _onCheckClick: function(e) {
  8623. e.stopPropagation();
  8624. this._submit();
  8625. },
  8626. _onFormSubmit: function(e) {
  8627. e.preventDefault();
  8628. e.stopPropagation();
  8629. this._submit();
  8630. },
  8631. _submit: function() {
  8632. var isActive = this._isActive();
  8633. this._$view.toggleClass('active', isActive);
  8634.  
  8635. if (isActive) {
  8636. this._content.setFilter(this._getFilter());
  8637. } else {
  8638. this._content.setFilter(null);
  8639. }
  8640.  
  8641. this.contentRefresh();
  8642. },
  8643. contentRefresh: function() {
  8644. var params = this._content.getParams();
  8645. params.page = 1;
  8646. this._content.changeState(params);
  8647. this._content.refresh({page: 1});
  8648. },
  8649. _getFilter: function() {
  8650. var to_h = function(str) {
  8651. return str.replace(/[A-Za-z0-9]/g, function(s) {
  8652. return String.fromCharCode(s.charCodeAt(0) - 65248);
  8653. }).toLowerCase();
  8654. };
  8655. var word = to_h(this._getWord());
  8656. var communityReg = /^so|^\d+$/;
  8657. var wordFilter = word.length > 0;
  8658. var communityFilter = !!this._$community.prop('checked');
  8659. var aliveFilter = !!this._$alive.prop('checked');
  8660. var isInvert = !!this._$invert.prop('checked');
  8661.  
  8662.  
  8663. var isCommunity = function(item) {
  8664. return communityReg.test(item.id);
  8665. };
  8666. var isMatch = function(item) {
  8667. var title = item.title;
  8668. var desc = item.description_full || item.description_short || '';
  8669. var mc = item.mylist_comment || '';
  8670. var text = to_h([title, desc, mc].join('\n'));
  8671.  
  8672. return text.indexOf(word) >= 0;
  8673. };
  8674. var isAlive = function(item) {
  8675. var thumbnail = item.thumbnail_url || '';
  8676. if (thumbnail.indexOf('http://res.nimg.jp/img/common/video_deleted') < 0) {
  8677. return true;
  8678. }
  8679. return false;
  8680. };
  8681.  
  8682. var grepFilter = function(item) {
  8683. var result = true, i = func.length, f;
  8684. while (--i >= 0 && (result || isInvert)) {
  8685. f = func[i];
  8686. result &= f(item);
  8687. }
  8688. return isInvert ? !result : result;
  8689. };
  8690.  
  8691. var func = [], f;
  8692. if (wordFilter) { func.push(isMatch); }
  8693. if (communityFilter) { func.push(isCommunity); }
  8694. if (aliveFilter) { func.push(isAlive); }
  8695.  
  8696. if (func.length < 1) { return null; }
  8697.  
  8698. return grepFilter;
  8699. }
  8700. }; // end GrepOptionView.prototype
  8701.  
  8702. function initMylistContent($, conf, w) {
  8703. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  8704. var ContentView = WatchApp.ns.components.videoexplorer.view.content.MylistVideoContentView;
  8705. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  8706. var explorer = vec.getVideoExplorer();
  8707. var myUserId = WatchController.getMyUserId();
  8708. var content = explorer.getContentList().getContent(ContentType.MYLIST_VIDEO);
  8709. var loader = content._mylistVideoAPILoader;
  8710. var pager = content._pager;
  8711. var watchingVideoView = new WatchingVideoView({
  8712. content: content,
  8713. watchInfoModel: watchInfoModel,
  8714. mylistController: Mylist,
  8715. type: 'mylist',
  8716. $view: $([
  8717. '<div class="watchingVideo">',
  8718. '<img class="thumbnail">',
  8719. '<p class="title"></p>',
  8720. '<span class="contains" >この動画はリストに登録されています</span>',
  8721. '<span class="not_contains">この動画はリストにありません</span>',
  8722. '<span class="edit">',
  8723. '<button class="add" >登録</button>',
  8724. '<button class="remove">外す</button>',
  8725. '</span>',
  8726. '</div>',
  8727. ''].join(''))
  8728. });
  8729.  
  8730. var grepOptionView = new GrepOptionView({
  8731. content: content,
  8732. listName: 'suggestMylistTitle',
  8733. $view: $([
  8734. '<div class="grepOption">',
  8735. '<form>',
  8736. '<input type="search" class="grepInput" autocomplete="on" placeholder="タイトル・説明文で絞り込む(G)" accesskey="g">',
  8737. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  8738. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  8739. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  8740. '</form>',
  8741. '</div>',
  8742. ].join(''))
  8743. });
  8744.  
  8745.  
  8746. pager._pageItemCount = conf.searchPageItemCount;
  8747. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  8748. pager._pageItemCount = v;
  8749. });
  8750.  
  8751. content._isOwnerNicorepo = false;
  8752. content._isRanking = false;
  8753. content.getIsMine = $.proxy(function() {
  8754. return parseInt(this.getUserId(), 10) === parseInt(myUserId, 10) && parseInt(this.getMylistId(), 10) > 0;
  8755. }, content);
  8756. content.getIsDummy = $.proxy(function() {
  8757. var id = this.getMylistId();
  8758. return parseInt(id, 10) <= 0 || id.toString().indexOf('repo') === 0;
  8759. }, content);
  8760. content.getIsOwnerNicorepo = $.proxy(function() { return this._isOwnerNicorepo; }, content);
  8761. content.getIsRanking = $.proxy(function() { return this._isRanking; }, content);
  8762.  
  8763. // grep対応のための拡張
  8764. content._rawList = [];
  8765. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  8766. content._filter = null;
  8767. content.setFilter = $.proxy(function(filter) {
  8768. this._filter = filter;
  8769. }, content);
  8770. content.getFilter = $.proxy(function() { return this._filter; }, content);
  8771.  
  8772. content.clear_org = content.clear;
  8773. content.clear = $.proxy(function() {
  8774. this.setFilter(null);
  8775. this.clear_org();
  8776. grepOptionView.clear();
  8777. }, content);
  8778.  
  8779. content.getNickname = $.proxy(function() {
  8780. if (this._nickname && this._nickname.length > 0) {
  8781. return this._nickname;
  8782. }
  8783. return 'no-name';
  8784. }, content);
  8785.  
  8786. content.onLoad_org = content.onLoad;
  8787. content.onLoad = $.proxy(function(err, result) {
  8788. this._isOwnerNicorepo = result.isOwnerNicorepo;
  8789. this._isRanking = result.isRanking;
  8790.  
  8791. var filter = this.getFilter();
  8792. if (err === null && result.list && result.list.length) {
  8793. if (!result.rawList) result.rawList = result.list.concat();
  8794. if (filter) {
  8795. var list = [];
  8796.  
  8797. for (var i = result.rawList.length - 1; i >= 0; i--) {
  8798. var item = result.rawList[i];
  8799. if (item.title && filter(item)) {
  8800. list.unshift(item);
  8801. }
  8802. }
  8803. result.list = list;
  8804. } else {
  8805. result.list = result.rawList.concat();
  8806. }
  8807. } else
  8808. if (result.rawList) {
  8809. result.list = result.rawList.concat();
  8810. }
  8811.  
  8812. this._rawList = result.rawList || [];
  8813. this.onLoad_org(err, result);
  8814. if (this.getIsMine()) {
  8815. EventDispatcher.dispatch('onMyMylistLoad', this.getMylistId(), {
  8816. name: this.getName(),
  8817. items: result.rawList
  8818. });
  8819. }
  8820. }, content);
  8821.  
  8822. content.containsWatchId = $.proxy(function(watchId) {
  8823. var list = this.getRawList();
  8824. if (!watchId) { watchId = WatchController.getWatchId(); }
  8825.  
  8826. for (var i = list.length - 1; i >= 0; i--) {
  8827. if (list[i].id === watchId) { return true; }
  8828. }
  8829. return false;
  8830. }, content);
  8831.  
  8832. loader.load_org = loader.load;
  8833. loader.load = $.proxy(function(params, callback) {
  8834. var isOwnerNicorepo = false, isRanking = false;
  8835. var id = params.id;
  8836. if (typeof id === 'string' && id.indexOf('repo-owner-') === 0) {
  8837. id = NicorepoVideo.REPO_OWNER;
  8838. }
  8839.  
  8840. var applyFilter = function(err, result) {
  8841. result.isOwnerNicorepo = isOwnerNicorepo;
  8842. result.isRanking = isRanking;
  8843. callback(err, result);
  8844. };
  8845. if (id < 0) {
  8846. var timeoutTimer = null;
  8847. var onload = function(result) {
  8848. window.clearTimeout(timeoutTimer);
  8849. // 投稿者ニコレポが0件で、投稿動画一覧を公開していたらそっちを開くタイマーをセット
  8850. if (result.list.length < 1 &&
  8851. parseInt(id, 10) === NicorepoVideo.REPO_OWNER &&
  8852. WatchController.isVideoPublic()) {
  8853. window.setTimeout(function() {
  8854. WatchController.openVideoOwnersVideo();
  8855. }, 500);
  8856. }
  8857. applyFilter(null, result);
  8858. };
  8859. var onerror = function(result) {
  8860. window.clearTimeout(timeoutTimer);
  8861. callback('error', result);
  8862. };
  8863. timeoutTimer = window.setTimeout(function() {
  8864. onload = onerror = window._.noop;
  8865. onerror({message: '通信がタイムアウトしました:' + id, status: 'fail'});
  8866. }, 30 * 1000);
  8867.  
  8868. // マイリストIDに負の数字(通常ないはず)が来たら乗っ取るサイン
  8869. // そもそもマイリストIDはstringのようなので数字にこだわる必要なかったかも
  8870. //
  8871. try {
  8872. if (typeof VideoRanking.getGenreName(id) === 'string') {
  8873. isRanking = true;
  8874. VideoRanking.load(null, {id: id}).then(onload, onerror);
  8875. return;
  8876. }
  8877. // TODO: マジックナンバーを
  8878. switch (parseInt(id, 10)) {
  8879. case -1:
  8880. VideoWatchHistory.load(onload);
  8881. break;
  8882. case -2:
  8883. VideoRecommendations.load(onload);
  8884. break;
  8885. case -3:
  8886. ChannelVideoList.loadOwnerVideo(null).then(onload, onerror);
  8887. break;
  8888. case NicorepoVideo.REPO_ALL:
  8889. NicorepoVideo.loadAll() .then(onload, onerror);
  8890. break;
  8891. case NicorepoVideo.REPO_USER:
  8892. NicorepoVideo.loadUser() .then(onload, onerror);
  8893. break;
  8894. case NicorepoVideo.REPO_CHCOM:
  8895. NicorepoVideo.loadChCom() .then(onload, onerror);
  8896. break;
  8897. case NicorepoVideo.REPO_MYLIST:
  8898. NicorepoVideo.loadMylist().then(onload, onerror);
  8899. break;
  8900. case NicorepoVideo.REPO_OWNER:
  8901. isOwnerNicorepo = true;
  8902. NicorepoVideo.loadOwner() .then(onload, onerror);
  8903. break;
  8904. default:
  8905. throw {message: '未定義のIDです:' + id, status: 'fail'};
  8906. }
  8907. } catch(e) {
  8908. // TODO: ここのエラーをちゃんと投げる
  8909. if (e.message && e.status) {
  8910. onerror({
  8911. status: e.status,
  8912. message: e.message
  8913. });
  8914. } else {
  8915. console.log(e); console.trace();
  8916. onerror({message: 'エラーが発生しました:' + id, status: 'fail'});
  8917. }
  8918. }
  8919. } else {
  8920. this.load_org(params, applyFilter);
  8921. }
  8922. }, loader);
  8923.  
  8924.  
  8925. var __css__ = Util.here(function() {/*
  8926. #videoExplorer .watchingVideo { display: none; }
  8927. #videoExplorer .watchingVideo .title { display: none; }
  8928. #videoExplorer .watchingVideo.updating * {
  8929. cursor: wait; opacity: 0.5;
  8930. }
  8931. #videoExplorer .watchingVideo button {
  8932. padding: 2px 12px; margin: 12px 24px;
  8933. }
  8934. #videoExplorer .isMine .watchingVideo {
  8935. display: block; background: #f4f4f4; border: 1px solid #ccc;
  8936. margin: auto; width: 500px; min-height: 48px; padding: 16px;
  8937. }
  8938.  
  8939. #videoExplorer .watchingVideo .thumbnail {
  8940. float: left; width: 72px; margin-right: 24px; cursor: pointer;
  8941. }
  8942.  
  8943.  
  8944. #videoExplorer .watchingVideo .title {
  8945. font-weight: bolder;
  8946. }
  8947. #videoExplorer .watchingVideo .title:before { content: ''; }
  8948. #videoExplorer .watchingVideo .title:after { content: ' '; }
  8949.  
  8950. #videoExplorer .watchingVideo .contains { display: none; }
  8951. #videoExplorer .containsWatchingVideo .watchingVideo .contains { display: inline; }
  8952. #videoExplorer .watchingVideo .not_contains { display: inline; }
  8953. #videoExplorer .containsWatchingVideo .watchingVideo .not_contains { display: none; }
  8954.  
  8955. #videoExplorer .watchingVideo .edit { display: none; }
  8956. #videoExplorer .isMine .watchingVideo .edit { display: inline-block; }
  8957.  
  8958. #videoExplorer .watchingVideo .add { display: inline-block; }
  8959. #videoExplorer .containsWatchingVideo .watchingVideo .add { display: none; }
  8960.  
  8961. #videoExplorer .watchingVideo .remove{ display: none; }
  8962. #videoExplorer .containsWatchingVideo .watchingVideo .remove{ display: inline-block; }
  8963.  
  8964. .isMine .editFavorite {
  8965. display: none; {* 自分のマイリストにはお気に入り登録ボタンを出さない *}
  8966. }
  8967. .watchingVideo button {
  8968. cursor: pointer;
  8969. }
  8970.  
  8971. .grepOption {
  8972. padding: 16px;
  8973. width: 500px;
  8974. margin: 16px auto;
  8975. background: #f4f4f4; border: 1px solid #ccc;
  8976. }
  8977. .grepOption .grepInput {
  8978. font-size: 120%;
  8979. width: 100%;
  8980. }
  8981.  
  8982. .grepOption .filter {
  8983. display: block; margin: 8px;
  8984. }
  8985. .grepOption .filter:hover {
  8986. background: #ccc;
  8987. }
  8988. .grepOption .filter.invertFilter {
  8989. display: none;
  8990. }
  8991. .grepOption.active .filter.invertFilter {
  8992. display: block; text-align: right;
  8993. }
  8994.  
  8995.  
  8996. */});
  8997. addStyle(__css__, 'mylistContentCss');
  8998.  
  8999. var MylistDetailView = WatchApp.ns.components.videoexplorer.view.content.parts.MylistDetailView;
  9000. MylistDetailView.prototype.update_org = MylistDetailView.prototype.update;
  9001. MylistDetailView.prototype.update = function(id, name, description, count) {
  9002. this.update_org(id, name, description, count);
  9003. if (id.toString().match(/repo-owner-(\d+)/)) {
  9004. this._$name.attr('href', '/user/' + RegExp.$1);
  9005. } else
  9006. if (parseInt(id, 10) <= 0) {
  9007. this._$name.attr('href', '');
  9008. }
  9009. };
  9010.  
  9011. var
  9012. overrideContentView = function(proto, watchingVideoView, grepOptionView) {
  9013. var updateCssClass = function(content) {
  9014. $('.videoExplorerBody')
  9015. .toggleClass('dummyMylist', content.getIsDummy())
  9016. .toggleClass('isMine', content.getIsMine())
  9017. .toggleClass('ownerNicorepo', content.getIsOwnerNicorepo())
  9018. .toggleClass('ranking', content.getIsRanking())
  9019. ;
  9020. };
  9021.  
  9022. proto.detach_org = proto.detach;
  9023. proto.detach = function() {
  9024. this.detach_org();
  9025. watchingVideoView.detach();
  9026. grepOptionView.detach();
  9027. };
  9028.  
  9029. proto.onUpdate_org = proto.onUpdate;
  9030. proto.onUpdate = function() {
  9031. this.onUpdate_org();
  9032. updateCssClass(this._content);
  9033. watchingVideoView.update();
  9034. grepOptionView.update();
  9035. this._$content.find('.mylistSortOrder').before(watchingVideoView.getView());
  9036. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9037. };
  9038.  
  9039. proto.onError_org = proto.onError;
  9040. proto.onError = function() {
  9041. this.onError_org();
  9042. updateCssClass(this._content);
  9043. watchingVideoView.update();
  9044. grepOptionView.update();
  9045. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9046. };
  9047.  
  9048. };
  9049.  
  9050. overrideContentView(ContentView.prototype, watchingVideoView, grepOptionView);
  9051. } // end initMylistContent
  9052.  
  9053. function initDeflistContent($, conf, w) {
  9054. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  9055. var ContentView = WatchApp.ns.components.videoexplorer.view.content.DeflistVideoContentView;
  9056. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  9057. var explorer = vec.getVideoExplorer();
  9058. var content = explorer.getContentList().getContent(ContentType.DEFLIST_VIDEO);
  9059. var loader = content._deflistVideoAPILoader;
  9060. var pager = content._pager;
  9061. var watchingVideoView = new WatchingVideoView({
  9062. content: content,
  9063. watchInfoModel: watchInfoModel,
  9064. mylistController: Mylist,
  9065. type: 'deflist',
  9066. $view: $([
  9067. '<div class="watchingVideo">',
  9068. '<img class="thumbnail">',
  9069. '<p class="title"></p>',
  9070. '<span class="contains" >この動画はリストに登録されています</span>',
  9071. '<span class="not_contains">この動画はリストにありません</span>',
  9072. '<span class="edit">',
  9073. '<button class="add" >登録</button>',
  9074. '<button class="remove">外す</button>',
  9075. '</span>',
  9076. '</div>',
  9077. ''].join(''))
  9078. });
  9079. var grepOptionView = new GrepOptionView({
  9080. content: content,
  9081. listName: 'suggestDeflistTitle',
  9082. $view: $([
  9083. '<div class="grepOption">',
  9084. '<form>',
  9085. '<input type="search" class="grepInput" autocomplete="on" placeholder="とりマイをタイトル・説明文で絞り込む(G)" accesskey="g">',
  9086. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  9087. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  9088. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  9089. '</form>',
  9090. '</div>',
  9091. ].join(''))
  9092. });
  9093.  
  9094.  
  9095.  
  9096. pager._pageItemCount = conf.searchPageItemCount;
  9097. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  9098. pager._pageItemCount = v;
  9099. });
  9100.  
  9101.  
  9102. content.changeState_org = content.changeState;
  9103. content.changeState = $.proxy(function(params, callback) {
  9104. console.log('deflist refresh! ', params, callback);
  9105. if (!this.isActive()) {
  9106. WatchController.clearDeflistCache();
  9107. }
  9108. this.changeState_org(params, callback);
  9109. }, content);
  9110.  
  9111. content.getIsMine = function() { return true; };
  9112.  
  9113. // grep対応のための拡張
  9114. content._rawList = [];
  9115. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  9116. content._filter = null;
  9117. content.setFilter = $.proxy(function(filter) {
  9118. this._filter = filter;
  9119. }, content);
  9120. content.getFilter = $.proxy(function() { return this._filter; }, content);
  9121.  
  9122. content.onLoad_org = content.onLoad;
  9123. content.onLoad = $.proxy(function(err, result) {
  9124. var filter = this.getFilter();
  9125. if (err === null && result.list && result.list.length) {
  9126. if (!result.rawList) result.rawList = result.list.concat();
  9127. if (filter) {
  9128. var list = [];
  9129.  
  9130. for (var i = result.rawList.length - 1; i >= 0; i--) {
  9131. var item = result.rawList[i];
  9132. if (item.title && filter(item)) {
  9133. list.unshift(item);
  9134. }
  9135. }
  9136. result.list = list;
  9137. } else {
  9138. result.list = result.rawList.concat();
  9139. }
  9140. } else
  9141. if (result.rawList) {
  9142. result.list = result.rawList.concat();
  9143. }
  9144. this._rawList = result.rawList || [];
  9145.  
  9146. this.onLoad_org(err, result);
  9147. }, content);
  9148.  
  9149. content.clear_org = content.clear;
  9150. content.clear = $.proxy(function() {
  9151. this.setFilter(null);
  9152. this.clear_org();
  9153. grepOptionView.clear();
  9154. }, content);
  9155.  
  9156. content.containsWatchId = $.proxy(function(watchId) {
  9157. var list = this.getRawList();
  9158. if (!watchId) { watchId = WatchController.getWatchId(); }
  9159.  
  9160. for (var i = list.length - 1; i >= 0; i--) {
  9161. if (list[i].id === watchId) { return true; }
  9162. }
  9163. return false;
  9164. }, content);
  9165.  
  9166. var
  9167. overrideContentView = function(proto, watchingVideoView) {
  9168. var updateCssClass = function(content) {
  9169. $('.videoExplorerBody').toggleClass('isMine', true);
  9170. };
  9171.  
  9172. proto.detach_org = proto.detach;
  9173. proto.detach = function() {
  9174. this.detach_org();
  9175. watchingVideoView.detach();
  9176. grepOptionView.detach();
  9177. };
  9178.  
  9179. proto.onUpdate_org = proto.onUpdate;
  9180. proto.onUpdate = function() {
  9181. this.onUpdate_org();
  9182. updateCssClass(this._content);
  9183. watchingVideoView.update();
  9184. grepOptionView.update();
  9185. this._$content.find('.deflistSortOrder').before(watchingVideoView.getView());
  9186. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9187. };
  9188.  
  9189. proto.onError_org = proto.onError;
  9190. proto.onError = function() {
  9191. this.onError_org();
  9192. updateCssClass(this._content);
  9193. watchingVideoView.update();
  9194. grepOptionView.update();
  9195. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9196. };
  9197.  
  9198. };
  9199.  
  9200. overrideContentView(ContentView.prototype, watchingVideoView);
  9201. } // end initDeflistContent
  9202.  
  9203.  
  9204.  
  9205.  
  9206. function showLargeThumbnail(baseUrl) {
  9207. var largeUrl = baseUrl, size;
  9208. if (baseUrl.indexOf('smilevideo.jp') >= 0) {
  9209. largeUrl = baseUrl.replace(/\.([LM])/, '') + '.L';
  9210. size = 'width: 360px; height: 270px; max-height: 500px;';
  9211. } else {
  9212. largeUrl = baseUrl.replace(/z$/, 'l');
  9213. size = 'width: 360px; max-height: 500px;';
  9214. }
  9215. var
  9216. html = [
  9217. '<div class="largeThumbnailPopup" onmousedown="if (event.button == 0) { $(\'#popupMarquee\').removeClass(\'show\'); event.preventDefault(); }" style="width: 360px; height: 270px; background-image: url(' , largeUrl, ')">',
  9218. '<img src="', largeUrl, '" style="display: none;" onload="$(\'#popupMarquee .largeThumbnailPopup *\').hide()">',
  9219. // '<img src="', baseUrl, '" style="', size, ' z-index: 2;">',
  9220. '<div style="', size, ' background-image: url(' + baseUrl + '); "></div>',
  9221. '</div>',
  9222. ''].join('');
  9223. Popup.show(html);
  9224. } //
  9225. WatchController.showLargeThumbnail = showLargeThumbnail;
  9226.  
  9227. function onVideoStopped() {
  9228. EventDispatcher.dispatch('onVideoStopped');
  9229. }
  9230.  
  9231. function onVideoEnded() {
  9232. EventDispatcher.dispatch('onVideoEnded');
  9233. }
  9234.  
  9235. var videoExplorerOpenCount = 0;
  9236. function onVideoExplorerOpened(params) {
  9237. window.console.timeEnd('onVideoExplorerOpen');
  9238. var target = params.target, contentList = params.contentList, content = params.content;
  9239. if (videoExplorerOpenCount++ === 0) {
  9240. EventDispatcher.dispatch('onFirstVideoExplorerOpened', content);
  9241. }
  9242. EventDispatcher.dispatch('onVideoExplorerOpened', content);
  9243.  
  9244. AnchorHoverPopup.hidePopup().updateNow();
  9245. }
  9246.  
  9247. function onVideoExplorerOpening(params) {
  9248. window.console.time('onVideoExplorerOpen');
  9249. var target = params.target, contentList = params.contentList, content = params.content;
  9250. if (videoExplorerOpenCount === 0) {
  9251. EventDispatcher.dispatch('onFirstVideoExplorerOpening', params);
  9252. }
  9253. EventDispatcher.dispatch('onVideoExplorerOpening', params);
  9254. }
  9255.  
  9256. function onVideoExplorerClosing(params) {
  9257. var target = params.target, contentList = params.contentList, content = params.content;
  9258. EventDispatcher.dispatch('onVideoExplorerClosing', content);
  9259. }
  9260.  
  9261. function onVideoExplorerClosed(params) {
  9262. var target = params.target, contentList = params.contentList, content = params.content;
  9263. AnchorHoverPopup.hidePopup().updateNow();
  9264. EventDispatcher.dispatch('onVideoExplorerClosed', content);
  9265. setTimeout(function() {
  9266. watch.PlaylistInitializer.playlistView.resetView();
  9267. }, 1000);
  9268. }
  9269.  
  9270. function onVideoExplorerRefreshStart(params) {
  9271. window.console.time('videoExplorerRefresh');
  9272. var target = params.target, contentList = params.contentList, content = params.content;
  9273. var
  9274. ContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  9275. type = content.getType(),
  9276. $ve = $('#videoExplorer')
  9277. .removeClass('w_user').removeClass('w_upload').removeClass('w_mylist')
  9278. .removeClass('w_deflist').removeClass('w_related').removeClass('w_search'),
  9279. $body = $ve.find('.videoExplorerBody')
  9280. .removeClass('isMine').removeClass('dummyMylist')
  9281. .removeClass('isRanking').removeClass('isOwnerNicorepo'),
  9282. className = 'w_user';
  9283. switch (type) {
  9284. case ContentType.USER_VIDEO:
  9285. className = 'w_user';
  9286. break;
  9287. case ContentType.UPLOADED_VIDEO:
  9288. className = 'w_uploaded';
  9289. break;
  9290. case ContentType.MYLIST_VIDEO:
  9291. className = 'w_mylist';
  9292. break;
  9293. case ContentType.DEFLIST_VIDEO:
  9294. className = 'w_deflist';
  9295. break;
  9296. case ContentType.RELATED_VIDEO:
  9297. className = 'w_related';
  9298. break;
  9299. case ContentType.SEARCH:
  9300. className = 'w_search';
  9301. break;
  9302. }
  9303. $ve.addClass(className);
  9304.  
  9305. EventDispatcher.dispatch('onVideoExplorerRefreshStart', content);
  9306. }
  9307. function onVideoExplorerRefreshEnd(params) {
  9308. window.console.timeEnd('videoExplorerRefresh');
  9309. var target = params.target, contentList = params.contentList, content = params.content;
  9310. EventDispatcher.dispatch('onVideoExplorerRefreshEnd', content);
  9311. }
  9312. function onVideoExplorerChangePage(params) {
  9313. var target = params.target, contentList = params.contentList, content = params.content;
  9314. EventDispatcher.dispatch('onVideoExplorerChangePage', content);
  9315. }
  9316.  
  9317. /**
  9318. * 検索中の動画サイズを無理矢理でっかくするよ。
  9319. */
  9320. var videoExplorerStyle = null, lastAvailableWidth = 0, lastBottomHeight = 0;
  9321. function adjustVideoExplorerSize(force) {
  9322. if (force !== true && (!conf.videoExplorerHack || !WatchController.isSearchMode())) { return; }
  9323. $('#videoExplorer, #content, #bottomContentTabContainer').toggleClass('w_adjusted', conf.videoExplorerHack);
  9324. var
  9325. rightAreaWidth = $('.videoExplorerBody').outerWidth(), // 592
  9326. availableWidth = Math.max($(window).innerWidth() - rightAreaWidth, 300),
  9327. commentInputHeight = $('#playerContainer').hasClass('oldTypeCommentInput') ? 30 : 0,
  9328. controlPanelHeight = $('#playerContainer').hasClass('controll_panel') ? 46 : 0;
  9329.  
  9330. var
  9331. defPlayerWidth = 300, otherPluginsHeight = 0,
  9332. defPlayerHeight = (defPlayerWidth - 32) * 9 / 16 + 10,
  9333. ratio = availableWidth / defPlayerWidth , availableHeight = defPlayerHeight * ratio + commentInputHeight + controlPanelHeight,
  9334. xdiff = (availableWidth - defPlayerWidth /*- 20 */), windowHeight = $(window).innerHeight(),
  9335. bottomHeight = windowHeight - availableHeight - (WatchController.isFixedHeader() ? $('#siteHeader').outerHeight() : 0) - otherPluginsHeight;
  9336.  
  9337. if (ratio < 1 || availableWidth <= 0 || bottomHeight <= 0 || (lastAvailableWidth === availableWidth && lastBottomHeight === bottomHeight)) { return; }
  9338.  
  9339. var seekbarWidth = 675, scaleX = (availableWidth) / seekbarWidth;
  9340.  
  9341. lastAvailableWidth = availableWidth;
  9342. lastBottomHeight = bottomHeight;
  9343.  
  9344. // コメントパネル召喚
  9345. var commentPanelWidth = 420;
  9346. var dynamic_css = [//'<style type="text/css" id="explorerHack">',
  9347. 'body.videoExplorer #content.w_adjusted #playerContainerWrapper, \n',
  9348. 'body.videoExplorer #content.w_adjusted #playerAlignmentArea, \n',
  9349. 'body.videoExplorer #content.w_adjusted #playerContainer, \n',
  9350. 'body.videoExplorer #content.w_adjusted #nicoplayerContainer ,\n',
  9351. 'body.videoExplorer #content.w_adjusted #external_nicoplayer \n',
  9352. '{',
  9353. 'width: ', availableWidth, 'px !important; height: ', availableHeight, 'px !important;padding: 0; margin: 0; ',
  9354. '}\n',
  9355. 'body.videoExplorer #content.w_adjusted .videoExplorerMenu { ',
  9356. 'position: absolute; width: 300px;',
  9357. 'margin-top: ', availableHeight, 'px !important; left: ', (xdiff - 2), 'px; ',
  9358. 'max-height: ', bottomHeight + 'px; overflow-y: auto; overflow-x: hidden; height: auto;',
  9359. '}\n',
  9360. 'body.videoExplorer #videoExplorer.w_adjusted { ',
  9361. 'margin-left: ', availableWidth, 'px !important;',
  9362. 'min-height: ', (windowHeight + 200) ,'px !important;',
  9363. 'overflow-x: hidden;',
  9364. ' }\n',
  9365. 'body.videoExplorer #content.w_adjusted #playlist { margin-left: ', xdiff, 'px; }\n',
  9366.  
  9367. 'body.videoExplorer #content.w_adjusted #nicoHeatMap {',
  9368. '-webkit-transform: scaleX(', availableWidth / 100, ');',
  9369. 'transform: scaleX(', availableWidth / 100, ');',
  9370. '}\n',
  9371. 'body.videoExplorer #content.w_adjusted #nicoHeatMapContainer {',
  9372. 'width: ', availableWidth, 'px;',
  9373. '}\n',
  9374. 'body.videoExplorer #content.w_adjusted #smart_music_kiosk {',
  9375. '-webkit-transform: scaleX(', scaleX, ');',
  9376. 'left: ', ((availableWidth - seekbarWidth) / 2) ,'px !important;',
  9377. '}\n',
  9378. 'body.videoExplorer #content.w_adjusted #songrium_logo_mini {',
  9379. 'left: ', (availableWidth + 5) ,'px !important;',
  9380. '}\n',
  9381. 'body.videoExplorer #content.w_adjusted #inspire_category {',
  9382. 'left: ', (availableWidth + 32) ,'px !important;',
  9383. '}\n',
  9384.  
  9385.  
  9386. 'body.videoExplorer #content.w_adjusted #leftPanel {',
  9387. ' display: block !important; top: ', (availableHeight + otherPluginsHeight - 1), 'px; max-height: ', bottomHeight, 'px; width: ', (xdiff - 4 + 1), 'px; left: 0;',
  9388. ' height:', bottomHeight, 'px; display: block; border-radius: 0;',
  9389. '}',
  9390. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo, body.size_small.no_setting_panel.videoExplorer #content.w_adjusted #leftPanel .sideIchibaPanel {',
  9391. 'width: ', Math.max((xdiff - 4), 130), 'px; border-radius: 0;',
  9392. '}',
  9393.  
  9394. ((xdiff >= 400) ?
  9395. [
  9396. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoTitleContainer{',
  9397. 'margin-left: 158px; border-radius: 0 0 ;background: #ddd; border: solid; border-color: #ccc; border-width: 1px 1px 0;',
  9398. '}',
  9399. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoThumbnailContainer{',
  9400. 'position: absolute; max-width: 150px; top: 0; ',
  9401. '}',
  9402. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoInfo{',
  9403. 'background: #ddd; margin-left: 158px; border-radius: 0 0; border: solid; border-color: #ccc; border-width: 0 1px 1px;',
  9404. '}',
  9405. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoDetails{',
  9406. 'margin-left: 158px; height: 100%; ',
  9407. '}',
  9408. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoOwnerInfoContainer{',
  9409. 'position: absolute; width: 150px; top: 0; border: 1px solid #ccc; margin: 0;',
  9410. '}',
  9411. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .userIconContainer, body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .ch_profile{',
  9412. 'background: #ddd; max-width: 150px; float: none; border-radius: 0;',
  9413. '}',
  9414. 'body.videoExplorer:not(.content-fix) .w_adjusted .videoDetails, ',
  9415. 'body.videoExplorer:not(.content-fix) .w_adjusted #videoExplorerMenu {',
  9416. // タグ領域三行分 bodyのスクロール位置をタグの場所にしてる時でもパネルは文章の末端までスクロールできるようにするための細工
  9417. // (四行以上あるときは表示しきれないが)
  9418. 'padding-bottom: 72px; ',
  9419. '}',
  9420. ].join('') :
  9421. (
  9422. (xdiff >= 154) ?
  9423. ['body.videoExplorer #content.w_adjusted #leftPanel #leftPanelTabContainer { padding: 4px 2px 3px 2px; }'].join('') :
  9424. ['body.videoExplorer #content.w_adjusted #leftPanel { display: none !important;}'].join('')
  9425. )
  9426. ),
  9427. 'body.videoExplorer #bottomContentTabContainer.w_adjusted .videoExplorerFooterAdsContainer { width: 520px; }\n',
  9428.  
  9429. 'body.videoExplorer #content.w_adjusted #playerTabWrapper {',
  9430. 'height: ', (availableHeight), 'px !important;',
  9431. '}',
  9432.  
  9433. 'body.videoExplorer #content.w_adjusted #playerTabWrapper .sidePanelInner {',
  9434. 'height: ', (availableHeight - 2), 'px;',
  9435. '}',
  9436. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active {',
  9437. 'right: -',(commentPanelWidth - 2), 'px !important; top: 0 !important; background: transparent; border: 0; margin-top: 0px;',
  9438. '}',
  9439. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active #playerCommentPanel {',
  9440. 'display: block; background: #dfdfdf; border: 1px solid;',
  9441. '}',
  9442. ''].join('');
  9443.  
  9444. if (videoExplorerStyle) {
  9445. videoExplorerStyle.innerHTML = dynamic_css;
  9446. } else {
  9447. videoExplorerStyle = addStyle(dynamic_css, 'videoExplorerStyle');
  9448. }
  9449.  
  9450. } // end adjustVideoExplorerSize
  9451.  
  9452. function setupVideoExplorerStaticCss() {
  9453. var __css__ = Util.here(function() {/*
  9454. body.videoExplorerOpening {
  9455. overflow-y: scroll;
  9456. }
  9457. body.videoExplorerOpening .videoExplorerMenu {
  9458. {* display: none; *}
  9459. }
  9460. body.videoExplorerOpening #playerTabWrapper {
  9461. visibility: hidden;
  9462. }
  9463. #videoExplorer {
  9464. transition: margin-left 0.4s ease 0.4s; overflow-x: hidden;
  9465. }
  9466. #videoExplorer.w_adjusted .videoExplorerBody, #videoExplorer.w_adjusted .videoExplorerContent .contentItemList {
  9467. width: 592px; padding-left: 0; min-width: 592px; max-width: auto;
  9468. }
  9469. #videoExplorer.w_adjusted .searchBox {
  9470. width: 574px;\
  9471. }
  9472. #videoExplorer.w_adjusted .videoExplorerContent {
  9473. width: 592px;
  9474. }
  9475. #videoExplorer.w_adjusted .videoExplorerBody .resultContentsWrap {
  9476. width: 592px; padding: 16px 0px;
  9477. }
  9478. #videoExplorer.w_adjusted .videoExplorerMenu, #content .videoExplorerMenu:not(.initialized) { display: none; }
  9479. .videoExplorerMenu {
  9480. transition: margin-top 0.4s ease 0.4s; {*, left 0.4s ease-in-out*};
  9481. }
  9482. #leftPanel {
  9483. {* 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;*}
  9484. transition: width 0.4s ease 0.4s, height 0.4s ease 0.4s, left 0.4s ease 0.4s;
  9485. }
  9486. #content.w_adjusted #playlist {
  9487. min-width: 592px;
  9488. }
  9489. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li, body.videoExplorer #content.w_adjusted #videoExplorerExpand {
  9490. height: 26px;
  9491. }
  9492. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li>a,body.videoExplorer #content.w_adjusted #videoExplorerExpand a{
  9493. line-height: 26px; font-size: 100%;
  9494. }
  9495. .errorMessage {
  9496. max-height: 0; line-height: 30px; overflow: hidden; text-align: center; color: #f88; cursor: pointer;
  9497. transition: max-height 0.8s ease;
  9498. }
  9499. .videoErrorOccurred .errorMessage {
  9500. max-height: 100px;
  9501. }
  9502.  
  9503. .w_adjusted .videoExplorerMenu .itemList li .arrow {
  9504. top: 8px;
  9505. }
  9506.  
  9507.  
  9508. .w_adjusted .videoExplorerMenu .closeVideoExplorer {
  9509. width: 300px; position: relative; padding: 2px 10px; background: #f5f5f5;
  9510. }
  9511. .w_adjusted .videoExplorerMenu .closeVideoExplorer:hover {
  9512. background: #dbdbdb;
  9513. }
  9514. .w_adjusted .videoExplorerMenu .closeVideoExplorer a{
  9515. display: block;line-height: 26px; {*color: #CC0000;*}
  9516. }
  9517.  
  9518. #searchResultNavigation > ul > li a:after, #content.w_adjusted #videoExplorerExpand a#closeSearchResultExplorer:after {
  9519. top: 8px;
  9520. }
  9521.  
  9522.  
  9523.  
  9524.  
  9525. #content.w_adjusted #playerContainerWrapper, #content.w_adjusted #playlist { box-shadow: none; }
  9526. #content.w_adjusted #videoExplorerExpand .arrow { display: none; }
  9527.  
  9528. body.videoExplorer #footer.w_adjusted {
  9529. display: none;
  9530. }
  9531. .w_adjusted .uadTagRelated .default .itemList .item .videoTitleContainer {
  9532. width: 130px;
  9533. text-align: center;
  9534. }
  9535. .w_adjusted .uadTagRelated .uadTagRelated {
  9536. margin-bottom: 30px;
  9537. }
  9538. .w_adjusted .uadTagRelated .itemList .item,
  9539. .w_adjusted .uadTagRelated .emptyItem,
  9540. .w_adjusted .uadTagRelated .default .landing {
  9541. width: 130px; margin: 0 10px 0 8px;
  9542. }
  9543. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper .itemImage {
  9544. width: 130px; height: auto; top: 0; left: 0;
  9545. }
  9546. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper {
  9547. width: 130px; height: 100px;
  9548. }
  9549. .w_adjusted .uadTagRelated .emptyItem .emptyMessageContainer {
  9550. width: 130px; height: 100px;
  9551. }
  9552. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link,
  9553. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link .title {
  9554. display: inline;
  9555. }
  9556.  
  9557. #videoExplorer.w_adjusted .videoExplorerContent .column1 .commentBlank {
  9558. width: 96%;
  9559. }
  9560. #videoExplorer.w_adjusted .videoExplorerContent .column4 .commentBlank {
  9561. width: 24%;
  9562. }
  9563. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item .createdTime .submit
  9564. {
  9565. display: none !important;
  9566. }
  9567. .nicorepoResult .column4 .videoInformation {
  9568. display: none;
  9569. }
  9570.  
  9571. #videoExplorer .pager { margin-right: 20px; }
  9572. #videoExplorer .contentItemList { clear: both; }
  9573.  
  9574. body.videoExplorer #content.w_adjusted #playerContainerWrapper { overflow: visible; }
  9575. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContent { padding: 20px 0px; }
  9576. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9577. { margin-left: 0; padding: 20px 340px 20px 0px; }
  9578. body.videoExplorer.playlist #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9579. { margin-left: 0; padding: 164px 340px 20px 0px; }
  9580.  
  9581. {* 謎のスペーサー *}
  9582. {*body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:first *}
  9583. {* body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:not(.videoExplorerMenuInner) { display: none; } *}
  9584. body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:nth-child(1) { display: none; }
  9585.  
  9586. body.videoExplorer #content.w_adjusted .videoExplorerMenu
  9587. { width: 300px; }
  9588.  
  9589. body.videoExplorer #content.w_adjusted .videoExplorerMenuInner
  9590. { position: static !important; top: 0 !important; left: 0 !important; }
  9591.  
  9592. body.videoExplorer #bottomContentTabContainer.w_adjusted { {*background: #ccc;*} }
  9593. body:not(.videoExplorer) .videoExplorerMenu { display: none; }
  9594.  
  9595. body.videoExplorer #content.w_adjusted #nicoplayerContainer {
  9596. z-index: 100;
  9597. }
  9598. body.videoExplorer #content.w_adjusted #playerTabWrapper {
  9599. top: 0px !important; background: #dfdfdf; border-radius: 4px;
  9600. z-index: 99;
  9601. transition: right 0.3s ease-out;
  9602. }
  9603. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column1 .item {
  9604. margin-left: 8px;
  9605. }
  9606. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  9607. width: 130px; margin-left: 8px; margin-right: 10px;
  9608. }
  9609. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .videoInformationOuter {
  9610. width: 414px;
  9611. }
  9612. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .nicorepoResult .videoInformationOuter {
  9613. width: auto;
  9614. }
  9615. #videoExplorer.w_adjusted .contentItemList .folder .column1 .description,
  9616. #videoExplorer.w_adjusted .suggestVideo .folder .column1 .description,
  9617. #videoExplorer.w_adjusted .descriptionShort {
  9618. width: 410px;
  9619. }
  9620.  
  9621. #videoExplorer .descriptionShort {
  9622. line-height: 1.5;
  9623. margin: 0 0 4px;
  9624. word-break: break-all;
  9625. clear: both;
  9626. color: #666;
  9627. font-size: 93%;
  9628. }
  9629.  
  9630. .w_adjusted .column1 .createdTime.at {
  9631. width: 160px; text-align: center;
  9632. }
  9633. .w_adjusted .createdTime {
  9634. white-space: nowrap;
  9635. }
  9636. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList .folder .container,
  9637. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .suggestVideo .folder .container {
  9638. background-position: -15px -270px; width: 130px; height: 100px;
  9639. }
  9640.  
  9641. body.size_small.no_setting_panel.videoExplorer #content #videoExplorerExpand { {*「閉じる」ボタン *}
  9642. position: static; top: auto; left: auto; margin-top: 0;',
  9643. }
  9644. body.videoExplorer #content.w_adjusted #playerTabWrapper #playerCommentPanel {
  9645. display: none;
  9646. }
  9647.  
  9648. .videoExplorerMenu .item:hover {
  9649. background: #dbdbdb; text-decoration: underline;
  9650. }
  9651. .videoExplorerMenu .item {
  9652. position: relative; border-bottom: 1px solid #CCCCCC; background: #f2f2f2;
  9653. text-decoration: none; cursor: pointer;
  9654. }
  9655. .videoExplorerMenu .item .arrow {
  9656. display: block; position: absolute; top: 14px; right: 12px; width: 9px; height: 12px;
  9657. background: url("http://res.nimg.jp/img/watch_q9/video_explorer/icon_normal.png") no-repeat 0 0;
  9658. }
  9659. .videoExplorerMenu .item .text {
  9660. position: relative; width: 100%; height: 100%; display: block; text-align: left;
  9661. text-decoration: none; padding: 0 12px; color: #000; box-sizing: border-box;
  9662. line-height: 26px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box;
  9663. }
  9664. .videoExplorerMenu .itemList>li:first {
  9665. position: relative;
  9666. }
  9667.  
  9668. #videoExplorer .videoExplorerMenu .closeVideoExplorer, #videoExplorer .quickSearchInput {
  9669. display: none;
  9670. }
  9671.  
  9672. #videoExplorer .videoExplorerBody .videoExplorerContent .contentItemList.column1 .video .column1 .videoInformationOuter .title
  9673. {
  9674. white-space: normal;
  9675. }
  9676.  
  9677. .videoExplorer #playlist {
  9678. transition: margin-left 0.4s ease 0.4s;
  9679. }
  9680.  
  9681. #playerAlignmentArea .toggleCommentPanel {
  9682. display: none;
  9683. position: absolute;
  9684. right: -119px;
  9685. bottom: 70px;
  9686. width: 100px;
  9687. height: 30px;
  9688. cursor: pointer;
  9689. outline: none;
  9690. background: #ccc;
  9691. border-radius: 16px 16px 0 0;
  9692. border: solid 1px black;
  9693. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  9694. transform: rotate(90deg); transform-origin: 0 0 0;
  9695. transition: 0.4s ease-in-out;
  9696. -webkit-transition: 0.4s ease-in-out;
  9697. }
  9698. #playerAlignmentArea .toggleCommentPanel::-moz-focus-inner {
  9699. border: 0px;
  9700. }
  9701. body.videoExplorer .w_adjusted #playerAlignmentArea .toggleCommentPanel { display: block; }
  9702. #playerAlignmentArea .toggleCommentPanel:before {
  9703. content: '↑ ';
  9704. }
  9705. #playerAlignmentArea .toggleCommentPanel:hover {
  9706. right: -129px;
  9707. }
  9708. #playerAlignmentArea .toggleCommentPanel.w_active {
  9709. right: -418px;
  9710. bottom: -29px;
  9711. border-radius: 0 0 16px 16px;
  9712. z-index: 10000;
  9713. -webkit-transform: rotate(360deg);
  9714. transform: rotate(360deg);
  9715. transition: none;
  9716. -webkit-transition: 0.4s ease-in-out;
  9717. {*transition: 0.4s ease-in-out;*}
  9718. }
  9719. #playerAlignmentArea .toggleCommentPanel.w_active:hover {
  9720. {*box-shadow: 2px 2px 2px #888;*}
  9721. right: -418px;
  9722. }
  9723. #playerAlignmentArea .toggleCommentPanel.w_active:before {
  9724. content: '← ';
  9725. }
  9726.  
  9727. .videoExplorerOpening .videoExplorerBody .videoExplorerConfig {
  9728. display: none;
  9729. }
  9730. .videoExplorerBody .videoExplorerConfig {
  9731. cursor: pointer;
  9732. width: 80px; margin-left: -36px; white-space: nowrap;
  9733. border-radius: 0 32px 0 0; border: solid 1px #666; border-width: 1px 1px 0;
  9734. color: #fff; background: #aaa;
  9735. }
  9736. #videoExplorer.w_adjusted .videoExplorerConfig .open,
  9737. #videoExplorer:not(.w_adjusted) .videoExplorerConfig .close {
  9738. display: none;
  9739. }
  9740. .videoExplorerConfig::-moz-focus-inner { border: 0px; }
  9741.  
  9742. .videoExplorer #playerContainer.appli_panel #appliPanel {
  9743. width: auto !important; background: #333;
  9744. }
  9745. .videoExplorerContent {
  9746. background: #fff;
  9747. }
  9748. */});
  9749. return addStyle(__css__, 'videoExplorerStyleStatic');
  9750. } // end setupVideoExplorerStaticCss
  9751.  
  9752. function initAutoComplete($searchInput) {
  9753. var
  9754. $suggestList = $('<datalist id="quickSearchSuggestList"></datalist>'),
  9755. wordSuggest = '',
  9756. favTagsSuggest = '',
  9757. loading = false,
  9758. val = '',
  9759. suggestLoader = new NicoSearchSuggest({}),
  9760. update = _.debounce(function() {
  9761. if (loading) {
  9762. return;
  9763. }
  9764. var value = $searchInput.val();
  9765. if (value.length >= 1 && val !== value) {
  9766. val = $searchInput.val();
  9767. loading = true;
  9768. suggestLoader.load(val, onSuggestLoaded);
  9769. } else {
  9770. loading = false;
  9771. }
  9772. }, 300),
  9773. onSuggestLoaded = function(err, result) {
  9774. if (err) {
  9775. return;
  9776. }
  9777. if (result.candidates) {
  9778. console.log(result.candidates);
  9779. var candidates = result.candidates;
  9780. var options = [];
  9781. for (var i = candidates.length - 1; i >= 0; i--) {
  9782. options.unshift(['<option value="', candidates[i], '"></option>'].join(''));
  9783. }
  9784. wordSuggest = options.join('');
  9785. refresh();
  9786. }
  9787. loading = false;
  9788. },
  9789. refresh = function() {
  9790. $suggestList.html(wordSuggest + favTagsSuggest);
  9791. },
  9792. bind = function($elm) {
  9793. $elm
  9794. .on('focus', update)
  9795. .on('keydown', update)
  9796. .on('keyup', update)
  9797. .on('keypress', update)
  9798. .on('click', update)
  9799. .on('mousedown', update)
  9800. .on('mouseup', update)
  9801. .attr({'autocomplete': 'on', 'list': 'quickSearchSuggestList', 'placeholder': '検索ワードを入力(Q)'});
  9802. // try {
  9803. // //$elm.attr('type', 'search');
  9804. // //$elm[0].setAttribute('type', 'search');//.attr('type', 'search');
  9805. // } catch (e) {
  9806. // console.log(e);
  9807. // }
  9808. };
  9809.  
  9810. EventDispatcher.addEventListener('onFavTagsLoad', function(tags) {
  9811. var options = [];
  9812. for (var i = tags.length - 1; i >= 0; i--) {
  9813. options.unshift(['<option value="', tags[i], '"></option>'].join(''));
  9814. }
  9815. favTagsSuggest = options.join('');
  9816. refresh();
  9817. });
  9818. $('body').append($suggestList);
  9819.  
  9820. bind($searchInput);
  9821. } //
  9822.  
  9823. function initVideoExplorer($, conf, w) {
  9824. setupVideoExplorerStaticCss();
  9825.  
  9826. var
  9827. _vp = WatchApp.ns.components.videoexplorer,
  9828. initializer = watch.VideoExplorerInitializer,
  9829. controller = initializer.videoExplorerController,
  9830. explorer = controller.getVideoExplorer(),
  9831. explorerConfig = _vp.config.VideoExplorerConfig,
  9832. menu = explorer.getMenu(),
  9833. ContentItemType = _vp.model.ContentItemType,
  9834. ContentType = _vp.model.ContentType,
  9835. watchPageRouter = WatchApp.ns.model.state.WatchPageRouter.getInstance(),
  9836. playerConnector = watch.PlayerInitializer.nicoPlayerConnector,
  9837. searchType = 'tag',
  9838. $menu = $('.videoExplorerMenu'),
  9839. $searchInput = $('<input class="quickSearchInput" type="search" name="q" accesskey="q" required="required" />')
  9840. .attr({'title': '検索ワードを入力', 'placeholder': '検索ワードを入力(Q)'}),
  9841. $closeExplorer = $('<div class="closeVideoExplorer"><a href="javascript:;">▲ 画面を戻す</a></div>'),
  9842. $inputForm = $('<form action="javascript:void(0);" />').append($searchInput),
  9843. $toggleCommentPanel = $('<button class="toggleCommentPanel">コメント</button>');
  9844.  
  9845. // init search menu
  9846. $searchInput.on('keyup', function(e) {
  9847. $('.searchText input').val(this.value);
  9848. }).on('click', function(e) {
  9849. e.stopPropagation();
  9850. });
  9851. $inputForm.on('submit', function(e) {
  9852. //e.preventDefault();
  9853. var val = $.trim($searchInput.val());
  9854. if (val.length > 0) {
  9855. if (val.match(/(sm|nm|so)\d+/)) {
  9856. WatchController.nicoSearch(val, 'tag');
  9857. } else {
  9858. WatchController.nicoSearch(val, 'keyword');
  9859. }
  9860. }
  9861. });
  9862. EventDispatcher.addEventListener('onSearchStart', function(word, type) {
  9863. searchType = type.replace(/^.*\./, '');
  9864. $searchInput.val(word);
  9865. });
  9866. initAutoComplete($searchInput);
  9867.  
  9868. $closeExplorer.find('a').on('click', function() {
  9869. WatchController.closeSearch();
  9870. });
  9871.  
  9872. // メニュー拡張
  9873. var
  9874. detachMenuItems = function() {
  9875. WatchItLater.videoExplorerMenu.myShortcuts .detach();
  9876. WatchItLater.videoExplorerMenu.videoRanking.detach();
  9877. WatchItLater.videoExplorerMenu.favTags .detach();
  9878. WatchItLater.videoExplorerMenu.favMylists .detach();
  9879.  
  9880. $inputForm.detach();
  9881. $closeExplorer.detach();
  9882. },
  9883. attachMenuItems = function() {
  9884. if (conf.enableFavTags ) { WatchItLater.videoExplorerMenu.favTags.attach(); }
  9885. if (conf.enableFavMylists) { WatchItLater.videoExplorerMenu.favMylists.attach(); }
  9886. WatchItLater.videoExplorerMenu.videoRanking.attach();
  9887. WatchItLater.videoExplorerMenu.myShortcuts.attach();
  9888.  
  9889.  
  9890. $('.videoExplorerMenu')
  9891. .find('.itemList>li:first').append($inputForm)
  9892. .end().find('.errorMessage').after($closeExplorer);
  9893. };
  9894. controller._refreshMenu_org = controller._refreshMenu;
  9895. controller._refreshMenu = $.proxy(function() {
  9896. detachMenuItems();
  9897. this._refreshMenu_org();
  9898. attachMenuItems();
  9899. }, controller);
  9900. controller.showDeflist_org = controller.showDeflist;
  9901. controller.showMylist_org = controller.showMylist;
  9902. controller.showOtherUserVideos_org = controller.showOtherUserVideos;
  9903. controller.showOwnerVideo_org = controller.showOwnerVideo;
  9904. controller.searchVideo_org = controller.searchVideo;
  9905. controller.showDeflist = $.proxy(function() {
  9906. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  9907. location.href = "/my/mylist";
  9908. return;
  9909. }
  9910. this.showDeflist_org();
  9911. }, controller);
  9912. controller.showMylist = $.proxy(function(id) {
  9913. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  9914. location.href = "/mylist/" + id;
  9915. return;
  9916. }
  9917. this.showMylist_org(id);
  9918. }, controller);
  9919. controller.showOtherUserVideos = $.proxy(function(id, name) {
  9920. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  9921. location.href = "/user/" + id;
  9922. return;
  9923. }
  9924. this.showOtherUserVideos_org(id, name);
  9925. }, controller);
  9926. controller.showOwnerVideo = $.proxy(function() {
  9927. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  9928. location.href = "/user/" + WatchController.getOwnerId();
  9929. return;
  9930. }
  9931. this.showOwnerVideo_org();
  9932. }, controller);
  9933. controller.searchVideo = $.proxy(function(word, type) {
  9934. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  9935. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  9936. location.href = (type === 'tag' ? 'tag' : 'search') + "/" + encodeURIComponent(word) + sortOrder;
  9937. return;
  9938. }
  9939. this.searchVideo_org(word, type);
  9940. }, controller);
  9941.  
  9942. EventDispatcher.addEventListener('onBottomContentTabViewReset', function(name) {
  9943. if (name === 'outline') {
  9944. WatchController.closeSearch();
  9945. }
  9946. });
  9947.  
  9948.  
  9949. EventDispatcher.addEventListener('on.config.enableFavTags',
  9950. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  9951. EventDispatcher.addEventListener('on.config.enableFavMylists',
  9952. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  9953.  
  9954. EventDispatcher.addEventListener('onVideoExplorerOpened', function(content) {
  9955. setTimeout(function() {
  9956. if (conf.videoExplorerHack) {
  9957. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  9958. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  9959. }
  9960. }, 100);
  9961.  
  9962. $('body').removeClass('videoExplorerOpening');
  9963. $('.videoExplorerMenu').addClass('initialized');
  9964. });
  9965. EventDispatcher.addEventListener('onVideoExplorerRefreshEnd', function(content) {
  9966. if (content.getType() === ContentType.USER_VIDEO) {
  9967. var items = content.getItems();
  9968. if (items.length === 1 && items[0].getContentItemType() !== ContentItemType.VIDEO) {
  9969. // ユーザーの投稿動画一覧が公開マイリスト一つだけだったら自動でそれを開く
  9970. items[0].stepIn();
  9971. return;
  9972. }
  9973. }
  9974. });
  9975. EventDispatcher.addEventListener('onVideoExplorerRefreshStart', function(content) {
  9976. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  9977. });
  9978. EventDispatcher.addEventListener('onVideoExplorerOpening', function(content) {
  9979. $('body').addClass('videoExplorerOpening');
  9980. adjustVideoExplorerSize(true);
  9981. });
  9982. EventDispatcher.addEventListener('onVideoExplorerClosing', function(content) {
  9983. });
  9984.  
  9985. EventDispatcher.addEventListener('onBeforeVideoExplorerMenuClear', function() {
  9986. detachMenuItems();
  9987. });
  9988.  
  9989. EventDispatcher.addEventListener('onUpdateSettingPanelVisible', function(isVisible, panel) {
  9990. if (isVisible && WatchController.isSearchMode()) {
  9991. setTimeout(function() {
  9992. WatchController.closeSearch();
  9993. setTimeout(function() {
  9994. playerConnector.updateSettingsPanelVisible(true, panel);
  9995. }, 800);
  9996. }, 100);
  9997. }
  9998. });
  9999.  
  10000. EventDispatcher.addEventListener('onFirstVideoExplorerOpened', function() {
  10001. window.console.time('onFirstVideoExplorerOpen');
  10002. EventDispatcher.addEventListener('onWindowResizeEnd', adjustVideoExplorerSize);
  10003. EventDispatcher.addEventListener('onVideoInitialized', adjustVideoExplorerSize);
  10004. window.console.timeEnd('onFirstVideoExplorerOpen');
  10005. });
  10006.  
  10007. var duration_match = /^([0-9]+):([0-9]+)/;
  10008. controller._item2playlistItem = function (item) {
  10009. // 動画長が入るようにする
  10010. var length_seconds = 0, len = item.getLength ? item.getLength() : '', m;
  10011. if (typeof len === 'string' && (m = duration_match.exec(len)) !== null) {
  10012. length_seconds = m[1] * 60 + m[2] * 1;
  10013. }
  10014. return new WatchApp.ns.model.playlist.PlaylistItem({
  10015. id : item.getId(),
  10016. title : item.getTitle(),
  10017. thumbnail_url : item.getThumbnailUrl(),
  10018. view_counter : item.getViewCounter(),
  10019. num_res : item.getNumRes(),
  10020. mylist_counter: item.getMylistCounter(),
  10021. mylist_comment: item.getMylistComment(),
  10022. first_retrieve: item.getFirstRetrieve(),
  10023. ads_counter : item.getUadCounter(),
  10024. length_seconds: length_seconds
  10025. });
  10026. };
  10027.  
  10028. initVideoExplorerItemContent();
  10029.  
  10030. $('#playerAlignmentArea').append($toggleCommentPanel);
  10031. $toggleCommentPanel.on('click', function() {
  10032. AnchorHoverPopup.hidePopup();
  10033. $('#playerTabWrapper').toggleClass('w_active');
  10034. $toggleCommentPanel.toggleClass('w_active', $('#playerTabWrapper').hasClass('w_active'));
  10035. setTimeout(function() {
  10036. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  10037. }, 1000);
  10038. }).on('mouseover', function() {
  10039. AnchorHoverPopup.hidePopup();
  10040. });
  10041.  
  10042. var toggleVideoExplorerHack = function(v) {
  10043. $('#videoExplorer, #content, #footer, #bottomContentTabContainer').toggleClass('w_adjusted', v);
  10044. if (v) {
  10045. $('#content').append($('.videoExplorerMenu'));
  10046. if (WatchController.isSearchMode()) {
  10047. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  10048. adjustVideoExplorerSize(true);
  10049. }
  10050. } else {
  10051. $('.videoExplorerContentWrapper').before($('.videoExplorerMenu'));
  10052. setTimeout(function() {
  10053. if (WatchController.isSearchMode()) {
  10054. playerConnector.updatePlayerConfig({playerViewSize: 'small'});
  10055. WatchApp.ns.util.WindowUtil.shake();
  10056. }
  10057. }, 1000);
  10058. }
  10059. };
  10060. EventDispatcher.addEventListener('on.config.videoExplorerHack', toggleVideoExplorerHack);
  10061. toggleVideoExplorerHack(conf.videoExplorerHack);
  10062.  
  10063.  
  10064. watchPageRouter._prepareState_org = watchPageRouter._prepareState;
  10065. watchPageRouter._prepareState = $.proxy(function(state) {
  10066. if (
  10067. conf.videoExplorerHack &&
  10068. WatchController.isSearchMode() &&
  10069. state.getVideoId() !== this._currentState.getVideoId()
  10070. ) {
  10071. state.prepare({
  10072. video: {id: this._watchInfoModel.v}
  10073. });
  10074. return state;
  10075. } else {
  10076. return this._prepareState_org(state);
  10077. }
  10078. }, watchPageRouter);
  10079. window.WatchApp.ns.model.state.WatchPageState.prototype.isVideoStateChange =
  10080. function(state) {
  10081. return this.getVideoId() !== state.getVideoId();
  10082. };
  10083.  
  10084.  
  10085. } // end initVideoExplorer
  10086.  
  10087. function initVideoExplorerItemContent() {
  10088.  
  10089. // 動画情報表示のテンプレートを拡張
  10090. var
  10091. overrideItemTemplate = function() {
  10092. var menu =
  10093. '<div class="thumbnailHoverMenu">' +
  10094. '<button class="showLargeThumbnail" onclick="WatchItLater.onShowLargeThumbnailClick(this);" title="大きいサムネイルを表示">+</button>' +
  10095. '<button class="deleteFromMyMylist" onclick="WatchItLater.onDeleteFromMyMylistClick(this);">マイリスト外す</button>' +
  10096. '</div>', $menu = $(menu);
  10097.  
  10098. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html());
  10099. $template.find('.column1 .thumbnailContainer').append($menu).end()
  10100. .find('.column4 .balloon').before($menu.clone()).end()
  10101. .find('.column4 .balloon').remove().end()
  10102. .find('.messageContainer').remove().end()
  10103. .find('.lastResBody')
  10104. .before($('<p class="descriptionShort"/><p class="itemMylistComment mylistComment"/>')).end()
  10105. .find('.noImage').remove().end()
  10106. //.find.remove('div.descriptionShort').end()
  10107. // .before($('<p class="descriptionShort"/>')).end()
  10108. // .find('.descriptionShort')
  10109. // .after($('<p class="itemMylistComment mylistComment"/>')).end()
  10110. .find('.createdTime').after($('<div class="nicorepoOwnerIconContainer"><a target="_blank"><img /></a></div>'));
  10111. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html($template.html());
  10112. $template = $menu = null;
  10113.  
  10114. },
  10115. onDeleteFromMyMylistClick = function(elm) {
  10116. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  10117. var contentList = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer.getContentList();
  10118. var
  10119. $elm = $(elm),
  10120. $videoItem = $elm.parent().parent(),
  10121. watchId = $elm.parent().attr('data-watch-id'),
  10122. ac = contentList.getActiveContent(),
  10123. type = contentList.getActiveContentType(),
  10124. onUpdate = function(status, result) {
  10125. if (status !== "ok") {
  10126. Popup.alert('削除に失敗: ' + result.error.description);
  10127. } else {
  10128. $videoItem.parent().animate({opacity: 0.3}, 500);
  10129. }
  10130. };
  10131.  
  10132. if (type === ContentType.MYLIST_VIDEO) {
  10133. if (!ac.getIsMine()) { return; }
  10134. Mylist.deleteMylistItem(watchId, ac.getMylistId(), onUpdate, 0);
  10135. } else
  10136. if (type === ContentType.DEFLIST_VIDEO) {
  10137. Mylist.deleteDefListItem(watchId, onUpdate, 0);
  10138. }
  10139. },
  10140. onShowLargeThumbnailClick = function (elm) {
  10141. var src = $(elm).parent().attr('data-thumbnail');
  10142. if (!src) { return; }
  10143. showLargeThumbnail(src);
  10144. };
  10145. overrideItemTemplate();
  10146. WatchItLater.onDeleteFromMyMylistClick = onDeleteFromMyMylistClick;
  10147. WatchItLater.onShowLargeThumbnailClick = onShowLargeThumbnailClick;
  10148.  
  10149. // 動画情報表示の拡張
  10150. var ItemView = WatchApp.ns.components.videoexplorer.view.content.item.AbstractVideoContentItemView;
  10151. ItemView.prototype._setView_org = ItemView.prototype._setView;
  10152.  
  10153. ItemView.prototype.update_org = ItemView.prototype.update;
  10154. ItemView.prototype.update = function() {
  10155. // 動画情報表示をゴリゴリいじる場所
  10156. var item = this._item, $item = this._$item;
  10157. this.update_org(item);
  10158.  
  10159. this._$item.find('.deleteFromMyMylist').data('watchId', this._item.getId());
  10160. if (item._mylistComment) { // マイリストコメント
  10161. $item.find('.mylistComment').css({display: 'block'});
  10162. } else {
  10163. $item.find('.mylistComment').remove();
  10164. }
  10165.  
  10166. var lastResBody = item.getLastResBody();
  10167. if (lastResBody.length > 0) {
  10168. this._$lastResBody.css('cssText', 'display: block !important').text(lastResBody);
  10169. } else {
  10170. this._$lastResBody.remove();
  10171. }
  10172.  
  10173. var thumbnail = this._$thumbnail.attr('src');
  10174. if (item.isMiddleThumbnail()) {
  10175. this._$item.find('.thumbnailContainer')
  10176. .css('background-image', 'url(' + thumbnail + ')').end()
  10177. .find('.showLargeThumbnail').attr('data-thumbnail', thumbnail);
  10178. this._$thumbnail.remove();
  10179. } else {
  10180. this._$item.find('.column4 .thumbnailContainer')
  10181. .css('background-image', 'url(' + thumbnail + ')').end()
  10182. .find('.showLargeThumbnail').attr('data-thumbnail', thumbnail);
  10183. }
  10184. this._$item.find('.thumbnailHoverMenu').attr({
  10185. 'data-thumbnail': thumbnail,
  10186. 'data-watch-id': item.getId()
  10187. });
  10188.  
  10189. if (item._seed && item._seed._info) {
  10190. var info = item._seed._info;
  10191. if (info.nicorepo_owner) { // ニコレポ
  10192. $item.addClass(info.nicorepo_className).addClass('nicorepoResult');
  10193. var owner = info.nicorepo_owner;
  10194. var $iconContainer = $item.find('.nicorepoOwnerIconContainer'), $icon = $iconContainer.find('img'), $link = $iconContainer.find('a');
  10195. $icon.attr('src', owner.icon);
  10196. $link.attr({'href': owner.page, 'data-ownerid': owner.id, 'title': owner.name + ' さん'});
  10197. if (info.nicorepo_className.indexOf('log-user-') >= 0) {
  10198. $link.attr(
  10199. 'onclick',
  10200. 'if (arguments[0].button > 0) return; arguments[0].preventDefault();' +
  10201. 'WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController.showOtherUserVideos(this.dataset.ownerid, this.title);' +
  10202. 'WatchApp.ns.util.WindowUtil.scrollFit($("#videoExplorer"));'
  10203. );
  10204. }
  10205. if (info.nicorepo_log.length > 1) {
  10206. $item.find('.descriptionShort').html(info.nicorepo_log.join('<br>'));
  10207. }
  10208. // ニコレポは再生数が取れないので-で埋める
  10209. this._$viewCount .html('-');
  10210. this._$commentCount.html('-');
  10211. this._$mylistCount .html('-');
  10212. $iconContainer = $icon = $link = null;
  10213. }
  10214. }
  10215. if (item._seed && typeof item._seed.description_full === 'string' && item._seed.description_full.length > 150) {
  10216. this._$descriptionShort.attr('title', item._seed.description_full);
  10217. }
  10218. $item = null;
  10219.  
  10220. };
  10221. ItemView = null;
  10222.  
  10223. } // end initVideoExplorerItemContent
  10224.  
  10225.  
  10226.  
  10227. var lastVideoOwnerJson = '';
  10228. function onWatchInfoReset(watchInfoModel) {
  10229. $('body').toggleClass('w_channel', watchInfoModel.isChannelVideo());
  10230. EventDispatcher.dispatch('onWatchInfoReset', watchInfoModel);
  10231. var owner = WatchController.getOwnerInfo(), owner_json = JSON.stringify(owner);
  10232. if (lastVideoOwnerJson.length > 0 && lastVideoOwnerJson !== owner_json) {
  10233. EventDispatcher.dispatch('onVideoOwnerChanged', owner);
  10234. }
  10235. lastVideoOwnerJson = owner_json;
  10236. }
  10237.  
  10238. function onScreenModeChange(sc) {
  10239. setTimeout(function() {
  10240. EventDispatcher.dispatch('onScreenModeChange', sc);
  10241. }, 500);
  10242. }
  10243.  
  10244. function initMylistPanel($, conf, w) {
  10245. var iframe = Mylist.getPanel('');
  10246. iframe.id = "mylist_add_frame";
  10247. iframe.className += " fixed";
  10248. w.document.body.appendChild(iframe);
  10249. iframe.hide(); // ページの初期化が終わるまでは表示しない
  10250.  
  10251. var $iframe = $(iframe);
  10252. $iframe.find('.mylistSelect').attr('accesskey', ':');
  10253.  
  10254. var toggleMylistMenuInFull = function(v) {
  10255. $('.mylistPopupPanel')
  10256. .toggleClass('hideInFull', v === 'hide')
  10257. .toggleClass('hideAllInFull', v === 'hideAll');
  10258. };
  10259. EventDispatcher.addEventListener('on.config.hideMenuInFull', toggleMylistMenuInFull);
  10260. toggleMylistMenuInFull(conf.hideMenuInFull);
  10261.  
  10262. var setMylistPanelPosition = function(v) {
  10263. $iframe
  10264. .toggleClass('left', v.indexOf('left') >= 0)
  10265. .toggleClass('top', v.indexOf('top') >= 0);
  10266. setTimeout(function() {
  10267. $('#yukkuriPanel')
  10268. .toggleClass('mylistPanelLeft', v.indexOf('left') >= 0 && v.indexOf('top') < 0);
  10269. }, 500);
  10270. };
  10271. EventDispatcher.addEventListener('on.config.mylistPanelPosition', setMylistPanelPosition);
  10272. if (conf.mylistPanelPosition !== '') setMylistPanelPosition(conf.mylistPanelPosition);
  10273.  
  10274. var $footer = $('#footer'), $window = $(window);
  10275. var toggleMylistPanelStyle = function() {
  10276. if ($footer.is(':visible')) {
  10277. $iframe.toggleClass('black', $window.scrollTop() + $window.innerHeight() - $footer.offset().top >= 0);
  10278. } else {
  10279. $iframe.removeClass('black');
  10280. }
  10281. };
  10282.  
  10283. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10284. toggleMylistPanelStyle();
  10285. var newVideoId = watchInfoModel.id;
  10286. var newWatchId = watchInfoModel.v;
  10287. iframe.watchId(newVideoId, newWatchId);
  10288. if (isFirst) iframe.show();
  10289. });
  10290. EventDispatcher.addEventListener('onScrollEnd', toggleMylistPanelStyle);
  10291. EventDispatcher.addEventListener('onWindowResizeEnd', toggleMylistPanelStyle);
  10292. EventDispatcher.addEventListener('onScreenModeChange', toggleMylistPanelStyle);
  10293. EventDispatcher.addEventListener('on.config.hidePlaylist', toggleMylistPanelStyle);
  10294. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', toggleMylistPanelStyle);
  10295. } //
  10296.  
  10297. function initScreenMode() {
  10298. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10299. if (conf.autoBrowserFull) {
  10300. setTimeout(function() {
  10301. if ($('body').hasClass('up_marquee') && conf.disableAutoBrowserFullIfNicowari) {
  10302. // ユーザーニコ割があるときは自動全画面にしない
  10303. return;
  10304. }
  10305. if (WatchController.isSearchMode()) { // TODO: localStorageに直接アクセスすんな
  10306. var settingSize = (localStorage["PLAYER_SETTINGS.LAST_PLAYER_SIZE"] === '"normal"') ? 'normal' : 'medium';
  10307. WatchController.changeScreenMode(settingSize);
  10308. }
  10309. WatchController.changeScreenMode('browserFull');
  10310. onWindowResizeEnd(); // TODO:;;;;
  10311. }, 100);
  10312. } else {
  10313. if (conf.autoOpenSearch && !WatchController.isSearchMode() && !$('body').hasClass('full_with_browser')) {
  10314. WatchController.openSearch();
  10315. }
  10316. if (conf.autoScrollToPlayer) {
  10317. // 初回のみ、プレイヤーが画面内に納まっていてもタグの位置まで自動スクロールさせる。(ファーストビューを固定するため)
  10318. // 二回目以降は説明文や検索結果からの遷移なので、必要最小限の動きにとどめる
  10319. if (!WatchController.isSearchMode() || isFirst) {
  10320. WatchController.scrollToVideoPlayer(isFirst);
  10321. }
  10322. }
  10323. }
  10324. });
  10325.  
  10326.  
  10327. var lastPlayerConfig = null, lastScreenMode = '';
  10328.  
  10329. function hideIfNeed() {
  10330. if (conf.controllerVisibilityInFull === 'hidden') {
  10331. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({oldTypeCommentInput: true, oldTypeControlPanel: false});
  10332. $('body').addClass('hideCommentInput');
  10333. } else {
  10334. var $w = $(window), iw = $w.innerWidth(), ih = $w.innerHeight();
  10335. var controllerH = 46, inputH = 30;
  10336. }
  10337. }
  10338.  
  10339. function restoreVisibility() {
  10340. if (lastPlayerConfig !== null) {
  10341. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10342. {oldTypeCommentInput: !!lastPlayerConfig.oldTypeCommentInput, oldTypeControlPanel: !!lastPlayerConfig.oldTypeControlPanel}
  10343. );
  10344. $(window).resize();
  10345. }
  10346. $('body').removeClass('hideCommentInput');
  10347. }
  10348.  
  10349. function toggleTrueBrowserFull(v) {
  10350. v = (typeof v === 'boolean') ? v : !$('body').hasClass('trueBrowserFull');
  10351. $('body').toggleClass('trueBrowserFull', v).toggleClass('full_and_mini', v);
  10352. conf.setValue('enableTrueBrowserFull', v);
  10353. if (!v) {
  10354. watch.PlaylistInitializer.playlistView.resetView();
  10355. }
  10356. return v;
  10357. }
  10358.  
  10359. function initShield() {
  10360. var shield = $('<div id="trueBrowserFullShield" />');
  10361. shield.click(function(e) {
  10362. e.stopPropagation();
  10363. toggleTrueBrowserFull();
  10364. });
  10365. $('#external_nicoplayer').after(shield);
  10366. shield = null;
  10367. }
  10368. initShield();
  10369.  
  10370. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  10371. var mode = sc.mode;
  10372. $('body').removeClass('w_fullScreenMenu');
  10373. if (mode === 'browserFull' && lastScreenMode !== mode) {
  10374. lastPlayerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get();
  10375. conf.setValue('lastControlPanelPosition', lastPlayerConfig.oldTypeControlPanel ? 'bottom' : 'over');
  10376. //$('body').toggleClass('w_fullWithPlaylist', WatchController.isFullScreenContentAll());
  10377. hideIfNeed();
  10378. if (conf.enableTrueBrowserFull) toggleTrueBrowserFull(conf.enableTrueBrowserFull);
  10379. } else
  10380. if (lastScreenMode === 'browserFull' && mode !== 'browserFull') {
  10381. conf.setValue('lastControlPanelPosition', '');
  10382. $('#playerContainerSlideArea').css({height: ''}); // wall bug fix
  10383. restoreVisibility();
  10384. }
  10385. lastScreenMode = mode;
  10386. });
  10387.  
  10388. $(window).on('beforeunload.watchItLater', function(e) {
  10389. conf.setValue('lastControlPanelPosition', '');
  10390. restoreVisibility();
  10391. });
  10392.  
  10393. var wheelCounter = 0, wheelTimer = null;
  10394. EventDispatcher.addEventListener('onWheelNoButton', function(e, delta) {
  10395. if (!conf.enableFullScreenMenu) return;
  10396. if ((e.target.tagName !== 'OBJECT' && e.target.tagName !== 'HTML') ||
  10397. !WatchController.isFullScreen()) return;
  10398. if (wheelTimer) {
  10399. wheelCounter += delta;
  10400. } else {
  10401. wheelCounter = 0;
  10402. wheelTimer = setTimeout(function() {//
  10403. wheelTimer = null;
  10404. if (Math.abs(wheelCounter) > 3) {
  10405. EventDispatcher
  10406. .dispatch('onToggleFullScreenMenu',
  10407. $('body').toggleClass('w_fullScreenMenu', wheelCounter < 0).hasClass('w_fullScreenMenu')
  10408. );
  10409. AnchorHoverPopup.hidePopup();
  10410. }
  10411. }, 500);
  10412. }
  10413. });
  10414.  
  10415. TouchEventDispatcher.onflick(function(e) {
  10416. if (!conf.enableFullScreenMenu) return;
  10417. if ((e.direction !=='up' && e.direction !=='down') || e.startEvent.target.tagName !== 'OBJECT' || !WatchController.isFullScreen()) return;
  10418. if (wheelTimer) {
  10419. clearTimeout(wheelTimer);
  10420. wheelTimer = null;
  10421. }
  10422. EventDispatcher
  10423. .dispatch('onToggleFullScreenMenu',
  10424. $('body').toggleClass('w_fullScreenMenu', e.direction === 'down').hasClass('w_fullScreenMenu')
  10425. );
  10426. AnchorHoverPopup.hidePopup();
  10427. });
  10428.  
  10429. var $fullScreenMenuContainer = $('<div id="fullScreenMenuContainer"/>');
  10430. var $fullScreenModeSwitch = $([
  10431. '<button class="fullScreenModeSwitch button">',
  10432. '画面モード: ',
  10433. '<span class="modeStatus mode_normal">標準</span>',
  10434. '<span class="modeStatus mode_noborder">最大化 </span>',
  10435. '</button>'
  10436. ].join('')).attr('title', '全画面時の表示切り替え').click(toggleTrueBrowserFull);
  10437. var $toggleStageVideo = $([
  10438. '<button class="stageVideoSwitch button">',
  10439. 'アクセラレーション: ',
  10440. '<span class="modeStatus mode_off">OFF</span>',
  10441. '<span class="modeStatus mode_on">ON</span>',
  10442. '</button>'
  10443. ].join('')).attr('title', 'ハードウェアアクセラレーションのON/OFF').click(function() { WatchController.toggleStageVideo(); });
  10444. var $toggleSetting = $([
  10445. '<button class="toggleSetting button">',
  10446. '</button>'
  10447. ].join('')).text('⛭設定').attr('title', '設定パネルを開閉します').click(function() { ConfigPanel.toggle();});
  10448. $fullScreenMenuContainer.append($fullScreenModeSwitch).append($toggleStageVideo).append($toggleSetting);
  10449. $('#nicoplayerContainerInner').append($fullScreenMenuContainer);
  10450.  
  10451.  
  10452. if (conf.lastControlPanelPosition === 'bottom' || conf.lastControlPanelPosition === 'over') {
  10453. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  10454. console.log('restore oldTypeControlPanel ? ', conf.lastControlPanelPosition === 'bottom');
  10455. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10456. {oldTypeControlPanel: conf.lastControlPanelPosition === 'bottom'}
  10457. );
  10458. });
  10459. }
  10460.  
  10461. } // end initScreenMode()
  10462.  
  10463.  
  10464.  
  10465.  
  10466. function initPlaylist($, conf, w) {
  10467. var
  10468. playlist = watch.PlaylistInitializer.playlist,
  10469. blankVideoId = 'sm20353707', blankVideoUrl = 'http://www.nicovideo.jp/watch/' + blankVideoId + '?',
  10470. redirectPageUrl = 'http://www.nicovideo.jp/stamp',
  10471. items = {},
  10472. toCenter = function() { // 表示位置調整
  10473. var
  10474. pm = WatchApp.ns.view.playlist.PlaylistManager,
  10475. pv = watch.PlaylistInitializer.playlistView,
  10476. pl = playlist,
  10477. current = pl.getPlayingIndex(),
  10478. cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()),
  10479. center = Math.round(cols / 2);
  10480.  
  10481. if (cols < 1) { return; }
  10482. var currentLeft = pm.getLeftSideIndex();
  10483. pv.scroll(Math.max(0, current - center + 1));
  10484. },
  10485. scroll = function(d) {
  10486. var isEffectEnabled = watch.PlaylistInitializer.playlistView.isEffectEnabled;
  10487. var left = WatchApp.ns.view.playlist.PlaylistManager.getLeftSideIndex();
  10488. watch.PlaylistInitializer.playlistView.isEffectEnabled = false;
  10489. watch.PlaylistInitializer.playlistView.scroll(Math.max(0, left + d));
  10490. watch.PlaylistInitializer.playlistView.isEffectEnabled = isEffectEnabled;
  10491. };
  10492.  
  10493. playlist.isAutoPlay = playlist.isContinuous; // 互換用
  10494. playlist.enableAutoPlay = playlist.enableContinuous;
  10495. playlist.disableAutoPlay = playlist.disableContinuous;
  10496.  
  10497. $('#playlist').find('.playlistInformation').on('dblclick.watchItLater', function(e) {
  10498. e.preventDefault();
  10499. e.stopPropagation();
  10500. toCenter();
  10501. });
  10502.  
  10503. EventDispatcher.addEventListener('onVideoInitialized', function() {
  10504. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView, pl = watch.PlaylistInitializer.playlist;
  10505. var current = pl.getPlayingIndex(), cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()), center = Math.floor(cols / 2);
  10506. if (pm.getLeftSideIndex() + cols <= pl.getNextPlayingIndex()) { toCenter(); }
  10507. });
  10508.  
  10509. $('#playlistContainer .prevArrow, #playlistContainer .nextArrow').on('mousewheel.watchItLater', function(e, delta) {
  10510. if (WatchController.isFullScreen()) { return; }
  10511. e.preventDefault();
  10512. e.stopPropagation();
  10513. scroll(delta *-1);
  10514. }).attr('title', 'ホイールで左右にスクロール');
  10515.  
  10516. // フルスクリーン中はプレイリストのどこでもスクロールできたほうがいいね
  10517. $('#playlist').on('mousewheel.watchItLater', function(e, delta) {
  10518. if (WatchController.isFullScreen() || WatchController.isSearchMode() || $('#footer').hasClass('noBottom')) {
  10519. e.preventDefault();
  10520. e.stopPropagation();
  10521. scroll(delta *-1);
  10522. }
  10523. });
  10524.  
  10525. EventDispatcher.addEventListener('onWheelAndButton', function(e, delta, button) {
  10526. if (WatchController.isFullScreen()) { return; }
  10527. if ($('#playlist').hasClass('dragging')) {
  10528. e.preventDefault();
  10529. scroll(delta *-1);
  10530. }
  10531. });
  10532.  
  10533. var
  10534. updatePos = function() {
  10535. if (
  10536. conf.hashPlaylistMode === 2 || (conf.hashPlaylistMode === 1 && WatchController.isPlaylistActive())) {
  10537. LocationHashParser.setValue('playlist', exportPlaylist());
  10538. LocationHashParser.updateHash();
  10539. }
  10540. if (conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') {
  10541. setTimeout(function() {
  10542. w[conf.storagePlaylistMode].setItem('watchItLater_playlist', JSON.stringify(exportPlaylist()));
  10543. }, 0);
  10544. }
  10545.  
  10546. var pos = Math.max((playlist.getPlayingIndex() + 1), 1) + '/' + Math.max(playlist.getItems().length, 1);
  10547. $('.generationMessage').text(pos + " - \n" + $('.generationMessage').text().replace(/^.*\n/, ''));
  10548. },
  10549. resetView = function() {
  10550. watch.PlaylistInitializer.playlistView.resetView();
  10551. },
  10552. exportPlaylist = function(option, type, continuous, shuffle) {
  10553. var
  10554. items = playlist.currentItems,
  10555. list = [],
  10556. current = 0,
  10557. len = conf.debugMode ? Math.min(600, items.length) : Math.min(300, items.length);
  10558.  
  10559. for (var i = 0; i < len; i++) {
  10560. var item = items[i];
  10561. if (item._isPlaying) current = i;
  10562. list.push([
  10563. item.id,
  10564. parseInt(item.mylistCounter, 10).toString(36),
  10565. parseInt(item.viewCounter, 10).toString(36),
  10566. parseInt(item.numRes, 10).toString(36),
  10567. (item.thumbnailUrl ? parseInt(item.thumbnailUrl.split('?i=')[1], 10).toString(36) : 'c490r'),
  10568. ].join(',') + ':' + item.title
  10569. );
  10570. }
  10571. return {
  10572. a: (typeof continuous === 'boolean') ? continuous : WatchController.isPlaylistContinuous(),
  10573. r: (typeof shuffle === 'boolean') ? shuffle : WatchController.isPlaylistRandom(),
  10574. o: option || playlist.option,
  10575. t: type || playlist.type,
  10576. i: list,
  10577. c: current
  10578. };
  10579. },
  10580. importPlaylist = function(list) {
  10581. var PlaylistItem = WatchApp.ns.model.playlist.PlaylistItem, newItems = [], uniq = {}, currentIndex = -1;
  10582.  
  10583. WatchController.clearPlaylist();
  10584. var currentItem = playlist.currentItems[0];
  10585. if (!currentItem) {
  10586. var wm = watchInfoModel;
  10587. currentItem = new PlaylistItem({
  10588. id: wm.v,
  10589. title: wm.title,
  10590. mylist_counter: wm.mylistCount,
  10591. view_counter: wm.viewCount,
  10592. num_res: wm.commentCount,
  10593. thumbnail_url: wm.thumbnail,
  10594. first_retriee: wm.postedAt
  10595. });
  10596. }
  10597.  
  10598. for (var i = 0, len = list.i.length; i < len; i++) {
  10599. var
  10600. dat = list.i[i],
  10601. c = dat.split(':')[0].split(','),
  10602. title = dat.replace(/^.*:/, ''),
  10603. id = c[0],
  10604. thumbnailId = parseInt(c[4], 36);
  10605.  
  10606. if (uniq[id] || typeof id !== 'string') { continue; }
  10607. uniq[id] = true;
  10608. if (id === watchInfoModel.v) {
  10609. currentIndex = i;
  10610. newItems.push(currentItem);
  10611. } else {
  10612. var item = new PlaylistItem({
  10613. id: id,
  10614. title: title.replace('<', '&lt;').replace('>', '&gt;'), // ないはずだけど一応
  10615. mylist_counter: parseInt(c[1], 36),
  10616. view_counter: parseInt(c[2], 36),
  10617. num_res: parseInt(c[3], 36),
  10618. thumbnail_url: 'http://tn-skr' + ((thumbnailId % 4) + 1) + '.smilevideo.jp/smile?i=' + thumbnailId,
  10619. first_retrieve: null
  10620. });
  10621. newItems.push(item);
  10622. }
  10623. }
  10624. // 復元するリストの中に現在の動画がなかった
  10625. if (currentIndex === -1) {
  10626. if (typeof list.c === 'number') {
  10627. if (list.c < newItems.length) {
  10628. currentIndex = list.c + 1;
  10629. newItems.splice(currentIndex, 0, currentItem);
  10630. } else {
  10631. currentIndex = list.length;
  10632. newItems.push(currentItem);
  10633. }
  10634. } else {
  10635. newItems.unshift(currentItem);
  10636. currentIndex = 0;
  10637. }
  10638. }
  10639.  
  10640. var isAutoPlay = playlist.isContinuous();//isAutoPlay();
  10641. playlist.reset(newItems, 'WatchItLater', list.t, list.o);
  10642. if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  10643. playlist.disableContinuous();
  10644. }
  10645. if (currentIndex >= 0) { playlist.playingItem = newItems[currentIndex]; }
  10646. if (list.a) { playlist.enableContinuous(); }
  10647. if (list.r) {
  10648. if (watchInfoModel.id === blankVideoId) {
  10649. setTimeout(function() {
  10650. WatchController.shufflePlaylist();
  10651. }, 3000);
  10652. } else {
  10653. playlist.enableContinuous();
  10654. }
  10655. }
  10656. },
  10657. $dialog = null, $savelink = null, $continuous, $shuffle,
  10658. openSaveDialog = function() {
  10659. function resetLink() {
  10660. var playlist = exportPlaylist(null, null, $continuous.is(':checked'), $shuffle.is(':checked'));
  10661. playlist.o = playlist.o || [];
  10662. playlist.o.name = $savelink.text();
  10663. playlist.t = 'mylist';
  10664. LocationHashParser.setValue('playlist', playlist);
  10665. if (!playlist.r) {
  10666. LocationHashParser.setValue('redirectWatchId', watchInfoModel.id);
  10667. } else {
  10668. LocationHashParser.deleteValue('redirectWatchId');
  10669. }
  10670. $savelink
  10671. //.attr('href', blankVideoUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10672. .attr('href', redirectPageUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10673. .unbind();
  10674. }
  10675. function closeDialog() {
  10676. $dialog.removeClass('show');
  10677. }
  10678.  
  10679. if (!$dialog) {
  10680. $dialog = $('<div id="playlistSaveDialog" />');
  10681. $dialog.append($([
  10682. '<div class="shadow"></div>',
  10683. '<div class="formWindow"><div class="formWindowInner">',
  10684. '<h3>プレイリスト保存用リンク(実験中)</h3>',
  10685. '<p class="link"><a target="_blank" class="playlistSaveLink">保存用リンク</a><button class="editButton">編集</button></p>',
  10686. '<label><input type="checkbox" class="continuous">開始時に連続再生をONにする</label><br>',
  10687. '<label><input type="checkbox" class="shuffle">開始時にリストをシャッフルする</label>',
  10688. '<p class="desc">リンクを右クリックしてコピーやブックマークする事で、現在のプレイリストを保存する事ができます。</p>',
  10689. '<button class="closeButton">閉じる</button>',
  10690. '</div></div>',
  10691. ''].join('')));
  10692. $savelink = $dialog.find('a').attr('added', 1);
  10693. $continuous = $dialog.find('.continuous');
  10694. $shuffle = $dialog.find('.shuffle');
  10695. $dialog.find('.shadow').on('click', closeDialog);
  10696. $dialog.find('.editButton').on('click', function() {
  10697. var newTitle = prompt('タイトルを編集', $savelink.text());
  10698. if (newTitle) {
  10699. $savelink.text(newTitle);
  10700. resetLink();
  10701. }
  10702. });
  10703. $continuous.on('click', resetLink);
  10704. $shuffle .on('click', resetLink);
  10705. $dialog.find('.closeButton').on('click', closeDialog);
  10706.  
  10707. $('body').append($dialog);
  10708. }
  10709. $savelink.text(
  10710. $('#playlist .generationMessage')
  10711. .text()
  10712. .replace(/^.*?\n/, '')
  10713. .replace(/^.*「/, '')
  10714. .replace(/」.*?$/, '')
  10715. .replace(/ *- \d{4}-\d\d-\d\d \d\d:\d\d$/, '') +
  10716. ' - ' + WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M', new Date())
  10717. );
  10718. $continuous.attr('checked', WatchController.isPlaylistActive());
  10719. $shuffle .attr('checked', WatchController.isPlaylistRandom());
  10720. resetLink();
  10721. $dialog.addClass('show');
  10722. },
  10723. PlaylistMenu = (function($, conf, w, playlist) {
  10724. var $popup = null, $generationMessage = $('#playlist').find('.generationMessage'), self;
  10725.  
  10726. var
  10727. enableContinuous = function() {
  10728. playlist.enableContinuous();
  10729. },
  10730. createDom = function() {
  10731. $popup = $('<div class="playlistMenuPopup popupMenu"/>')
  10732. .addClass('pop')
  10733. .toggleClass('w_touch', isTouchActive);
  10734. var $ul = $('<ul/>');
  10735. $popup.click(function() {
  10736. self.hide();
  10737. });
  10738. var $shuffle = $('<li>シャッフル: 全体</li>').click(function(e) {
  10739. WatchController.shufflePlaylist();
  10740. enableContinuous();
  10741. });
  10742. $ul.append($shuffle);
  10743. var $shuffleR = $('<li>シャッフル: 右</li>').click(function(e) {
  10744. WatchController.shufflePlaylist('right');
  10745. enableContinuous();
  10746. });
  10747. $ul.append($shuffleR);
  10748.  
  10749. var $next = $('<li>検索結果を追加: 次に再生</li>').click(function() {
  10750. WatchController.appendSearchResultToPlaylist('next');
  10751. enableContinuous();
  10752. });
  10753. $ul.append($next);
  10754.  
  10755. var $insert = $('<li>検索結果を追加: 末尾</li>').click(function() {
  10756. WatchController.appendSearchResultToPlaylist();
  10757. enableContinuous();
  10758. });
  10759. $ul.append($insert);
  10760.  
  10761. var $clear = $('<li>リストを消去: 全体</li>').click(function() {
  10762. WatchController.clearPlaylist();
  10763. //watch.PlaylistInitializer.playlist.setPlaybackMode('normal');
  10764. });
  10765. $ul.append($clear);
  10766.  
  10767. var $clearLeft = $('<li>リストを消去: 左</li>').click(function() {
  10768. WatchController.clearPlaylist('left');
  10769. });
  10770. $ul.append($clearLeft);
  10771. var $clearRight = $('<li>リストを消去: 右</li>').click(function() {
  10772. WatchController.clearPlaylist('right');
  10773. });
  10774. $ul.append($clearRight);
  10775.  
  10776. var $saver = $('<li>リストを保存(実験中)</li>').click(function() {
  10777. openSaveDialog();
  10778. });
  10779.  
  10780. $ul.append($saver);
  10781. $popup.append($ul);
  10782. $('body').append($popup);
  10783. },
  10784. show = function() {
  10785. if ($popup === null) { createDom(); }
  10786. var offset = $generationMessage.offset(), $window = $(window) , pageBottom = $window.scrollTop() + $window.innerHeight();
  10787. $popup.css({
  10788. left: offset.left,
  10789. top: Math.min(offset.top + 24, pageBottom - $popup.outerHeight())
  10790. }).show();
  10791. },
  10792. hide = function() {
  10793. if ($popup) { $popup.hide(); }
  10794. },
  10795. toggle = function() {
  10796. if ($popup === null || !$popup.is(':visible')) {
  10797. show();
  10798. } else {
  10799. hide();
  10800. }
  10801. };
  10802.  
  10803. $generationMessage.click(function(e) {
  10804. e.preventDefault();
  10805. self.toggle();
  10806. });
  10807.  
  10808. $('body').on('click.watchItLater', function(e) {
  10809. var tagName = e.target.tagName, className = e.target.className;
  10810. if (className !== 'generationMessage') {
  10811. self.hide();
  10812. }
  10813. });
  10814. self = {
  10815. show: show,
  10816. hide: hide,
  10817. toggle: toggle
  10818. };
  10819. return self;
  10820. })($, conf, w, playlist);
  10821.  
  10822.  
  10823. var hashlist = LocationHashParser.getValue('playlist');
  10824. if (hashlist && hashlist.i && hashlist.i.length > 0) {
  10825. try {
  10826. console.log('restore playlist!!');
  10827. importPlaylist(hashlist);
  10828. if (conf.hashPlaylistMode < 1) {
  10829. LocationHashParser.removeHash();
  10830. }
  10831. setTimeout(function() { resetView(); } , 3000);
  10832. } catch (e) {
  10833. console.log(e);
  10834. console.trace();
  10835. }
  10836. } else
  10837. if ((conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') && w[conf.storagePlaylistMode] && !playlist.isContinuous()) {
  10838. try {
  10839. console.log('restore playlist:' + conf.storagePlaylistMode);
  10840. var list = JSON.parse(w[conf.storagePlaylistMode].getItem('watchItLater_playlist'));
  10841. if (list !== null) { importPlaylist(list); }
  10842. setTimeout(function() { resetView(); } , 3000);
  10843. } catch (e) {
  10844. console.log('プレイリストの復元に失敗!', e);
  10845. }
  10846. } else {
  10847. updatePos();
  10848. }
  10849.  
  10850.  
  10851. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  10852. if ($('body').hasClass('full_with_browser')) {
  10853. // フル画面時プレイリストを閉じる
  10854. if (conf.autoClosePlaylistInFull) { $('#content').find('.browserFullPlaylistClose:visible').click(); }
  10855. }
  10856. });
  10857.  
  10858. EventDispatcher.addEventListener('onVideoExplorerOpened', function() {
  10859. // 2013/09/26 本家側で開閉を記録するようになった -> 2014/03/03 また記憶しなくなった
  10860.  
  10861. // 通常画面でプレイリストを表示にしてるなら、開いた状態をデフォルトにする
  10862. if (conf.hidePlaylistInVideoExplorer === false) {
  10863. playlist.open();
  10864. //$('#playlist').find('.browserFullOption a:visible').click();
  10865. }
  10866. });
  10867. $('#playlist .browserFullOption a').on('click', function() {
  10868. if (WatchController.isSearchMode()) {
  10869. conf.setValue('hidePlaylistInVideoExplorer', !conf.hidePlaylistInVideoExplorer);
  10870. }
  10871. });
  10872.  
  10873. EventDispatcher.addEventListener('on.config.hashPlaylistMode', function(v) {
  10874. if (v === 0) {
  10875. LocationHashParser.deleteValue('playlist');
  10876. LocationHashParser.removeHash();
  10877. } else
  10878. if (v === 1 || v === 2) {
  10879. var msg = [
  10880. '【警告】「プレイリストが消えないモード」は実験中の機能です。',
  10881. '',
  10882. 'この機能を使うと、ページをリロードしたりブックマークしてもプレイリストが消えなくなりますが、',
  10883. 'データを力技で保持するため、ページのURLがものすごく長く(※)なります。',
  10884. '',
  10885. 'そのため、ブラウザのパフォーマンスが低下したり、未知の不具合が発生する可能性があります。',
  10886. 'それでもこの機能を使ってみたい!という方だけ「OK」を押してください。',
  10887. '',
  10888. '※ 数千~数万文字くらい!',
  10889. ''].join('\n');
  10890. if (confirm(msg)) {
  10891. LocationHashParser.setValue('playlist', exportPlaylist());
  10892. LocationHashParser.updateHash();
  10893. } else {
  10894. conf.setValue('hashPlaylistMode', 0);
  10895. ConfigPanel.refresh();
  10896. }
  10897. }
  10898. });
  10899.  
  10900. $('#playlist .browserFullOption').on('click.bugfix', resetView);
  10901.  
  10902. $('.generationMessage, .prevArrow, .nextArrow, .playbackOption').on('mouseover', function() {
  10903. AnchorHoverPopup.hidePopup();
  10904. });
  10905.  
  10906. playlist.addEventListener('changePlaybackMode', function(mode) {
  10907. console.log('changePlaybackMode', mode, conf.hashPlaylistMode);
  10908. if (mode === 'normal' && conf.hashPlaylistMode < 2) {
  10909. LocationHashParser.removeHash();
  10910. } else {
  10911. updatePos();
  10912. }
  10913. });
  10914.  
  10915. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  10916. updatePos();
  10917. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  10918. resetView();
  10919. });
  10920. EventDispatcher.addEventListener('onWatchInfoReset', function() {
  10921. updatePos();
  10922. });
  10923. playlist.addEventListener('reset', function() {
  10924. EventDispatcher.dispatch('onPlaylistReset');
  10925. updatePos();
  10926. });
  10927. playlist.addEventListener('update', function() {
  10928. EventDispatcher.dispatch('onPlaylistUpdate');
  10929. updatePos();
  10930. });
  10931. });
  10932.  
  10933. var togglePlaylistDisplay = function(v) {
  10934. var $playlist = $('#playlist');
  10935. if (!v) {
  10936. $playlist.addClass('w_show').removeClass('w_closing');
  10937. } else {
  10938. $playlist.addClass('w_closing');
  10939. setTimeout(function() { $playlist.removeClass('w_show');}, 500);
  10940. }
  10941. };
  10942. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  10943. togglePlaylistDisplay(conf.hidePlaylist);
  10944.  
  10945. // プレイリスト消えないモードの時はプレイリストを勝手におすすめに置き換える機能をキャンセル
  10946. (function() {
  10947. var ld = WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController._videoExplorerPlaylistResetArgumentsLoader;
  10948. ld.load_org = ld.load;
  10949. ld.load = $.proxy(function(a, b, c) {
  10950. if (conf.storagePlaylistMode !== '') {
  10951. return;
  10952. }
  10953. this.load_org(a, b, c);
  10954. }, ld);
  10955. ld = null;
  10956. })();
  10957.  
  10958. } // end initPlaylist
  10959.  
  10960.  
  10961. function initPageHeader($, conf, w) {
  10962. $('.videoDetailExpand h2').addClass('videoDetailToggleButton');
  10963. } // end initPageHeader
  10964.  
  10965.  
  10966. function initVideoTagContainer($, conf, w) {
  10967. var $videoHeaderTagEditLinkArea = null, $toggleTagEditText = null, baseTagHeight = 72, currentHeight = 72;
  10968. var tagListView = watch.TagInitializer.tagViewController.tagListView, $videoHeader = $('.videoHeaderOuter');
  10969.  
  10970. tagListView.getCurrentDefaultHeight_org = tagListView.getCurrentDefaultHeight;
  10971. tagListView.getCurrentDefaultHeight = function() {
  10972. if ($('body').hasClass('full_with_browser')) {
  10973. return tagListView.getCurrentDefaultHeight_org();
  10974. }
  10975. return currentHeight;
  10976. };
  10977.  
  10978. $videoHeaderTagEditLinkArea = $('.toggleTagEditInner .videoHeaderTagEditLinkArea');
  10979. $('.toggleTagEdit').append($videoHeaderTagEditLinkArea);
  10980. $toggleTagEditText = $('<span class="toggleText">' + $('.toggleTagEditInner').text() + '</span>');
  10981. $('.toggleTagEditInner').empty().append($toggleTagEditText).append($videoHeaderTagEditLinkArea);
  10982.  
  10983. var onTagReset = function() {
  10984. try {
  10985. // タグが2行以下だったら自動的に狭くする処理
  10986. if (!conf.enableAutoTagContainerHeight) { return; }
  10987. currentHeight = Math.min(baseTagHeight, $('#videoTagContainer').find('.tagInner').innerHeight());
  10988.  
  10989. if (baseTagHeight !== currentHeight) {
  10990. var $toggle = $('#videoTagContainer').find('.toggleTagEdit');
  10991. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  10992.  
  10993. if (currentHeight < 36) { // 1行以下の時
  10994. $videoHeader.addClass('tag1Line');
  10995. } else {
  10996. if (currentHeight <= 60) { // 2行以下の時
  10997. $videoHeader.addClass('tag2Lines');
  10998. }
  10999. }
  11000. watch.TagInitializer.tagViewController.tagListView.fit();
  11001. } else {
  11002. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  11003. watch.TagInitializer.tagViewController.tagListView.fit();
  11004. }
  11005. } catch (e) {
  11006. console.log(e);
  11007. }
  11008. };
  11009.  
  11010. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11011. EventDispatcher.addEventListener('onVideoInitialized', onTagReset);
  11012. });
  11013. watch.TagInitializer.tagList.addEventListener('reset', onTagReset);
  11014. window.setTimeout(onTagReset, 1000);
  11015.  
  11016. $videoHeaderTagEditLinkArea = $toggleTagEditText = null;
  11017.  
  11018.  
  11019. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org = WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived;
  11020. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived = function(a) {
  11021. //console.log('onTagDataReceived', a);
  11022. if (conf.disableTagReload) {
  11023. return;
  11024. }
  11025. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org(a);
  11026. };
  11027.  
  11028.  
  11029. } // end initVideoTagContainer
  11030.  
  11031.  
  11032.  
  11033. function initVideoReview($, conf, w) {
  11034. var __css__ = Util.here(function() {/*
  11035. .sidePanel #videoReview { margin: 0 auto; }
  11036. #outline.w_compact #videoReview { width: 300px; }
  11037. #outline.w_compact textarea.newVideoReview { width: 277px; }
  11038. #outline.w_compact #videoReviewHead { width: 283px; }
  11039. #outline.w_compact #videoReview .stream { width: 300px; }
  11040. #outline.w_compact #videoReview .inner { width: 300px; }
  11041. #outline.w_compact .commentContent { width: 278px; }
  11042. #outline.w_compact .commentContentBody { width: 232px; }
  11043. .sidePanel.w_review #videoReview { width: 308px; }
  11044. .sidePanel.w_review textarea.newVideoReview { width: 286px; }
  11045. .sidePanel.w_review #videoReviewHead { width: 291px; }
  11046. .sidePanel.w_review #videoReview .stream { width: 308px; }
  11047. .sidePanel.w_review #videoReview .inner { width: 308px; }
  11048. .sidePanel.w_review .commentContent { width: 286px; }
  11049. .sidePanel.w_review .commentContentBody { width: 240px; }
  11050. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview { width: 400px; }
  11051. body:not(.full_with_browser) .w_wide .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11052. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReviewHead { width: 383px; }
  11053. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .stream { width: 400px; }
  11054. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .inner { width: 400px; }
  11055. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContent { width: 378px; }
  11056. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContentBody { width: 332px; }
  11057. body.videoExplorer .sidePanel.w_review #videoReview { width: 400px; }
  11058. body.videoExplorer .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11059. body.videoExplorer .sidePanel.w_review #videoReviewHead { width: 383px; }
  11060. body.videoExplorer .sidePanel.w_review #videoReview .stream { width: 400px; }
  11061. body.videoExplorer .sidePanel.w_review #videoReview .inner { width: 400px; }
  11062. body.videoExplorer .sidePanel.w_review .commentContent { width: 378px; }
  11063. body.videoExplorer .sidePanel.w_review .commentContentBody { width: 332px; }
  11064.  
  11065. body:not(.videoExplorer) .sidePanel .commentUserProfile, body:not(.videoExplorer) .sidePanel .panelTrigger {
  11066. display: none !important;
  11067. }
  11068. body.videoExplorer .sidePanel .commentUserProfile {
  11069. position: fixed;
  11070. top: 36px !important;
  11071. left: auto !important;
  11072. right: 0 !important;
  11073. z-index: 11000;
  11074. }
  11075.  
  11076. .sidePanel .getMoreReviewComment {
  11077. margin-bottom: 256px;
  11078. }
  11079. */});
  11080. var reviewCss = addStyle(__css__, 'videoReviewCss');
  11081.  
  11082. /*
  11083. EventDispatcher.addEventListener('onFirstVideoInitialized', function() { setTimeout(function() {
  11084. var elms = [
  11085. '#videoReview',
  11086. 'textarea.newVideoReview',
  11087. '#videoReviewHead',
  11088. '#videoReview .stream',
  11089. '#videoReview .inner',
  11090. '.commentContent',
  11091. '.commentContentBody'
  11092. ];
  11093. var css = [], $baseElement = $('#videoReview');
  11094. var makeCss = function (targetWidth, preSel) {
  11095. var px = targetWidth - $baseElement.outerWidth();
  11096. for (var v in elms) {
  11097. var $e = $(elms[v]), newWidth = $e.width() + px;
  11098. css.push([
  11099. preSel, elms[v], ' { width: ', newWidth,'px; }\n'
  11100. ].join(''));
  11101. }
  11102. };
  11103. makeCss(300, '#outline.w_compact ');
  11104. makeCss(308, '.sidePanel.w_review ');
  11105. makeCss(400, '.sidePanel.w_review.w_wide ');
  11106. makeCss(400, 'body.videoExplorer .sidePanel.w_review ');
  11107. console.log(css.join(''));
  11108. var reviewCss = addStyle(css.join(''), 'videoReviewCss');
  11109. }, 3000);});
  11110. */
  11111. } // end initVideoReview
  11112.  
  11113. function initNews() {
  11114. var stopNicoNewsPolling = function() {
  11115. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemDispatcher.stop();
  11116. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemList.list.length = 0;
  11117. };
  11118. var toggleNoNews = function() {
  11119. $('#content').toggleClass('noNews', conf.hideNicoNews || conf.customPlayerSize !== '');
  11120. if ($('#content').hasClass('noNews')) {
  11121. stopNicoNewsPolling();
  11122. }
  11123. };
  11124.  
  11125. EventDispatcher.addEventListener('on.config.hideNicoNews', toggleNoNews);
  11126. EventDispatcher.addEventListener('on.config.customPlayerSize', toggleNoNews);
  11127.  
  11128. toggleNoNews();
  11129.  
  11130. if (conf.enableNewsHistory) { NicoNews.initialize(w); }
  11131. if (conf.hideNewsInFull) { $('body').addClass('hideNewsInFull'); }
  11132. } //
  11133.  
  11134.  
  11135. function initEvents() {
  11136. var pac = watch.PlayerInitializer.playerAreaConnector;
  11137.  
  11138. pac.addEventListener("onVideoInitialized", onVideoInitialized);
  11139. pac.addEventListener("onVideoEnded", onVideoEnded);
  11140. pac.addEventListener("onVideoStopped", onVideoStopped);
  11141. // pac.addEventListener('onSystemMessageFatalErrorSended', onSystemMessageFatalErrorSended);
  11142. // watch.WatchInitializer.watchModel.addEventListener('error', function() {console.log(arguments);});
  11143.  
  11144. pac.addEventListener('updateSettingsPanelVisible', function(isVisible, panel) {
  11145. EventDispatcher.dispatch('onUpdateSettingPanelVisible', isVisible, panel);
  11146. });
  11147.  
  11148. watchInfoModel.addEventListener('reset', onWatchInfoReset);
  11149. watchInfoModel.addEventListener('beforeReset', function() {
  11150. window.console.time('watchInfoModelReset');
  11151. EventDispatcher.dispatch('onWatchInfoBeforeReset');
  11152. });
  11153. watchInfoModel.addEventListener('afterReset', function() {
  11154. window.console.timeEnd('watchInfoModelReset');
  11155. EventDispatcher.dispatch('onWatchInfoAfterReset');
  11156. });
  11157. watch.PlayerInitializer.playerScreenMode.addEventListener('change', onScreenModeChange);
  11158.  
  11159. var explorer = watch.VideoExplorerInitializer.videoExplorer;
  11160. explorer.addEventListener('openStart', onVideoExplorerOpening);
  11161. explorer.addEventListener('openEnd', onVideoExplorerOpened);
  11162. explorer.addEventListener('closeStart', onVideoExplorerClosing);
  11163. explorer.addEventListener('closeEnd', onVideoExplorerClosed);
  11164. explorer.addEventListener('refreshStart', onVideoExplorerRefreshStart);
  11165. explorer.addEventListener('refreshEnd', onVideoExplorerRefreshEnd);
  11166. explorer.addEventListener('changeCurrentPage', onVideoExplorerChangePage); //
  11167.  
  11168.  
  11169. $('body').dblclick(function(e){
  11170. var tagName = e.target.tagName, cls = e.target.className || '';
  11171. if (tagName === 'SELECT' || tagName === 'INPUT' || tagName === 'BUTTON' || cls.match(/mylistPopupPanel/)) {
  11172. return;
  11173. }
  11174. if (!WatchController.isFullScreen()) {
  11175. AnchorHoverPopup.hidePopup();
  11176. if (conf.doubleClickScroll) {
  11177. e.preventDefault();
  11178. EventDispatcher.dispatch('onScrollReset');
  11179. WatchController.scrollToVideoPlayer(true);
  11180. }
  11181. }
  11182. });
  11183.  
  11184. var bottomContentTabView = WatchApp.ns.view.BottomContentTabView.getInstance();
  11185. bottomContentTabView.addEventListener('changeContent', function(name) {
  11186. EventDispatcher.dispatch('onBottomContentTabViewReset', name);
  11187. });
  11188.  
  11189.  
  11190. Mylist.onDefMylistUpdate(function() {
  11191. //WatchController.clearDeflistCache();
  11192. });
  11193. Mylist.onMylistUpdate(function(info) {
  11194. WatchController.clearMylistCache(info.groupId);
  11195. });
  11196.  
  11197. $(window).on('beforeunload.watchItLater', function(e) {
  11198. conf.setValue('lastCommentVisibility', WatchController.commentVisibility() ? 'visible' : 'hidden');
  11199. }).on('resize', window._.debounce(function() {
  11200. AnchorHoverPopup.hidePopup();
  11201. EventDispatcher.dispatch('onWindowResizeEnd');
  11202. }, 1000));
  11203.  
  11204. //$(document).on('scroll', WatchApp.ns.event.EventDispatcher.throttle(function() {
  11205. $(document).on('scroll', function() {
  11206. if (document.body.style.pointerEvents !== 'none') {
  11207. document.body.style.pointerEvents = 'none';
  11208. }
  11209. });
  11210. $(document).on('scroll', window._.debounce(function() {
  11211. document.body.style.pointerEvents = '';
  11212. EventDispatcher.dispatch('onScrollEnd');
  11213. }, 500));
  11214.  
  11215. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11216. pac.addEventListener('onVideoChangeStatusUpdated', onVideoChangeStatusUpdated);
  11217. });
  11218.  
  11219. window.addEventListener('message', function(event) {
  11220. if (event.origin.indexOf('nicovideo.jp') < 0) return;
  11221. try {
  11222. var data = JSON.parse(event.data);
  11223. if (data.id !== 'WatchItLater') { return; }
  11224.  
  11225. EventDispatcher.dispatch('onMessage', data.body, data.type);
  11226.  
  11227. } catch (e) {
  11228. console.log(
  11229. '%cError: window.onMessage - ',
  11230. 'color: red; background: yellow',
  11231. e, event.origin, event.data);
  11232. }
  11233. });
  11234. } //
  11235.  
  11236. function initAdditionalButtons() {
  11237.  
  11238. var createPlaylistToggle = function() {
  11239. var $playlistToggle = $('<button title="プレイリスト表示/非表示" class="playlistToggle">プレイリスト</button>');
  11240.  
  11241. $playlistToggle.on('click', function() {
  11242. AnchorHoverPopup.hidePopup();
  11243. conf.setValue('hidePlaylist', !!!conf.hidePlaylist);
  11244. });
  11245.  
  11246. var togglePlaylistDisplay = function(v) {
  11247. $playlistToggle.toggleClass('w_show', !v);
  11248. };
  11249.  
  11250. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  11251. togglePlaylistDisplay(conf.hidePlaylist);
  11252.  
  11253. return $playlistToggle;
  11254. };
  11255. var createOpenExplorer = function() {
  11256. return $('<button class="openVideoExplorer">検索▼</button>').on('click', function() {
  11257. WatchController.openSearch();
  11258. if (!$('body').hasClass('content-fix')) {
  11259. WatchController.scrollToVideoPlayer(true);
  11260. }
  11261. });
  11262. };
  11263. var $div = $('<div class="bottomAccessContainer"/>').append(createPlaylistToggle()).append(createOpenExplorer());
  11264.  
  11265.  
  11266. var $headerMenu = $('<li class="watchItLaterSettingMenu"><a href="javascript:;" title="WatchItLaterの設定">WatchItLater設定</a></li>');
  11267. $('#siteHeaderRightMenuFix').after($headerMenu);
  11268.  
  11269. $('#outline .outer').before($div);
  11270. var $container = $('<div class="bottomConfButtonContainer" />'), $conf = $('<button title="WatchItLaterの設定">設定</button>');
  11271. var $explorerConf = $('<button><span class="open">`・ω・´</span><span class="close">´・ω・`</span></button>');
  11272. var toggleConf = function(e) {
  11273. e.stopPropagation();
  11274. AnchorHoverPopup.hidePopup();
  11275. ConfigPanel.toggle();
  11276. };
  11277. $container.append($conf);
  11278. $conf.addClass('openConfButton');
  11279. $conf.on('click', toggleConf);//.attr('accesskey', 'p');
  11280. $('#outline .outer').before($container);
  11281. $headerMenu.find('a').on('click', toggleConf);//.attr('accesskey', 'p');
  11282.  
  11283.  
  11284. $('.videoExplorerBody').append($explorerConf);
  11285. $explorerConf
  11286. .on('click',
  11287. function() { WatchItLater.config.set('videoExplorerHack', !WatchItLater.config.get('videoExplorerHack')); })
  11288. .addClass('videoExplorerConfig');
  11289.  
  11290. var $body = $('body'), $window = $(window);
  11291. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  11292. if (WatchController.isSearchMode() || WatchController.isFullScreen()) { return; }
  11293. var w = $div.outerWidth(), threshold = ($(window).innerWidth() - 960) / 2;
  11294. $('#outline').toggleClass('under960', w > threshold && !$('#footer').hasClass('noBottom'));
  11295. });
  11296. } // end initAdditionalButtons
  11297.  
  11298.  
  11299. function initSearchContent($, conf, w) {
  11300. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11301. var SearchSortOrder = WatchApp.ns.components.videoexplorer.model.SearchSortOrder;
  11302. var View = WatchApp.ns.components.videoexplorer.view.content.SearchContentView;
  11303. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11304. var explorer = vec.getVideoExplorer();
  11305. var content = explorer.getContentList().getContent(ContentType.SEARCH);
  11306. var relatedTag = new NicoSearchRelatedTag({});
  11307. var newSearch = new NewNicoSearch({});
  11308. var newSearchWrapper = new NewNicoSearchWrapper({search: newSearch});
  11309. var pager = content._pager;
  11310. var __css__ = Util.here(function() {/*
  11311. .newSearchOption {
  11312. text-align: center; margin-bottom: 16px; padding: 8px;
  11313. background: #eee;
  11314. display: none;
  11315. }
  11316. .newSearchOption select, .newSearchOption label{
  11317. margin-right: 32px;
  11318. }
  11319. .newSearchOption .reset{
  11320. cursor: pointer; background: #eee;
  11321. }
  11322. .newSearchOption p{
  11323. margin: 8px;
  11324. }
  11325. .newSearchOption .ownerName {
  11326. }
  11327. .w_sugoiSearch .newSearchOption {
  11328. display: block;
  11329. }
  11330.  
  11331. .relatedTagList {
  11332. }
  11333. .relatedTagList p{
  11334. display: inline-block; margin: 4px;
  11335. }
  11336. .relatedTagList li, .relatedTagList ul {
  11337. display: inline;
  11338. margin: 0 8px 0 0;
  11339. list-style: none;
  11340. word-break: break-all;
  11341. }
  11342. .relatedTagList li {
  11343. background: #f4f4f4; padding: 2px 4px;
  11344. border: solid 1px #999;
  11345. border-radius: 4px;
  11346. line-height: 180%;
  11347. }
  11348. .relatedTagList li:hover {
  11349. }
  11350. .relatedTagList li:hover a{
  11351. text-decoration: none;
  11352. }
  11353.  
  11354. .sugoiOption {
  11355. display: none;
  11356. }
  11357. .w_sugoiSearch .sugoiOption {
  11358. display: block; background: #eef;
  11359. }
  11360. .w_sugoiSearch optgroup.sugoiOption {
  11361. font-weight: bolder;
  11362. }
  11363.  
  11364. */});
  11365. addStyle(__css__, 'searchContent');
  11366.  
  11367. // 動画表示のテンプレート拡張
  11368. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html());
  11369. $template.find('.searchSortOrder')
  11370. .append([
  11371. '<optgroup label="新検索専用" class="sugoiOption">',
  11372. '<option value="sort=_hot&amp;order=d"" class="sugoiOption">人気が高い順</option>',
  11373. '<option value="sort=_popular&amp;order=d" class="sugoiOption">並び順指定なし</option>',
  11374. '<option value="sort=_explore&amp;order=d" class="sugoiOption">新着優先</option>',
  11375. '</optgroup>'
  11376. ].join(''));
  11377. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html($template.html());
  11378. $template = null;
  11379.  
  11380.  
  11381.  
  11382. var RelatedTagView = function() { this.initialize.apply(this, arguments); };
  11383. RelatedTagView.prototype = {
  11384. _$view: null,
  11385. _relatedTag: null,
  11386. initialize: function(params) {
  11387. this._relatedTag = params.relatedTag;
  11388. this._$view = params.$view;
  11389. this._$list = this._$view.find('ul');
  11390. },
  11391. getView: function() {
  11392. return this._$view;
  11393. },
  11394. detach: function() {
  11395. this._$view.detach();
  11396. },
  11397. update: function(candidates) {
  11398. if (!candidates || candidates.length < 1) {
  11399. this.detach();
  11400. return;
  11401. }
  11402. if (candidates.length > 10) {
  11403. candidates = candidates
  11404. .map(function(a){return {weight:Math.random(), value:a};})
  11405. .sort(function(a, b){return a.weight - b.weight;})
  11406. .map(function(a){return a.value;});
  11407. }
  11408. var $ul = this._$list.empty();
  11409. for (var i = 0, len = Math.min(10, candidates.length); i < len; i++) {
  11410. $ul.append(this._create$tag(candidates[i].tag));
  11411. }
  11412. },
  11413. clear: function() {
  11414. this._$list.empty();
  11415. },
  11416. _create$tag: function(text) {
  11417. var
  11418. $a = $('<a/>')
  11419. .html(text)
  11420. .attr('href', 'http://search.nicovideo.jp/video/tag/' + encodeURIComponent(text))
  11421. .on('click', Util.Closure.openNicoSearch(text)),
  11422. $tag = $('<li/>').append($a);
  11423. return $tag;
  11424. }
  11425. };
  11426.  
  11427. var NewSearchOptionView = function() { this.initialize.apply(this, arguments); };
  11428. NewSearchOptionView.prototype = {
  11429. _content: null,
  11430. _$view: null,
  11431. _$startTimeRange: null,
  11432. _$lengthSecondsRange: null,
  11433. initialize: function(params) {
  11434. this._content = params.content;
  11435. this._$view = params.$view;
  11436. this._$startTimeRange = this._$view.find('.startTimeRange');
  11437. this._$lengthSecondsRange = this._$view.find('.lengthSecondsRange');
  11438. this._$musicDlFilter = this._$view.find('.musicDlFilter');
  11439. this._$ownerFilter = this._$view.find('.ownerFilter');
  11440. this._$ownerName = this._$view.find('.ownerName');
  11441. this._$resetButton = this._$view.find('.reset');
  11442.  
  11443. this._$startTimeRange .val(params.startTimeRange || '');
  11444. this._$lengthSecondsRange.val(params.lengthSecondsRange || '');
  11445. this._$musicDlFilter .attr('checked', !!params.musicDlFilter);
  11446.  
  11447. this._$startTimeRange .on('change', $.proxy(this._onStartTimeRangeSelect , this));
  11448. this._$lengthSecondsRange.on('change', $.proxy(this._onLengthSecondsRangeSelect, this));
  11449. this._$musicDlFilter .on('click', $.proxy(this._onMusicDlFilterChange , this));
  11450. this._$ownerFilter .on('click', $.proxy(this._onOwnerFilterChange , this));
  11451. this._$resetButton .on('click', $.proxy(this.reset , this));
  11452.  
  11453. EventDispatcher.addEventListener('onVideoOwnerChanged', $.proxy(this.onVideoOwnerChange, this));
  11454. this._$ownerName.text(WatchController.getOwnerName());
  11455. },
  11456. getView: function() {
  11457. return this._$view;
  11458. },
  11459. detach: function() {
  11460. this._$view.detach();
  11461. },
  11462. update: function() {
  11463. },
  11464. onVideoOwnerChange: function(ownerInfo) {
  11465. this._content.setOwnerFilter(false);
  11466. this._$ownerFilter.prop('checked', false);
  11467. this._$ownerName.text(ownerInfo.name);
  11468. },
  11469. _onStartTimeRangeSelect: function() {
  11470. this._content.setStartTimeRange(this._$startTimeRange.val());
  11471. this.contentRefresh();
  11472. },
  11473. _onLengthSecondsRangeSelect: function() {
  11474. this._content.setLengthSecondsRange(this._$lengthSecondsRange.val());
  11475. this.contentRefresh();
  11476. },
  11477. _onMusicDlFilterChange: function() {
  11478. this._content.setMusicDlFilter(!!this._$musicDlFilter.prop('checked'));
  11479. this.contentRefresh();
  11480. },
  11481. _onOwnerFilterChange: function() {
  11482. this._content.setOwnerFilter(!!this._$ownerFilter.prop('checked'));
  11483. this.contentRefresh();
  11484. },
  11485. contentRefresh: function() {
  11486. var params = this._content.getParams();
  11487. params.page = 1;
  11488. this._content.changeState(params);
  11489. this._content.refresh({page: 1});
  11490. },
  11491. refresh: function() {
  11492. //console.log('refresh!', this._content.getOwnerFilter(), this._content.getMusicDlFilter(false));
  11493. this._$startTimeRange .val(this._content.getStartTimeRange('') || '');
  11494. this._$lengthSecondsRange.val(this._content.getLengthSecondsRange('') || '');
  11495. this._$musicDlFilter .prop('checked', !!this._content.getMusicDlFilter(false));
  11496. this._$ownerFilter .prop('checked', !!this._content.getOwnerFilter());
  11497. },
  11498. reset: function() {
  11499. var v = this._$startTimeRange.val() + this._$lengthSecondsRange.val();
  11500. if (v !== '') {
  11501. this._content.setStartTimeRange('');
  11502. this._content.setLengthSecondsRange('');
  11503. this._content.setMusicDlFilter(false);
  11504. this._$startTimeRange.val('');
  11505. this._$lengthSecondsRange.val('');
  11506. this._$musicDlFilter.prop('checked', false);
  11507. this._content.changeState({ page: 1 });
  11508. //this._content.refresh({ page: 1 });
  11509. }
  11510. }
  11511. };
  11512.  
  11513. var relatedTagView = new RelatedTagView({
  11514. relatedTag: relatedTag,
  11515. $view: $('<div class="relatedTagList"><p>関連タグ: </p><ul></ul></div>')
  11516. });
  11517. var newSearchOptionView = new NewSearchOptionView({
  11518. content: content,
  11519. startTimeRange: conf.searchStartTimeRange,
  11520. lengthSecondsRange: conf.searchLengthSecondsRange,
  11521. musicDlFilter: conf.searchMusicDlFilter,
  11522. $view: $([
  11523. '<div class="newSearchOption">',
  11524. '<span>投稿日時: </span>',
  11525. '<select class="startTimeRange" name="u">',
  11526. '<option selected="selected" value="" >指定なし</option>',
  11527. '<option value="24h">24時間以内</option>',
  11528. '<option value="1w" >1週間以内</option>',
  11529. '<option value="1m" >1ヶ月(30日)以内</option>',
  11530. '<option value="3m" >3ヶ月(90日)以内</option>',
  11531. '<option value="6m" >6ヶ月(180日)以内</option>',
  11532. '</select>',
  11533. '<span>再生時間: </span>',
  11534. '<select class="lengthSecondsRange" name="l">',
  11535. '<option selected="selected" value="" >指定なし</option>',
  11536. '<option value="short">5分以内</option>',
  11537. '<option value="long" >20分以上</option>',
  11538. '</select>',
  11539. '<p>',
  11540. '<label>',
  11541. '<input type="checkbox" name="m" class="musicDlFilter">音楽DL対応のみ</input>',
  11542. '</label>',
  11543. '<label>',
  11544. '<input type="checkbox" name="owner" class="ownerFilter"><span class="ownerName">この投稿者</span>&nbsp;の動画のみ</input>',
  11545. '</label>',
  11546. '</p>',
  11547. '</div>',
  11548. ''].join(''))
  11549. });
  11550.  
  11551.  
  11552.  
  11553. content._originalWord = '';
  11554. content.changeState_org = content.changeState;
  11555. content.changeState = $.proxy(function(params, callback) {
  11556. var word = WatchApp.get(params, 'searchWord', 'string', '');
  11557. var type = WatchApp.get(params, 'searchType', 'string', this.getSearchType());
  11558. if (typeof word === 'string' && word.length > 0) {
  11559. this._originalWord = word;
  11560.  
  11561. if (conf.defaultSearchOption && conf.defaultSearchOption !== '') {
  11562. if (word.indexOf(conf.defaultSearchOption) < 0 && !word.match(/(sm|nm|so)\d+/)) {
  11563. params.searchWord += " " + conf.defaultSearchOption;
  11564. }
  11565. }
  11566. }
  11567. AnchorHoverPopup.hidePopup();
  11568.  
  11569. EventDispatcher.dispatch('onSearchStart', this._originalWord, type);
  11570. this.changeState_org(params, callback);
  11571. }, content);
  11572.  
  11573. // ニコニコ新検索エンジンを使うための布石
  11574. content._searchEngineType = conf.searchEngine;
  11575. content._lastSearchEngineType = conf.searchEngine;
  11576. content.setSearchEngineType = $.proxy(function(type) {
  11577. this._searchEngineType = type;
  11578. this.updateSearchPageItemCount();
  11579. }, content);
  11580. content.updateSearchPageItemCount = $.proxy(function() {
  11581. this._pager._pageItemCount = this._searchEngineType === 'sugoi' ? conf.searchPageItemCount : 32;
  11582. }, content);
  11583. content.getSearchEngineType = $.proxy(function() {
  11584. return this._searchEngineType === 'sugoi' ? 'sugoi' : 'normal';
  11585. }, content);
  11586. content.setLastSearchEngineType = $.proxy(function(type) { this._lastSearchEngineType = type; }, content);
  11587. content.getLastSearchEngineType = $.proxy(function() { return this._lastSearchEngineType; }, content);
  11588. content._newSearchWrapper = newSearchWrapper;
  11589.  
  11590. content._startTimeRange = conf.searchStartTimeRange;
  11591. content._lengthSecondsRange = conf.searchLengthSecondsRange;
  11592. content._musicDlFilter = conf.searchMusicDlFilter;
  11593. content._ownerFilter = false;
  11594.  
  11595. content.getStartTimeRange = $.proxy(function() { return this._startTimeRange; }, content);
  11596. content.getLengthSecondsRange = $.proxy(function() { return this._lengthSecondsRange; }, content);
  11597. content.getMusicDlFilter = $.proxy(function() { return this._musicDlFilter; }, content);
  11598. content.getOwnerFilter = $.proxy(function() { return this._ownerFilter; }, content);
  11599. content.setStartTimeRange = $.proxy(function(value) {
  11600. this._startTimeRange = value;
  11601. conf.setValue('searchStartTimeRange', value);
  11602. }, content);
  11603. content.setLengthSecondsRange = $.proxy(function(value) {
  11604. this._lengthSecondsRange = value;
  11605. conf.setValue('searchLengthSecondsRange',value);
  11606. }, content);
  11607. content.setMusicDlFilter = $.proxy(function(value) {
  11608. this._musicDlFilter = !!value;
  11609. conf.setValue('searchMusicDlFilter', !!value);
  11610. }, content);
  11611. content.setOwnerFilter = $.proxy(function(value) {
  11612. this._ownerFilter = !!value;
  11613. }, content);
  11614.  
  11615. // 新検索独自のソート順への対応
  11616. content._searchSortOrder._flush_org = content._searchSortOrder._flush;
  11617. content._searchSortOrder._flush = $.proxy(function() {
  11618. var sort = this._sort[WatchApp.ns.components.videoexplorer.model.SearchType.KEYWORD];
  11619. if (sort === '_hot' || sort === '_popular' || sort === '_explore') { // 新検索にしかないパラメータは保存しない
  11620. return;
  11621. }
  11622. this._flush_org();
  11623. }, content._searchSortOrder);
  11624.  
  11625.  
  11626. EventDispatcher.addEventListener('on.config.searchPageItemCount', function() {
  11627. content.updateSearchPageItemCount();
  11628. });
  11629.  
  11630. content.getParams_org = content.getParams;
  11631. content.getParams = $.proxy(function() {
  11632. var params = this.getParams_org();
  11633. params = $.extend(true, {
  11634. l: this.getLengthSecondsRange(),
  11635. u: this.getStartTimeRange(),
  11636. m: this.getMusicDlFilter(),
  11637. size: this._pager._pageItemCount
  11638. }, params);
  11639. if (this.getOwnerFilter()) {
  11640. if (WatchController.isChannelVideo()) {
  11641. params.channelId = WatchController.getOwnerId();
  11642. } else {
  11643. params.userId = WatchController.getOwnerId();
  11644. }
  11645. }
  11646. return params;
  11647. }, content);
  11648.  
  11649. // タグ検索だけ毎回ソート順がデフォルトにリセットされるようになったので、
  11650. // デフォルト値を書き換えるという力技で対抗
  11651. SearchSortOrder.TAG_DEFAULT_SORT = conf.searchSortType;
  11652. SearchSortOrder.TAG_DEFAULT_ORDER = conf.searchSortOrder;
  11653. content._searchSortOrder.getSortFromCookie = function() { return conf.searchSortType; };
  11654. content._searchSortOrder.getOrderFromCookie = function() { return conf.searchSortOrder; };
  11655.  
  11656. content.load_org = content.load;
  11657. content.load = $.proxy(function(params, callback) {
  11658. var word = this.getSearchWord();
  11659. if (this.getSearchEngineType() !== 'sugoi' || word.length <= 0 || word.match(/(sm|nm|so)\d+/)) {
  11660. // 新検索ではもしかして~が取得できないため、検索ワードに動画IDっぽい文字列が含まれてる場合は旧タグ検索を使う。
  11661. this.setLastSearchEngineType('normal');
  11662. params.sort = 'n';
  11663. params.order = 'd';
  11664. this.load_org(params, callback);
  11665. } else {
  11666. this.setLastSearchEngineType('sugoi');
  11667. params = this.getParams();
  11668.  
  11669.  
  11670. this._newSearchWrapper.load(params, function(err, result) {
  11671. console.log('%cNewNicoSearchWrapper result', 'color: green;', result);
  11672. callback(err, result);
  11673. });
  11674. }
  11675. }, content);
  11676. content.setSearchEngineType(conf.searchEngine);
  11677.  
  11678. EventDispatcher.addEventListener('on.config.searchEngine', function(type) {
  11679. content.setSearchEngineType(type);
  11680. });
  11681.  
  11682.  
  11683. var
  11684. overrideSearchSortOrder = function(proto) { // ソート順を記憶するためのフック
  11685. proto.getSort_org = proto.getSort;
  11686. proto.getSort = function() {
  11687. var sort = conf.searchSortType;
  11688. if ((sort === '_hot' || sort === '_popular' || sort === '_explore') && content.getLastSearchEngineType() !== 'sugoi') {
  11689. // 通常検索で新検索にしかないソート順だったらデフォルトのnを返す
  11690. return 'n';
  11691. }
  11692. return conf.searchSortType;
  11693. };
  11694.  
  11695. proto.setSort_org = proto.setSort;
  11696. proto.setSort = function(type, sort) {
  11697. conf.setValue('searchSortType', sort);
  11698. SearchSortOrder.TAG_DEFAULT_SORT = sort;
  11699. this.setSort_org(type, sort);
  11700. };
  11701.  
  11702. proto.getOrder_org = proto.getOrder;
  11703. proto.getOrder = function() {
  11704. return conf.searchSortOrder;
  11705. };
  11706.  
  11707. proto.setOrder_org = proto.setOrder;
  11708. proto.setOrder = function(type, order) {
  11709. if (content.getLastSearchEngineType() === 'sugoi') { // 新検索の時だけソート順を記憶
  11710. SearchSortOrder.TAG_DEFAULT_ORDER = order;
  11711. conf.setValue('searchSortOrder', order);
  11712. }
  11713. this.setOrder_org(type, order);
  11714. };
  11715. },
  11716. overrideSearchContentView = function(proto, relatedTag) {
  11717. proto._updateRelatedTag = function() {
  11718. if (!conf.enableRelatedTag) { return; }
  11719. var word = this._content._originalWord;
  11720. relatedTagView.clear();
  11721.  
  11722. if (typeof word === 'string' && word.length > 0) {
  11723. this._$header.append(relatedTagView.getView());
  11724. relatedTag.load(word, function(err, result) {
  11725. console.log('SearchContentView._updateRelatedTag', err, result);
  11726. if (err) {
  11727. console.log('load suggest fail', err, result);
  11728. } else {
  11729. relatedTagView.update(result.values);
  11730. }
  11731. });
  11732. }
  11733. };
  11734.  
  11735. proto.detach_org = proto.detach;
  11736. proto.detach = function() {
  11737. this.detach_org();
  11738. newSearchOptionView.detach();
  11739. relatedTagView.detach();
  11740. };
  11741.  
  11742. proto.onUpdate_org = proto.onUpdate;
  11743. proto.onUpdate = function() {
  11744. this.onUpdate_org();
  11745. this._$content.find('.searchBox').after(newSearchOptionView.getView());
  11746. this._updateRelatedTag();
  11747. var engine = this._content.getLastSearchEngineType();
  11748. newSearchOptionView.refresh();
  11749. $('.videoExplorerBody')
  11750. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  11751. .toggleClass('w_normalSearch', engine !== 'sugoi');
  11752. };
  11753.  
  11754. proto.onError_org = proto.onError;
  11755. proto.onError = function() {
  11756. this.onError_org();
  11757. this._$header.append(newSearchOptionView.getView());
  11758. this._updateRelatedTag();
  11759. var engine = this._content.getLastSearchEngineType();
  11760. $('.videoExplorerBody')
  11761. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  11762. .toggleClass('w_normalSearch', engine !== 'sugoi');
  11763. };
  11764.  
  11765. };
  11766.  
  11767. overrideSearchSortOrder(SearchSortOrder.prototype);
  11768. overrideSearchContentView(View.prototype, relatedTag);
  11769.  
  11770. } // end initSearchContent
  11771.  
  11772. function initUserVideoContent($, conf, w) {
  11773. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11774. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11775. var explorer = vec.getVideoExplorer();
  11776. var content = explorer.getContentList().getContent(ContentType.USER_VIDEO);
  11777. var pager = content._pager;
  11778.  
  11779. pager._pageItemCount = conf.searchPageItemCount;
  11780. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  11781. pager._pageItemCount = v;
  11782. });
  11783. }
  11784.  
  11785. function initUploadedVideoContent($, conf, w) {
  11786. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11787. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11788. var explorer = vec.getVideoExplorer();
  11789. var content = explorer.getContentList().getContent(ContentType.UPLOADED_VIDEO);
  11790. var pager = content._pager;
  11791.  
  11792. pager._pageItemCount = conf.searchPageItemCount;
  11793. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  11794. pager._pageItemCount = v;
  11795. });
  11796. }
  11797.  
  11798.  
  11799. var isSquareCssInitialized = false;
  11800. function initSquareThumbnail() {
  11801. var isSquare = true;// !!conf.squareThumbnail;
  11802. if (isSquare && !isSquareCssInitialized) {
  11803. var __css__ = Util.here(function() {/*
  11804. {* 元のCSSを打ち消すためにやや冗長 *}
  11805. #videoExplorer .noImage,
  11806. #videoExplorer.w_adjusted .column4 .item .thumbnail
  11807. {* #videoExplorer.w_adjusted .column1 .item .thumbnail:not(.smallThumbnail) *}{
  11808. display: none !important;
  11809. }
  11810. #videoExplorer .thumbnailContainer {
  11811. background-size: contain;
  11812. background-repeat: no-repeat;
  11813. background-position: center center;
  11814. }
  11815. #videoExplorer.w_adjusted .column4 .item .thumbnailContainer {
  11816. width: 130px; height: 100px;
  11817. margin-right: 7px;
  11818. border: 1px solid #888;
  11819. }
  11820.  
  11821. #videoExplorer.w_adjusted .column4 .uadFrame,
  11822. #videoExplorer.w_adjusted .uadTagRelated .uadFrame {
  11823. width: 130px; height: 100px;
  11824. background-size: 100% 100%;
  11825. }
  11826. #videoExplorer.w_adjusted .uadTagRelated .default .itemList .item .imageContainer {
  11827. width: 130px; height: 100px;
  11828. }
  11829.  
  11830. #videoExplorer.w_adjusted .column1 .item .thumbnailContainer {
  11831. border: 1px solid #888;
  11832. margin-right: 8px;
  11833. }
  11834. */});
  11835.  
  11836. addStyle(__css__, 'squareThumbnailCss');
  11837. isSquareCssInitialized = true;
  11838. }
  11839. //$('#videoExplorer').toggleClass('squareThumbnail', isSquare);
  11840. } //
  11841.  
  11842. function initPageBottom($, conf, w) {
  11843. function updateHideVideoExplorerExpand(v) {
  11844. $('#content, #outline').toggleClass('w_hideSearchExpand', v === true);
  11845. }
  11846. function updateIchibaVisibility(v) {
  11847. $('#outline').toggleClass('noIchiba', v === 'hidden');
  11848. }
  11849. function updateReviewVisibility(v) {
  11850. $('#outline').toggleClass('noReview', v === 'hidden');
  11851. }
  11852. function updateBottomContentsVisibility(v) {
  11853. $('#bottomContentTabContainer, #footer').toggleClass('noBottom', v === 'hidden');
  11854. }
  11855.  
  11856. EventDispatcher.addEventListener('on.config.hideVideoExplorerExpand', updateHideVideoExplorerExpand);
  11857. EventDispatcher.addEventListener('on.config.ichibaVisibility', updateIchibaVisibility);
  11858. EventDispatcher.addEventListener('on.config.reviewVisibility', updateReviewVisibility);
  11859. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', updateBottomContentsVisibility);
  11860. if (conf.hideVideoExplorerExpand === true) { updateHideVideoExplorerExpand(true); }
  11861. if (conf.ichibaVisibility !== 'visible') { updateIchibaVisibility(conf.ichibaVisibility); }
  11862. if (conf.reviewVisibility !== 'visible') { updateReviewVisibility(conf.reviewVisibility); }
  11863. if (conf.bottomContentsVisibility !== 'visible') { updateBottomContentsVisibility(conf.bottomContentsVisibility); }
  11864.  
  11865. var $bottomToggle = $('<div class="toggleBottom"><div class="openBottom">▽</div><div class="closeBottom">△</div></div>');
  11866. $bottomToggle.on('click', function() {
  11867. var v = conf.bottomContentsVisibility;
  11868. conf.setValue('bottomContentsVisibility', v === 'hidden' ? 'visible' : 'hidden');
  11869. //ConfigPanel.refresh();
  11870. }).attr('title', '市場・レビューの開閉');
  11871. $('#footer').append($bottomToggle);
  11872. } //
  11873.  
  11874.  
  11875.  
  11876. function initShortcutKey() {
  11877. var list = [
  11878. {name: 'shortcutTogglePlay', exec: function(e) {
  11879. WatchController.togglePlay();
  11880. }},
  11881. {name: 'shortcutDefMylist', exec: function(e) {
  11882. WatchController.addDefMylist();
  11883. }},
  11884. {name: 'shortcutMylist', exec: function(e) {
  11885. $('#mylist_add_frame').find('.mylistAdd').click();
  11886. }},
  11887. {name: 'shortcutOpenDefMylist', exec: function(e) {
  11888. WatchController.showDeflist();
  11889. WatchController.scrollToVideoPlayer(true);
  11890. }},
  11891. {name: 'shortcutOpenSearch', exec: function(e) {
  11892. WatchController.openSearch();
  11893. if (!$('body').hasClass('content-fix')) {
  11894. WatchController.scrollToVideoPlayer(true);
  11895. }
  11896. }},
  11897. {name: 'shortcutOpenRecommend', exec: function(e) {
  11898. WatchController.openRecommend();
  11899. if (!$('body').hasClass('content-fix')) {
  11900. WatchController.scrollToVideoPlayer(true);
  11901. }
  11902. }},
  11903. {name: 'shortcutScrollToNicoPlayer', exec: function(e) {
  11904. WatchController.scrollToVideoPlayer(true);
  11905. }},
  11906. {name: 'shortcutCommentVisibility', exec: function(e) {
  11907. WatchController.commentVisibility('toggle');
  11908. }},
  11909. {name: 'shortcutShowOtherVideo', exec: function(e) {
  11910. WatchController.openVideoOwnersVideo();
  11911. }},
  11912. {name: 'shortcutMute', exec: function(e) {
  11913. WatchController.mute('toggle');
  11914. }},
  11915. {name: 'shortcutDeepenedComment', exec: function(e) {
  11916. WatchController.deepenedComment('toggle');
  11917. }},
  11918. {name: 'shortcutToggleStageVideo', exec: function(e) {
  11919. WatchController.toggleStageVideo();
  11920. }},
  11921. {name: 'shortcutInvisibleInput', exec: function(e) {
  11922. $('.invisibleCommentInput').focus();
  11923. }}
  11924. ];
  11925. for (var v in list) {
  11926. var n = list[v].name;
  11927. list[v].keyMatch = KeyMatch.create(conf[n]);
  11928. }
  11929.  
  11930. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  11931. for (var v in list) {
  11932. var n = list[v].name;
  11933. if (n === name) {
  11934. list[v].keyMatch = KeyMatch.create(newValue);
  11935. }
  11936. }
  11937. });
  11938.  
  11939. $('body').on('keydown.watchItLater', function(e) {
  11940. // 一部のキーボードについているMusic Key(正式名称不明)に対応 Chromeしか拾えない?
  11941. if (e.keyCode === 178) { // 停止
  11942. WatchController.togglePlay();
  11943. } else
  11944. if (e.keyCode === 179) { // 一時停止
  11945. WatchController.togglePlay();
  11946. } else
  11947. if (e.keyCode === 177) { // 前の曲
  11948. if (WatchController.vpos() > 2000) {
  11949. WatchController.vpos(0);
  11950. } else {
  11951. WatchController.prevVideo();
  11952. }
  11953. } else
  11954. if (e.keyCode === 176) { // 次の曲
  11955. WatchController.nextVideo();
  11956. }
  11957. if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
  11958. return;
  11959. }
  11960. // 全画面時はFlashにフォーカスがなくてもショートカットキーが効くようにする
  11961.  
  11962. for (var v in list) {
  11963. var n = list[v].name;
  11964. if (list[v].keyMatch.test(e)) {
  11965. list[v].exec(e);
  11966. }
  11967. }
  11968. });
  11969. } //
  11970.  
  11971. function initNicoS($, conf, w) {
  11972. WatchJsApi.nicos.addEventListener('nicoSJump', function(e) {
  11973. if (conf.ignoreJumpCommand) {
  11974. e.cancel();
  11975. Popup.show('「@ジャンプ」コマンドをキャンセルしました');
  11976. }
  11977. });
  11978. var seekCount = 0;
  11979. WatchApp.ns.model.player.NicoSClientConnector.addEventListener('nicoSSeek', function(e) {
  11980. seekCount++;
  11981. if (conf.nicoSSeekCount < 0) return;
  11982. if (seekCount > conf.nicoSSeekCount) {
  11983. e.cancel();
  11984. }
  11985. });
  11986. // 動画が切り替わったか、最後まで視聴したらカウンターリセット
  11987. EventDispatcher.addEventListener('onVideoInitialized', function() {
  11988. seekCount = 0;
  11989. });
  11990. EventDispatcher.addEventListener('onVideoEnded', function() {
  11991. seekCount = 0;
  11992. });
  11993. } //
  11994.  
  11995. function initMouse() {
  11996. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  11997. if (name === 'mouseClickWheelVolume') {
  11998. if (oldValue === 0) {
  11999. initWheelWatch();
  12000. } else
  12001. if (newValue === 0) {
  12002. $(document)
  12003. .unbind('mousewheel.watchItLaterWheelWatch')
  12004. .unbind('mousedown.watchItLaterWheelWatch')
  12005. .unbind('mouseup.watchItLaterWheelWatch');
  12006. }
  12007. }
  12008. });
  12009.  
  12010. function initWheelWatch() {
  12011. var leftDown = false, rightDown = false, isVolumeChanged = false;
  12012. var event = {
  12013. cancel: false,
  12014. reset: function() { this.cancel = false; return this; },
  12015. preventDefault: function() { this.cancel = true;}
  12016. };
  12017. $(document).on('mousewheel.watchItLaterWheelWatch', function(e, delta) {
  12018. var button = -1;
  12019. // TODO: マジックナンバーを
  12020. if (typeof e.buttons === 'number') { // firefox
  12021. button = e.buttons;
  12022. } else { // chrome
  12023. if (leftDown) { button = 1; }
  12024. else
  12025. if (rightDown) { button = 2; }
  12026. }
  12027. if (button < 1) {
  12028. EventDispatcher._dispatch('onWheelNoButton', e, delta);
  12029. return;
  12030. }
  12031. EventDispatcher.dispatch('onWheelAndButton', event.reset(), delta, button);
  12032. if (event.cancel) {
  12033. e.preventDefault();
  12034. return;
  12035. }
  12036. if (conf.mouseClickWheelVolume !== button) {
  12037. return;
  12038. }
  12039.  
  12040. var v = WatchController.volume(), r;
  12041. isVolumeChanged = true;
  12042. // 音量を下げる時は「うわ音でけぇ!」
  12043. // 音量を上げる時は「ちょっと聞こえにくいな」…というパターンが多いので、変化の比率が異なる
  12044. if (delta > 0) {
  12045. v = Math.max(v, 1);
  12046. r = (v < 5) ? 1.3 : 1.1;
  12047. v = WatchController.volume(v * r);
  12048. } else {
  12049. v = WatchController.volume(Math.floor(v / 1.2));
  12050. }
  12051. e.preventDefault();
  12052. }).on('mousedown.watchItLaterWheelWatch', function(e) { // chromeはホイールイベントでe.buttonsが取れないため
  12053. if (e.which === 1) leftDown = true;
  12054. if (e.which === 3) rightDown = true;
  12055. }).on('mouseup.watchItLaterWheelWatch', function(e) {
  12056. if (e.which === 1) leftDown = false;
  12057. if (e.which === 3) rightDown = false;
  12058. }).on('contextmenu.watchItLaterWheelWatch', function(e) {
  12059. if (isVolumeChanged) {
  12060. e.preventDefault();
  12061. }
  12062. isVolumeChanged = false;
  12063. });
  12064. }
  12065. window.setTimeout(function() { initWheelWatch(); }, 5000);
  12066. } // end initMouse
  12067.  
  12068. function initTouch() {
  12069. var touchInitialized = false;
  12070. TouchEventDispatcher.onflick(function(e) {
  12071. var se = e.startEvent;
  12072. if (!conf.enableQTouch) {return; }
  12073. if (e.direction === 'right') {
  12074. if (se.target.id === 'playerTabWrapper') {
  12075. $(se.target).addClass('w_active');
  12076. }
  12077. if (!touchInitialized) {
  12078. $('#mylist_add_frame, #leftPanelTabContainer, .videoExplorerMenu, #playerTabWrapper').addClass('w_touch');
  12079. $('.userProfile, .resultPagination, #searchResultContainer select, .playlistMenuPopup').addClass('w_touch');
  12080. isTouchActive = true;
  12081. touchInitialized = true;
  12082. }
  12083. } else
  12084. if (e.direction === 'left') {
  12085. if (se.target.tagName === 'DIV' &&
  12086. $.contains('#playerTabWrapper', se.target)) {
  12087. $('#playerTabWrapper').removeClass('w_active');
  12088. }
  12089. }
  12090. });
  12091. } //
  12092.  
  12093. function initOtherCss() {
  12094. var __dynamic_css_template__ = Util.here(function() {/*
  12095. .full_with_browser.w_fullScreenMenu #nicoHeatMap {
  12096. transform: scaleX($scale); -webkit-transform: scaleX($scale); display: block;
  12097. }
  12098. */});
  12099. var exStyle = null;
  12100. var updateDynamicCss = function() {
  12101. var css = __dynamic_css_template__;
  12102. var innerWidth = $('body').innerWidth();
  12103. css = css.split('$scale').join($('body').innerWidth() / 100);
  12104. if (exStyle) {
  12105. exStyle.innerHTML = css;
  12106. return exStyle;
  12107. } else {
  12108. return addStyle(css, 'expression');
  12109. }
  12110. };
  12111. exStyle = updateDynamicCss();
  12112.  
  12113. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12114. updateDynamicCss();
  12115. });
  12116.  
  12117. var __gpuLayer__ = (function() {/*
  12118. body.videoExplorer.content-fix #playerTabWrapper,
  12119. body.videoExplorer.content-fix .videoExplorerMenu,
  12120. body.videoExplorer.content-fix #playlist,
  12121. {*#playerTabWrapper .playerCommentPanel*}
  12122. body:not(.full_with_browser) .mylistPopupPanel.fixed
  12123. {
  12124. -moz-transform: translateZ(0);
  12125. -webkit-transform: translateZ(0);
  12126. transform: translateZ(0);
  12127. }
  12128. {* Firefoxだと問題がある要素はこちら *}
  12129. body.videoExplorer.content-fix #leftPanel,
  12130. body.videoExplorer.content-fix #content,
  12131. #popupMarquee
  12132. {
  12133. -webkit-transform: translateZ(0);
  12134. }
  12135. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  12136. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  12137. if (conf.enableGpuLayer) {
  12138. addStyle(__gpuLayer__, 'watchItLaterGpuLayer');
  12139. }
  12140.  
  12141. var __debug_css__ = Util.here(function() {/*
  12142. .videoExplorer #playerContainerWrapper, .videoExplorer #external_nicoplayer,
  12143. .videoExplorerOpening #playerContainerWrapper, .videoExplorerOpening #external_nicoplayer
  12144. {
  12145. {*transition: width 0.4s ease, height 0.4s ease;*}
  12146. }
  12147. #playerAlignmentArea .toggleCommentPanel {
  12148. {*transition: 0.4s ease-in-out;*}
  12149. }
  12150. {*
  12151. body:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer,
  12152. body:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea{
  12153. transition: width 0.4s ease;
  12154. }
  12155. body:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer{
  12156. transition: height 0.4s ease 0.4s;
  12157. }
  12158. *}
  12159.  
  12160. */});
  12161. if (conf.debugMode) addStyle(__debug_css__, 'watchItLater_debug_css');
  12162. } // end initOtherCss
  12163.  
  12164. function initCustomPlayerSize($, conf, w) {
  12165. var tpl = Util.here(function() {/*
  12166. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea
  12167. { width: {$alignmentAreaWidth}px; }
  12168. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea
  12169. { width: {$alignmentAreaWideWidth}px; }
  12170.  
  12171. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer {
  12172. height: {$playerHeight}px !important;
  12173. }
  12174. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer
  12175. { width: {$playerWidth}px !important;}
  12176.  
  12177. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #external_nicoplayer
  12178. { width: {$playerWidth}px !important; height: {$playerHeight}px !important; }
  12179.  
  12180. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMapContainer {
  12181. width: {$playerWidth}px !important;
  12182. }
  12183. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMap {
  12184. transform: scaleX({$heatMapScale}); -webkit-transform: scaleX({$heatMapScale});
  12185. }
  12186. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #smart_music_kiosk {
  12187. -webkit-transform: scaleX({$songriumScale}); -webkit-transform-origin: 0 0;
  12188. }
  12189.  
  12190. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #inspire_category {
  12191. left: {$songriumCategoryLeft}px !important;
  12192. }
  12193.  
  12194. */});
  12195. var PROFILE_SET = {
  12196. 'WQHD': [2560, 1440],
  12197. '1080p': [1920, 1080],
  12198. 'HD+': [1600, 900],
  12199. 'WXGA+': [1400, 810],
  12200. 'WXGA': [1366, 768],
  12201. '720p': [1280, 720],
  12202. 'WSVGA+': [1152, 648],
  12203. 'WSVGA': [1024, 576],
  12204. 'QHD': [ 960, 540]//, // 元より小さいパターンもサポートする?
  12205. // 'WVGA': [ 854, 480],
  12206. // 'NORMAL': [ 640, 360],
  12207. // 'SMALL': [ 512, 288],
  12208. // 'ECO': [ 352, 200],
  12209. };
  12210. var CONTROL_HEIGHT = 46, INPUT_HEIGHT = 30, PLAYER_TAB_WIDTH = 326 + 10, PLAYER_TAB_WIDTH_WIDE = 420 + 10, TAB_MARGIN = 0;
  12211. var SONGRIUM_WIDTH = 898;
  12212. var HORIZONTAL_MARGIN = 1.05; // 両端に 2.5% x 2 のマージンがある
  12213. var $videoHeader = $('#videoHeader');
  12214.  
  12215. var getTargetSize = function(targetWidth, targetHeight) {
  12216. var plWidth = Math.round(targetWidth * HORIZONTAL_MARGIN);
  12217. var plHeight = targetHeight + CONTROL_HEIGHT + INPUT_HEIGHT;
  12218. var alWidth = plWidth + PLAYER_TAB_WIDTH;
  12219. var alWidthW = plWidth + PLAYER_TAB_WIDTH_WIDE;
  12220. return {
  12221. playerWidth: plWidth,
  12222. playerHeight: plHeight,
  12223. alignmentAreaWidth: alWidth,
  12224. alignmentAreaWideWidth: alWidthW,
  12225. heatMapScale: plWidth / 100,
  12226. songriumScale: plWidth / SONGRIUM_WIDTH,
  12227. songriumCategoryLeft: plWidth + 32
  12228. };
  12229. };
  12230. var suggestProfile = function() {
  12231. var iw = $(window).innerWidth(), ih = $(window).innerHeight();
  12232. var hh = (WatchController.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  12233. iw -= (conf.wideCommentPanel ? PLAYER_TAB_WIDTH_WIDE : PLAYER_TAB_WIDTH);
  12234. iw -= TAB_MARGIN;
  12235.  
  12236. ih -= hh;
  12237. for (var v in PROFILE_SET) {
  12238. var w = PROFILE_SET[v][0], h = PROFILE_SET[v][1];
  12239. if (w * HORIZONTAL_MARGIN <= iw && h <= ih) {
  12240. return {w: w, h: h, name: v};
  12241. }
  12242. }
  12243. return null;
  12244. };
  12245. var generateCss = function() {
  12246. var profile = '';
  12247. if (PROFILE_SET[conf.customPlayerSize]) {
  12248. var s = PROFILE_SET[conf.customPlayerSize];
  12249. profile = {w: s[0], h: s[1], name: conf.customPlayerSize};
  12250. } else {
  12251. profile = suggestProfile();
  12252. }
  12253. if (!profile) return {css: '', profile: ''};
  12254. var ts = getTargetSize(profile.w, profile.h);
  12255. var css = tpl;
  12256. for (var v in ts) {
  12257. css = css.split('{$' + v + '}').join(ts[v]);
  12258. }
  12259. return {css: css, profile: profile};
  12260. };
  12261. var customStyleElement = null, lastCssName = '';
  12262. var updateStyle = function() {
  12263. if (WatchController.isFullScreen() || WatchController.isSearchMode()) {
  12264. return;
  12265. }
  12266.  
  12267. var result = generateCss();
  12268. var css = result.css, profile = result.profile, name = profile.name;
  12269.  
  12270. if (lastCssName === name) {
  12271. return;
  12272. }
  12273. lastCssName = name;
  12274. if (customStyleElement) {
  12275. customStyleElement.innerHTML = css;
  12276. } else {
  12277. customStyleElement = addStyle(css, 'customPlayerSize');
  12278. }
  12279. EventDispatcher.dispatch('onPlayerAlignmentAreaResize');
  12280. };
  12281. var toggleCustomSize = function(v) {
  12282. if (typeof v === 'boolean') {
  12283. $('body').toggleClass('w_size_custom', v);
  12284. } else {
  12285. $('body').toggleClass('w_size_custom');
  12286. }
  12287. };
  12288. var clearStyle = function() {
  12289. if (customStyleElement) {
  12290. customStyleElement.innerHTML = '';
  12291. toggleCustomSize(false);
  12292. }
  12293. lastCssName = '';
  12294. };
  12295. if (conf.customPlayerSize !== '') {
  12296. updateStyle();
  12297. toggleCustomSize();
  12298. }
  12299. EventDispatcher.addEventListener('on.config.customPlayerSize', function(v) {
  12300. if (v === '') {
  12301. clearStyle();
  12302. } else {
  12303. updateStyle();
  12304. toggleCustomSize(true);
  12305. }
  12306. });
  12307. EventDispatcher.addEventListener('on.config.wideCommentPanel', function(v) {
  12308. if (conf.customPlayerSize !== '') {
  12309. updateStyle();
  12310. }
  12311. });
  12312. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12313. if (conf.customPlayerSize !== '') {
  12314. updateStyle();
  12315. }
  12316. });
  12317.  
  12318. if (conf.debugMode) {
  12319. WatchItLater.debug.customSize = {
  12320. suggestProfile: suggestProfile,
  12321. getTargetSize: getTargetSize,
  12322. generateCss: generateCss,
  12323. updateStyle: updateStyle,
  12324. toggleCustomSize: toggleCustomSize
  12325. };
  12326. }
  12327.  
  12328. } //
  12329.  
  12330. function initStageVideo($, conf, w) {
  12331. var onStageVideoAvailabilityUpdated = function(v) {
  12332. $('#nicoplayerContainerInner').toggleClass('stageVideo', v);
  12333. };
  12334.  
  12335. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  12336. onStageVideoAvailabilityUpdated(WatchController.isStageVideoAvailable());
  12337. if (conf.forceEnableStageVideo) {
  12338. try {$('#external_nicoplayer')[0].setIsForceUsingStageVideo(true); } catch (e) { console.log(e);}
  12339. }
  12340. });
  12341.  
  12342. var pac = watch.PlayerInitializer.playerAreaConnector;
  12343.  
  12344. pac.addEventListener('onStageVideoAvailabilityUpdated', onStageVideoAvailabilityUpdated);
  12345.  
  12346. // console.log('StageVideo', $('#external_nicoplayer')[0].isStageVideoSupported() ? 'supported' : 'not supported');
  12347. // console.log('ColorSpaces', $('#external_nicoplayer')[0].getStageVideoSupportedColorSpaces());
  12348.  
  12349. } //
  12350.  
  12351. /**
  12352. * Chromeなら ALT+C
  12353. * Firefoxなら ALT+SHIFT+C で召喚
  12354. *
  12355. * どこでもコメント入力開始する隠し機能
  12356. *
  12357. * :m[0-9a-p] xxxxx でマイリストする機能もある
  12358. *
  12359. **/
  12360. function initInvisibleCommentInput($, conf, w) {
  12361. var $view = $(Util.here(function() {/*
  12362. <div class="invisibleInput">
  12363. <form action="javascript: void(0);">
  12364. <input type="text" value="" autocomplete="on" name="chat" accesskey="c"
  12365. list="myMylist" placeholder="コメント入力" class="invisibleCommentInput"
  12366. maxlength="75"
  12367. ></form>
  12368. <label><input type="checkbox" class="autoPause" checked="checked">動画を自動で一時停止する</label>
  12369. </div>
  12370. */}));
  12371. var css = Util.here(function() {/*
  12372. .invisibleInput {
  12373. position: fixed; z-index: 10000;
  12374. left: 100px; bottom: -100px; width: 300px;
  12375. padding: 16px;
  12376. border: 1px solid black;
  12377. background: #eee;
  12378.  
  12379. transition: bottom 0.4s ease 0.1s;
  12380. }
  12381. .invisibleInput.active {
  12382. bottom: 50px;
  12383. transition: bottom 0.4s ease;
  12384. box-shadow: 2px 2px 2px #333;
  12385. }
  12386.  
  12387. .invisibleInput .invisibleCommentInput {
  12388. width: 100%; font-size: 140%;
  12389. }
  12390. */});
  12391. var $dataList = $('<datalist id="myMylist"></datalist>');
  12392. var $form = $view.find('form');
  12393. var $input = $view.find('input');
  12394. var $autoPause = $view.find('.autoPause').prop('checked', conf.autoPauseInvisibleInput);
  12395.  
  12396. var prevent = function(e) { e.stopPropagation(); e.preventDefault(); };
  12397. var preventEsc = function(e) { if (e.keyCode === 27) { prevent(e); } };
  12398. var isAutoPause = function() { return !!$autoPause.prop('checked'); };
  12399.  
  12400. var mylistList = [];
  12401. var
  12402. onMylistUpdate = function(status, result) {
  12403. if (status === "ok") {
  12404. Popup.show('マイリストに追加しました');
  12405. } else {
  12406. Popup.alert('マイリスト追加に失敗: ' + result.error.description);
  12407. }
  12408. }, addMylist = function(n, d) {
  12409. var num = parseInt(n, 36);
  12410. var description = d || '';
  12411. if (num === 0) {
  12412. Mylist.addDefListItem(WatchController.getWatchId(), onMylistUpdate, description);
  12413. } else
  12414. if (mylistList[num]) {
  12415. Mylist.addMylistItem (WatchController.getWatchId(), mylistList[num].id, onMylistUpdate, description);
  12416. }
  12417. }, showMylist = function(n) {
  12418. var num = parseInt(n, 36);
  12419. if (num === 0) {
  12420. WatchController.showDeflist();
  12421. } else
  12422. if (mylistList[num]) {
  12423. WatchController.showMylist(mylistList[num].id);
  12424. }
  12425. }, seekVideo = function(v) {
  12426. var vpos = WatchController.vpos(), currentVpos = vpos;
  12427. if (v.match(/^([\-+]\d+)/)) {
  12428. vpos += parseInt(RegExp.$1, 10) * 1000;
  12429. } else
  12430. if (v.match(/^\d+$/)) {
  12431. vpos = parseInt(v, 10) * 1000;
  12432. } else
  12433. if (v.match(/^(\d+):(\d+)$/)) {
  12434. vpos = parseInt(RegExp.$1, 10) * 60 * 1000 + parseInt(RegExp.$2, 10) * 1000;
  12435. }
  12436.  
  12437. vpos = Math.max(vpos, 0);
  12438.  
  12439. if (vpos != currentVpos) {
  12440. console.log('seek video', vpos / 1000);
  12441. WatchController.vpos(vpos);
  12442. }
  12443. };
  12444.  
  12445. var isPlaying = false;
  12446. $input
  12447. .on('focus', function(e) {
  12448. isPlaying = WatchController.isPlaying();
  12449. if (isAutoPause()) {
  12450. WatchController.pause();
  12451. }
  12452. $view.addClass('active');
  12453. }).on('blur', function(e) {
  12454. if (isAutoPause() && isPlaying) {
  12455. WatchController.play();
  12456. }
  12457. $view.removeClass('active');
  12458. }).on('keyup', function(e) {
  12459. if (e.keyCode === 27) { // ESC
  12460. prevent(e);
  12461. $input.blur();
  12462. }
  12463. }).on('keydown', preventEsc).on('keyup', preventEsc);
  12464.  
  12465. $autoPause.on('click', function() {
  12466. conf.setValue('autoPauseInvisibleInput', !!$autoPause.prop('checked'));
  12467. });
  12468.  
  12469. $form
  12470. .on('submit', function(e) {
  12471. //prevent(e);
  12472. var val = $.trim($input.val());
  12473.  
  12474. if (val.match(/^:m([0-9a-p])(.*)/)) {
  12475. addMylist(RegExp.$1, RegExp.$2);
  12476. } else
  12477. if (val.match(/^:o([0-9a-p])/)) {
  12478. showMylist(RegExp.$1);
  12479. } else
  12480. if (val.match(/^:s[  \s](.+)/)) {
  12481. WatchController.nicoSearch(RegExp.$1, 'keyword');
  12482. } else
  12483. if (val.match(/^:t[  \s](.+)/)) {
  12484. WatchController.nicoSearch(RegExp.$1, 'tag');
  12485. } else
  12486. if (val.match(/^:v[  \s]([0-9:+\-]+)$/)) {
  12487. seekVideo(RegExp.$1);
  12488. } else {
  12489. WatchController.postComment(val);
  12490. }
  12491.  
  12492. setTimeout(function() { $input.val(''); } , 100);
  12493. $input.blur();
  12494. });
  12495.  
  12496. Mylist.loadMylistList(function(list) {
  12497. mylistList = list.concat();
  12498. mylistList.unshift({description: '', id: '', name: 'とりあえずマイリスト'});
  12499. var isFx = Util.Browser.isFx();
  12500.  
  12501. var tmp = [];
  12502. for (var i = 0, len = mylistList.length; i < len; i++) {
  12503. var c = i.toString(36);
  12504. // それぞれのブラウザで補完しやすい形式に
  12505. if (isFx) { // Fx
  12506. tmp.push('<option value=":m' + c + '">:m'+c+'\t 「' + mylistList[i].name + '」に追加</option>');
  12507. tmp.push('<option value=":o' + c + '">:o'+c+'\t 「' + mylistList[i].name + '」を開く</option>');
  12508. } else { // Chrome
  12509. tmp.push('<option value=":m' + c + '">「' + mylistList[i].name + '」に追加</option>');
  12510. tmp.push('<option value=":o' + c + '">「' + mylistList[i].name + '」を開く</option>');
  12511. }
  12512. }
  12513. tmp.sort();
  12514. if (isFx) {
  12515. tmp.push('<option value=":s ">:s [キーワード検索]</option>');
  12516. tmp.push('<option value=":t ">:t [タグ検索]</option>');
  12517. tmp.push('<option value=":v ">:v [シーク(秒)]</option>');
  12518. } else {
  12519. tmp.push('<option value=":s ">キーワード検索</option>');
  12520. tmp.push('<option value=":t ">タグ検索</option>');
  12521. tmp.push('<option value=":v ">シーク(秒)</option>');
  12522. }
  12523. $dataList.html(tmp.join('\n'));
  12524. });
  12525.  
  12526.  
  12527. addStyle(css, 'invisibleInput');
  12528. $('body').append($view).append($dataList);
  12529. } //
  12530.  
  12531. function initHeatMap($, conf, w) {
  12532. if (!conf.enableHeatMap) return;
  12533. //if (!w.Worker) return;
  12534. //
  12535. // TODO: Web Workers
  12536. var canvasWidth = 100, canvasHeight = 12;
  12537. var comments = [], duration = 0, canvas = null, context = null;
  12538. var commentReady = false, videoReady = false, updated = false, palette = [];
  12539. var __css__ = Util.here(function(){/*
  12540. #nicoHeatMapContainer {
  12541. position: absolute; z-index: 200;
  12542. bottom: 0px; left: 0;
  12543. width: 672px;
  12544. background: #000; height: 6px;
  12545. overflow: hidden;
  12546. }
  12547. .setting_panel #nicoHeatMapContainer { display: none; opacity: 0; }
  12548. .size_normal #nicoHeatMapContainer {
  12549. width: 898px;
  12550. }
  12551. .oldTypeCommentInput #nicoHeatMapContainer {
  12552. bottom: 29px;
  12553. display: none;
  12554. }
  12555. #nicoHeatMap {
  12556. position: absolute; top: 0; left: 0;
  12557. transform-origin: 0 0 0;-webkit-transform-origin: 0 0 0;
  12558. transform: scaleX(6.72);-webkit-transform: scaleX(6.72);
  12559. }
  12560. {* パズルみたいになってきた *}
  12561. body.size_normal:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12562. body.size_medium:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12563. body.videoExplorer #content.w_adjusted:hover #nicoHeatMapContainer,
  12564. body:not(.full_with_browser) #nicoHeatMapContainer.displayAlways {
  12565. display: block;
  12566. }
  12567. #nicoHeatMapContainer.displayAlways {
  12568. cursor: pointer;
  12569. }
  12570. .size_normal #nicoHeatMap {
  12571. transform: scaleX(8.98); -webkit-transform: scaleX(8.98);
  12572. }
  12573. .setting_panel #nicoHeatMapContainer, .full_with_browser #nicoHeatMapContainer, .size_small #content:not(.w_adjusted) #nicoHeatMapContainer {
  12574. display: none;
  12575. }
  12576. body.full_with_browser.w_fullScreenMenu.trueBrowserFull #nicoHeatMapContainer {
  12577. bottom: 0; position: fixed;
  12578. }
  12579. .full_with_browser.w_fullScreenMenu #nicoHeatMapContainer {
  12580. display: block;
  12581. }
  12582. .full_with_browser.w_fullScreenMenu .oldTypeCommentInput #nicoHeatMapContainer {
  12583. bottom: 29px; height: 6px;
  12584. }
  12585. .full_with_browser.w_fullScreenMenu #nicoHeatMapContainer {
  12586. width: 100%;
  12587. }
  12588. */});
  12589. addStyle(__css__, 'NicoHeatMapCss');
  12590.  
  12591. watch.PlayerInitializer.playerAreaConnector.addEventListener('onCommentListInitialized', function() {
  12592. w.setTimeout(function() {
  12593. commentReady = true;
  12594. update();
  12595. }, 1000);
  12596. });
  12597. EventDispatcher.addEventListener('onVideoInitialized', function() {
  12598. videoReady = true;
  12599. update();
  12600. });
  12601. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function() {
  12602. commentReady = videoReady = updated = false;
  12603. clearCanvas();
  12604. });
  12605.  
  12606. var update = function() {
  12607. if (!commentReady || !videoReady || updated) return;
  12608. updated = true;
  12609. initCanvas();
  12610. getComments();
  12611. getDuration();
  12612. if (comments.length < 1 || duration < 1) {
  12613. return;
  12614. }
  12615. getHeatMap(function(map) {
  12616. var scale = duration >= canvasWidth ? 1 : (canvasWidth / duration);
  12617. var blockWidth = (canvasWidth / map.length) * scale;
  12618. for (i = map.length - 1; i >= 0; i--) {
  12619. context.fillStyle = palette[map[i]] || palette[0];
  12620. context.beginPath();
  12621. context.fillRect(i * scale, 0, blockWidth, canvasHeight);
  12622. }
  12623. });
  12624. };
  12625.  
  12626. var getComments = function() {
  12627. comments = [];
  12628.  
  12629. var list = watch.PlayerInitializer.commentPanelViewController.commentLists;
  12630. for (var i = 0; i < list.length; i++) {
  12631. if (list[i].listName === 'commentlist:main' && list[i].comments.length > 0) {
  12632. comments = list[i].comments;
  12633. break;
  12634. }
  12635. var ct = list[i].comments;
  12636. comments = (comments.length < ct.length) ? ct : comments;
  12637. }
  12638. list = null;
  12639. };
  12640. var getDuration = function() {
  12641. var exp = document.getElementById('external_nicoplayer');//$('#external_nicoplayer')[0];
  12642. duration = exp.ext_getTotalTime(); //
  12643. };
  12644. var initCanvas = function() {
  12645. if (!canvas) {
  12646. var $container = $('<div id="nicoHeatMapContainer" />');
  12647. $container.on('dblclick', function(e) {
  12648. e.preventDefault();
  12649. e.stopPropagation();
  12650. var $this = $(this).toggleClass('displayAlways');
  12651. conf.setValue('heatMapDisplayMode', $this.hasClass('displayAlways') ? 'always' : 'hover');
  12652. });
  12653. canvas = document.createElement('canvas');
  12654. canvas.id = 'nicoHeatMap';
  12655. canvas.width = canvasWidth;
  12656. canvas.height = canvasHeight;
  12657. $container.append(canvas);
  12658. $('#nicoplayerContainerInner').append($container);
  12659. context = canvas.getContext('2d');
  12660. if (conf.heatMapDisplayMode === 'always') {
  12661. $container.addClass('displayAlways');
  12662. }
  12663.  
  12664. initPalette();
  12665. }
  12666. clearCanvas();
  12667. };
  12668. var initPalette = function() {
  12669. for (var c = 0; c < 256; c++) {
  12670. var
  12671. r = Math.floor((c > 127) ? (c / 2 + 128) : 0),
  12672. g = Math.floor((c > 127) ? (255 - (c - 128) * 2) : (c * 2)),
  12673. b = Math.floor((c > 127) ? 0 : (255 - c * 2));
  12674. palette.push('rgb(' + r + ', ' + g + ', ' + b + ')');
  12675. }
  12676. };
  12677. var clearCanvas = function() {
  12678. if (!context) return;
  12679. context.fillStyle = palette[0];
  12680. context.beginPath();
  12681. context.fillRect(0, 0, canvasWidth, canvasHeight);
  12682. };
  12683.  
  12684. var getHeatMap = function(callback) {
  12685. var map = new Array(100), i = map.length; while(i > 0) map[--i] = 0;
  12686. var exp = $('#external_nicoplayer')[0];
  12687. var ratio = duration > map.length ? (map.length / duration) : 1;
  12688.  
  12689. for (i = comments.length - 1; i >= 0; i--) {
  12690. var pos = comments[i].vpos , mpos = Math.min(Math.floor(pos * ratio / 1000), map.length -1);
  12691. map[mpos]++;
  12692. }
  12693.  
  12694. var max = 0;
  12695. for (i = map.length - 4; i >= 0; i--) max = Math.max(map[i], max); // 末尾は固まってる事があるので参考にしない
  12696. if (max > 0) {
  12697. var rate = 255 / max;
  12698. for (i = map.length - 1; i >= 0; i--) {
  12699. map[i] = Math.min(255, Math.floor(map[i] * rate));
  12700. }
  12701. }
  12702. if (typeof callback === 'function') {
  12703. callback(map);
  12704. }
  12705. };
  12706. } // end of initHeatMap
  12707.  
  12708. /**
  12709. * 既存のポップアップの難点
  12710. *
  12711. * ・閉じる機能がなく、邪魔でも消えるまで待つしかない
  12712. * ・消えるまでの時間が毎回違う?
  12713. * ・クリックしたら消えるのかなと思ったらマイページに飛ばされる
  12714. * ・Chrome以外では動画プレイヤーの上に表示できない (半透明の部分が欠ける)
  12715. * ・↑によってプレイヤー上でフェードイン・アウトが出来ないため、まったく見えない状態から突然出現したようになる
  12716. * ・タイマー処理がバグっていて、一個目の表示中に2個目を連続表示すると2個目がすぐ消える
  12717. *
  12718. * … という所があんまりなので、パッチをあてて直す。
  12719. * ・Chrome以外は半透明をやめて画面外からのスライドにする
  12720. * ・CSS3アニメーションを使う(jQueryより軽い)
  12721. * ・クリックでマイページに飛ぶのをやめて、クリックで消えるようにする
  12722. * ・マウスオーバーしてる間は引っ込まない
  12723. * ・消えるまでの時間を4秒に固定
  12724. *
  12725. *
  12726. * このパッチでも直らない問題
  12727. * ・自分が動画投稿やレビューをしたという情報がなぜか自分にも通知される
  12728. *
  12729. */
  12730. function initPopupMarquee() {
  12731. if (!conf.replacePopupMarquee) { return; }
  12732. var
  12733. marquee = watch.PopupMarqueeInitializer.popupMarqueeViewController,
  12734. itemList = marquee.itemList,
  12735. $popup = $('#popupMarquee'),
  12736. $inner = $popup.find('.popupMarqueeContent'),
  12737. closeTimer = null,
  12738. popupDuration = 6000;
  12739.  
  12740. var
  12741. resetCloseTimer = function() {
  12742. if (closeTimer) {
  12743. clearTimeout(closeTimer);
  12744. closeTimer = null;
  12745. }
  12746. },
  12747. setCloseTimer = function() {
  12748. resetCloseTimer();
  12749. closeTimer = setTimeout(function() {
  12750. disappear();
  12751. closeTimer = null;
  12752. }, popupDuration);
  12753. },
  12754. onData = function(data) {
  12755. $inner.html(data);
  12756.  
  12757. $popup.removeClass('hide').removeClass('show');
  12758. setTimeout(function() {
  12759. $popup.removeClass('hide').addClass('show');
  12760. }, 100);
  12761. setCloseTimer();
  12762. },
  12763. disappear = function() {
  12764. $popup.removeClass('show');
  12765. resetCloseTimer();
  12766. setTimeout(function() {
  12767. if (!$popup.hasClass('show')) $popup.addClass('hide');
  12768.  
  12769. setTimeout(function() {
  12770. itemList.next();
  12771. }, Math.random() * 5000 + 5000);
  12772.  
  12773. }, 500);
  12774. },
  12775. __css__ = Util.here(function() {/*
  12776. #popupMarquee {
  12777. -webkit-filter: opacity( 0%); {* chrome以外はflashの上に半透明要素を置けない *}
  12778. background: #000 !important;
  12779. transition: -webkit-filter 0.25s ease-in, top 0.5s ease-in, bottom 0.5s ease-in; display: block;
  12780. }
  12781. #popupMarquee.show {
  12782. -webkit-filter: opacity(100%);
  12783. transition: -webkit-filter 1.00s ease-out, top 0.5s ease-out, bottom 0.5s ease-out; display: block;
  12784. }
  12785.  
  12786. #popupMarquee.hide {
  12787. opacity: 0; z-index: -1;
  12788. }
  12789.  
  12790. #popupMarquee.popupMarqueeTopRight:not(.show), #popupMarquee.popupMarqueeTopLeft:not(.show) { top: -600px; }
  12791. #popupMarquee.popupMarqueeBottomRight:not(.show), #popupMarquee.popupMarqueeBottomLeft:not(.show) { bottom: -600px; }
  12792. */});
  12793.  
  12794. addStyle(__css__, 'popupMarqueeFix');
  12795.  
  12796. itemList.eventTypeListenerMap.popup = []; //itemList.removeEventListener('popup', marquee.onData);
  12797. $popup
  12798. .css({opacity: ''})
  12799. .off('click').off('mouseover').off('mouseleave').off('mousemove')
  12800. .on('mouseover', resetCloseTimer)
  12801. .on('mouseout', setCloseTimer)
  12802. .on('click', disappear);
  12803.  
  12804. marquee.onData = $.proxy(onData, marquee);
  12805. marquee.disappear = $.proxy(disappear, marquee);
  12806. itemList.addEventListener('popup', $.proxy(onData, marquee));
  12807. } //
  12808.  
  12809.  
  12810. function initScroll($, conf, w) {
  12811. // 動画切り換え時にページの一番上までスクロールするようになったのを強引に阻止する
  12812. window.WatchApp.ns.model.state.WatchPageRouter.getInstance()._scroll = function() {};
  12813.  
  12814. var beforePlayerOffsetTop = 0, $playerAlignmentArea = $('#playerAlignmentArea');
  12815. var $window = $(window);
  12816. var beforeReset = function() {
  12817. beforePlayerOffsetTop = $playerAlignmentArea.offset().top;
  12818. };
  12819. var afterReset = function() {
  12820. var diff = $playerAlignmentArea.offset().top - beforePlayerOffsetTop;
  12821. var scrollTop = $window.scrollTop();
  12822. $window.scrollTop(scrollTop + diff);
  12823. };
  12824. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  12825. watchInfoModel.addEventListener('beforeReset', beforeReset);
  12826. watchInfoModel.addEventListener('afterReset', afterReset);
  12827.  
  12828.  
  12829. // 動画選択画面閉じた時にページの一番上までスクロールするようになったのを強引に阻止する
  12830. window.WatchApp.ns.util.WindowUtil.scroll_org = window.WatchApp.ns.util.WindowUtil.scroll;
  12831. var no_thanks = function() {
  12832. window.WatchApp.ns.util.WindowUtil.scroll = function() {};
  12833. };
  12834. var restore = function() {
  12835. window.WatchApp.ns.util.WindowUtil.scroll = window.WatchApp.ns.util.WindowUtil.scroll_org;
  12836. };
  12837.  
  12838. var vv = window.WatchApp.ns.init.BottomContentInitializer.videoExplorerModeViewController;
  12839. vv.onVideoExplorerClose_org = vv.onVideoExplorerClose;
  12840. vv.onVideoExplorerClose = $.proxy(function() {
  12841. no_thanks();
  12842. this.onVideoExplorerClose_org();
  12843. restore();
  12844. window.WatchApp.ns.util.WindowUtil.scrollFit('#playerContainerWrapper');
  12845. }, vv);
  12846.  
  12847. $ = conf = w = null;
  12848. } //
  12849.  
  12850. function initOther() {
  12851. if (conf.headerViewCounter) $('#siteHeaderInner').width($('#siteHeaderInner').width() + 200);
  12852.  
  12853. initAdditionalButtons();
  12854. initSquareThumbnail();
  12855.  
  12856. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12857. if (name === 'squareThumbnail') {
  12858. initSquareThumbnail();
  12859. } else
  12860. if (name === 'enableAutoTagContainerHeight') {
  12861. if (newValue) { watch.TagInitializer.tagViewController.tagViewPinStatus.changeStatus(true); }
  12862. } else
  12863. if (name === 'enableMylistDeleteButton') {
  12864. $('.videoExplorerBody').toggleClass('enableMylistDeleteButton', newValue);
  12865. } else
  12866. if (name === 'enableYukkuriPlayButton') {
  12867. newValue ? Yukkuri.show() : Yukkuri.hide();
  12868. } else
  12869. if (name === 'noNicoru') {
  12870. $('body').toggleClass('w_noNicoru', newValue);
  12871. } else
  12872. if (name === 'playerBgStyle') {
  12873. $('#content')
  12874. .toggleClass('w_flat_gray', newValue === 'gray')
  12875. .toggleClass('w_flat_white', newValue === 'white');
  12876. } else
  12877. if (name === 'compactVideoInfo') {
  12878. $('#content, #outline').toggleClass('w_compact', newValue);
  12879. } else
  12880. if (name === 'disableHorizontalScroll') {
  12881. $('body').toggleClass('w_disableHorizontalScroll', newValue);
  12882. } else
  12883. if (name === 'hideCommentPanelSocialButtons') {
  12884. $('#playerTabContainer').toggleClass('w_noSocial', newValue);
  12885. }
  12886. });
  12887.  
  12888. if (conf.enableMylistDeleteButton) $('.videoExplorerBody').addClass('enableMylistDeleteButton');
  12889.  
  12890. if (conf.noNicoru) $('body').addClass('w_noNicoru');
  12891.  
  12892. if (conf.playerBgStyle !== '') $('#content').addClass('w_flat_' + conf.playerBgStyle);
  12893.  
  12894. if (conf.compactVideoInfo) $('#content, #outline').addClass('w_compact');
  12895. onWatchInfoReset(watchInfoModel);
  12896.  
  12897. if (conf.enableYukkuriPlayButton) { Yukkuri.show(); }
  12898.  
  12899. if (conf.disableHorizontalScroll) $('body').addClass('w_disableHorizontalScroll');
  12900.  
  12901. if (conf.hideCommentPanelSocialButtons) $('#playerTabContainer').addClass('w_noSocial');
  12902.  
  12903. $('#videoHeaderMenu .searchText input').attr({'accesskey': '@'}).on('focus', function() {
  12904. WatchController.scrollTop(0, 400);
  12905. });
  12906.  
  12907. watch.PlayerInitializer.commentPanelViewController.commentPanelContentModel.addEventListener('change', function(name) {
  12908. if (name === 'log_comment') {
  12909. $('.logDateSelect .logTime input')[0].setAttribute('type', 'time');
  12910. }
  12911. });
  12912.  
  12913. if (!w.Ads) {
  12914. // hostsに 0.0.0.0 ads.nicovideo.jp してるとスクリプトエラーがうるさいのでダミーを入れる
  12915. w.Ads = {
  12916. Advertisement: function() { return {set: function() {}}; }
  12917. };
  12918. }
  12919.  
  12920. var overrideGenerateURL = function() {
  12921. var wpc = WatchApp.ns.init.WatchPageInitializer.watchPageController;
  12922. wpc.generateWatchURL_org = wpc.generateWatchURL;
  12923. wpc.generateWatchURL = $.proxy(function(s) {
  12924. var ret = this.generateWatchURL_org(s);
  12925. // これのせいで既読リンクの色が変わらないので除去
  12926. ret = ret.replace(/\/(videoExplorer|ichiba)/, '');
  12927. return ret;
  12928. }, wpc);
  12929. };
  12930. overrideGenerateURL();
  12931.  
  12932. // 再現性不明のエラーをとりあえず握りつぶしつつ自動再生を3/2までの仕様に戻す
  12933. var overrrideWindowUtil = function() {
  12934. var wu = WatchApp.ns.util.WindowUtil;
  12935. wu.checkInview_org = wu.checkInview;
  12936. wu.checkInview = function() { return true; };
  12937. //wu.checkInview = $.proxy(function(a) {
  12938. // if (a.length < 0) { return true; }
  12939. // try {
  12940. // this.checkInview_org(a);
  12941. // } catch (e) {
  12942. // console.log('%cerror in WindowUtil.checkInview', 'color: red; ', e, a);
  12943. // console.trace();
  12944. // }
  12945. //}, wu);
  12946. };
  12947. overrrideWindowUtil();
  12948.  
  12949. // ニコる数を取得するためにコメントパネルがめちゃくちゃ重くなってるのを改善
  12950. WatchApp.ns.model.player.NicoPlayerConnector.getCommentNicoruCount = function(name, num) {
  12951. if (conf.noNicoru) {
  12952. return 0;
  12953. }
  12954. return window.PlayerApp.ns.player.Nicoplayer.getInstance().getCommentNicoruCount(name, num);
  12955. };
  12956.  
  12957. var playerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig;
  12958. if (conf.autoPlayIfWindowActive === 'yes') {
  12959. playerConfig.set({autoPlay: false});
  12960. }
  12961. if (conf.autoPlay2ndVideo) {
  12962. playerConfig.set({autoPlay: false});
  12963. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  12964. WatchController.pause();
  12965. WatchController.vpos(0);
  12966. setTimeout(function() {
  12967. playerConfig.set({autoPlay: true});
  12968. }, 3000);
  12969. });
  12970. $(window).on('beforeunload.watchItLater.autoPlay2ndVideo', function(e) {
  12971. playerConfig.set({autoPlay: false});
  12972. });
  12973. }
  12974.  
  12975. if (conf.debugMode) {
  12976. watch.PopupMarqueeInitializer.popupMarqueeViewController.itemList.addEventListener('popup', function(body) {
  12977. console.log('%c popup: ' + body, 'background: #0ff');
  12978. });
  12979. console.log(JSON.parse($('#watchAPIDataContainer').text()));
  12980.  
  12981. //WatchApp.ns.util.WindowUtil.shake = function() { console.log('%cshake', 'background: lightgreen;');};
  12982. //NicoPlayerConnector.getCommentNicoruCount_org = NicoPlayerConnector.getCommentNicoruCount;
  12983. }
  12984. }
  12985.  
  12986. // ?ref=等がついてるせいで未読既読のリンクの色が変わらなくなる問題の対策
  12987. // ShinjukuWatchと違いこっちはプレイリスト消えないモードがあるので、マイリスト等からの遷移でも遠慮無く全部消す
  12988. if (location.href.indexOf('?') >= 0) {
  12989. window.history.replaceState('', '', location.href.split('?')[0]);
  12990. }
  12991.  
  12992.  
  12993. function initTest(test) {
  12994. var console = window.console;
  12995. var expect = test.expect;
  12996. WatchApp.mixin(WatchItLater.test.spec, {
  12997. testChannelVideo: function(def) {
  12998. ChannelVideoList.load(function(result) {
  12999. console.log('ChannelVideoList.load', result);
  13000. expect(result.name).toEqual('ニコニコアプリちゃんねるの動画', 'チャンネル名');
  13001. expect(result.list.length >= 30).toBeTrue('2013/08/28時点で33件');
  13002. def.resolve();
  13003. }, {id: '55', ownerName: 'ニコニコアプリちゃんねる'});
  13004. },
  13005. testNewNicoSearch: function(def) {
  13006. var size = 15;
  13007. var search = new NewNicoSearch({});
  13008. search.load({query: 'vocaloid', size: size}, function(err, result) {
  13009. console.log('testNewNicoSearch.load', err, result);
  13010. expect(err).toBeNull('err === null');
  13011. expect(result[0].dqnid) .toBeTruthy('先頭にdqnidが含まれる(なんの略?)');
  13012. expect(typeof result[0].values[0].total).toEqual('number', 'ヒット件数');
  13013. expect(result[0].values[0].service) .toEqual('video', '検索の種類');
  13014.  
  13015. expect(result[1].type).toEqual('stats', 'type === stats'); // データの開始?
  13016.  
  13017. expect(result[2].type ).toEqual ('hits', 'type === hits');
  13018. expect(result[2].values ).toBeTruthy('ヒットした内容');
  13019. expect(result[2].values.length ).toEqual (size, 'sizeで指定した件数が返る');
  13020. expect(result[2].values[0].cmsid).toBeTruthy('ヒットした内容にデータが含まれる');
  13021.  
  13022. expect(result[3].type).toEqual('hits', 'type === stats'); // データの終了?
  13023. def.resolve();
  13024. });
  13025. },
  13026. testNewNicoSearchWrapperQuery: function(def) {
  13027. var wrapper = new NewNicoSearchWrapper({search: {}});
  13028. var params = {
  13029. searchWord: 'VOCALOID',
  13030. searchType: 'tag',
  13031. u: '1m',
  13032. l: 'short',
  13033. sort: 'l',
  13034. order: 'a',
  13035. page: 3
  13036. };
  13037. var query = wrapper._buildSearchQuery(params);
  13038.  
  13039. console.log(params, query);
  13040. expect(query.query).toEqual(params.searchWord, '検索ワードのセット');
  13041. expect(query.from).toEqual(params.page * 32 - 32, 'ページ番号 -> fromの変換');
  13042. expect(query.sort_by).toEqual('length_seconds', 'l -> length_seconds');
  13043. expect(query.order).toEqual('asc', 'a -> asc');
  13044.  
  13045. // TODO:
  13046. expect(JSON.stringify(query.search).indexOf('["tags"]') >= 0).toBeTrue('タグ検索');
  13047. var filters = JSON.stringify(query.filters);
  13048. //console.log(filters);
  13049. expect(query.filters.length >= 2).toBeTrue('filters.lengthが2以上');
  13050. expect(filters.indexOf('"field":"start_time"') >= 0).toBeTrue('filtersにstart_timeが含まれる');
  13051. expect(filters.indexOf('"field":"length_seconds"') >= 0).toBeTrue('filtersにlength_secondsが含まれる');
  13052. def.resolve();
  13053. },
  13054. testNewNicoSearchWrapper: function(def) {
  13055. console.log('testNewNicoSearchWrapper');
  13056. var search = new NewNicoSearch({});
  13057. var wrapper = new NewNicoSearchWrapper({search: search});
  13058. wrapper.load({searchWord: 'ぬこぬこ動画', size: 100}, function(err, result) {
  13059. console.log('testNewNicoSearchWrapper.load', err, result);
  13060. expect(err).toBeNull('err === null');
  13061. expect(typeof result.count).toEqual('number', '件数がnumber');
  13062. expect(result.count > 0).toBeTrue('件数が入っている');
  13063. expect(result.list.length).toBeTruthy('データが入っている');
  13064. expect(result.list.length).toEqual(100, 'sizeで指定した件数が入っている');
  13065. expect(result.list[0].type).toEqual(0, 'type === 0');
  13066. expect(/^\d+:\d+/.test(result.list[0].length)).toBeTrue('動画長がmm:dd形式で入ってる');
  13067. def.resolve();
  13068.  
  13069. });
  13070. },
  13071. testNicoSearchRelatedTag: function(def) {
  13072. var related = new NicoSearchRelatedTag({});
  13073. related.load('voiceroid', function(err, result) {
  13074. console.log('testNicoSearchRelatedTag.load', err, result);
  13075. console.log(expect(err));
  13076. expect(err).toBeNull('err === null');
  13077. expect(result.type).toEqual('tags', 'type === "tags"');
  13078. expect(result.values).toBeTruthy('データが入っている');
  13079. expect(typeof result.values[0]._rowid).toEqual('number', 'データに_rowidが入っている');
  13080. expect(typeof result.values[0].tag) .toEqual('string', 'データにtagが入っている');
  13081. def.resolve();
  13082. });
  13083. },
  13084. testSearchSuggest: function(def) {
  13085. var suggest = new NicoSearchSuggest({});
  13086. suggest.load('MMD', function(err, result) {
  13087. console.log('testSearchSuggest.load', err, result);
  13088. console.log(expect(err));
  13089. expect(err).toBeNull('err === null');
  13090. expect(result.candidates).toBeTruthy('suggestの中身がある');
  13091. expect(result.candidates.length).toBeTruthy('suggestのlengthがある');
  13092. def.resolve();
  13093. });
  13094. },
  13095. testUpdateMylistComment: function(def) {
  13096. // 一個以上マイリストがあって先頭のマイリストになにか登録されている必要がある
  13097. var Mylist = WatchItLater.mylist;
  13098. var randomMessage = 'RND: ' + Math.random();
  13099.  
  13100. var d = new $.Deferred();
  13101. d.promise()
  13102. .then(function() {
  13103. var d = new $.Deferred();
  13104. Mylist.loadMylistList(function(mylistList) {
  13105. expect(mylistList.length > 0).toBeTruthy('マイリスト一覧が1件以上');
  13106. console.log('先頭のマイリスト', mylistList[0].id, mylistList[0].name);
  13107. var groupId = mylistList[0].id;
  13108. if (mylistList.length <= 0) {
  13109. d.reject();
  13110. return;
  13111. }
  13112. d.resolve(groupId);
  13113. });
  13114. return d.promise();
  13115. })
  13116. .then(function(groupId) {
  13117. var d = new $.Deferred();
  13118. Mylist.reloadMylist(groupId, function(mylist) {
  13119. expect(mylist.length > 0).toBeTruthy('マイリストアイテムが一個以上');
  13120. var item = mylist[0];
  13121. var watchId = item.item_data.watch_id;
  13122. console.log('マイリスト先頭のアイテム', watchId, item.item_data.title);
  13123. d.resolve(watchId, groupId);
  13124. });
  13125. return d.promise();
  13126. })
  13127. .then(function(watchId, groupId) {
  13128. var d = new $.Deferred();
  13129. Mylist.updateMylistItem(watchId, groupId, function(result) {
  13130. expect(result).toEqual('ok', 'updateMylistItem() result=ok');
  13131. d.resolve(watchId, groupId);
  13132. }, randomMessage);
  13133. return d.promise();
  13134. })
  13135. .then(Util.Deferred.wait(500))
  13136. .then(function(watchId, groupId) {
  13137. var d = new $.Deferred();
  13138. Mylist.reloadMylist(groupId, function(newlist) {
  13139. console.log('reloadMylist', groupId, newlist);
  13140. expect(newlist[0].description)
  13141. .toEqual(randomMessage, 'マイリストコメントが更新できている => ' + newlist[0].description);
  13142. d.resolve();
  13143. });
  13144. return d.promise();
  13145. }).then(function() {
  13146. def.resolve();
  13147. });
  13148. d.resolve();
  13149. },
  13150. testVideoRanking: function(def) {
  13151. VideoRanking.load(null, {id: -4000})
  13152. .then(function(result) {
  13153. console.log('VideoRanking.load result:', result);
  13154. expect(result.name).toEqual('カテゴリ合算', 'ダミーマイリストの名前が一致');
  13155. expect(result.list.length).toEqual(300, 'カテゴリ合算ランキングは300件');
  13156. expect(result.list[ 0].title.indexOf('第001位')).toEqual(0,'ランキング1位のタイトル');
  13157. expect(result.list[299].title.indexOf('第300位')).toEqual(0,'ランキング300位のタイトル');
  13158. def.resolve();
  13159. },
  13160. function() {
  13161. def.reject();
  13162. });
  13163. },
  13164. testNicorepoVideo: function(def) {
  13165. NicorepoVideo.loadAll(null, null)
  13166. .then(function(result) {
  13167. console.log('NicorepoVideo.loadAll result:', result);
  13168. expect(result.name).toEqual('【ニコレポ】すべての動画', 'ダミーマイリストの名前が一致');
  13169. expect(result.list).toBeTruthy('ニコレポがある');
  13170. def.resolve();
  13171. },
  13172. function() {
  13173. def.reject();
  13174. });
  13175. },
  13176. testVideoInfoLoader: function(def) {
  13177. var loader = new VideoInfoLoader({});
  13178. $.when(
  13179. loader.load('sm9').then(function(result) {
  13180. expect(result.id).toEqual('sm9', '存在する動画ID');
  13181. expect(result.length).toEqual('5:19', 'length');
  13182. return this.done();
  13183. }, function(err) {
  13184. return this.fail();
  13185. }),
  13186.  
  13187. loader.load('sm1').then(function(result) {
  13188. return new $.Deferred().reject().promise();
  13189. }, function(resp) {
  13190. expect(resp.status).toEqual('fail', '存在しない動画ID');
  13191. return new $.Deferred().resolve().promise();
  13192. })
  13193.  
  13194. ).then(function() { def.resolve(); }, function() { def.reject(); });
  13195. },
  13196. testRelatedVideo: function(def) {
  13197. var loader = new RelatedVideo({});
  13198. loader.load('sm9').then(function(result) {
  13199. console.log('RelatedVideo', result);
  13200. expect(result.list).toBeTruthy('関連動画がある');
  13201. expect(result.list[0].title).toBeTruthy('タイトルがある');
  13202. expect(result.list[0].title.length >= 0).toBeTrue('タイトル長がある');
  13203. expect(typeof result.list[0].type === 'number').toBeTrue('type属性がある');
  13204. def.resolve();
  13205. });
  13206. },
  13207. testVideoArray: function(def) {
  13208. window.WatchItLater.loader.videoArrayAPILoader.load(['sm9', 'sm13']).then(function(result) {
  13209. console.log('VideoArrayAPILoader', result);
  13210. expect(result['sm9']).toBeTruthy('動画情報');
  13211. expect(result['sm13'].title).toBeTruthy('タイトルがある');
  13212. expect(result['sm13'].title.length >= 0).toBeTrue('タイトル長がある');
  13213. window.WatchItLater.loader.videoArrayAPILoader.load(['sm13', '1394785382']).then(function(result) {
  13214. console.log('VideoArrayAPILoader', result);
  13215. expect(result['1394785382']).toBeTruthy('スレッドIDでも引ける');
  13216. expect(result['1394785382'].title).toEqual('鬼灯の冷徹 第10話「十王の晩餐」「ダイエットは地獄みたいなもの」', '動画タイトル一致');
  13217. def.resolve();
  13218. });
  13219. });
  13220. }
  13221.  
  13222.  
  13223. }); // end WatchApp.mixin
  13224.  
  13225. } // end initTest
  13226.  
  13227. window.console.time('init WatchItLater');
  13228. // window.console.profile('init WatchItLater');
  13229. LocationHashParser.initialize();
  13230. initNews();
  13231. initShortcutKey();
  13232. initMouse();
  13233. initTouch();
  13234. initEvents();
  13235.  
  13236. initSearchContent($, conf, w);
  13237. initUserVideoContent($, conf, w);
  13238. initMylistContent($, conf, w);
  13239. initUploadedVideoContent($, conf, w);
  13240. initDeflistContent($, conf, w);
  13241. initVideoExplorer($, conf, w);
  13242.  
  13243. initRightPanel($, conf, w);
  13244. initLeftPanel($, conf, w);
  13245. initVideoReview($, conf, w);
  13246.  
  13247. initHidariue();
  13248. initVideoCounter();
  13249. initScreenMode();
  13250. initPlaylist($, conf, w);
  13251.  
  13252. initPageBottom($, conf, w);
  13253. initPageHeader($, conf, w);
  13254. initVideoTagContainer($, conf, w);
  13255.  
  13256. initNicoS($, conf, w);
  13257. initInvisibleCommentInput($, conf, w);
  13258. initOtherCss();
  13259. initCustomPlayerSize($, conf, w);
  13260.  
  13261. initStageVideo($, conf, w);
  13262. initHeatMap($, conf, w);
  13263. initPopupMarquee();
  13264. initMylistPanel($, conf, w);
  13265. initScroll($, conf, w);
  13266. initOther();
  13267. // window.console.profileEnd('init WatchItLater');
  13268. window.console.timeEnd('init WatchItLater');
  13269.  
  13270. onWindowResizeEnd();
  13271.  
  13272. if (conf.debugMode) {
  13273. initTest(WatchItLater.test);
  13274. }
  13275. };
  13276.  
  13277. if (window.PlayerApp) {
  13278. (function() {
  13279. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  13280. if (watchInfoModel.initialized) {
  13281. window.WatchItLater.WatchController =
  13282. window.WatchController =
  13283. _watchController(window);
  13284. ZeroFunc(window);
  13285. } else {
  13286. var onReset = function() {
  13287. watchInfoModel.removeEventListener('reset', onReset);
  13288. window.setTimeout(function() {
  13289. window.WatchItLater.WatchController =
  13290. window.WatchController =
  13291. _watchController(window);
  13292.  
  13293. ZeroFunc(window);
  13294. }, 0);
  13295. };
  13296. watchInfoModel.addEventListener('reset', onReset);
  13297. }
  13298. })();
  13299. } else
  13300. if (location.host === 'www.nicovideo.jp' && location.pathname ==='/stamp') {
  13301. niconicodoRedirect();
  13302. }
  13303.  
  13304.  
  13305. /**
  13306. * 原宿プレイヤーでのあれこれ
  13307. *
  13308. * マイリストパネルだけ追加
  13309. *
  13310. */
  13311. (function() {
  13312. if (!w.Video) return;
  13313. if (!location.href.match(/\/watch\/(sm\d+|nm\d+|so\d+|\d+)/)) return;
  13314. var watchId = undefined, videoId = undefined;
  13315. if (w.Video === null) {
  13316. watchId = RegExp.$1;
  13317. w.Video = {id: watchId};
  13318. } else {
  13319. Video = w.Video;
  13320. watchId = Video.v;
  13321. videoId = Video.id;
  13322. }
  13323. var watchId = RegExp.$1;
  13324. var iframe = Mylist.getPanel('');
  13325. iframe.id = "mylist_add_frame";
  13326. iframe.setAttribute('style', 'position: fixed; right: 0; bottom: 0;');
  13327.  
  13328. document.body.appendChild(iframe);
  13329. iframe.watchId(watchId, videoId);
  13330. })();
  13331.  
  13332.  
  13333. /**
  13334. * キーボードイベント他
  13335. *
  13336. */
  13337. (function() {
  13338. w.document.body.addEventListener('keydown', function(e) {
  13339. if (e.keyCode === 27 || e.keyCode === 88) { // ESC or x
  13340. AnchorHoverPopup.hidePopup();
  13341. Popup.hide();
  13342. }
  13343. }, false);
  13344. w.document.body.addEventListener('click', function(e) {
  13345. var tagName = e.target.tagName, className = e.target.className;
  13346. //console.log(tagName, className);
  13347. if (tagName !== 'BUTTON' && tagName !== 'SELECT' && tagName !== 'OPTION' && className !== 'popupTagItem' && className.indexOf('mylistPopupPanel') < 0) {
  13348. AnchorHoverPopup.hidePopup();
  13349. }
  13350.  
  13351. }, false);
  13352. var touchInitialized = false;
  13353. TouchEventDispatcher.onflick(function(e) {
  13354. if (e.direction === 'right') {
  13355. if (!touchInitialized) {
  13356. document.getElementById('videoTagPopupContainer').className += ' w_touch';
  13357. touchInitialized = true;
  13358. }
  13359. }
  13360. }, false);
  13361. // w.document.body.addEventListener('dblclick', function(e) {var tagName = e.target.tagName, className = e.target.className;console.log(tagName, className);});
  13362.  
  13363. })(w);
  13364.  
  13365. //===================================================
  13366. //===================================================
  13367. //===================================================
  13368.  
  13369. }); // end of monkey();
  13370.  
  13371. /**
  13372. * スマートフォン用APIを利用して動画情報を取得する
  13373. */
  13374. var spapi = (function() {
  13375. if (window.name.indexOf('watchItLaterAPILoader') < 0 ) { return; }
  13376. var resp = document.getElementsByTagName('nicovideo_video_response');
  13377. var session = location.hash.length > 1 ? location.hash.substr(1) : location.search;
  13378. var origin = 'http://' + location.host.replace(/^.*?\./, 'www.');
  13379. var xml = '';
  13380. if (resp.length > 0) {
  13381. xml = resp[0].outerHTML;
  13382. }
  13383.  
  13384. try {
  13385. parent.postMessage(JSON.stringify({
  13386. id: 'WatchItLater',
  13387. type: 'VideoArrayAPILoader',
  13388. body: {
  13389. session: session,
  13390. xml: xml
  13391. }
  13392. }),
  13393. origin);
  13394. } catch (e) {
  13395. console.log('err', e);
  13396. }
  13397. });
  13398.  
  13399. try {
  13400. if (location.host === 'flapi.nicovideo.jp') {
  13401. return;
  13402. } else
  13403. if (location.host === 'i.nicovideo.jp') {
  13404. spapi();
  13405. } else
  13406. if (location.host.indexOf('smile-') >= 0) {
  13407. return;
  13408. } else
  13409. if (location.host.indexOf('localhost.') === 0 || location.host.indexOf('www.') === 0 || !this.GM_getValue || this.GM_getValue.toString().indexOf("not supported")>-1) {
  13410. isNativeGM = false;
  13411. var inject = document.createElement("script");
  13412. inject.id = "monkey";
  13413. inject.setAttribute("type", "text/javascript");
  13414. inject.setAttribute("charset", "UTF-8");
  13415.  
  13416. inject.appendChild(document.createTextNode("(" + monkey + ")(false)"));
  13417. // inject.appendChild(document.createTextNode("try {(" + monkey + ")(false) } catch(e) { console.log(e); }"));
  13418.  
  13419. if (document.body) {
  13420. document.body.appendChild(inject);
  13421. } else {
  13422. document.documentElement.appendChild(inject);
  13423. }
  13424. } else {
  13425. // やや古いFirefoxはここらしい
  13426. monkey(true);
  13427. }
  13428.  
  13429. } catch(e) {
  13430. // 最近のFirefoxはここに飛んでくる
  13431. monkey(true);
  13432. }
  13433. })();
  13434.  
  13435.