WatchItLater

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

目前为 2014-05-14 提交的版本,查看 最新版本

  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.140515
  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.140428
  45. // - 本家の仕様変更で使えなくなっていた、プレイリストのブックマーク保存機能を復活
  46.  
  47. // * ver 1.140319
  48. // - 謎の技術によって、ニコメンドがなくても説明文の動画リンクにサムネイルを出せるように
  49. // - 細かなスタイル調整
  50.  
  51. // * ver 1.140303
  52. // - 動画選択画面で再生リストの開閉が記憶されなくなったのに対抗
  53. // - 動画切換え時に一番上までスクロールするようになったのに対抗
  54. // - 本家の内部仕様変更(jQuery ver up等)に対応
  55.  
  56. // * ver 1.140227
  57. // - タグ検索のソート順が毎回リセットされるようになったのに対抗
  58.  
  59. // * ver 1.140218
  60. // - コメント重複を勝手に直してたけど不要になったので除去
  61. // - 二本目以降の動画だけ自動再生を追加
  62.  
  63. // * ver 1.140207
  64. // - テレビちゃんメニューの表示修正
  65. // - スレッドIDのリンクからもタグを取得できるように(watchページ内のみ)
  66. // - マイリスト選択メニュー部分の右クリックでとりマイの位置に戻る隠し機能
  67.  
  68. // * ver 1.140122
  69. // - テレビちゃんメニューをShinjukuWatch仕様に
  70.  
  71. // * ver 1.140110
  72. // - 検索フォームのオートコンプリートを調整
  73. // - ニコメンドまわりのコード除去
  74. // - 微妙にNicorenizerとの相性を改善
  75.  
  76. // * ver 1.131224
  77. // - 本家のCSS更新に対応
  78.  
  79. // * ver 1.131216
  80. // - 一部入力欄でオートコンプリートが効くようにした (Firefoxだけ?)
  81.  
  82. // * ver 1.131213
  83. // - マイリストメニューの位置を右下・左下から選べるようにした
  84.  
  85. // * ver 1.131203
  86. // - コメントパネル下のソーシャルボタンを隠す設定を追加
  87.  
  88. // * ver 1.131128
  89. // - スクロールが若干軽くなったかも
  90.  
  91. // * ver 1.131125
  92. // - 本家バージョンアップで動かなくなっていた部分の対応
  93.  
  94. // * ver 1.131122
  95. // - 本家の更新にとりあえず対応
  96.  
  97. // * ver 1.131115
  98. // - 動画ランキングをカテゴリごとに折りたたむ対応
  99.  
  100. // * ver 1.131110
  101. // - プレイリスト復元機能の挙動修正
  102.  
  103. // * ver 1.131107
  104. // - 新検索β有効時、「人気が高い順」の並び替えに対応 -> http://blog.nicovideo.jp/niconews/ni042607.html
  105. // - Nicorenizerとの干渉を少し改善
  106.  
  107. // * ver 1.131101
  108. // - GINZAで不要になったコードをコメントアウト
  109. // - 説明文のURL自動リンクの正規表現を調整
  110.  
  111. // * ver 1.131023
  112. // - 検索結果の一番下にチャンネル動画が出るようになったので、色をつけてわかりやすく
  113. // - 広告が0ポイントの時は表示を消す
  114.  
  115. // * ver 1.131010
  116. // - ?ref=xxxxつきのURLでページを開いたら除去する対応 (リンク側の除去処理とはまた別)
  117. // - 細かい凡ミスタイプミス・GINZAでいらなくなったCSSなどを修正
  118.  
  119. // * ver 1.131008
  120. // - 銀座対応?
  121. // - 省スペース/軽量化の設定に「横スクロールバーを出なくする」を追加
  122.  
  123. // * ver 1.131004
  124. // - 動画リンクの?ref=xxxxを除去
  125.  
  126. // * ver 1.131002
  127. // - プレーヤーのサイズが変わったのに対応
  128.  
  129. // * ver 1.130930
  130. // - タグの自動更新無効化の設定を追加
  131.  
  132. // * ver 1.130926
  133. // - プレイリストがプレイリストじゃなくなったのに対応
  134.  
  135. // * ver 1.130925
  136. // - ニコるをなくす設定にするとコメントパネルが軽くなるようにした。ホイール操作の謎の重さがなくなる。
  137. // (Chromeだと変わらないけどFirefoxだと効果が大きいです)
  138.  
  139. // * ver 1.130924
  140. // - レイアウトの崩れを修正
  141.  
  142. // * ver 1.130923
  143. // - 検索モードを無効にするやつ
  144.  
  145. // * ver 1.130914
  146. // - 急に原宿で動かなくなったので暫定対応
  147. // - 省スペース/軽量化設定 → コメントパネルのマウスオーバー時にチラチラするのをなくす設定 (動作の軽さを優先する用)
  148.  
  149. // * ver 1.130906
  150. // - 動画説明文中の動画IDに「次に再生」ボタンを追加
  151.  
  152. // * ver 1.130905
  153. // - 本家側の仕様変更によりマイリスト外すボタンが出なくなったのを修正
  154.  
  155. // * ver 1.130903
  156. // - 全画面モードの仕様変更に暫定対応
  157. // - トップページのホラーっぽいのが表示されなくなっていた不具合を修正
  158.  
  159. // * ver 1.130828
  160. // - 4列表示の改善
  161. // - songriumも大・大画面に合わせる
  162. // - チャンネル動画を60件まで表示するように
  163.  
  164. // * ver 1.130821
  165. // - マイリストパネルを自動で目立たない色に
  166. // - 不要になったコードの整理
  167.  
  168. // * ver 1.130816
  169. // - プレイヤーの設定 → 「大画面をもっと大画面にする」追加。 大画面がモニターに合わせてもっと大きくなります。(WQHDモニターだと1080p)
  170.  
  171. // * ver 1.130813
  172. // - 隠し機能 にシークコマンドをつけた
  173. // - ショートカットキー「停止/再生」
  174.  
  175. // * ver 1.130812
  176. // - マイリストの絞り込み条件に「チャンネル・コミュニティ・マイメモリーのみ」を追加
  177. // - 隠し機能 ChromeはALT+C FirefoxはALT+SHIFT+Cでいつでもコメント入力欄
  178. // - 検索モードの1ページに表示する件数の設定を追加
  179.  
  180. // * ver 1.130810
  181. // - 細かな処理タイミングの調整
  182. // - 検索モードで画面を大きくする設定の変更がリロード不要になった
  183.  
  184. // * ver 1.130809
  185. // - マイリストの動画一覧をタイトル/説明文で絞り込み検索出来るようにした。(ランキングやとりマイでも使用可能)
  186. // - 見ている動画が今開いているマイリストにあるかどうかをわかりやすく
  187. // - 検索モードのコメントパネルの出し方を変更。 ホバーをやめてボタンでトグルにした
  188. // - とりマイとマイリストのUIを統一
  189.  
  190. // * ver 1.130807
  191. // - ニコレポの表示を修正。同じ動画はなるべくまとめる (マイリストコメント付やレビューはまとめない)
  192. // - プレイリストの仕様変更で動かなくなった機能を対応
  193.  
  194. // * ver 1.130805b
  195. // - コメントパネルを広くするとフルスクリーンが崩れる不具合の修正
  196.  
  197. // * ver 1.130805
  198. // - コメントパネルを広くする機能の復活
  199. // - 新検索β使用時、現在見ている動画の投稿者の動画だけを探す機能を追加
  200. // - 角丸をなくす設定が無意味になったので廃止。 プレイヤーの背景色の設定を追加
  201.  
  202. // * ver 1.130804
  203. // - 検索画面のメニューの所にある入力欄を少しだけ賢く&サジェスト対応
  204.  
  205. // * ver 1.130803
  206. // - 動画終了時に自動でニコメンドを開かない・または中身がある時だけ開くようにする設定を追加
  207.  
  208. // * ver 1.130801
  209. // - タグ・キーワード検索にniconico新検索βを組み込んでみた
  210.  
  211. // * ver 1.130731
  212. // - 関連タグの取得はもっといいAPIがあった
  213. // - ニコニコ新検索を使うようにするための布石
  214.  
  215. // * ver 1.130730
  216. // - キーワード/タグ検索時結果に関連タグが出るようにしてみた(リアルタイムの表示はできない)
  217. // - ニコニコ新検索を使うようにするための布石
  218.  
  219. // * ver 1.130729
  220. // - プレイリスト消えないモードの挙動改善
  221.  
  222. // * ver 1.130728
  223. // - プレイリストメニューが一部機能しなくなっていたのを対応
  224. // - ニコレポなどが出るポップアップをいじった。クリックですぐ消えるように & Firefoxでもプレイヤーの上に表示できるように(まだデフォルトではオフ)
  225. // - ニコメンドが空かどうかクリックするまでもなくわかるよう、グレーにする
  226. // - タグ表示のポップアップからniconico新検索(http://search.nicovideo.jp/)に飛べるようにした
  227.  
  228. // * ver 1.130727
  229. // - ダミーマイリスト系のソートがおかしい問題を解決
  230. // - マイリスト・とりあえずマイリストを100件ずつ表示にしてみた
  231. // - 検索画面から「次に再生」した時に動画時間が入るようにした
  232.  
  233. // * ver 1.130726
  234. // - 本家の更新に暫定対応。まだ不安定だったり動かない機能もあります。
  235. // - チャンネルのアイコンをクリックしたらチャンネル動画一覧を表示。ただし最新20件のみ
  236.  
  237. (function() {
  238. var isNativeGM = true;
  239. var monkey =
  240. (function(isNativeGM){
  241. var w;
  242. try { w = unsafeWindow || window; } catch (e) { w = window;}
  243. var document = w.document;
  244.  
  245. var conf = {
  246. autoBrowserFull: false, // 再生開始時に自動全画面化
  247. disableAutoBrowserFullIfNicowari: false, // ユーザーニコ割があるときは自動全画面化しない
  248. autoNotFull: true, // 再生完了時にフルスクリーン解除(原宿と同じにする)
  249. autoTagPin: false,
  250. topPager: true, // 検索ボックスのページャを上にする
  251. hideLeftIchiba: false,
  252. autoClosePlaylistInFull: true, // 全画面時にプレイリストを自動で閉じる
  253. autoOpenSearch: false, // 再生開始時に自動検索画面
  254. autoScrollToPlayer: true, // プレイヤー位置に自動スクロール(自動全画面化オフ時)
  255. hideNewsInFull: true, // 全画面時にニュースを閉じる
  256. wideCommentPanel: false, // コメントパネルをワイドにする
  257. removeLeftPanel: true, // 左パネルを消滅させる
  258. leftPanelJack: false, // 左パネルに動画情報を表示
  259. rightPanelJack: true, // 右パネルに動画情報を表示
  260. headerViewCounter: false, // ヘッダに再生数コメント数を表示
  261. popupViewCounter: 'full', // 動画切り替わり時にポップアップで再生数を表示
  262. ignoreJumpCommand: false, // @ジャンプ無効化
  263. nicoSSeekCount: -1, // @ジャンプによるシーク(ループなど)を許可する回数 -1=無限 0以上はその回数だけ許可
  264. doubleClickScroll: true, // 空白部分ををダブルクリックで動画の位置にスクロールする
  265. hidePlaylist: true, // プレイリストを閉じる
  266. hidePlaylistInVideoExplorer: true, // 動画選択画面でプレイリストを閉じる
  267. hidariue: false, // てれびちゃんメニュー内に、原宿以前のランダム画像復活
  268. videoExplorerHack: true, // 検索画面を乗っ取る
  269. squareThumbnail: true, // 検索画面のサムネを4:3にする
  270. enableFavTags: false, // 動画検索画面にお気に入りタグを表示
  271. enableFavMylists: false, // 動画検索画面にお気に入りマイリストを表示
  272. searchPageItemCount: 50, // 検索モードの1ページあたりの表示数
  273. enableMylistDeleteButton: false, // 動画検索画面で、自分のマイリストから消すボタンを追加する
  274. enableHoverPopup: true, // 動画リンクのマイリストポップアップを有効にする
  275. enableAutoTagContainerHeight: false, // タグが2行以内なら自動で高さ調節(ピン留め時のみ
  276. autoSmallScreenSearch: false, // ポップアップからのタグ検索でもプレイヤーを小さくする
  277. enableNewsHistory: false, // ニコニコニュースの履歴を保持する
  278. defaultSearchOption: '', // 検索時のデフォルトオプション
  279. autoPlayIfWindowActive: 'no', // 'yes' = ウィンドウがアクティブの時だけ自動再生する
  280. autoPlay2ndVideo: false, // 2本目以降の動画だけ自動再生
  281. enableYukkuriPlayButton: false, // スロー再生ボタンを表示する
  282. noNicoru: false, // ニコるボタンをなくす
  283. enoubleTouchPanel: false, // タッチパネルへの対応を有効にする
  284. mouseClickWheelVolume: 0, // マウスボタン+ホイールで音量調整を有効にする 1 = 左ボタン 2 = 右ボタン
  285. enableQTouch: false, // タッチパネルモード有効
  286. commentVisibility: 'visible', // 'visible', 'hidden', 'lastState'
  287. lastCommentVisibility: 'visible',
  288. controllerVisibilityInFull: '', // 全画面時に操作パネルとコメント入力欄を出す設定
  289. enableTrueBrowserFull: false, // フチなし全画面モードにする (Chromeは画面ダブルクリックで切り替え可能)
  290. enableSharedNgSetting: false, //
  291. hideNicoNews: false, // ニコニコニュースを消す
  292. hashPlaylistMode: 0, // location.hashにプレイリストを保持 0 =無効 1=連続再生時 2=常時
  293. storagePlaylistMode: '', // localStorageにプレイリストを保持
  294. compactVideoInfo: true, //
  295. hoverMenuDelay: 0.4, // リンクをホバーした時のメニューが出るまでの時間(秒)
  296. enableFullScreenMenu: true, // 全画面時にホイールでメニューを出す
  297. enableHeatMap: false, //
  298. heatMapDisplayMode: 'hover', // 'always' 'hover'
  299. replacePopupMarquee: true, //
  300. enableRelatedTag: true, // 関連タグを表示するかどうか
  301. // playerTabAutoOpenNicommend: 'enable', // 終了時にニコメンドを自動で開くかどうか 'enable' 'auto' 'disable'
  302. autoPauseInvisibleInput: true, //
  303. customPlayerSize: '', //
  304. removeCommentPanelHoverEvent: false, //
  305. disableVideoExplorer: false, //
  306. disableTagReload: false, //
  307. disableHorizontalScroll: false, // 横スクロールバーを出なくする
  308. hideCommentPanelSocialButtons: false, // コメントパネル下のソーシャルボタンを隠す
  309. mylistPanelPosition: '',
  310. enableDescriptionThumbnail: false, // 説明文の動画リンクにサムネイルとタイトル表示
  311.  
  312. enableLocalMylistCache: false,
  313.  
  314. rankingCategory_g_ent2_Close: true,
  315. rankingCategory_g_life2_Close: true,
  316. rankingCategory_g_tech_Close: true,
  317. rankingCategory_g_culture2_Close: true,
  318. rankingCategory_g_other_Close: true,
  319.  
  320. searchEngine: 'sugoi', // 'normal' 'sugoi'
  321. searchStartTimeRange: '', //
  322. searchLengthSecondsRange: '', //
  323. searchMusicDlFilter: false, //
  324.  
  325. hideVideoExplorerExpand: true, // 「動画をもっと見る」ボタンを小さくする
  326. // nicommendVisibility: 'visible', // ニコメンドの表示 'visible', 'underIchiba', 'hidden'
  327. ichibaVisibility: 'visible', // 市場の表示 '', 'visible', 'hidden'
  328. reviewVisibility: 'visible', // レビューの表示 'visible', 'hidden'
  329. bottomContentsVisibility: 'hidden', // 動画下のコンテンツ表示表示非表示
  330. hideMenuInFull: 'hide', // 全画面時にマイリストメニューを隠す '', 'hide' = 目立たなくする, 'hideAll' = 完全非表示
  331.  
  332. flatDesignMode: '', // 'on' グラデーションや角丸をなくす。 7/25からQwatchがフラットデザインになったので不要になった
  333. playerBgStyle: '', // ↑ の後継パラメータ 'gray' 'white' ''
  334.  
  335. shortcutTogglePlay: {char: 'P', shift: false, ctrl: false, alt: true, enable: false}, // 停止/再生
  336. shortcutDefMylist: {char: 'M', shift: true, ctrl: false, alt: false, enable: false}, // とりマイ登録のショートカット
  337. shortcutMylist: {char: 'M', shift: false, ctrl: true , alt: false, enable: false}, // マイリスト登録のショートカット
  338. shortcutOpenSearch: {char: 'S', shift: true, ctrl: false, alt: false, enable: false}, // 検索オープンのショートカット
  339. shortcutOpenDefMylist: {char: 'D', shift: true, ctrl: false, alt: false, enable: false}, // とりマイオープンのショートカット
  340. shortcutOpenRecommend: {char: 'R', shift: true, ctrl: false, alt: false, enable: false}, // 関連動画(オススメ)を開くショートカット
  341. shortcutCommentVisibility: {char: 'V', shift: true, ctrl: false, alt: false, enable: false}, // コメント表示ON/OFFのショートカット
  342. shortcutScrollToNicoPlayer: {char: 'P', shift: true, ctrl: false, alt: false, enable: false}, // プレイヤーまでスクロールのショートカット
  343. shortcutShowOtherVideo: {char: 'U', shift: true, ctrl: false, alt: false, enable: false}, // 投稿者の関連動画表示のショートカット
  344. shortcutMute: {char: 'T', shift: true, ctrl: false, alt: false, enable: false}, // 音量ミュートのショートカット
  345. shortcutDeepenedComment: {char: 'B', shift: true, ctrl: false, alt: false, enable: false}, // コメント背面表示
  346. shortcutToggleStageVideo: {char: 'H', shift: true, ctrl: false, alt: false, enable: false}, // ハードウェアアクセラレーション(StageVideo)のショートカット
  347.  
  348. shortcutInvisibleInput: {char: 'C', shift: false, ctrl: false, alt: true, enable: true}, // 停止/再生
  349.  
  350. watchCounter: 0, // お前は今までに見た動画の数を覚えているのか?をカウントする
  351. forceEnableStageVideo: false,
  352. forceExpandStageVideo: false,
  353. enableAutoPlaybackContinue: false, // 一定時間操作しなかくても自動再生を続行
  354. lastLeftTab: 'videoInfo',
  355. lastRightTab: 'w_videoInfo',
  356. lastRightTabInExplorer: 'comment',
  357. lastControlPanelPosition: '',
  358. enableSortTypeMemory: true, // 検索のソート順を記憶する
  359. searchSortType: 'n', //
  360. searchSortOrder: 'd', // 'd'=desc 'a' = asc
  361. fxInterval: 40, // アニメーションのフレームレート 40 = 25fps
  362. enableGpuLayer: false, // 一部の要素でGPU描画を有効にしてみる?
  363. debugMode: false
  364. };
  365.  
  366.  
  367. //===================================================
  368. //===================================================
  369. //===================================================
  370.  
  371. function addStyle(styles, id) {
  372. var elm = document.createElement('style');
  373. window.setTimeout(function() {
  374. elm.type = 'text/css';
  375. if (id) { elm.id = id; }
  376.  
  377. var text = styles.toString();
  378. text = document.createTextNode(text);
  379. elm.appendChild(text);
  380. var head = document.getElementsByTagName('head');
  381. head = head[0];
  382. head.appendChild(elm);
  383. }, 0);
  384. return elm;
  385. }
  386.  
  387. if (!isNativeGM) {
  388. this.GM_xmlhttpRequest = function(options) {
  389. try {
  390. var req = new XMLHttpRequest();
  391. var method = options.method || 'GET';
  392. req.onreadystatechange = function() {
  393. if (req.readyState === 4) {
  394. if (typeof options.onload === "function") options.onload(req);
  395. }
  396. };
  397. req.open(method, options.url, true);
  398. if (options.headers) {
  399. for (var h in options.headers) {
  400. req.setRequestHeader(h, options.headers[h]);
  401. }
  402. }
  403.  
  404. req.send(options.data || null);
  405. } catch (e) {
  406. if (conf.debugMode) console.log(e);
  407. }
  408. };
  409. }
  410.  
  411. (function() { // 各ページ共通
  412. var __css__ = (function() {/*
  413. .tagItemsPopup {
  414. background: #eef;
  415. }
  416. .popupMenu {
  417. position: absolute;
  418. min-width: 200px;
  419. font-Size: 12pt;
  420. z-index: 2000000;
  421. box-shadow: 2px 2px 2px #888;
  422. }
  423. .popupMenu ul, .popupMenu ul li {
  424. position: relative;
  425. list-style-type: none;
  426. margin: 0; padding: 0;
  427. white-space: nowrap;
  428. }
  429. .tagItemsPopup .icon{
  430. width: 17px;
  431. height: 15px;
  432. }
  433. .tagItemsPopup .nicodic, .tagItemsPopup .newsearch {
  434. margin: 1px 4px 1px 1px;
  435. }
  436. .tagItemsPopup .nicodic:hover, .tagItemsPopup .newsearch:hover {
  437. margin: 0px 3px 0px 0px;
  438. border: 1px outset;
  439. }
  440.  
  441. .mylistListPopup {
  442. position:absolute;
  443. background: #fff;
  444. overflow: visible;
  445. padding: 8px;
  446. border: 1px outset #333;
  447. transition: opacity 0.3s ease;
  448. }
  449. .mylistListPopup.popupMenu ul li {
  450. position: relative;
  451. margin: 2px 8px;
  452. overflow-y: visible;
  453. }
  454. .mylistListPopup:not(.show) {
  455. left: -9999px;
  456. top: -9999px;
  457. opacity: 0;
  458. }
  459. .mylistListPopup.show {
  460. opacity: 1;
  461. }
  462. .mylistListPopup .listInner {
  463. -webkit-column-width: auto; -moz-column-width: auto;
  464. -webkit-column-count: 1 !important; {* Chromeだけバグるので *}
  465. }
  466. .mylistListPopup .icon {
  467. display: inline-block;
  468. width: 18px;
  469. height: 14px;
  470. margin: -4px 4px 0 0;
  471. vertical-align: middle;
  472. margin-right: 15px;
  473. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  474. transform: scale(1.5); -webkit-transform: scale(1.5);
  475. transform-origin: 0 0 0; -webkit-transform-origin: 0 0 0;
  476. transition: transform 0.1s ease, box-shadow 0.1s ease;
  477. -webkit-transition: -webkit-transform 0.1s ease, box-shadow 0.1s ease;
  478. cursor: pointer;
  479. }
  480. .mylistListPopup .icon:hover {
  481. background-color: #ff9;
  482. transform: scale(2); -webkit-transform: scale(2);
  483. }
  484. .mylistListPopup .icon:after {
  485. content: '開く';
  486. position: absolute;
  487. bottom: 0px;
  488. right: 12px;
  489. padding: 2px;
  490. opacity: 0;
  491. transform: scale(0.5); -webkit-transform: scale(0.5);
  492. z-index: -1;
  493. }
  494. .mylistListPopup .icon:hover:after {
  495. {*box-shadow: 2px 2px 2px #888;*}
  496. background: #fff;
  497. z-index: 100;
  498. opacity: 1;
  499. }
  500. .mylistListPopup .deflist .icon { background-position: 0 -253px; }
  501. .mylistListPopup .folder1 .icon { background-position: 0 -23px;}
  502. .mylistListPopup .folder2 .icon { background-position: 0 -46px;}
  503. .mylistListPopup .folder3 .icon { background-position: 0 -69px;}
  504. .mylistListPopup .folder4 .icon { background-position: 0 -92px;}
  505. .mylistListPopup .folder5 .icon { background-position: 0 -115px;}
  506. .mylistListPopup .folder6 .icon { background-position: 0 -138px;}
  507. .mylistListPopup .folder7 .icon { background-position: 0 -161px;}
  508. .mylistListPopup .folder8 .icon { background-position: 0 -184px;}
  509. .mylistListPopup .folder9 .icon { background-position: 0 -207px;}
  510.  
  511.  
  512. .mylistListPopup .name {
  513. display: inline-block;
  514. vertical-align: middle;
  515. font-size: 110%;
  516. color: #666;
  517. text-derocation: none !important;
  518. }
  519. .mylistListPopup .name:hover {
  520. color: #000;
  521. background-color: #ff9;
  522. }
  523. .mylistListPopup .name:after {
  524. content: ' に登録';
  525. font-size: 75%;
  526. color: #fff;
  527. }
  528. .mylistListPopup .name.exist:after {
  529. content: ' に登録済';
  530. color: #933;
  531. }
  532. .mylistListPopup .name:hover:after {
  533. color: #666;
  534. }
  535.  
  536. {* マイリスト登録パネル *}
  537. .mylistPopupPanel {
  538. height: 24px;
  539. z-index: 10000;
  540. {*border: 1px solid silver;
  541. border-radius: 3px; *}
  542. padding: 0;
  543. margin: 0;
  544. overflow: hidden;
  545. display: inline-block;
  546. background: #eee;
  547. }
  548. .mylistPopupPanel.fixed {
  549. position: fixed; right: 0; bottom: 0;
  550. transition: right 0.1s ease-out;
  551. }
  552. .mylistPopupPanel.fixed.left {
  553. right: auto;
  554. left: 0;
  555. }
  556. .mylistPopupPanel.fixed.top {
  557. bottom: auto;
  558. top: 0;
  559. }
  560.  
  561. .mylistPopupPanel.fixed>* {
  562. transition: opacity 0.1s ease-out;
  563. }
  564. .mylistPopupPanel>*>* {
  565. transition: background 0.5s ease 0.5s, color 0.5s ease 0.5s;
  566. }
  567. .full_with_browser .mylistPopupPanel, .mylistPopupPanel.black {
  568. background: #000; border: 0;
  569. }
  570. body.full_with_browser .mylistPopupPanel *,body .mylistPopupPanel.black.fixed * {
  571. background: #000; color: #888; border-color: #333;
  572. }
  573. .full_with_browser .mylistPopupPanel.hideAllInFull{
  574. display: none;
  575. }
  576. .full_with_browser .mylistPopupPanel.hideInFull.fixed:not(:hover) {
  577. right: -100px;
  578. transition: right 0.8s ease-in-out 0.5s;
  579. }
  580. .full_with_browser .mylistPopupPanel.hideInFull.fixed.right:not(:hover) {
  581. left: -100px; right: auto;
  582. transition: left 0.8s ease-in-out 0.5s;
  583. }
  584. .full_with_browser .mylistPopupPanel.hideInFull:not(:hover)>*{
  585. opacity: 0;
  586. transition: opacity 0.3s ease-out 1s;
  587. }
  588. .mylistPopupPanel.w_touch {
  589. height: 40px;
  590. }
  591. iframe.popup {
  592. position: absolute;
  593. }
  594. {* マウスホバーで出るほうのマイリスト登録パネル *}
  595. .mylistPopupPanel.popup {
  596. position: absolute;
  597. z-index: 1000000;
  598. box-shadow: 2px 2px 2px #888;
  599. }
  600. {* マイリスト登録パネルの中の各要素 *}
  601. .mylistPopupPanel .mylistSelect {
  602. width: 64px;
  603. margin: 0;
  604. padding: 0;
  605. font-size: 80%;
  606. white-space: nowrap;
  607. {*background: #eee;*}
  608. border: 1px solid silver;
  609. }
  610. .mylistSelect:focus {
  611. border-style: outset;
  612. }
  613. {* 誤操作を減らすため、とりマイの時だけスタイルを変える用 *}
  614. .mylistPopupPanel.w_touch button {
  615. padding: 8px 18px;
  616. }
  617. .mylistPopupPanel.w_touch .mylistSelect {
  618. font-size: 170%; width: 130px; border: none;
  619. }
  620. .mylistPopupPanel.w_touch .mylistSelect:focus {
  621. border: 1px dotted;
  622. }
  623. .mylistPopupPanel.deflistSelected button {
  624. }
  625. .mylistPopupPanel.mylistSelected button {
  626. color: #ccf;
  627. }
  628. .mylistPopupPanel button {
  629. margin: 0;
  630. font-weight: bolder;
  631. cursor: pointer;
  632. }
  633. .mylistPopupPanel button:active, #outline .playlistToggle:active, #outline .openVideoExplorer:active, #content .openConfButton:active {
  634. border:1px inset !important
  635. }
  636. .mylistPopupPanel button:hover, #outline .playlistToggle:hover, #outline .openVideoExplorer:hover, #outline .openConfButton:hover, #yukkuriPanel .yukkuriButton:hover {
  637. border:1px outset
  638. }
  639. .mylistPopupPanel .mylistAdd, .mylistPopupPanel .tagGet, #yukkuriPanel .yukkuriButton {
  640. 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;
  641. }
  642. .mylistPopupPanel .mylistAdd:focus, .mylistPopupPanel .tagGet:focus, #yukkuriPanel .yukkuriButton:focus {
  643. border-style: outset; color: orange;
  644. }
  645. .mylistPopupPanel.deflistSelected {
  646. color: #ff9;
  647. }
  648. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  649. 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;
  650. }
  651. .mylistPopupPanel.deflistSelected {
  652. color: #ff9;
  653. }
  654. .mylistPopupPanel .deflistRemove, #yukkuriPanel .yukkuriButton.active{
  655. 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;
  656. }
  657. .mylistPopupPanel.mylistSelected .deflistRemove {
  658. display: none;
  659. }
  660. .mylistPopupPanel .closeButton{
  661. color: #339;
  662. padding: 0;
  663. margin: 0;
  664. font-size: 80%;
  665. text-decoration: none;
  666. }
  667. .mylistPopupPanel .newTabLink{
  668. padding: 0 2px; text-decoration: underline; text-shadow: -1px -1px 0px #442B2B;
  669. }
  670. .mylistPopupPanel.fixed .newTabLink, .mylistPopupPanel.fixed .closeButton {
  671. display: none;
  672. }
  673. .w_fullScreenMenu .mylistPopupPanel.fixed { bottom: 2px; }
  674.  
  675. {* ポイントが無いときは表示しない *}
  676. .item:not(.silver):not(.gold) .uadContainer {
  677. display: none !important;
  678. }
  679.  
  680. .watchItLaterAPILoaderFrame {
  681. width: 1px;
  682. height: 1px;
  683. position: fixed;
  684. top: -100px;
  685. left: -100px;
  686. border: 0;
  687. overflow: hidden;
  688. }
  689.  
  690. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  691. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  692. addStyle(__css__, 'watchItLaterCommonStyle');
  693. })(); // end of commoncss
  694.  
  695.  
  696.  
  697.  
  698. (function() { // watchページだけのstyle
  699. if (!w.WatchApp) { return; }
  700. var __css__ = (function() { /*
  701. {* 動画タグとプレイリストのポップアップ *}
  702. #videoTagPopupContainer {
  703. }
  704. #videoTagPopupContainer.w_touch {
  705. line-height: 200%; font-size: 130%;
  706. }
  707. #videoTagPopupContainer.w_touch .nicodic{
  708. margin: 4px 14px;
  709. }
  710. .playlistMenuPopup {
  711. background: #666; color: white; padding: 4px 8px;
  712. }
  713. .playlistMenuPopup.w_touch {
  714. line-height: 250%;
  715. }
  716. #playlistSaveDialog {
  717. display: none;
  718. }
  719. #playlistSaveDialog.show {
  720. display: block;
  721. }
  722. #playlistSaveDialog.show .shadow{
  723. position: fixed;
  724. top: 0; left: 0; width: 100%; height: 100%;
  725. background: #000; opacity: 0.5;
  726. z-index: 30000;
  727. }
  728. #playlistSaveDialog.show .formWindow{
  729. position: fixed;
  730. margin: 0 auto;
  731. text-align: center;
  732. width: 100%;
  733. top: 45%;
  734. z-index: 30001;
  735. }
  736. #playlistSaveDialog .formWindow .formWindowInner{
  737. -webkit-transition: opacity 1s ease-out;
  738. transition: opacity 1s ease-out;
  739. opacity: 0;
  740. }
  741. #playlistSaveDialog.show .formWindow .formWindowInner{
  742. text-align: left;
  743. opacity: 1;
  744. margin: 0 auto;
  745. background: #f4f4f4;
  746. width: 500px;
  747. padding: 8px;
  748. border: 1px solid;
  749. }
  750. #playlistSaveDialog.show .formWindow .formWindowInner a{
  751. font-weight: bolder;
  752. }
  753. #playlistSaveDialog.show .formWindow .formWindowInner a:hover{
  754. text-decoration: underline; background: white;
  755. }
  756. #playlistSaveDialog.show .formWindow .formWindowInner label{
  757. margin: 8px;
  758. }
  759. #playlistSaveDialog.show .formWindow .formWindowInner input{
  760.  
  761. }
  762. #playlistSaveDialog.show .formWindow .formWindowInner .desc{
  763. font-size: 80%;
  764. }
  765. .playlistMenuPopup ul li {
  766. cursor: pointer;
  767. }
  768. .playlistMenuPopup ul li.savelist {
  769. color: #aaa;
  770. }
  771. .playlistMenuPopup ul li:hover {
  772. text-decoration: underline; background: #888;
  773. }
  774. #yukkuriPanel .yukkuriButton.active {
  775. border:1px inset
  776. }
  777.  
  778. #content .openConfButton {
  779. 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;
  780. }
  781. #outline .playlistToggle, #outline .openVideoExplorer, #outline .openConfButton {
  782. 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;
  783. height: 24px; border-radius: 0 0 8px 8px;
  784. }
  785. #outline .openConfButton { padding: 0 8px; letter-spacing: 4px; width: 60px; }
  786.  
  787. {* 全画面時にタグとプレイリストを表示しない時*}
  788. body.full_and_mini.full_with_browser #playerAlignmentArea{
  789. margin-bottom: 0 !important;
  790. }
  791. body.full_and_mini.full_with_browser #playlist{
  792. z-index: auto;
  793. }
  794. body.full_and_mini.full_with_browser .generationMessage{
  795. display: inline-block;
  796. }
  797. {* 全画面時にタグとプレイリストを表示する時 *}
  798. body.full_with_browser #playlist{
  799. z-index: 100;
  800. }
  801. body.full_with_browser .generationMessage{
  802. display: none;
  803. }
  804. body.full_with_browser .browserFullOption{
  805. padding-right: 200px;
  806. }
  807. {* 全画面時にニュースを隠す時 *}
  808. body.full_with_browser.hideNewsInFull #playerAlignmentArea{
  809. margin-bottom: -37px;
  810. }
  811. {* 少しでも縦スクロールを減らすため、動画情報を近づける。人によっては窮屈に感じるかも *}
  812. #outline {
  813. margin-top: -16px;
  814. }
  815. #outline #feedbackLink{
  816.  
  817. }
  818. #outline .videoEditMenuExpand{
  819. position: absolute;right: 0;top: 26px; z-index: 1;
  820. }
  821. {* ヘッダに表示する再生数 *}
  822. #videoCounter {
  823. color: #ff9; font-size: 70%;
  824. }
  825. {* 右に表示する動画情報 *}
  826. .sidePanel .sideVideoInfo, .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  827. padding: 0px 0px 0 0px; width: 196px; height: 100%; z-index: 10019;
  828. position:absolute; top:0; right:0; border: 1px solid #000;
  829. overflow-x: visible; overflow-y: auto;
  830. }
  831. {* 右に表示する動画情報 *}
  832. #playerTabWrapper.sidePanel .sideVideoInfo, #playerTabWrapper.sidePanel .sideIchibaPanel, #playerTabWrapper.sidePanel .sideReviewPanel {
  833. padding: 0px 0px 0 0px; width: 324px; height: 100%;
  834. position: absolute; top: 0; right:0;
  835. }
  836.  
  837. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideVideoInfo,
  838. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideIchibaPanel,
  839. body:not(.full_with_browser) .w_wide #playerTabWrapper .sideReviewPanel,
  840. .videoExplorer #playerTabWrapper .sideVideoInfo,
  841. .videoExplorer #playerTabWrapper .sideIchibaPanel,
  842. .videoExplorer #playerTabWrapper .sideReviewPanel {
  843. width: 418px;
  844. }
  845. #playerTabWrapper.w_videoInfo #appliPanel, #playerTabWrapper.w_ichiba #appliPanel, #playerTabWrapper.w_review #appliPanel {
  846. top: -9999px;
  847. }
  848. .sidePanel .sideVideoInfo {
  849. background: #f4f4f4; text-Align: left; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%; border: 1px solid black;
  850. }
  851. .sidePanel .sideIchibaPanel, .sidePanel .sideReviewPanel {
  852. background: #f4f4f4; text-Align: center; overflow-x: hidden; overflow-Y: auto; box-shadow: none; font-size: 90%;
  853. }
  854. .sidePanel .sideVideoInfo .sideVideoInfoInner {
  855. padding: 0 4px; position: relative;
  856. }
  857. .sidePanel .sideVideoInfo .videoTitleContainer {
  858. background: #ddd; text-align: center; color: #000; margin: 6px 6px 0;
  859. border: solid; border-width: 2px 2px 0; border-color: #888;
  860. }
  861. .sidePanel .sideVideoInfo .videoOwnerInfoContainer {
  862. margin: 0 6px; border: solid; border-width: 0 2px 0; border-color: #888;
  863. }
  864. .sidePanel .sideVideoInfo .videoThumbnailContainer {
  865. background: #ddd; text-align: center; color: #000; margin: 0;
  866. }
  867. .sidePanel .sideVideoInfo .videoThumbnailContainer img {
  868. cursor: pointer;
  869. }
  870. .sidePanel .sideVideoInfo .videoTitle {
  871. padding: 8px;
  872. }
  873. .sidePanel .sideVideoInfo .videoPostedAt {
  874. color: #333;
  875. }
  876. .sidePanel .sideVideoInfo .videoStats{
  877. font-size:90%;
  878. }
  879. .sidePanel .sideVideoInfo .videoStats li{
  880. display: inline-block; margin: 0 2px;
  881. }
  882. .sidePanel .sideVideoInfo .videoStats li span{
  883. font-weight: bolder;
  884. }
  885. .sidePanel .sideVideoInfo .videoStats .ranking{
  886. display: none !important;
  887. }
  888. .sidePanel .sideVideoInfo .videoInfo{
  889. background: #ddd; text-align: center; padding: 4px; margin: 0 6px 6px;
  890. border: solid; border-width: 0 2px 2px; border-color: #888;
  891. }
  892. .sideVideoInfo .sideVideoInfoInner{
  893. -webkit-transition: opacity 1s ease-out, color 3s ease-out;
  894. transition: opacity 1s ease-out, color 3s ease-out;
  895. opacity: 0;
  896. }
  897. .sideVideoInfo.show .sideVideoInfoInner{
  898. opacity: 1;
  899. }
  900. .videoCount.blink {
  901. color: #ccc;
  902. }
  903. .sidePanel .videoCountDiff {
  904. position: absolute; color: white; right: 0; opacity: 0; z-index: 100; text-shadow: 1px 1px 0 orange;
  905. }
  906. .sidePanel .videoCountDiff.blink {
  907. opacity: 1; color: white;
  908. }
  909. #siteHeader .videoCount, #siteHeader .videoCountDiff {
  910. min-width: 32px; text-align: right; display: inline-block;
  911. }
  912. #siteHeader .videoCountDiff, #trueBrowserFullShield .videoCountDiff {
  913. position: absolute; color: yellow; opacity: 0; font-weight: bolder; text-shadow: 1px 1px 0 red;
  914. }
  915. #siteHeader .videoCountDiff.blink, #trueBrowserFullShield .videoCountDiff.blink {
  916. opacity: 1; color: yellow;
  917. }
  918. #trueBrowserFullShield .blink, #videoCounter .blink {
  919. color: #000;
  920. }
  921. .videoCountDiff:before {content: '+';}
  922. .videoCountDiff.down:before {content: ''; }
  923. #popupMarquee .videoCountDiff {display: none;}
  924. .sidePanel .sideVideoInfo .videoDescription{
  925. overflow-x: hidden; text-align: left;
  926. }
  927. .sidePanel .sideVideoInfo .videoDescriptionInner{
  928. margin: 0 8px;
  929. }
  930. .sidePanel .sideVideoInfo .videoDetails{
  931. min-width: 150px;
  932. }
  933. .sidePanel .sideVideoInfo .videoDetails a{
  934. margin: auto 4px;
  935. }
  936. .sidePanel .sideVideoInfo .videoDetails a:visited{
  937. color: #04618c;
  938. }
  939. .sidePanel .sideVideoInfo .videoDetails a.watch{
  940. margin: auto 30px auto 4px;
  941. display:inline-block;
  942. }
  943. .sideVideoInfo .userName, .sideVideoInfo .channelName{
  944. display: block;
  945. font-size: 120%;
  946. cursor: pointer;
  947. }
  948. .sideVideoInfo .userIconContainer, .sideVideoInfo .channelIconContainer {
  949. background: #ddd; width: 100%; text-align: center; float: none;
  950. }
  951. .sidePanel .userIcon, .sidePanel .channelIcon{
  952. min-width: 128px; max-width: 150px; width: auto; height: auto; border: 0;
  953. box-shadow: 0 0 4px #666; cursor: pointer;
  954. }
  955. .sideVideoInfo .descriptionThumbnail {
  956. text-align: left; font-size: 90%; padding: 4px; background: #ddd;
  957. min-height: 50px; margin: 4px 8px; font-weight: normal; color: black;
  958. }
  959. .sideVideoInfo .descriptionThumbnail p {
  960. margin: 0 8px;
  961. font-weight: bolder;
  962. }
  963. .sideVideoInfo .descriptionThumbnail .uploadAt {
  964. font-size: 90%;
  965. margin: 4px;
  966. color: #333;
  967. }
  968. .sideVideoInfo .descriptionThumbnail .counterContainer {
  969. text-align: center;
  970. }
  971. .sideVideoInfo .descriptionThumbnail .view,
  972. .sideVideoInfo .descriptionThumbnail .comment,
  973. .sideVideoInfo .descriptionThumbnail .mylist
  974. {
  975. font-size: 90%;
  976. white-space: nowrap;
  977. margin-right: 4px;
  978. color: #333;
  979. }
  980. .sideVideoInfo .descriptionThumbnail .count {
  981. font-weight: bolder;
  982. }
  983.  
  984. .sideVideoInfo .descriptionThumbnail.video img{
  985. height: 50px; cursor: pointer; float: left;
  986. }
  987. .sideVideoInfo .descriptionThumbnail.mylist img{
  988. height: 40px; cursor: pointer;
  989. }
  990. .sideVideoInfo .descriptionThumbnail.illust img{
  991. height: 60px; cursor: pointer;
  992. }
  993. .sideVideoInfo a.otherSite {
  994. font-weight: bolder; text-decoration: underline;
  995. }
  996. body:not(.videoExplorer) #leftPanel.removed {
  997. display: none; left: 0px;
  998. }
  999. body:not(.videoExplorer) #leftPanel.removed .sideVideoInfo {
  1000. display: none; width: 0px !important; border: none; margin: 0; padding: 0; right: auto;
  1001. }
  1002. .sideVideoInfo .userIconContainer.isUserVideoPublic .notPublic { display: none; }
  1003. .sideVideoInfo .userIconContainer .isPublic { display: none; }
  1004. .sideVideoInfo .userIconContainer.isUserVideoPublic .isPublic { display: inline; }
  1005. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem,
  1006. .sidePanel .sideIchibaPanel .ichiba_mainitem {
  1007. width: 180px; display:inline-block; vertical-align: top;
  1008. margin: 4px 3px; border 1px solid silver;
  1009. }
  1010.  
  1011. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  1012. font-size: 60px;
  1013. }
  1014. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem>div>dt {
  1015. height: 50px;position: relative;
  1016. }
  1017. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  1018. position: absolute;width: 100%;
  1019. }
  1020. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonUe {
  1021. position: absolute;
  1022. }
  1023. body.videoExplorer #content.w_adjusted .sideIchibaPanel .ichiba_mainitem .balloonShita {
  1024. position: absolute;
  1025. }
  1026.  
  1027. .sidePanel.videoInfo, .sidePanel.ichiba{
  1028. background: none;
  1029. }
  1030.  
  1031. .sideVideoInfo.isFavorite .userName:after, .sideVideoInfo.isFavorite.isChannel .videoOwnerInfoContainer .channelName:after{
  1032. content: ' ★ '; color: gold; text-shadow: 1px 1px 1px black;
  1033. }
  1034.  
  1035. .sidePanel.videoInfo #leftPanelContent, .sidePanel.ichiba #leftPanelContent {
  1036. display: none;
  1037. }
  1038. .sidePanel.w_comment #playerTabContainer,
  1039. .sidePanel.videoInfo .sideVideoInfo,
  1040. .sidePanel.ichiba .sideIchibaPanel,
  1041. .sidePanel.w_videoInfo .sideVideoInfo,
  1042. .sidePanel.w_ichiba .sideIchibaPanel,
  1043. .sidePanel.w_review .sideReviewPanel {
  1044. display: block; z-index: 10060;
  1045. }
  1046. .sidePanel:not(.w_comment) .watchWatchContainer {
  1047. display: none;
  1048. }
  1049.  
  1050. #leftPanelTabContainer {
  1051. display:none; background: #666; position: absolute; right: 4px; top: -27px; list-style-type: none; padding: 4px 6px 3px 60px; height: 20px;
  1052. }
  1053. #sidePanelTabContainer {
  1054. display: none;
  1055. position: absolute; list-style-type: none;
  1056. padding: 5px 10px 0; right: -408px; top: 0; width: 350px; height: 34px;
  1057. transform: rotate(90deg); transform-origin: 0 0 0;
  1058. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  1059. z-index: 1000;
  1060. }
  1061. .full_with_browser #sidePanelTabContainer {
  1062. background: #000 !important;
  1063. }
  1064. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left {
  1065. background: #000; right: auto; left: -375px; padding: 0; height: 27px;
  1066. transform: rotate(-90deg); transform-origin: 100% 0 0;
  1067. -webkit-transform: rotate(-90deg); -webkit-transform-origin: 100% 0 0;
  1068. }
  1069.  
  1070. #leftPanelTabContainer.w_touch {
  1071. top: -40px; height: 33px;
  1072. }
  1073. .sidePanel:hover #sidePanelTabContainer, .sidePanel:hover #leftPanelTabContainer {
  1074. display: block;
  1075. }
  1076. #leftPanelTabContainer .tab{
  1077. display: inline-block; cursor: pointer; background: #999; padding: 2px 4px 0px; border-width: 2px 2px 0px;
  1078. }
  1079. #leftPanelTabContainer.w_touch .tab, #sidePanelTabContainer.w_touch .tab {
  1080. padding: 8px 12px 8px;
  1081. }
  1082. #sidePanelTabContainer .tab {
  1083. background: none repeat scroll 0 0 #999999; border-width: 2px 2px 0; cursor: pointer;
  1084. display: inline-block; font-size: 13px; padding: 5px 10px 8px;
  1085. border-radius: 8px 8px 0px 0px;
  1086. }
  1087. body:not(.videoExplorer):not(.full_with_browser) #sidePanelTabContainer.left .tab {
  1088. display: inline-block; font-size: 13px; padding: 5px 10px 0px;
  1089. }
  1090. #leftPanel.videoInfo .tab.videoInfo, #leftPanel.ichiba .tab.ichiba {
  1091. background: #f4f4f4; border-style: outset;
  1092. }
  1093. #playerTabWrapper.w_comment .tab.comment,
  1094. #playerTabWrapper.w_videoInfo .tab.videoInfo,
  1095. #playerTabWrapper.w_ichiba .tab.ichiba,
  1096. #playerTabWrapper.w_review .tab.review
  1097. {
  1098. background: #dfdfdf; border-style: outset;
  1099. }
  1100.  
  1101. #playerTabWrapper.w_videoInfo #playerCommentPanel,
  1102. #playerTabWrapper.w_ichiba #playerCommentPanel,
  1103. #playerTabWrapper.w_review #playerCommentPanel {
  1104. {*display: none;*} top: -9999px;
  1105. }
  1106. .sidePanel.ichibaEmpty .tab.ichiba, .sidePanel.reviewEmpty .tab.review {
  1107. color: #ccc;
  1108. }
  1109.  
  1110. .sideIchibaPanel .ichibaPanelInner {
  1111. margin:0; color: #666;
  1112. }
  1113. .sideIchibaPanel .ichibaPanelHeader .logo{
  1114. text-shadow: 1px 1px 1px #666; cursor: pointer; padding: 4px 0px 4px; font-size: 125%;
  1115. }
  1116. .sideIchibaPanel .ichibaPanelFooter{
  1117. text-align: center;
  1118. }
  1119. .sideIchibaPanel .ichiba_mainitem {
  1120. margin: 0 0 8px 0; background: white; border-bottom : 1px dotted #ccc;
  1121. }
  1122. .sideIchibaPanel .ichiba_mainitem a:hover{
  1123. background: #eef;
  1124. }
  1125. .sideIchibaPanel .ichiba_mainitem>div {
  1126. max-width: 266px; margin: auto; text-align: center;
  1127. }
  1128. .sideIchibaPanel .ichiba_mainitem .blomagaArticleNP {
  1129. background: url("http://ichiba.nicovideo.jp/embed/zero/img/bgMainBlomagaArticleNP.png") no-repeat scroll 0 0 transparent;
  1130. height: 170px;
  1131. margin: 0 auto;
  1132. width: 180px;
  1133. }
  1134. .sideIchibaPanel .ichiba_mainitem .blomagaLogo {
  1135. color: #FFFFFF;font-size: 9px;font-weight: bold;padding-left: 10px;padding-top: 8px;
  1136. }
  1137. .sideIchibaPanel .ichiba_mainitem .blomagaLogo span{
  1138. background: none repeat scroll 0 0 #AAAAAA;padding: 0 3px;
  1139. }
  1140. .sideIchibaPanel .ichiba_mainitem .blomagaText {
  1141. color: #666666;font-family: 'HGS明朝E','MS 明朝';font-size: 16px;height: 100px;
  1142. padding: 7px 25px 0 15px;text-align: center;white-space: normal;word-break: break-all;word-wrap: break-word;
  1143. }
  1144. .sideIchibaPanel .ichiba_mainitem .blomagaAuthor {
  1145. color: #666666; font-size: 11px;padding: 0 20px 0 10px;text-align: right;
  1146. }
  1147. .sideIchibaPanel .ichiba_mainitem .balloonUe{
  1148. bottom: 12px; display: block; max-width: 266px;
  1149. }
  1150. .sideIchibaPanel .ichiba_mainitem .balloonUe a{
  1151. background: url("/img/watch_zero/ichiba/imgMainBalloonUe.png") no-repeat scroll center top transparent;
  1152. color: #666666 !important;
  1153. display: block;
  1154. font-size: 108%;
  1155. line-height: 1.2em;
  1156. margin: 0 auto;
  1157. padding: 8px 15px 3px;
  1158. text-align: center;
  1159. text-decoration: none;
  1160. word-wrap: break-word;
  1161. }
  1162. .sideIchibaPanel .ichiba_mainitem .balloonShita{
  1163. height: 12px; bottom: 0; left: 0;
  1164. }
  1165. .sideIchibaPanel .ichiba_mainitem .balloonShita img{
  1166. vertical-align: top !important;
  1167. }
  1168. .sideIchibaPanel .ichiba_mainitem .ichibaMarquee {
  1169. display: none;
  1170. }
  1171. .sideIchibaPanel .ichiba_mainitem .thumbnail span {
  1172. font-size: 22px; color: #0066CC;
  1173. font-family: 'ヒラギノ明朝 Pro W3','Hiragino Mincho Pro','MS P明朝','MS PMincho',serif;
  1174. }
  1175. .sideIchibaPanel .ichiba_mainitem .action {
  1176. font-size: 85%;
  1177. }
  1178. .sideIchibaPanel .ichiba_mainitem .action .buy {
  1179. font-weight: bolder; color: #f60;
  1180. }
  1181. .sideIchibaPanel .ichiba_mainitem .itemname {
  1182. font-weight: bolder;
  1183. }
  1184. .sideIchibaPanel .ichiba_mainitem .maker {
  1185. font-size: 77%; margin-bottom: 2px;
  1186. }
  1187. .sideIchibaPanel .ichiba_mainitem .price {
  1188. }
  1189. .sideIchibaPanel .ichiba_mainitem .action .click {
  1190. font-weight: bolder;
  1191. }
  1192. .sideIchibaPanel .ichiba_mainitem .goIchiba {
  1193. font-size: 77%; margin: 5px 0;
  1194. }
  1195. .sideIchibaPanel .addIchiba, .sideIchibaPanel .reloadIchiba {
  1196. cursor: pointer;
  1197. }
  1198. .sideIchibaPanel .noitem {
  1199. cursor: pointer;
  1200. }
  1201.  
  1202. #outline .bottomAccessContainer {
  1203. position: absolute; top: 12px;
  1204. }
  1205. #outline .bottomConfButtonContainer {
  1206. position: absolute; top: 12px; right: 0px;
  1207. }
  1208. body.videoExplorer .bottomAccessContainer{
  1209. display: none;
  1210. }
  1211. #outline.under960 .bottomAccessContainer{
  1212. right: 60px;
  1213. }
  1214. .watchItLaterSettingMenu {
  1215. font-weight: bolder;
  1216. white-space: nowrap;
  1217. }
  1218. #outline .sidebar {
  1219. -webkit-transition: margin-top 0.3s ease-out;
  1220. transition: margin-top 0.3s ease-out;
  1221. }
  1222. #outline.under960 .sidebar {
  1223. margin-top: 24px;
  1224. }
  1225. #videoHeader.menuClosed .watchItLaterMenu, #videoHeader.menuClosed .hidariue { display: none; }
  1226. #videoHeader .watchItLaterMenu {
  1227. position: absolute; width: 100px; left: -55px; top: 32px;
  1228. }
  1229. {* タイトルクリックでヘッダが開閉できるのをわかりやすく *}
  1230. .videoDetailToggleButton:hover {
  1231. text-decoration: underline;
  1232. }
  1233. .videoDetailToggleButton:hover:after {
  1234. content: '▼';
  1235. position: absolute;
  1236. width: 32px;
  1237. height: 20px;
  1238. top: 0;
  1239. bottom: 0;
  1240. right: -32px;
  1241. margin: auto;
  1242. color: #888;
  1243. font-size: 80%;
  1244. }
  1245. .infoActive .videoDetailToggleButton:hover:after {
  1246. content: '▲';
  1247. }
  1248.  
  1249. {* プレイリスト出したり隠したり *}
  1250. #playlist>* {
  1251. -webkit-transition: opacity 0.6s; transition: opacity 0.6s;
  1252. }
  1253. body:not(.full_with_browser):not(.videoExplorer) #playlist.w_closing>* {
  1254. opacity: 0;
  1255. }
  1256. body:not(.full_with_browser):not(.videoExplorer) #playlist:not(.w_show){
  1257. position: absolute; top: -9999px;
  1258. }
  1259. #playlist.w_show{
  1260. {*max-height: 180px;*}
  1261. }
  1262. .playlistToggle:after {
  1263. content: "▼";
  1264. }
  1265. .playlistToggle.w_show:after {
  1266. content: "▲";
  1267. }
  1268.  
  1269. body.videoExplorer #content.w_adjusted #playlist .playlistInformation {
  1270. white-space: nowrap;
  1271. }
  1272. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .playbackOption {
  1273. position: absolute;
  1274. }
  1275. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .generationMessage{
  1276. margin-left: 90px; max-width: 350px; overflow: hidden; text-overflow: ellipsis;
  1277. }
  1278. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption {
  1279. position: absolute; right: 0; top: 0;
  1280. }
  1281. body.videoExplorer #content.w_adjusted #playlist .playlistInformation .browserFullOption a {
  1282. background: #444;
  1283. }
  1284. #playlistContainerInner .thumbContainer, #playlistContainerInner .balloon{
  1285. cursor: move;
  1286. }
  1287.  
  1288.  
  1289. {* ページャーの字が小さくてクリックしにくいよね *}
  1290. #resultPagination {
  1291. padding: 5px; font-weight: bolder; border: 1px dotted silter; font-size: 130%;
  1292. }
  1293.  
  1294. #playlistContainer #playlistContainerInner .playlistItem .balloon {
  1295. bottom: auto; top: -2px; padding: auto;
  1296. }
  1297.  
  1298. body.w_channel #leftPanel .userIconContainer{
  1299. display: none;
  1300. }
  1301. {* WatchItLater設定パネル *}
  1302. #watchItLaterConfigPanel {
  1303. position: fixed; bottom: 0px; right: 16px; z-index: 10001;
  1304. width: 460px; padding: 0;
  1305. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1306. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1307. transform: scaleY(0); -webkit-transform: scaleY(0);
  1308. }
  1309. #watchItLaterConfigPanel.open {
  1310. transform: scaleY(1); -webkit-transform: scaleY(1);
  1311. }
  1312. #watchItLaterConfigPanelShadow {
  1313. position: fixed; bottom: 16px; right: 16px; z-index: 10000;
  1314. width: 460px; height: 559px; padding: 0;
  1315. background: #000; {*box-shadow: 0 0 2px black; border-radius: 8px;*} -webkit-filter: opacity(70%);
  1316. transition: transform 0.4s ease-in-out; -webkit-transition: -webkit-transform 0.4s ease-in-out;
  1317. transform-origin: 50% 0; -webkit-transform-origin: 50% 0;
  1318. transform: scaleY(0); -webkit-transform: scaleY(0);
  1319. }
  1320. #watchItLaterConfigPanelShadow.open {
  1321. transform: scaleY(1); -webkit-transform: scaleY(1);
  1322. }
  1323. #watchItLaterConfigPanelShadowTop {
  1324. position: fixed; bottom: 563px; right:0px; z-index: 10000; background: #333;
  1325. width: 492px; height: 20px; padding: 0; border-radius: 32px; -webkit-filter: opacity(90%); display: none;
  1326. }
  1327. #watchItLaterConfigPanelOverShadow {
  1328. position: fixed; bottom: 575px; right: 0px; width: 488px; height: 8px;
  1329. box-shadow: 0 4px 16px #333;z-index: 10002; display: none;
  1330. }
  1331. #watchItLaterConfigPanel .head {
  1332. background-color: #CCCCCC;border-radius: 0;color: black;height: 50px;
  1333. overflow: hidden;padding: 5px 0 0 16px;position: relative;
  1334. }
  1335. #watchItLaterConfigPanel .head h2 {
  1336. font-size: 135%;
  1337. }
  1338. #watchItLaterConfigPanel .inner{
  1339. height: 500px; overflow-y: auto;border-width: 4px 16px 16px 16px; border-radius: 0 0 16px 16px;
  1340. border-style: solid;border-color: #ccc;
  1341. }
  1342. #watchItLaterConfigPanel ul{
  1343. border-style: inset; border-color: #ccc; border-width: 0 1px 0;
  1344. }
  1345. #watchItLaterConfigPanel ul.shortcutContainer{
  1346. border-width: 0 1px 1px;
  1347. }
  1348. #watchItLaterConfigPanel ul.videoStart{
  1349. border-width: 1px 1px 0;
  1350. }
  1351. #watchItLaterConfigPanel li{
  1352. }
  1353. #watchItLaterConfigPanel li:hover{
  1354. {*background: #ddd;*}
  1355. }
  1356. #watchItLaterConfigPanel li.buggy{
  1357. color: #888;
  1358. }
  1359. #watchItLaterConfigPanel label{
  1360. margin: 0 5px;
  1361. }
  1362. #watchItLaterConfigPanel label:hover{
  1363. }
  1364. #watchItLaterConfigPanel .foot {
  1365. text-align: right; padding: 0 12px;
  1366. }
  1367. #watchItLaterConfigPanel .closeButton{
  1368. border: 0 none;border-radius: 0 0 4px 4px;box-shadow: 0 1px 2px white;color: #666; border: 1px solid #999;
  1369. cursor: pointer;float: right;margin-top: 8px;position: absolute;right: 16px;
  1370. text-shadow: 0 1px 0 white;top: -10px; width: 60px;
  1371. }
  1372. #watchItLaterConfigPanel.autoBrowserFull_false .disableAutoBrowserFullIfNicowari,
  1373. #watchItLaterConfigPanel.autoBrowserFull_true .autoScrollToPlayer,
  1374. #watchItLaterConfigPanel.autoBrowserFull_true .autoOpenSearch,
  1375. #watchItLaterConfigPanel.removeLeftPanel_true .leftPanelJack {
  1376. color: #ccc; text-shadow: -1px -1px 0 #888;
  1377. }
  1378. #watchItLaterConfigPanel .reload .title:after {
  1379. content: ' (※)'; font-size: 80%; color: #900;
  1380. }
  1381. #watchItLaterConfigPanel .debugOnly {
  1382. display: none;
  1383. }
  1384. #watchItLaterConfigPanel.debugMode .debugOnly {
  1385. display: block; background: #888;
  1386. }
  1387. #watchItLaterConfigPanel .section {
  1388. border-style: solid;border-width: 10px 12px 10px 12px;color: white; font-size: 135%; position: relative;
  1389. font-weight: bolder; cursor: pointer; {*text-shadow: 2px 2px 1px #000000;*}
  1390. 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;
  1391. }
  1392. #watchItLaterConfigPanel .open .section {
  1393. border-width: 20px 12px 12px 12px;
  1394. transition: border-width 0.2s ease-in-out ; -webkit-transition: border-width 0.2s ease-in-out ;
  1395. }
  1396. #watchItLaterConfigPanel .section:hover:after {
  1397. content: '▼';
  1398. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1399. transition: transform 0.2s ease-in-out 0.4s; -webkit-transition: -webkit-transform 0.2s ease-in-out 0.4s;
  1400. }
  1401. #watchItLaterConfigPanel .open .section:after {
  1402. content: '▼';
  1403. position: absolute; top: 0px; right: 10px; font-size: 150%;
  1404. transform: rotate(180deg); -webkit-transform: rotate(180deg);
  1405. transition: transform 0.2s ease-in-out ; -webkit-transition: -webkit-transform 0.2s ease-in-out;
  1406. }
  1407. #watchItLaterConfigPanel .section > div {
  1408. padding: 8px 0 8px 12px; box-shadow: 0 0 4px black;
  1409. }
  1410. #watchItLaterConfigPanel .section > div > span {
  1411. {*background: #333;*}
  1412. }
  1413. #watchItLaterConfigPanel li:not(.section) {
  1414. background: #fff; border-width: 0px 0px 0px 24px; border-style: solid; border-color: #fff;
  1415. max-height: 0px; overflow: hidden;
  1416. transition: max-height 0.4s ease-in-out , border-width 0.4s ease-in-out;
  1417. }
  1418. #watchItLaterConfigPanel .open li:not(.section) {
  1419. max-height: 100px; border-width: 4px 0px 4px 24px;
  1420. transition: max-height 0.4s ease-in-out 0.2s, border-width 0.4s ease-in-out 0.2s;
  1421. }
  1422. #watchItLaterConfigPanel .section .description{
  1423. display: block; font-size: 80%;;
  1424. }
  1425. #watchItLaterConfigPanel .shortcutSetting:not(.enable) span :not(.enable){
  1426. color: silver;
  1427. }
  1428. #watchItLaterConfigPanel .shortcutSetting .enable {
  1429. cursor: pointer; margin: auto 10px;
  1430. }
  1431. #watchItLaterConfigPanel .shortcutSetting .enable:before {
  1432. content: '○ ';
  1433. }
  1434. #watchItLaterConfigPanel .shortcutSetting.enable .enable:before {
  1435. content: '㋹ '; color: blue;
  1436. }
  1437. #watchItLaterConfigPanel .shortcutSetting .ctrl, #watchItLaterConfigPanel .shortcutSetting .alt, #watchItLaterConfigPanel .shortcutSetting .shift {
  1438. cursor: pointer; border: 2px outset; margin: 4px 4px; padding: 2px 4px; width: 180px; border-radius: 4px;background: #eee;
  1439. }
  1440. #watchItLaterConfigPanel .shortcutSetting.ctrl .ctrl, #watchItLaterConfigPanel .shortcutSetting.alt .alt, #watchItLaterConfigPanel .shortcutSetting.shift .shift {
  1441. border: 2px inset; color: blue;
  1442. }
  1443. #watchItLaterConfigPanel .hoverMenuDelay input {
  1444. width: 50px; ime-mode: disabled; text-align: center;
  1445. }
  1446.  
  1447.  
  1448. {* 動画検索画面に出るお気に入りタグ・お気に入りマイリスト *}
  1449. .videoExplorerMenu .watchItLaterMenu.open,
  1450. .videoExplorerMenu .watchItLaterMenu.opening {
  1451. background: -moz-linear-gradient(center top , #D1D1D1, #FDFDFD) repeat scroll 0 0 transparent !important;
  1452. background: -webkit-gradient(linear, left top, left bottom, from(#D1D1D1), to(#FDFDFD)) !important;
  1453. border-bottom: 0 !important;
  1454. }
  1455. .videoExplorerMenu .watchItLaterMenu {
  1456. position: relative;
  1457. {*background: -moz-linear-gradient(center top , whitesmoke 0%, #E1E1E1 100%) repeat scroll 0 0 transparent;*}
  1458. {*box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.1) inset;*}
  1459. {*background: #f5f5f5;*}
  1460. border-bottom: 1px solid #CCCCCC;
  1461. }
  1462. .videoExplorerMenu .watchItLaterMenu:hover{
  1463. background: #dbdbdb;
  1464. }
  1465. .videoExplorerMenu .watchItLaterMenu {
  1466. padding: 0 12px; display: block; color: black;
  1467. }
  1468. .videoExplorerMenu .slideMenu{
  1469. width: 100%; height: auto !important;
  1470. overflow-x: hidden;
  1471. overflow-y: auto;
  1472. padding: 0;
  1473. background: #fdfdfd;
  1474. border-top: 0 !important;
  1475. display: block;
  1476. max-height: 0;
  1477. transition: max-height 0.5s ease-in-out;
  1478. }
  1479. .videoExplorerMenu .slideMenu.open {
  1480. max-height: 2000px;
  1481. transition: max-height 1s ease-in-out;
  1482. }
  1483. .videoExplorerMenu .toggleVideoExplorerMenu a {
  1484. color: black; display: block;
  1485. }
  1486. .videoExplorerMenu .toggleVideoExplorerMenu a:after {
  1487. content: "▼"; position: absolute; background: none; top: 0px; right: 10px; color: #ccc;
  1488. }
  1489. .videoExplorerMenu .toggleVideoExplorerMenu.open a:after {
  1490. content: "▲";
  1491. }
  1492.  
  1493. .videoRankingList .isCategory {
  1494. position: relative;
  1495. }
  1496.  
  1497. .rankingCategoryToggle {
  1498. position: absolute;
  1499. display: none;
  1500. height: 20px;
  1501. padding: 0px 8px;
  1502. right: 14px;
  1503. top: 0;
  1504. cursor: pointer;
  1505. border: 1px solid;
  1506. color: #666;
  1507. outline: none;
  1508. }
  1509. .rankingCategoryToggle::-moz-focus-inner {
  1510. border: 0px;
  1511. }
  1512. .slideMenu.open .isCategory:hover .rankingCategoryToggle {
  1513. display: block;
  1514. }
  1515. .categoryClose .rankingCategoryToggle .close, .rankingCategoryToggle .open{
  1516. display: none;
  1517. }
  1518. .categoryClose .rankingCategoryToggle .open{
  1519. display: inline;
  1520. }
  1521. .videoRankingList li:not(.isCategory) {
  1522. transition: max-height 0.5s;
  1523. max-height: 50px; overflow:hidden;
  1524. margin-left: 8px;
  1525. }
  1526. .videoRankingList .categoryClose:not(.isCategory) {
  1527. max-height: 0px;
  1528. }
  1529.  
  1530.  
  1531. .videoExplorerMenu .slideMenu ul{
  1532. }
  1533. .videoExplorerMenu .slideMenu ul li{
  1534. background: #fdfdfd; padding: 0; border: 0;font-size: 90%; height: auto !important;
  1535. }
  1536. .videoExplorerMenu .slideMenu ul li a{
  1537. line-height: 165%; background: none; display: block;
  1538. }
  1539. .videoExplorerMenu.w_touch .slideMenu ul li a{
  1540. line-height: 300%; font-size: 120%; color: black;
  1541. }
  1542. .videoExplorerMenu .slideMenu ul li a:before{
  1543. background: url("http://uni.res.nimg.jp/img/zero_my/icon_folder_default.png") no-repeat scroll 0 0 transparent;
  1544. display: inline-block;
  1545. height: 14px;
  1546. margin: -4px 4px 0 0;
  1547. vertical-align: middle;
  1548. width: 18px;
  1549. content: ""
  1550. }
  1551. .videoExplorerMenu .slideMenu ul li a.defMylist:before{ background-position: 0 -253px;}
  1552. .videoExplorerMenu .slideMenu ul li.folder0 a:before{ background-position: 0 0;}
  1553. .videoExplorerMenu .slideMenu ul li.folder1 a:before{ background-position: 0 -23px;}
  1554. .videoExplorerMenu .slideMenu ul li.folder2 a:before{ background-position: 0 -46px;}
  1555. .videoExplorerMenu .slideMenu ul li.folder3 a:before{ background-position: 0 -69px;}
  1556. .videoExplorerMenu .slideMenu ul li.folder4 a:before{ background-position: 0 -92px;}
  1557. .videoExplorerMenu .slideMenu ul li.folder5 a:before{ background-position: 0 -115px;}
  1558. .videoExplorerMenu .slideMenu ul li.folder6 a:before{ background-position: 0 -138px;}
  1559. .videoExplorerMenu .slideMenu ul li.folder7 a:before{ background-position: 0 -161px;}
  1560. .videoExplorerMenu .slideMenu ul li.folder8 a:before{ background-position: 0 -184px;}
  1561. .videoExplorerMenu .slideMenu ul li.folder9 a:before{ background-position: 0 -207px;}
  1562.  
  1563. .videoExplorerMenu .slideMenu ul li.g_ent2 a:before { background-position: 0 -23px;}
  1564. .videoExplorerMenu .slideMenu ul li.g_life2 a:before { background-position: 0 -46px;}
  1565. .videoExplorerMenu .slideMenu ul li.g_politics a:before { background-position: 0 -69px;}
  1566. .videoExplorerMenu .slideMenu ul li.g_tech a:before { background-position: 0 -92px;}
  1567. .videoExplorerMenu .slideMenu ul li.g_culture2 a:before { background-position: 0 -115px;}
  1568. .videoExplorerMenu .slideMenu ul li.g_other a:before { background-position: 0 -138px;}
  1569. .videoExplorerMenu .slideMenu ul li.r18 a:before { background-position: 0 -207px;}
  1570. .videoExplorerMenu .slideMenu ul li.all a.all,
  1571. .videoExplorerMenu .slideMenu ul li.g_ent2 a.g_ent2,
  1572. .videoExplorerMenu .slideMenu ul li.g_life2 a.g_life2,
  1573. .videoExplorerMenu .slideMenu ul li.g_politics a.g_politics,
  1574. .videoExplorerMenu .slideMenu ul li.g_tech a.g_tech,
  1575. .videoExplorerMenu .slideMenu ul li.g_culture2 a.g_culture2,
  1576. .videoExplorerMenu .slideMenu ul li.g_other a.g_other,
  1577. .videoExplorerMenu .slideMenu ul li.r18 a.r18
  1578. { font-weight: bolder; border-top: 1px dotted #ccc; }
  1579.  
  1580.  
  1581. .videoExplorerMenu .slideMenu ul li a:after{
  1582. background: none !important;
  1583. }
  1584. .videoExplorerMenu .slideMenu ul li a:hover{
  1585. background: #f0f0ff;
  1586. }
  1587. .videoExplorerMenu .slideMenu ul .reload{
  1588. cursor: pointer; border: 1px solid; padding: 0;
  1589. }
  1590.  
  1591. .videoExplorerMenu .tagSearchHistory {
  1592. border-radius: 0px; margin-top: 2px; padding: 4px; background: #ccc;
  1593. }
  1594. .videoExplorerMenu .itemList > li, #videoExplorerExpand {
  1595. background: #f5f5f5;
  1596. }
  1597. .videoExplorerMenu .itemList ul > li:hover {
  1598. background: #e7e7e7;
  1599. }
  1600. .videoExplorerMenu .itemList ul > li.active {
  1601. background: #343434;
  1602. }
  1603.  
  1604.  
  1605. {* 動画タグが1行以下の時 *}
  1606. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1607. height: 12px; padding: 6px 4px 2px;
  1608. }
  1609. body:not(.full_with_browser) .tag1Line #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit .toggleText{
  1610. display: none;
  1611. }
  1612. {* 動画タグが2行以下の時 *}
  1613. body:not(.full_with_browser) .tag2Lines #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  1614. height: 36px;
  1615. }
  1616. {* タグ領域とプレイヤーの隙間をなくす *}
  1617. body:not(.full_with_browser) #videoTagContainer, body:not(.full_with_browser) #videoHeader .videoMenuToggle {
  1618. margin-bottom: -10px;
  1619. }
  1620. #videoHeaderMenu .searchContainer .searchText {
  1621. margin-top: -8px;
  1622. }
  1623.  
  1624. body.size_small #playerContainerWrapper {
  1625. padding: 0;
  1626. }
  1627.  
  1628. {* ニュース履歴 *}
  1629. body.videoExplorer #textMarquee .openNewsHistory, body.videoExplorer #textMarquee .newsHistory {
  1630. display: none;
  1631. }
  1632. #textMarquee .openNewsHistory {
  1633. position: absolute; width: 30px;
  1634. font-size: 13px; padding: 0; margin: 0; height: 28px;
  1635. cursor: pointer;
  1636. bottom: 0;
  1637. background: none repeat scroll 0 0 transparent;
  1638. border: 1px none;
  1639. border-radius: 2px 2px 2px 2px;
  1640. cursor: pointer;
  1641. right: 18px;
  1642. z-index: 200;
  1643. }
  1644. #textMarquee .newsHistory {
  1645. position: absolute;
  1646. bottom: 0px; right: 0px; width: 100%;
  1647. max-height: 132px;
  1648. min-height: 40px;
  1649. overflow-y: auto;
  1650. overflow-x: hidden;
  1651. z-index: 1;
  1652. padding: 4px;
  1653. display: none;
  1654. background: #333;
  1655. text-align: left;
  1656. font-size: 14px;
  1657. padding: 0;
  1658. }
  1659. #textMarquee .newsHistory li{
  1660. padding: 0 2px;
  1661. }
  1662. #textMarquee .newsHistory li:nth-child(odd){
  1663. background: #444;
  1664. }
  1665. #textMarquee .newsHistory li:nth-child(even){
  1666. background: #333;
  1667. }
  1668. body.full_with_browser.hideNewsInFull #textMarquee .newsHistory {
  1669. display: none !important;
  1670. }
  1671. body #popupMarquee {
  1672. width: 360px;
  1673. }
  1674. {* 半透明だとflashの上に来ると描画されないので強制的に黒にする(Chromeは平気) *}
  1675. body.full_with_browser #popupMarquee.popupMarqueeBottomLeft {
  1676. background: #000 !important; width: 400px; opacity: 1;
  1677. }
  1678. body.full_with_browser #playerContainer {
  1679. margin-left: 0 !important;
  1680. }
  1681. body:not(.full_with_browser) #playerContainer {
  1682. {*top: -8px;*}
  1683. }
  1684. body:not(.full_with_browser) #playerContainerWrapper {
  1685. padding: 0px;
  1686. }
  1687. body.full_with_browser #playerContainer, body.size_small #playerContainer {
  1688. top: auto;
  1689. }
  1690. body.full_with_browser.no_setting_panel .videoExplorerMenu {
  1691. display:none;
  1692. }
  1693.  
  1694.  
  1695. body:not(.videoExplorer) {*#playlist:not(.nico-bucket-videoExplorer-b)*} #videoExplorerExpand {
  1696. display: none;
  1697. }
  1698. #outline .openVideoExplorer {
  1699. display: none;
  1700. }
  1701. #outline.w_hideSearchExpand .openVideoExplorer {
  1702. display: inline-block;
  1703. }
  1704.  
  1705. {* 1列表示の時、動画タイトルの横の空白部分にまでクリック判定があるのはVistaのエクスプローラみたいで嫌なので、文字部分だけにしたい *}
  1706. {* GINZAで改善したのでいったんコメントアウト *}
  1707. {*
  1708. #videoExplorer .videoExplorerBody .videoExplorerContent .contentItemList.column1 .video .column1 .videoInformationOuter .title,
  1709. #videoExplorer .videoExplorerBody .videoExplorerContent .suggestVideo .video .column1 .videoInformationOuter .title {
  1710. display: inline;
  1711. }
  1712. *}
  1713. .videoExplorerMenu .quickSearchInput {
  1714. background: none repeat scroll 0 0 #F4F4F4;
  1715. border: 1px inset silver;
  1716. left: 60px;
  1717. padding-left: 4px;
  1718. position: absolute;
  1719. top: 2px;
  1720. width: 180px;
  1721. }
  1722. .videoExplorerMenu.w_touch .quickSearchInput {
  1723. top: 4px; font-size: 20px;
  1724. }
  1725.  
  1726. .videoExplorerContent .contentItemList .column4 {
  1727. text-align: center;
  1728. }
  1729. .videoExplorerContent .contentItemList .column4 .balloon {
  1730. bottom: auto; top: 10px;
  1731. }
  1732. .videoExplorerContent .contentItemList .column4 .videoInformation>.info {
  1733. font-size: 85%;
  1734. }
  1735. .videoExplorerContent .contentItemList .column4 .videoInformation>.info .info{
  1736. color: #000;
  1737. }
  1738. .videoExplorerContent .contentItemList .column4 .videoInformationOuter {
  1739. width: 100px; height: 48px; margin: auto; color: #666; text-align: left;
  1740. }
  1741. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  1742. height: 220px;
  1743. }
  1744. #videoExplorer .videoExplorerBody .videoExplorerContent .column1 .thumbnailContainer .balloon {
  1745. {* top: -20px; 一列の時に「再生リストに追加しました」が上の動画に被るのを防ぐ *}
  1746. }
  1747. .column1 .itemMylistComment {
  1748. font-size: 85%; color: #666; display: none;
  1749. color: #400; border: 1px solid #ccc; padding: 0 4px 0px; line-height: 130%; border-radius: 4px;
  1750. }
  1751. .column1 .itemMylistComment:before {
  1752. content: '本人コメント ';
  1753. background: #ccc; border-radius: 0 0 8px 0; display: inline-block; margin: 0 4px 4px -4px; padding: 2px;
  1754. }
  1755. .column1 .itemMylistComment:after {
  1756. content: '';
  1757. }
  1758. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer {
  1759. display: none;
  1760. }
  1761. .videoExplorerContent .contentItemList .nicorepoResult .column1 .nicorepoOwnerIconContainer {
  1762. float: right; display: block;
  1763. padding: 24px 14px 0 4px;
  1764. }
  1765. .videoExplorerContent .contentItemList .column1 .nicorepoOwnerIconContainer img {
  1766. height: 48px;
  1767. }
  1768.  
  1769. .videoExplorerBody.dummyMylist #searchResultContainer .favMylistEditContainer,
  1770. .videoExplorerBody.dummyMylist:not(.ranking) #searchResultMylistSortOptions,
  1771. .videoExplorerBody.dummyMylist .favMylistEditContainer,
  1772. .videoExplorerBody.dummyMylist:not(.ownerNicorepo) #searchResultHeader {
  1773. display: none !important;
  1774. }
  1775.  
  1776. .videoExplorerContent .contentItemList .thumbnailHoverMenu {
  1777. position: absolute; padding: 0; z-index: 100;
  1778. display: none;
  1779. bottom: -1px; left: 0px;
  1780. }
  1781. .videoExplorerContent .contentItemList .deleteFromMyMylist {
  1782. cursor: pointer; font-size: 70%; border: 1px solid #ccc; padding: 0;
  1783. display: none;
  1784. }
  1785. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1786. cursor: pointer; font-size: 70%; border: 1px solid #ccc;;
  1787. }
  1788. .videoExplorerContent .contentItemList .showLargeThumbnail {
  1789. padding: 0 4px;
  1790. }
  1791. .videoExplorerContent .contentItemList .item:hover .thumbnailHoverMenu {
  1792. display: block;
  1793. }
  1794. .videoExplorerContent .contentItemList .log-user-video-upload {
  1795. background: #ffe; border-radius: 4px;
  1796. }
  1797. .videoExplorerContent .contentItemList .nicorepoResult .itemVideoDescription, .videoExplorerContent .contentItemList .nicorepoResult .videoTitle{
  1798. }
  1799. .videoExplorerContent .contentItemList.channelGuideVideo {
  1800. background: #eff; {* 検索結果にチャンネル動画が紛れ込むようになったのでわかりやすく *}
  1801. }
  1802.  
  1803. #videoExplorer.w_deflist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist,
  1804. #videoExplorer.w_mylist .videoExplorerBody.isMine.enableMylistDeleteButton .item:hover .deleteFromMyMylist
  1805. {
  1806. display: inline-block;
  1807. }
  1808.  
  1809. #playlist .generationMessage {
  1810. cursor: pointer;
  1811. }
  1812. #playlist .generationMessage:hover {
  1813. text-decoration: underline;
  1814. }
  1815. #playlist .generationMessage:after {
  1816. content: "▼";
  1817. }
  1818.  
  1819. #yukkuriPanel {
  1820. position: fixed; z-index: 1500; bottom: 0; left: 0; display: inline-block;
  1821. transition: bottom 0.2s ease;
  1822. }
  1823. #yukkuriPanel.mylistPanelLeft {
  1824. bottom: 24px;
  1825. }
  1826. body.w_noNicoru .nicoru-button{
  1827. left: -9999; display: none !important;
  1828. }
  1829. body.w_noNicoru .menuOpened #videoMenuTopList li.videoMenuListNicoru .nicoru-button{
  1830. display: block !important;
  1831. }
  1832. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li {
  1833. margin: 0 18px 4px 0;
  1834. }
  1835. body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlContainer, body.w_noNicoru #videoTagContainer .tagInner #videoHeaderTagList li .tagControlEditContainer {
  1836. padding: 1px 0;
  1837. }
  1838.  
  1839. .userProfile.w_touch {
  1840. font-size: 150%; line-height: 120%;
  1841. }
  1842. .resultPagination.w_touch {
  1843. font-size: 200%;
  1844. }
  1845. .resultPagination.w_touch li{
  1846. padding: 4px 16px;
  1847. }
  1848. select.w_touch {
  1849. font-size: 200%;
  1850. }
  1851. {* 真・browserFullモード *}
  1852. body.full_with_browser.hideCommentInput #nicoplayerContainerInner {
  1853. {* コメント入力欄は動画上表示にするのではなく、画面外に押し出す事によって見えなくする *}
  1854. margin-top: -10px; margin-bottom: -30px;
  1855. }
  1856. body.full_with_browser.trueBrowserFull #playerContainerWrapper {
  1857. margin: 0 !important;
  1858. }
  1859. body.full_with_browser.trueBrowserFull #playlist {
  1860. display: none;
  1861. }
  1862. body.full_with_browser.trueBrowserFull:not(.w_fullScreenMenu) .mylistPopupPanel.fixed,body.full_with_browser.trueBrowserFull .yukkuriButton { display:none; }
  1863. #trueBrowserFullShield {
  1864. -webkit-transition: opacity 0.2s ease-out;
  1865. position:absolute;
  1866. display: none;
  1867. }
  1868. body.full_with_browser #trueBrowserFullShield {
  1869. background: black;
  1870. display: block;
  1871. bottom: 100px;
  1872. right: 50px;
  1873. z-index: 10000;
  1874. min-width: 400px;
  1875. cursor: nw-resize;
  1876. opacity: 0;
  1877. color: white;
  1878. box-shadow: 2px 2px 2px silver;
  1879. border-radius: 4px;
  1880. }
  1881. body.full_with_browser #trueBrowserFullShield .title {
  1882. color: #ffc; font-size: 120%;
  1883. }
  1884. body.full_with_browser #trueBrowserFullShield .ownerIcon {
  1885. float: left; height: 55px; padding: 8px;
  1886. }
  1887. body.full_with_browser #trueBrowserFullShield:hover, body.full_with_browser #trueBrowserFullShield.active, body.w_fullScreenMenu #trueBrowserFullShield {
  1888. opacity: 1;
  1889. }
  1890. body:not(.full_with_browser) #trueBrowserFullShield { display: none; }
  1891.  
  1892. #sharedNgSettingContainer {
  1893. display: inline-block; font-size: 80%; position: absolute; top: -18px; left: 5px;
  1894. }
  1895. #sharedNgSetting {
  1896. background: #ddd; border: 1px solid silver;
  1897. }
  1898. {* ニュース消す *}
  1899. #content.noNews #textMarquee {
  1900. display: none !important;
  1901. }
  1902. body:not(.videoExplorer):not(.full_with_browser) #content.noNews #playerContainer {
  1903. min-height: 461px;
  1904. }
  1905. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper {
  1906. height: auto !important; position: absolute; bottom: 18px;
  1907. }
  1908. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabContainer {
  1909. bottom: -17px;
  1910. }
  1911. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #playerTabContainer {
  1912. bottom: 20px;
  1913. }
  1914. #playerTabWrapper.w_videoInfo #playerTabContainer, #playerTabWrapper.w_ichiba #playerTabContainer, #playerTabWrapper.w_review #playerTabContainer {
  1915. bottom: 0px !important;
  1916. }
  1917. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_videoInfo,
  1918. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_ichiba,
  1919. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerTabWrapper.w_review
  1920. {
  1921. height: auto !important; position: absolute; bottom: 2px;
  1922. }
  1923. {* body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #leftPanel {
  1924. height: auto !important; position: absolute; bottom: 2px;
  1925. }*}
  1926. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerCommentPanel {
  1927. height: 100% !important;
  1928. }
  1929. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer.appli_panel #appliPanel {
  1930. bottom: -18px !important;
  1931. }
  1932. body:not(.videoExplorer):not(.setting_panel):not(.full_with_browser) #content.noNews #playerContainer {
  1933. height: auto;
  1934. }
  1935. #outline.noIchiba #nicoIchiba, #outline.noReview #videoReview{
  1936. display: none;
  1937. }
  1938. #bottomContentTabContainer.noBottom .outer, #bottomContentTabContainer.noBottom #pageFooter {
  1939. display: none;
  1940. }
  1941. #bottomContentTabContainer.noBottom #outline {
  1942. background: #141414; padding-top: 0; padding-bottom: 35px;
  1943. }
  1944.  
  1945. #content.w_flat_gray #playerContainerWrapper {
  1946. background: #666;
  1947. }
  1948. #content.w_flat_white #playerContainerWrapper {
  1949. background: #f4f4f4;
  1950. }
  1951. #content.w_flat_gray #wallImageContainer, #content.w_flat_white #wallImageContainer,
  1952. #content.w_flat_gray #chipWallList, #content.w_flat_white #chipWallList {
  1953. display: none !important;
  1954. }
  1955. #content #chipWallList {
  1956. right: auto; left: -42px;
  1957. }
  1958. #content #playlist .playlistInformation {
  1959. background: #444;
  1960. }
  1961. #content #videoExplorerExpand a {
  1962. text-shadow: none;
  1963. }
  1964.  
  1965. .videoMenuToggle {
  1966. -webkit-transform-origin: 100% 100%; -webkit-transition: -webkit-transform 0.4s;
  1967. transform-origin: 100% 100%; transition: transform 0.4s;
  1968. z-index: 1000;
  1969. }
  1970. #content.w_compact .videoHeaderTitle {
  1971. letter-spacing: -1px;
  1972. }
  1973. #content.w_compact .videoDetailExpand .arrow {
  1974. position: absolute; top: 8px; right: -24px;
  1975. }
  1976. #content.w_compact .tag1Line .videoMenuToggle {
  1977. transform: scale(0.8, 0.41); -webkit-transform: scale(0.8, 0.41);
  1978. }
  1979. #content.w_compact .tag2Lines .videoMenuToggle {
  1980. transform: scale(0.8); -webkit-transform: scale(0.8);
  1981. }
  1982. #content.w_compact #topVideoInfo .parentVideoInfo {
  1983. margin-top: -9px; margin-bottom: 9x;
  1984. }
  1985. #content.w_compact #topVideoInfo .parentVideoInfo .cct{
  1986. margin-bottom: 0;
  1987. }
  1988. #content.w_compact #topVideoInfo .parentVideoInfo .videoThumb{
  1989. margin-top: 4px;
  1990. }
  1991. #content.w_compact #topVideoInfo .ch_prof, #content.w_compact #topVideoInfo .userProfile {
  1992. min-width: 297px; margin-top: -1px; border: 1px solid #e7e7e7;
  1993. }
  1994. #content.w_compact #videoHeaderDetail .videoDetailExpand{
  1995. height: auto; padding: 0;
  1996. }
  1997. #content.w_compact #topVideoInfo .videoDescription.description {
  1998. background: #fff; margin: 10px 0 0;padding: 4px ;width: 1000px;{* base - 8 *} {*font-size: 90%;*}
  1999. }
  2000.  
  2001. {* 本家の幅が変わったら変える必要がある。 変数化した方が楽かも base = 1008 *}
  2002. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  2003. width: 1318px; {* base + 310 *}
  2004. }
  2005. body:not(.full_with_browser):not(.videoExplorer).size_normal #content.w_compact #topVideoInfo .videoDescription.description {
  2006. width: 1226px; {* base + 218 *}
  2007. }
  2008. body:not(.full_with_browser) #content.w_compact.w_wide #topVideoInfo .videoDescription.description {
  2009. width: 1092px; {* base + 84 *}
  2010. }
  2011. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoTagContainer {
  2012. width: 1263px; {* base + 255 *}
  2013. }
  2014. body:not(.full_with_browser) #content.w_compact.w_wide #videoTagContainer {
  2015. width: 1040px; {* base + 32 *}
  2016. }
  2017. body:not(.full_with_browser) #content.w_compact #videoTagContainer {
  2018. width: 948px; {* base - 60 *}
  2019. }
  2020. body:not(.full_with_browser) #content.w_compact #videoHeader, #foot_inner {
  2021. width: 1008px; {* base + 48 *}
  2022. }
  2023. body:not(.full_with_browser).size_normal #content.w_compact #videoHeader, .size_normal #foot_inner {
  2024. width: 1234px; {* base + 226 *}
  2025. }
  2026. body:not(.full_with_browser) #content.w_compact.w_wide #videoHeader {
  2027. width: 1100px;
  2028. }
  2029. body:not(.full_with_browser).size_normal #content.w_compact.w_wide #videoHeader {
  2030. width: 1326px;
  2031. }
  2032.  
  2033. #content.w_compact #topVideoInfo .videoMainInfoContainer{
  2034. padding: 0;
  2035. }
  2036. #content.w_compact #videoDetailInformation{
  2037. border-top: 0;
  2038. }
  2039. #content.w_compact #videoHeaderMenu .searchContainer {
  2040. top: -16px;
  2041. }
  2042. #content.w_compact .videoInformation{
  2043. margin: -4px 0 ;
  2044. }
  2045. #content.w_compact #topVideoInfo .videoStats {
  2046. margin-bottom: 2px;
  2047. }
  2048. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList .toggleTagEdit {
  2049. width: 72px;
  2050. }
  2051. body:not(.full_with_browser) #content.w_compact #videoTagContainer .tagInner #videoHeaderTagList {
  2052. padding-left: 85px;
  2053. }
  2054. body.full_with_browser #videoHeaderTagList { background: #fafafa; }
  2055. #content.w_compact #topVideoInfo {
  2056. margin: 4px 0 4px;
  2057. }
  2058. #content.w_compact #topVideoInfo .videoShareLinks .socialLinks {
  2059. margin-top: -6px;
  2060. }
  2061. #outline.w_compact #videoInfoHead{
  2062. margin: 0 ;
  2063. }
  2064. #outline.w_compact .videoInformation #videoTitle {
  2065. margin: -4px 0 0;
  2066. }
  2067. #outline.w_compact .videoInformation #videoStats {
  2068. margin-top: -4px;
  2069. }
  2070. #outline.w_compact .videoInformation #videoStats .ranking {
  2071. margin: 0 0 4px;
  2072. }
  2073. #outline.w_compact #videoShareLinks {
  2074. margin: 0;
  2075. }
  2076. #outline.w_compact #bottomVideoDetailInformation {
  2077. margin: -18px 0 0;
  2078. }
  2079. #outline.w_compact .infoHeadOuter .videoEditMenuExpand {
  2080. position: absolute; top: 0;
  2081. }
  2082. #outline.w_compact .videoEditMenu {
  2083. margin: 0;
  2084. }
  2085. #outline.w_compact .videoDescription {
  2086. font-size: 90%; margin-top: -8px; padding: 0 0 4px 4px;
  2087. }
  2088. #outline.w_compact #videoComment {
  2089. margin: 0px; border: 1px solid silver; border-radius: 4px 4px 4px 4px; padding: 0 4px;
  2090. }
  2091. #outline.w_compact #videoComment h4{
  2092. padding-left: 4px;
  2093. }
  2094. #outline.w_compact .videoMainInfoContainer {
  2095. border-bottom: 0; margin-bottom: 0;
  2096. }
  2097. #outline.w_compact {
  2098. border-bottom: 0; margin-bottom: 0;
  2099. }
  2100.  
  2101. #outline.w_compact .sidebar { width: 300px; }
  2102.  
  2103. #outline.w_compact #ichibaMain dl.ichiba_mainitem {
  2104. margin: 0 22px 30px 0;
  2105. }
  2106. #footer { z-index: 1; }
  2107.  
  2108. body.en-us #playerAlignmentArea, body.zh-tw #playerAlignmentArea {
  2109. {*padding-right: 0;*}
  2110. }
  2111. #footer .toggleBottom {
  2112. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  2113. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  2114. }
  2115. #footer:hover .toggleBottom {
  2116. border: 1px outset; background: #ccc;
  2117. }
  2118. #footer .toggleBottom:hover {
  2119. box-shadow: 0px 0px 8px #fff;
  2120. }
  2121. #footer.noBottom .toggleBottom {
  2122. border-radius: 0 0 16px 16px;
  2123. }
  2124. #footer .toggleBottom .openBottom, #footer.noBottom .toggleBottom .closeBottom {
  2125. display: none;
  2126. }
  2127. #footer.noBottom .toggleBottom .openBottom {
  2128. display: block;
  2129. }
  2130. #footer .toggleBottom>div {
  2131. -webkit-transform: scaleX(3); transform: scaleX(3);
  2132. }
  2133. #footer .toggleBottom {
  2134. cursor: pointer; text-align: center; width: 200px; padding: 0px 12px; margin: auto; border-radius: 16px 16px 0 0;
  2135. border: 1px solid #333; background: #666; transition: background 0.4s ease-out, box-shadow 0.4s;
  2136. }
  2137. #footer:hover .toggleBottom {
  2138. border: 1px outset; background: #ccc;
  2139. }
  2140. #footer .toggleBottom:hover {
  2141. box-shadow: 0px 0px 8px #fff;
  2142. }
  2143.  
  2144. #footer.noBottom #foot_inner { padding: 0; }
  2145. #footer.noBottom a:nth-of-type(3):after, #footer.noBottom a:nth-of-type(6):after {
  2146. content: ' | '; color: white;
  2147. }
  2148. #footer.noBottom br { display: none; }
  2149. html { background: #141414; }
  2150. .videoExplorer #videoExplorer,
  2151. .videoExplorer #videoExplorer .videoExplorerBody,
  2152. .videoExplorerContentWrapper
  2153. {
  2154. background: none;
  2155. }
  2156.  
  2157. .animateBlink {
  2158. -webkit-transition: 1s ease-in; transition: 1s ease-in;
  2159. }
  2160.  
  2161. .w_compact .toggleDetailExpand, .w_compact .shortVideoInfo {
  2162. display: none;
  2163. }
  2164. .videoDetailToggleButton {
  2165. cursor: pointer;
  2166. }
  2167. #leftPanel {
  2168. {*border-radius: 4px 4px 4px 4px;*}
  2169. display: none; padding: 0; position: absolute; text-align: left; top: 0; z-index: 101;
  2170. }
  2171. body.ja-jp #leftPanel { display: none; }
  2172. body:not(.videoExplorer) #leftPanel { display: none; }
  2173.  
  2174.  
  2175. body.full_with_browser #playerTabWrapper, body.full_with_browser:not(.videoExplorer) .w_wide #playerTabWrapper {
  2176. top: auto !important; bottom: 3000px !important; right: 50px !important;
  2177. transition: bottom 0.2s ease-out; max-height: 500px;
  2178. }
  2179.  
  2180. body.full_with_browser.w_fullScreenMenu:not(.videoExplorer) #playerTabWrapper {
  2181. top: auto !important; bottom: 200px !important; right: 50px !important;
  2182. }
  2183.  
  2184. #fullScreenMenuContainer { display: none; }
  2185. body.full_with_browser #fullScreenMenuContainer {
  2186. display: block; position: absolute; bottom: 3000px; left: 50px; z-index: 10000;
  2187. background: #fff; cursor: pointer; transition: bottom 0.2s ease-out;
  2188. }
  2189. body.full_with_browser.w_fullScreenMenu #fullScreenMenuContainer {
  2190. bottom: 100px;
  2191. }
  2192.  
  2193. #fullScreenMenuContainer .button {
  2194. cursor: pointer; transition: color 0.4s ease-out;
  2195. }
  2196. #fullScreenMenuContainer .modeStatus { display: none; font-weight: bolder; }
  2197. body.trueBrowserFull #fullScreenMenuContainer .fullScreenModeSwitch { color: blue; }
  2198. body:not(.trueBrowserFull) #fullScreenMenuContainer .fullScreenModeSwitch .mode_normal,
  2199. body.trueBrowserFull #fullScreenMenuContainer .fullScreenModeSwitch .mode_noborder { display: inline; }
  2200.  
  2201. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch { color: blue; }
  2202. #nicoplayerContainerInner:not(.stageVideo) #fullScreenMenuContainer .stageVideoSwitch .mode_off,
  2203. #nicoplayerContainerInner.stageVideo #fullScreenMenuContainer .stageVideoSwitch .mode_on { display: inline; }
  2204.  
  2205.  
  2206. body.full_with_browser.w_fullScreenMenu .videoHeaderOuter {
  2207. position: absolute; z-index: 1000; width: 100%;
  2208. }
  2209. body.full_with_browser.w_fullScreenMenu #videoTagContainer { width: 100%; display: block; }
  2210.  
  2211. .popupMarqueeContent {
  2212. background: black;
  2213. }
  2214.  
  2215. #videoExplorer, #playlist {
  2216. transition: margin-left 0.2s ease-in-out;
  2217. }
  2218.  
  2219. .dummyMylist .editFavorite {
  2220. display: none;
  2221. }
  2222.  
  2223. {* 不要な時まで横スクロールバーが出てしまうので *}
  2224. #songrium_inline { overflow: hidden; }
  2225.  
  2226. .sideVideoInfo .nextPlayButton {
  2227. position: absolute;
  2228. margin-top: -6px;
  2229. margin-left: -30px;
  2230. width: 30px;
  2231. height: 30px;
  2232. background: url(http://res.nimg.jp/img/watch_q9/icon_nextplay.png);
  2233. {*background: url("http://res.nimg.jp/img/watch_zero/videoexplorer-s90d011f9a7.png") no-repeat scroll -37px 0 rgba(0, 0, 0, 0);*}
  2234. z-index: 100;
  2235. cursor: pointer;
  2236. text-indent: -999em;
  2237. overflow: hidden;
  2238. display: inline-block;
  2239. -webkit-transform: scale(1.0); transform: scale(1.0);
  2240. }
  2241.  
  2242. .nextPlayButton {
  2243. -webkit-transform: scale(1.5); transform: scale(1.5);
  2244. transition: transform 0.1s ease; -webkit-transition: -webkit-transform 0.1s ease;
  2245. }
  2246. .sideVideoInfo .nextPlayButton:hover {
  2247. -webkit-transform: scale(1.5); transform: scale(1.5);
  2248. }
  2249. .nextPlayButton:active, .sideVideoInfo .nextPlayButton:active {
  2250. -webkit-transform: scale(1.2); transform: scale(1.2);
  2251. }
  2252.  
  2253. .sideVideoInfo .nextPlayButton:active {
  2254. background-position-y: 30px;
  2255. }
  2256.  
  2257. body.w_disableHorizontalScroll {
  2258. overflow-x: hidden !important;
  2259. }
  2260.  
  2261. #videoTagContainerPin { display: none !important; } {* タグを固定しているか4行以上の時に現われるピン *}
  2262.  
  2263. .w_adjusted #selectionSideAdAds >* {
  2264. width: 100%; height: auto; max-width: 300px; max-height: 250px;
  2265. }
  2266.  
  2267. {* *}
  2268. .w_noHover {
  2269. pointer-events: none !important;
  2270. }
  2271. .w_noHover #playlist {
  2272. pointer-events: auto !important;
  2273. }
  2274. {* ソーシャルボタン *}
  2275. .area-JP .panel_ads_shown #playerTabContainer.w_noSocial.has_panel_ads .playerTabContent {
  2276. bottom: 80px;
  2277. }
  2278. .area-JP #playerTabContainer.w_noSocial .playerTabContent {
  2279. bottom: 4px;
  2280. }
  2281. #playerTabContainer.w_noSocial .playerTabAds {
  2282. bottom: 0;
  2283. }
  2284. #playerTabContainer.w_noSocial .socialButtons{
  2285. display: none;
  2286. }
  2287. .w_noSocial .nicoSpotAds {
  2288. bottom: 8px;
  2289. }
  2290.  
  2291.  
  2292. {* テレビちゃんメニュー スライドをやめる *}
  2293. body #videoHeader #videoMenuWrapper{
  2294. position: absolute; width: 324px; height: auto !important;
  2295. opacity: 0;
  2296. transition: opacity 0.4s ease;
  2297. right: 0px;
  2298. }
  2299. body #videoHeader.menuOpened #videoMenuWrapper{
  2300. z-index: 1000 !important;
  2301. border: 1px solid #000;
  2302. background: white;
  2303. box-shadow: 0px 0px 4px #000;
  2304. top: 110px;
  2305. bottom: auto;
  2306. opacity: 1;
  2307. }
  2308. body .tag1Line #videoHeader.menuOpened #videoMenuWrapper{
  2309. top: 62px;
  2310. }
  2311. body .tag2Lines #videoHeader.menuOpened #videoMenuWrapper{
  2312. top: 86px;
  2313. }
  2314. body #videoHeader.infoActive.menuOpened #videoMenuWrapper{
  2315. top: auto;
  2316. bottom: 48px;
  2317. }
  2318. {* body #videoHeader #videoMenuWrapper .defmylistButton, body #videoHeader #videoMenuWrapper .mylistButton {
  2319. display: none !important;
  2320. } *}
  2321. body #videoHeader #videoMenuTopList{
  2322. position: relative;
  2323. width: auto;
  2324. }
  2325. body #videoHeader.menuOpened #videoMenuWrapper .videoMenuList{
  2326. display: inline-block;
  2327. width: 60px;
  2328. min-height: 72px;
  2329. }
  2330. body #videoMenuTopList li.videoMenuListNicoru {
  2331. float: right;
  2332. min-height: 72px;
  2333. }
  2334. body #videoHeader.isAdult .videoMenuToggle, body #videoHeader.noAudioDownload .downloadButton {
  2335. display: inline-block;
  2336. opacity: 0.5;
  2337. pointer-events: none !important;
  2338. }
  2339. {* テレビちゃんメニューのスライド殺す *}
  2340. body #videoHeader.menuOpened #videoMenuWrapper {
  2341. margin-bottom: 0;
  2342. }
  2343. body #videoHeader.menuOpened #videoHeaderDetail {
  2344. margin-top: 8px;
  2345. }
  2346.  
  2347. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  2348. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  2349. addStyle(__css__, 'watchItLaterStyle');
  2350. })(); // end of watchItLaterStyle
  2351.  
  2352.  
  2353.  
  2354.  
  2355.  
  2356. conf.load = function() {
  2357. try {
  2358. function loadStorage(key, def) {
  2359. if (window.localStorage[key] === undefined) { return def; }
  2360. return JSON.parse(window.localStorage.getItem(key));
  2361. }
  2362.  
  2363. for (var v in conf) {
  2364. if (typeof conf[v] === 'function') { continue; }
  2365. conf[v] = loadStorage('watchItLater_' + v, conf[v]);
  2366. }
  2367. } catch (e) {
  2368. }
  2369. };
  2370.  
  2371. conf.getValue = function(varName) {
  2372. return conf[varName];
  2373. };
  2374. conf.setValue = function(k, v) {
  2375. var lastValue = conf[k];
  2376. if (lastValue !== v) {
  2377. conf[k] = v;
  2378. window.localStorage.setItem('watchItLater_' + k, JSON.stringify(v));
  2379. EventDispatcher.dispatch('on.config.' + k, v, lastValue);
  2380. }
  2381. };
  2382. conf.load();
  2383.  
  2384. var console = (function(conf) {
  2385. if (conf.debugMode) { return window.console; }
  2386. var noop = function() {};
  2387. return {
  2388. log: noop,
  2389. error: noop,
  2390. trace: noop,
  2391. warn: noop,
  2392. table: noop
  2393. };
  2394. })(conf);
  2395.  
  2396. var ConfigPanel = (function($, conf, w) {
  2397. var pt = function(){};
  2398. var $panel = null, $shadow = null;
  2399. var menus = [
  2400. {title: '再生開始・終了時の設定', className: 'videoStart'},
  2401. {title: '自動で全画面モードにする', varName: 'autoBrowserFull',
  2402. values: {'する': true, 'しない': false}, addClass: true},
  2403. {title: '自動全画面化オンでも、ユーザーニコ割のある動画は', varName: 'disableAutoBrowserFullIfNicowari',
  2404. values: {'全画面化しない': true, '全画面化する': false}},
  2405. {title: '自動で検索モードにする(自動全画面化オフ時)', varName: 'autoOpenSearch',
  2406. values: {'する': true, 'しない': false}},
  2407. {title: '動画の位置に自動スクロール(自動全画面化オフ時)', varName: 'autoScrollToPlayer',
  2408. values: {'する': true, 'しない': false}},
  2409. // {title: '終了時に全画面モードを解除(原宿と同じにする)', varName: 'autoNotFull',
  2410. // values: {'する': true, 'しない': false},
  2411. // description: '連続再生中は解除しません'},
  2412. {title: 'ウィンドウがアクティブの時だけ自動再生する', varName: 'autoPlayIfWindowActive',
  2413. description: 'QWatch側の設定パネルの自動再生はオフにしてください。\n■こんな人におすすめ\n・自動再生ONにしたいけど別タブで開く時は自動再生したくない\n・複数タブ開いたままブラウザ再起動したら全部のタブで再生が始まって「うるせー!」という経験のある人',
  2414. values: {'する': 'yes', 'しない': 'no'}},
  2415. {title: '動画が切り替わる時、ポップアップでタイトルと再生数を表示', varName: 'popupViewCounter',
  2416. description: '全画面状態で連続再生している時などに便利です',
  2417. values: {'する': 'always', '全画面時のみ': 'full', 'しない': 'none'}},
  2418.  
  2419. {title: 'プレイヤーの設定', className: 'playerSetting'},
  2420. {title: 'コメントパネルを広くする', varName: 'wideCommentPanel',
  2421. values: {'する': true, 'しない': false}},
  2422. {title: 'コメントパネルにNG共有設定を表示', varName: 'enableSharedNgSetting',
  2423. values: {'する': true, 'しない': false}, addClass: true},
  2424. {title: 'コメントの表示', varName: 'commentVisibility',
  2425. values: {'オフ': 'hidden', '最後の状態を記憶': 'lastState', 'オン': 'visible'}},
  2426. {title: '右のパネルに動画情報・市場・レビューを表示', varName: 'rightPanelJack', reload: true,
  2427. values: {'する': true, 'しない': false}},
  2428. {title: 'ページのヘッダに再生数表示', varName: 'headerViewCounter', reload: true,
  2429. values: {'する': true, 'しない': false}},
  2430. {title: 'ニコニコニュースの履歴を保持する', varName: 'enableNewsHistory', reload: true,
  2431. values: {'する': true, 'しない': false}},
  2432. {title: 'ニコニコニュースを消す', varName: 'hideNicoNews',
  2433. values: {'消す': true, '消さない': false}},
  2434. {title: 'プレイヤーの背景', varName: 'playerBgStyle',
  2435. description: 'ウォール機能より優先されます',
  2436. values: {'白': 'white', 'グレー': 'gray', 'ウォール': ''}},
  2437. {title: 'コメントの盛り上がりをグラフ表示', varName: 'enableHeatMap', reload: true,
  2438. description: '動画のどのあたりが盛り上がっているのか、わかりやすくなります',
  2439. values: {'する': true, 'しない': false}},
  2440. {title: '大画面をもっと大画面にする', varName: 'customPlayerSize',
  2441. description: '※有効にするとニコニコニュースが表示できなくなります。',
  2442. values: {'フルHD': '1080p', '720p': '720p', '自動調整(推奨)': 'auto', 'しない': ''}},
  2443. {title: 'プレイリスト消えないモード(実験中)', varName: 'storagePlaylistMode', reload: true,
  2444. description: '有効にすると、リロードしてもプレイリストが消えなくなります。',
  2445. values:
  2446. (conf.debugMode ?
  2447. {'ウィンドウを閉じるまで': 'sessionStorage', 'ずっと保持': 'localStorage', 'しない': ''} :
  2448. {'有効(ウィンドウを閉じるまで)': 'sessionStorage', '無効': ''})
  2449. },
  2450. {title: '説明文中の動画IDにサムネイル表示(実験中)', varName: 'enableDescriptionThumbnail', reload: true,
  2451. values: {'有効': true, '無効': false}},
  2452.  
  2453.  
  2454. {title: '検索モードの設定', className: 'videoExplorer'},
  2455. {title: '検索モードを無効化', varName: 'disableVideoExplorer',
  2456. description: '無効にするとタグ検索などが原宿と同じになります。\nただし、自分で検索モードにしている時は検索モードで開きます',
  2457. values: {'する': true, 'しない': false}},
  2458. {title: 'プレイヤーをできるだけ大きくする (コメントやシークも可能にする)', varName: 'videoExplorerHack',
  2459. description: '便利ですがちょっと重いです。\n大きめのモニターだと快適ですが、小さいといまいちかも',
  2460. values: {'する': true, 'しない': false}},
  2461. {title: 'お気に入りタグを表示', varName: 'enableFavTags',
  2462. values: {'する': true, 'しない': false}},
  2463. {title: 'お気に入りマイリストを表示', varName: 'enableFavMylists',
  2464. description: '更新のあったリストが上に来るので、新着動画のチェックに便利です。',
  2465. values: {'する': true, 'しない': false}},
  2466. // {title: 'サムネを4:3にする', varName: 'squareThumbnail',
  2467. // description: '上下がカットされなくなり、サムネの全体が見えるようになります。',
  2468. // values: {'する': true, 'しない': false}},
  2469. {title: '「マイリストから外す」ボタンを表示', varName: 'enableMylistDeleteButton',
  2470. description: 'マイリストの整理に便利。\n ※ 消す時に確認ダイアログは出ないので注意',
  2471. values: {'する': true, 'しない': false}},
  2472. {title: '検索時に関連タグを表示する', varName: 'enableRelatedTag',
  2473. values: {'する': true, 'しない': false}},
  2474. {title: 'niconico新検索βを使う', varName: 'searchEngine',
  2475. description: '投稿期間や動画長による絞り込みができるようになります',
  2476. values: {'使う': 'sugoi', '使わない': 'normal'}},
  2477. {title: '1ページの表示件数', varName: 'searchPageItemCount',
  2478. values: {'100件': 100, '50件': 50, '32件': 32}},
  2479.  
  2480. {title: '全画面モードの設定', className: 'fullScreen'},
  2481. {title: '操作パネルとコメント入力欄を隠す', varName: 'controllerVisibilityInFull',
  2482. description: '全画面の時は少しでも動画を大きくしたい場合に便利',
  2483. values: {'隠す': 'hidden', '隠さない': ''}},
  2484. {title: '右下のマイリストメニュー', varName: 'hideMenuInFull',
  2485. values: {'完全に消す': 'hideAll', '色だけ変える': '', '目立たなくする': 'hide'}},
  2486. {title: 'ホイールを回したら動画情報を出す', varName: 'enableFullScreenMenu',
  2487. description: 'ホイールを大きく下に回すとメニューが出ます。タッチパネルも対応',
  2488. values: {'する': true, 'しない': false}},
  2489.  
  2490. {title: 'ページ下半身の設定', className: 'playerBottom'},
  2491. {title: 'ニコニコ市場の表示', varName: 'ichibaVisibility',
  2492. values: {'非表示': 'hidden', '表示': 'visible'}},
  2493. {title: 'レビューの表示', varName: 'reviewVisibility',
  2494. values: {'非表示': 'hidden', '表示': 'visible'}},
  2495.  
  2496. {title: '省スペース/軽量化設定', className: 'compact'},
  2497. {title: 'タグが2行以内の時に高さを詰める(ピン留め時のみ)', varName: 'enableAutoTagContainerHeight', reload: true,
  2498. values: {'詰める': true, '詰めない': false}},
  2499. {title: '動画情報の空きスペースを詰める', varName: 'compactVideoInfo',
  2500. description: '原宿ぐらいの密度になります。ちょっと窮屈かも',
  2501. values: {'詰める': true, '詰めない': false}},
  2502. // {title: '背景のグラデーションをなくす', varName: 'flatDesignMode',
  2503. // description: '軽い表示になります',
  2504. // values: {'なくす': 'on', 'なくさない': ''}},
  2505. {title: '「ニコる」をなくす', varName: 'noNicoru',
  2506. description: '画面上から見えなくなります。\nまた、コメントパネルの処理が軽くなります',
  2507. values: {'なくす': true, 'なくさない': false}},
  2508. {title: 'コメントパネルのマウスオーバー処理をなくす', varName: 'removeCommentPanelHoverEvent', reload: true,
  2509. description: 'マウスオーバー時のちらちらした物がなくなり、表示が軽くなります',
  2510. values: {'なくす': true, 'なくさない': false}},
  2511. {title: 'タグの自動更新を無効化', varName: 'disableTagReload',
  2512. values: {'する': true, 'しない': false}},
  2513. {title: '横スクロールバーを出なくする', varName: 'disableHorizontalScroll',
  2514. values: {'する': true, 'しない': false}},
  2515. {title: 'コメントパネル下のソーシャルボタン', varName: 'hideCommentPanelSocialButtons',
  2516. values: {'隠す': true, '隠さない': false}},
  2517. {title: 'GPUレイヤーを使用してみる(上級者用)', varName: 'enableGpuLayer', reload: true, debugOnly: true,
  2518. description: '環境によっては軽くなる かも しれません',
  2519. values: {'する': true, 'しない': false}},
  2520.  
  2521. {title: 'その他の設定', className: 'otherSetting'},
  2522. {title: '動画リンクにカーソルを重ねたらマイリストメニューを表示', varName: 'enableHoverPopup', reload: true,
  2523. description: 'マウスカーソルを重ねた時に出るのが邪魔な人はオフにしてください',
  2524. values: {'する': true, 'しない': false}},
  2525. {title: '動画リンクにカーソルを重ねてからメニューが出るまでの時間(秒)', varName: 'hoverMenuDelay',
  2526. type: 'text', description: '単位は秒。 標準は0.4です'},
  2527. {title: 'ニコレポのポップアップを置き換える', varName: 'replacePopupMarquee', reload: true,
  2528. description: '画面隅に出るポップアップの不可解な挙動を調整します',
  2529. values: {'する': true, 'しない': false}},
  2530. {title: '検索時のデフォルトパラメータ', varName: 'defaultSearchOption', type: 'text',
  2531. description: '常に指定したいパラメータ指定するのに便利です\n例: 「-グロ -例のアレ」とすると、その言葉が含まれる動画が除外されます'},
  2532. {title: '「@ジャンプ」を無効化', varName: 'ignoreJumpCommand', reload: true,
  2533. description: '勝手に他の動画に飛ばされる機能を無効化します。',
  2534. values: {'する': true, 'しない': false}},
  2535. {title: '「@ジャンプ」によるシーク無効化(無限ループなど)', varName: 'nicoSSeekCount', reload: true,
  2536. description: '完全に無効にする以外に、一動画あたりの回数を指定できます',
  2537. values: {'2回まで有効': 2, '1回まで有効': 1, '完全無効化': 0, 'しない': -1}},
  2538. {title: 'タッチパネル向けモード(画面を右フリックで開始)', varName: 'enableQTouch',
  2539. description: '指で操作しやすいように、一部のボタンやメニューが大きくなります',
  2540. values: {'使う': true, '使わない': false}},
  2541. {title: 'マイリストメニューの位置', varName: 'mylistPanelPosition',
  2542. values: {'左下': 'left', '右下': ''}},
  2543. {title: '2本目以降の動画だけ自動再生 (※プレミアム用)', varName: 'autoPlay2ndVideo', reload: true,
  2544. values: {'する': true, 'しない': false}},
  2545. {title: 'マイリストのローカルキャッシュ', varName: 'enableLocalMylistCache', reload: true,
  2546. description: '動画がどのマイリストに登録されてるかの情報をキャッシュします。\n「my」ボタンの右クリックを活用する人はおすすめ。',
  2547. values: {'有効': true, '無効': false}},
  2548.  
  2549.  
  2550. {title: 'マウスとキーボードの設定', description: '※Chromeはコメント入力中も反応してしまいます', className: 'shortcut'},
  2551. {title: '背景ダブルクリックで動画の位置にスクロール', varName: 'doubleClickScroll',
  2552. description: 'なにもない場所をダブルクリックすると、動画の位置にスクロールします。\n 市場を見てからプレイヤーに戻りたい時などに便利',
  2553. values: {'する': true, 'しない': false}},
  2554. {title: 'マウスのボタン+ホイールでどこでも音量調整', varName: 'mouseClickWheelVolume',
  2555. description: 'とっさに音量を変えたい時に便利',
  2556. values: {'左ボタン+ホイール': 1, '右ボタン+ホイール': 2, '使わない': 0}},
  2557. {title: '停止/再生', varName: 'shortcutTogglePlay', type: 'keyInput'},
  2558. {title: 'とりあえずマイリスト登録', varName: 'shortcutDefMylist', type: 'keyInput'},
  2559. {title: 'マイリスト登録', varName: 'shortcutMylist', type: 'keyInput',
  2560. description: '右下で選択中のマイリストに登録'},
  2561. {title: 'とりあえずマイリストを開く', varName: 'shortcutOpenDefMylist', type: 'keyInput'},
  2562. {title: '動画投稿者の関連動画を開く', varName: 'shortcutShowOtherVideo', type: 'keyInput'},
  2563. {title: '検索画面を開く', varName: 'shortcutOpenSearch', type: 'keyInput'},
  2564. {title: '関連動画(オススメ)を開く', varName: 'shortcutOpenRecommend', type: 'keyInput'},
  2565. {title: 'コメント表示ON/OFF', varName: 'shortcutCommentVisibility', type: 'keyInput'},
  2566. {title: 'プレイヤーの位置までスクロール', varName: 'shortcutScrollToNicoPlayer', type: 'keyInput'},
  2567. {title: 'ミュート', varName: 'shortcutMute', type: 'keyInput'},
  2568. {title: 'コメントの背面表示ON/FF', varName: 'shortcutDeepenedComment', type: 'keyInput'},
  2569. {title: 'ハードウェアアクセラレーションON/FF', varName: 'shortcutToggleStageVideo', type: 'keyInput'},
  2570.  
  2571. {title: 'その他2(一発ネタ系)', description: 'いつのまにか消えるかもしれません', className: 'shortcut'},
  2572. {title: 'テレビちゃんメニュー内にランダム画像(左上)表示', varName: 'hidariue',
  2573. values: {'する': true, 'しない': false}},
  2574. {title: 'ゆっくり再生(スロー再生)ボタンを表示', varName: 'enableYukkuriPlayButton',
  2575. values: {'する': true, 'しない': false}},
  2576.  
  2577. {title: '実験中の設定', debugOnly: true, className: 'forDebug'},
  2578. // {title: 'プレイリスト消えないモード(※実験中)', varName: 'hashPlaylistMode', debugOnly: true, reload: true,
  2579. // values: {'有効(連続再生中のみ)': 1, '有効(常時)': 2, '無効': 0}},
  2580.  
  2581.  
  2582.  
  2583. ];
  2584.  
  2585. var listener = [];
  2586. function dispatchEvent(name, value, lastValue) {
  2587. for (var i = 0; i < listener.length; i++) {
  2588. (listener[i])(name, value, lastValue);
  2589. }
  2590. }
  2591. pt.createPanelDom = function() {
  2592. if ($panel === null) {
  2593. $panel = w.jQuery([
  2594. '<div id="watchItLaterConfigPanel">',
  2595. '<div class="head"><button class="closeButton" title="閉じる">▲</button><h2>WatchItLaterの設定</h2>(※)のつく項目は、リロード後に反映されます</div>',
  2596. '<div class="inner"></div></div>'
  2597. ].join(''));
  2598. $panel.on('click', function(e) { e.stopPropagation(); });
  2599.  
  2600. var scrollTo = function() {
  2601. var $target = this;
  2602. var isOpen = $target.parent().toggleClass('open').hasClass('open');
  2603. if (isOpen) {
  2604. setTimeout(function() {
  2605. var $inner = $('#watchItLaterConfigPanel .inner');
  2606. $inner.animate({
  2607. scrollTop: $inner.scrollTop() + $target.parent().position().top - 50
  2608. }, 400);
  2609. }, 200);
  2610. }
  2611. };
  2612.  
  2613. var $ul = null, $inner = $panel.find('.inner'), $item; //$panel.find('ul'), $item;
  2614. for (var i = 0, len = menus.length; i < len; i++) {
  2615. if (menus[i].varName) {
  2616. $item = this.createMenuItem(menus[i]);
  2617. } else {
  2618. if (menus[i].description) {
  2619. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span><span class="description">'+ menus[i].description + '</span></div></li>');
  2620. } else {
  2621. $item = $('<li class="section ' +menus[i].className + '"><div><span>'+ menus[i].title + '</span></div></li>');
  2622. }
  2623. if ($ul) $inner.append($ul);
  2624. $ul =$('<ul class="sectionContainer"/>').addClass(menus[i].className + 'Container');
  2625. $item.click($.proxy(scrollTo, $item));
  2626. }
  2627. $item.toggleClass('debugOnly', menus[i].debugOnly === true).toggleClass('reload', menus[i].reload === true);
  2628. if ($ul) $ul.append($item);
  2629. }
  2630. if ($ul) $inner.append($ul);
  2631. $panel.toggleClass('debugMode', conf.debugMode);
  2632. var $bottom = w.jQuery('<div class="foot"></div>'), self = this;
  2633. $panel.append($bottom);
  2634. $panel.find('.closeButton').click(function() {
  2635. self.close();
  2636. });
  2637. if ($shadow === null) {
  2638. $shadow = $('<div id="watchItLaterConfigPanelShadow" /><div id="watchItLaterConfigPanelShadowTop"/><div id="watchItLaterConfigPanelOverShadow"/>');
  2639. }
  2640. }
  2641. };
  2642.  
  2643. pt.refresh = function() {
  2644. var isVisible = $panel.hasClass('open');
  2645. $panel.remove().empty();
  2646. $panel = null;
  2647. this.createPanelDom();
  2648. if (isVisible) { $panel.show(); }
  2649. };
  2650.  
  2651. pt.createMenuItem = function(menu) {
  2652. if (menu.type === 'text') {
  2653. return this.createTextMenuItem(menu);
  2654. } else
  2655. if (menu.type === 'keyInput') {
  2656. return this.createKeyInputMenuItem(menu);
  2657. } else {
  2658. return this.createRadioMenuItem(menu);
  2659. }
  2660. };
  2661. pt.createRadioMenuItem = function(menu) {
  2662. var title = menu.title, varName = menu.varName, values = menu.values;
  2663. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2664. if (menu.className) { $menu.addClass(menu.className);}
  2665. if (menu.description) { $menu.attr('title', menu.description); }
  2666. var currentValue = conf.getValue(varName);
  2667. $menu.addClass(menu.varName);
  2668. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2669. for (var k in values) {
  2670. var v = values[k];
  2671. var $label = w.jQuery('<label></label>');
  2672. var $chk = w.jQuery('<input>');
  2673. $chk.attr({type: 'radio', name: varName, value: JSON.stringify(v)});
  2674.  
  2675. if (currentValue === v) {
  2676. $chk.prop('checked', 'checked');
  2677. }
  2678. $chk.click(function() {
  2679. var newValue = JSON.parse(this.value), oldValue = conf.getValue(varName);
  2680. if (oldValue !== newValue) {
  2681. if (menu.addClass) {
  2682. $panel.removeClass(menu.varName + '_' + oldValue).addClass(menu.varName + '_' + newValue);
  2683. }
  2684. conf.setValue(menu.varName, newValue);
  2685. if (typeof menu.onchange === 'function') {
  2686. menu.onchange(newValue, oldValue);
  2687. }
  2688. dispatchEvent(menu.varName, newValue, oldValue);
  2689. }
  2690. });
  2691. $label.append($chk).append(w.jQuery('<span>' + k + '</span>'));
  2692. $menu.append($label);
  2693. }
  2694. return $menu;
  2695. };
  2696. pt.createTextMenuItem = function(menu) {
  2697. var title = menu.title, varName = menu.varName;
  2698. var $menu = w.jQuery('<li><p class="title">' + title + '</p></li>');
  2699. if (menu.className) { $menu.addClass(menu.className);}
  2700. if (menu.description) { $menu.attr('title', menu.description); }
  2701. var currentValue = conf.getValue(varName);
  2702. var $input = w.jQuery('<input type="text" />');
  2703. $menu.addClass(menu.varName);
  2704. if (menu.addClass) { $panel.addClass(menu.varName + '_' + currentValue);}
  2705. $input.val(currentValue);
  2706. $input.change(function() {
  2707. var newValue = $input.val(), oldValue = conf.getValue(varName);
  2708. if (oldValue !== newValue) {
  2709. conf.setValue(varName, newValue);
  2710. if (typeof menu.onchange === 'function') {
  2711. menu.onchange(newValue, oldValue);
  2712. }
  2713. dispatchEvent(menu.varName, newValue, oldValue);
  2714. }
  2715. });
  2716. $menu.append($input);
  2717. return $menu;
  2718. };
  2719.  
  2720. pt.createKeyInputMenuItem = function(menu) {
  2721. var title = menu.title, varName = menu.varName;
  2722. var currentValue = conf.getValue(varName), currentKey = currentValue.char;
  2723.  
  2724. function update() {
  2725. var newValue = {char: $sel.val(), ctrl: $menu.hasClass('ctrl'), alt: $menu.hasClass('alt'), shift: $menu.hasClass('shift'), enable: $menu.hasClass('enable')};
  2726. conf.setValue(varName, newValue);
  2727. if (typeof menu.onchange === 'function') {
  2728. menu.onchange(newValue);
  2729. }
  2730. dispatchEvent(menu.varName, newValue, conf.getValue(varName));
  2731. }
  2732.  
  2733. var $menu = w.jQuery('<li class="shortcutSetting"><p class="title">' + title + '</p></li>');
  2734. var sel = ['<select>'], $sel;
  2735. for (var v = 48; v <= 90; v++) {
  2736. if (v >= 0x3c && v <= 0x3f) continue;
  2737. var c = String.fromCharCode(v);
  2738. var op = ['<option value="', c, '">', c, '</option>' ].join('');
  2739. sel.push(op);
  2740. }
  2741. sel.push('</select>');
  2742. $sel = w.jQuery(sel.join(''));
  2743. 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) {
  2744. var meta = w.jQuery(e.target).attr('data-meta');
  2745. $menu.toggleClass(meta);
  2746. update();
  2747. });
  2748. $sel.change(update);
  2749.  
  2750. $menu.toggleClass('enable', currentValue.enable).toggleClass('ctrl', currentValue.ctrl).toggleClass('alt', currentValue.alt).toggleClass('shift', currentValue.shift);
  2751. $sel.val(currentKey);
  2752.  
  2753. if (menu.className) { $menu.addClass(menu.className);}
  2754. if (menu.description) { $menu.attr('title', menu.description); }
  2755.  
  2756. $menu.append(w.jQuery('<span/>').append($meta).append($sel));
  2757.  
  2758. return $menu;
  2759. };
  2760.  
  2761. pt.toggleOpenSection = function(sectionName, toggle) {
  2762. $('#watchItLaterConfigPanel .'+ sectionName + 'Container').toggleClass('open', toggle);
  2763. $('#watchItLaterConfigPanel .inner').scrollTop($('#watchItLaterConfigPanel .' + sectionName).position().top - 50);
  2764. };
  2765.  
  2766. pt.addChangeEventListener = function(callback) {
  2767. listener.push(callback);
  2768. };
  2769. pt.open = function() {
  2770. $('body').append($shadow).append($panel);
  2771. setTimeout(function() {
  2772. $shadow.addClass('open'); $panel.addClass('open');
  2773. }, 50);
  2774. setTimeout(function() {
  2775. if (WatchController.isFullScreen()) {
  2776. pt.toggleOpenSection('fullScreen', true);
  2777. } else
  2778. if (WatchController.isSearchMode()) {
  2779. pt.toggleOpenSection('videoExplorer', true);
  2780. }
  2781. }, 1000);
  2782. };
  2783. pt.close = function() {
  2784. $shadow.removeClass('open'); $panel.removeClass('open');
  2785. setTimeout(function() {
  2786. $shadow.detach(); $panel.detach();
  2787. }, 800);
  2788. };
  2789. pt.toggle = function() {
  2790. this.createPanelDom();
  2791. if ($panel.hasClass('open')) {
  2792. this.close();
  2793. } else {
  2794. this.open();
  2795. }
  2796. };
  2797.  
  2798. return pt;
  2799. })(w.jQuery, conf, w);
  2800.  
  2801.  
  2802. /**
  2803. * 通信用
  2804. */
  2805. window.WatchItLater = {
  2806. config: {
  2807. get: function(varName) {
  2808. return conf.getValue(varName);
  2809. },
  2810. set: function(varName, value) {
  2811. conf.setValue(varName, value);
  2812. },
  2813. open: function() {
  2814. ConfigPanel.open();
  2815. }
  2816. },
  2817. loader: {},
  2818. debug: {},
  2819. init: {},
  2820. test: {
  2821. assert: function(v, m) {
  2822. if (v === true) {
  2823. window.console.log('%c OK: ', 'color: black; background: lime;', m);
  2824. } else {
  2825. window.console.log('%cFail: ', 'color: white; background: red;', m);
  2826. throw {message: 'Fail'};
  2827. }
  2828. },
  2829. expect: function(a) {
  2830. try {
  2831. var assert = window.WatchItLater.test.assert, exp = {
  2832. toBeTrue: function( desc) { assert(a === true , desc); },
  2833. toBeFalse: function( desc) { assert(a === false , desc); },
  2834. toEqual: function(b, desc) { assert(a === b , desc); },
  2835. toBeNull: function( desc) { assert(a === null , desc); },
  2836. toBeNotNull: function( desc) { assert(a !== null , desc); },
  2837. toBeDefined: function( desc) { assert(a !== undefined , desc); },
  2838. toBeTruthy: function( desc) { assert(a ? true : false, desc); }
  2839. };
  2840. return exp;
  2841. } catch(e) {
  2842. window.console.log('%c', a);
  2843. }
  2844. },
  2845. spec: {},
  2846. run: function(name) {
  2847. var def = (new $.Deferred()), promise = def.promise();
  2848. var con = function(name) {
  2849. return function() {
  2850. var d = new $.Deferred();
  2851. setTimeout(function() {
  2852. window.console.log('%c RUN: ' + name, 'background: #8ff;');
  2853. d.resolve();
  2854. }, 100);
  2855. return d.promise();
  2856. };
  2857. };
  2858. var wrap = function(self, name) {
  2859. return function() {
  2860. var d = new $.Deferred();
  2861. setTimeout(function() {
  2862. try {
  2863. $.proxy(self.spec[name], self)(d);
  2864. } catch (e) {
  2865. window.console.log(e);
  2866. d.reject();
  2867. }
  2868. }, 0);
  2869. return d.promise();
  2870. };
  2871. };
  2872. var onFail = function(e) {
  2873. window.console.log('%c fail : ','background: red;', e);
  2874. };
  2875.  
  2876. if (name) {
  2877. promise = promise.then(con(name)).then(wrap(this, name), onFail);
  2878. } else {
  2879. for(var v in this.spec) {
  2880. if (!v.match(/^test/)) continue;
  2881. promise = promise.then(con(v)) .then(wrap(this, v), onFail);
  2882. }
  2883. }
  2884. promise.then(
  2885. function() { window.console.log('%cテスト完了', 'background: #8ff'); },
  2886. function() { window.console.log('%cテスト失敗', 'background: #f00'); }
  2887. );
  2888. def.resolve();
  2889. }
  2890. }
  2891. };
  2892. // w.WatchItLater = window.WatchItLater;
  2893.  
  2894.  
  2895. var EventDispatcher = (function(conf) {
  2896. var events = {};
  2897.  
  2898. function addEventListener(name, callback) {
  2899. name = name.toLowerCase();
  2900. if (!events[name]) {
  2901. events[name] = [];
  2902. }
  2903. events[name].push(callback);
  2904. }
  2905.  
  2906. function _dispatch(name) {
  2907. name = name.toLowerCase();
  2908. if (!events[name]) { return; }
  2909. var e = events[name];
  2910. for (var i =0, len = e.length; i < len; i++) {
  2911. try {
  2912. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  2913. } catch (ex) {
  2914. console.log('%c' + name, 'background:red; color: white;', i, e[i], ex);
  2915. }
  2916. }
  2917. }
  2918. function dispatch(name) {
  2919. console.log('%cevent:', 'background: blue; color: white;', name);//, arguments);
  2920. _dispatch.apply(null, arguments);
  2921. }
  2922. return {
  2923. addEventListener: addEventListener,
  2924. dispatch: dispatch,
  2925. _dispatch: _dispatch // コンソール汚したくない用
  2926. };
  2927. })(conf);
  2928. window.WatchItLater.event = EventDispatcher;
  2929.  
  2930. /*
  2931. * 通算視聴回数をカウント。 カウントしても意味はないけど、どれだけ無駄な時間を費やしたかを知りたくて実装。
  2932. */
  2933. var WatchCounter = (function(conf, w) {
  2934. var key = 'watchItLater_watchCounter';
  2935. function get() {
  2936. return JSON.parse(w.localStorage.getItem(key));
  2937. }
  2938. function add() {
  2939. var v = get() + 1;
  2940. w.localStorage.setItem(key, JSON.stringify(v));
  2941. console.log('%cwatchCounter: %c%d', 'color: orange;', 'font-weight: bolder;', v);
  2942. return v;
  2943. }
  2944. var self = {
  2945. get: get,
  2946. add: add
  2947. };
  2948. return self;
  2949. })(conf, w);
  2950. window.WatchItLater.counter = WatchCounter;
  2951.  
  2952. /**
  2953. * 動画タグ取得とポップアップ
  2954. *
  2955. */
  2956. var VideoTags = (function(conf, w){
  2957.  
  2958. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  2959. var pt = function(){};
  2960. var lastPopup = null;
  2961.  
  2962. pt.get = function(watchId, callback) {
  2963. var _get = function(watchId, callback) {
  2964. var url = 'http://' + host + '/tag_edit/' + watchId + '/?res_type=json&cmd=tags';
  2965. //http://www.nicovideo.jp/tag_edit/sm9/?res_type=json&cmd=tags
  2966. var req = {
  2967. method: 'GET',
  2968. url: url,
  2969. onload: function(resp) {
  2970. var result = JSON.parse(resp.responseText);
  2971. if (typeof callback === 'function') callback(result.status, result);
  2972. }
  2973. };
  2974. GM_xmlhttpRequest(req);
  2975. };
  2976.  
  2977. WatchController.getTid2Vid(watchId, function(videoId) {
  2978. _get(videoId, callback);
  2979. });
  2980. };
  2981.  
  2982. pt.hidePopup = function() {
  2983. if (lastPopup) {
  2984. lastPopup.style.display = 'none';
  2985. }
  2986. };
  2987.  
  2988. var uniq = null, $history = null, popupContainer = null;
  2989. pt.popupItems = function(watchId, baseX, baseY) {
  2990. var self = this;
  2991. popupContainer.innerHTML = '';
  2992. this.get(watchId, function(status, resp) {
  2993. if (status === 'ok') {
  2994. var tags = resp.tags;
  2995. self.hidePopup();
  2996. if (tags.length > 0) {
  2997. lastPopup = createPopup(tags, baseX, baseY);
  2998. } else {
  2999. Popup.show('この動画のタグはありません');
  3000. }
  3001. } else {
  3002. Popup.alert(resp.error_message);
  3003. }
  3004. });
  3005.  
  3006. function createPopup(tags, baseX, baseY) {
  3007. var popup = createDOM(tags, baseX, baseY);
  3008. popupContainer.appendChild(popup);
  3009. popup.style.right = null;
  3010. popup.style.left = baseX + 'px';
  3011. popup.style.top = Math.max(baseY - popup.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  3012. if (popup.offsetLeft + popup.offsetWidth > document.body.clientWidth) {
  3013. popup.style.left = null;
  3014. popup.style.right = 0;
  3015. }
  3016.  
  3017. return popup;
  3018. }
  3019.  
  3020. function createDOM(tags) {
  3021. var items = document.createElement('ul');
  3022. for (var i = 0, len = tags.length; i < len; i++) {
  3023. items.appendChild(createItemDOM(tags[i]));
  3024. }
  3025. var popup = createPopupDOM();
  3026.  
  3027. popup.appendChild(items);
  3028. return popup;
  3029. }
  3030.  
  3031. function createPopupDOM() {
  3032. var popup = document.createElement('div');
  3033. popup.className = 'tagItemsPopup popupMenu';
  3034. popup.addEventListener('click', createPopupOnClick(), false);
  3035. return popup;
  3036. }
  3037.  
  3038. function createPopupOnClick() {
  3039. return function(e) {
  3040. if (e.button !== 0 || e.shiftKey || e.ctrlKey || e.altKey || e.target.className === 'icon' || e.target.tagName === 'A') {
  3041. return;
  3042. }
  3043. this.style.display = 'none';
  3044. e.preventDefault();
  3045. e.stopPropagation();
  3046. };
  3047. }
  3048.  
  3049. function appendTagHistory(dom, text, dic) {
  3050. var $ = w.$;
  3051. if (uniq === null) {
  3052. uniq = {};
  3053. $history = $('<div class="tagSearchHistory"><h3 class="title">タグ検索履歴</h3></div>');
  3054. $history.css({width: $('.videoExplorerMenu').width() - 8, maxHeight: '300px', overflowY: 'auto'});
  3055. $('.videoExplorerMenu').append($history);
  3056. }
  3057. if (!uniq[text]) {
  3058. var a = $(dom).clone().css({marginRight: '8px', fontSize: '80%'}).click(Util.Closure.openNicoSearch(text));
  3059. dic.style.marginRight = '0';
  3060. $history.find('.title').after(a).after(dic);
  3061. }
  3062. uniq[text] = 1;
  3063. }
  3064.  
  3065. function createItemDOM(tag) {
  3066. var text = tag.tag;
  3067. var li = document.createElement('li');
  3068. li.className = 'popupTagItem';
  3069.  
  3070. // 大百科アイコン
  3071. var dic = createDicIconDOM(tag, text);
  3072. li.appendChild(dic);
  3073.  
  3074. // 新検索(search.nicovideo.jp)へのリンク
  3075. var newSearchIcon = createNewSearchIconDOM(tag, text);
  3076. li.appendChild(newSearchIcon);
  3077.  
  3078. // 本文リンク
  3079. var a = document.createElement('a');
  3080. a.appendChild(document.createTextNode(text));
  3081.  
  3082. var href = text;
  3083. if (conf.defaultSearchOption && conf.defaultSearchOption !== '' && !text.match(/(sm|nm|so)\d+/)) {
  3084. href += ' ' + conf.defaultSearchOption;
  3085. }
  3086. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  3087. a.href = 'http://' + host + '/tag/' + encodeURIComponent(href) + sortOrder;
  3088. a.addEventListener('click', createItemOnClick(text, dic), false);
  3089. li.appendChild(a);
  3090.  
  3091. return li;
  3092. }
  3093.  
  3094. function createItemOnClick(text, dic) {
  3095. return function(e) {
  3096. if (e.button !== 0 || e.metaKey) return;
  3097. if (w.WatchApp) {
  3098. WatchController.nicoSearch(text, 'tag');
  3099. e.preventDefault();
  3100. appendTagHistory(this, text, dic);
  3101. }
  3102. return false;
  3103. };
  3104. }
  3105.  
  3106. function createNewSearchIconDOM(tag, text) {
  3107. var link = document.createElement('a');
  3108. link.className = 'newsearch';
  3109. link.title = 'niconico新検索で開く';
  3110.  
  3111. // TODO: パラメータの対応表作ってあわせる
  3112. var newSortOrder = '';
  3113. link.href = 'http://search.nicovideo.jp/video/search/' + encodeURIComponent(text) + newSortOrder;
  3114. if (location.host !== 'search.nicovdieo.jp') {
  3115. link.target = '_blank';
  3116. }
  3117.  
  3118. var icon = document.createElement('img');
  3119. icon.className = 'icon';
  3120. icon.src = 'http://uni.res.nimg.jp/img/favicon.ico';
  3121. link.appendChild(icon);
  3122.  
  3123. return link;
  3124. }
  3125. function createDicIconDOM(tag, text) {
  3126. var dic = document.createElement('a');
  3127. dic.className = 'nicodic';
  3128. dic.href = 'http://dic.nicovideo.jp/a/' + encodeURIComponent(text);
  3129. dic.target = '_blank';
  3130. var icon = document.createElement('img');
  3131. icon.className = 'icon';
  3132. icon.src = tag.dic ? 'http://live.nicovideo.jp/img/2012/watch/tag_icon002.png' : 'http://live.nicovideo.jp/img/2012/watch/tag_icon003.png';
  3133. dic.appendChild(icon);
  3134. return dic;
  3135. }
  3136. };
  3137. popupContainer = document.createElement('div');
  3138. popupContainer.id = 'videoTagPopupContainer';
  3139. document.body.appendChild(popupContainer);
  3140.  
  3141. return pt;
  3142. })(conf, w);
  3143.  
  3144.  
  3145.  
  3146.  
  3147.  
  3148.  
  3149.  
  3150.  
  3151.  
  3152. /**
  3153. * マイリスト登録API
  3154. *
  3155. * (9)の頃は、iframeを作ってその中にマイリスト登録のポップアップウィンドウを開くという手抜きを行っていたが、
  3156. * ポップアップウィンドウは評判が悪いし、そのうち廃止されるだろうなと思うので、
  3157. * 真面目にAPIを叩くようにした。 (マイリストの新規作成機能は省略)
  3158. *
  3159. * …と思っていたのだが、(9)からQになった今でもポップアップウィンドウは廃止されないようだ。
  3160. */
  3161. var Mylist = window.WatchItLater.mylist = (function(){
  3162. var mylistlist = [];
  3163. var initialized = false;
  3164. var defListItems = [], mylistItems = {};
  3165. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  3166. var token = '';//
  3167.  
  3168. function Mylist() {
  3169. this.initialize();
  3170. }
  3171.  
  3172. function getToken() {
  3173. if (!isNativeGM && host !== location.host) return null; //
  3174.  
  3175. var _token = (w.NicoAPI) ? w.NicoAPI.token : '';
  3176. if (w.NicoAPI) {
  3177. return w.NicoAPI.token;
  3178. } else
  3179. if (w.WatchApp && w.WatchJsApi) {
  3180. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  3181. watchInfoModel.addEventListener('reset', function(watchInfoModel) {
  3182. token = watchInfoModel.csrfToken;
  3183. });
  3184. if (watchInfoModel.initialized) {
  3185. return watchInfoModel.csrfToken;
  3186. } else {
  3187. var dc = JSON.parse($("#watchAPIDataContainer").text());
  3188. return dc.flashvars.csrfToken;
  3189. }
  3190. } else
  3191. if (_token === null && w.FavMylist && w.FavMylist.csrf_token) {
  3192. _token = w.FavMylist.csrf_token;
  3193. }
  3194.  
  3195. if (_token !== '') {
  3196. return _token;
  3197. }
  3198. var url = 'http://' + host + '/mylist_add/video/sm9'; // マイリスト登録ウィンドウから強引にtoken取得
  3199. // var url = 'http://' + host + '/my/mylist'; // マイリスト登録ウィンドウから強引にtoken取得
  3200. GM_xmlhttpRequest({
  3201. url: url,
  3202. onload: function(resp) {
  3203. var result = resp.responseText;
  3204. if (result.match(/NicoAPI\.token = "([a-z0-9\-]+)";/)) {
  3205. token = RegExp.$1;
  3206. }
  3207. }
  3208. });
  3209. return _token;
  3210. }
  3211.  
  3212. var pt = Mylist.prototype, events = {defMylistUpdate: [], mylistUpdate: []};
  3213.  
  3214. function dispatchEvent(name) {
  3215. var e = events[name];
  3216. for (var i =0, len = e.length; i < len; i++) {
  3217. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  3218. }
  3219. }
  3220.  
  3221. pt.onDefMylistUpdate = function(callback) {
  3222. events.defMylistUpdate.push(callback);
  3223. };
  3224.  
  3225. pt.onMylistUpdate = function(callback) {
  3226. events.mylistUpdate.push(callback);
  3227. };
  3228.  
  3229. pt.getUserId = function() {
  3230. if (document.cookie.match(/user_session_(\d+)/)) {
  3231. return RegExp.$1;
  3232. } else {
  3233. return false;
  3234. }
  3235. };
  3236.  
  3237. var onInitialized = [];
  3238. pt.initialize = function() {
  3239. if (initialized) return;
  3240. var uid = this.getUserId();
  3241. if (!uid) {
  3242. return;
  3243. }
  3244. if (!isNativeGM && host !== location.host) {
  3245. initialized = true;
  3246. return;
  3247. }
  3248. token = getToken();
  3249. //var url = 'http://' + host + '/api/watch/uservideo?user_id=' + uid;
  3250. var url = 'http://' + host + '/api/mylistgroup/list';
  3251. GM_xmlhttpRequest({
  3252. url: url,
  3253. onload: function(resp) {
  3254. var result = JSON.parse(resp.responseText);
  3255. if (result.status === "ok" && result.mylistgroup) {
  3256. mylistlist = result.mylistgroup;
  3257. initialized = true;
  3258. for (var i = 0; i < onInitialized.length; i++) {
  3259. onInitialized[i](mylistlist.concat());
  3260. }
  3261. }
  3262. }
  3263. });
  3264. this.reloadDefList();
  3265. };
  3266.  
  3267. pt.loadMylistList = function(callback) {
  3268. if (initialized) {
  3269. setTimeout(function() { callback(mylistlist.concat()); }, 0);
  3270. } else {
  3271. onInitialized.push(callback);
  3272. }
  3273. };
  3274.  
  3275. pt.isMine = function(id) {
  3276. if (!initialized) { return false; }
  3277. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3278. if (mylistlist[i].id == id) { return true; }
  3279. }
  3280. return false;
  3281. };
  3282.  
  3283. pt.reloadDefList = function(callback) {
  3284. var url = 'http://' + host + '/api/deflist/list';
  3285. GM_xmlhttpRequest({
  3286. url: url,
  3287. onload: function(resp) {
  3288. try {
  3289. JSON.parse(resp.responseText);
  3290. } catch (e) {
  3291. window.console.log(e);
  3292. window.console.log(resp.responseText);
  3293. }
  3294. if (!resp.responseText) return;
  3295. var result = JSON.parse(resp.responseText);
  3296. if (result.status === "ok" && result.mylistitem) {
  3297. defListItems = result.mylistitem;
  3298. if (typeof callback === "function") callback(defListItems);
  3299. }
  3300. }
  3301. });
  3302. };
  3303.  
  3304. pt.loadMylist = function(groupId, callback) {
  3305. if (mylistItems[groupId]) {
  3306. setTimeout(function() {callback(mylistItems[groupId]); }, 0);
  3307. return;
  3308. }
  3309. var url = 'http://' + host + '/api/mylist/list?group_id=' + groupId;
  3310. GM_xmlhttpRequest({
  3311. url: url,
  3312. onload: function(resp) {
  3313. var result = JSON.parse(resp.responseText);
  3314. if (result.status === "ok" && result.mylistitem) {
  3315. mylistItems[groupId] = result.mylistitem;
  3316. if (typeof callback === "function") callback(result.mylistitem);
  3317. }
  3318. }
  3319. });
  3320. };
  3321.  
  3322. pt.clearMylistCache = function(groupId) {
  3323. delete mylistItems[groupId];
  3324. };
  3325.  
  3326. pt.reloadMylist = function(groupId, callback) {
  3327. this.clearMylistCache(groupId);
  3328. return this.loadMylist(groupId, callback);
  3329. };
  3330.  
  3331.  
  3332. pt.findDeflistByWatchId = function(watchId) {
  3333. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3334.  
  3335. for (var i = 0, len = defListItems.length; i < len; i++) {
  3336. var item = defListItems[i], wid = item.item_data.watch_id;
  3337. if (wid == watchId) return item;
  3338. }
  3339. return null;
  3340. };
  3341.  
  3342. pt.findMylistByWatchId = function(watchId, groupId) {
  3343. // if (/^[0-9]+$/.test(watchId)) return watchId; // スレッドIDが来た
  3344. var items = mylistItems[groupId];
  3345. if (!items) { return null; }
  3346. for (var i = 0, len = items.length; i < len; i++) {
  3347. var item = items[i], wid = item.item_data.watch_id;
  3348. if (wid == watchId) return item;
  3349. }
  3350. return null;
  3351. };
  3352.  
  3353. // おもに参考にしたページ
  3354. // http://uni.res.nimg.jp/js/nicoapi.js
  3355. // http://d.hatena.ne.jp/lolloo-htn/20110115/1295105845
  3356. // http://d.hatena.ne.jp/aTaGo/20100811/1281552243
  3357. pt.deleteDefListItem = function(watchId, callback) {
  3358. var item = this.findDeflistByWatchId(watchId);
  3359. if (!item) return false;
  3360. var item_id = item.item_id;
  3361. var url = 'http://' + host + '/api/deflist/delete';
  3362. var data = 'id_list[0][]=' + item_id + '&token=' + token;
  3363. var req = {
  3364. method: 'POST',
  3365. data: data,
  3366. headers: {'Content-Type': 'application/x-www-form-urlencoded'}, // これを忘れて小一時間はまった
  3367. url: url,
  3368. onload: function(resp) {
  3369. var result = JSON.parse(resp.responseText);
  3370. if (typeof callback === "function") callback(result.status, result);
  3371. if (window.jQuery) {
  3372. defListItems = window.jQuery.grep(defListItems, function(item) {
  3373. return item.item_data.watch_id !== watchId;
  3374. });
  3375. }
  3376. dispatchEvent('defMylistUpdate');
  3377. }
  3378. };
  3379. GM_xmlhttpRequest(req);
  3380. return true;
  3381. };
  3382.  
  3383. pt.addDefListItem = function(watchId, callback, description) {
  3384. var url = 'http://' + host + '/api/deflist/add';
  3385.  
  3386. // 例えば、とりマイの300番目に登録済みだった場合に「登録済みです」と言われても探すのがダルいし、
  3387. // 他の動画を追加していけば、そのうち押し出されて消えてしまう。
  3388. // なので、重複時にエラーを出すのではなく、「消してから追加」することによって先頭に持ってくる。
  3389. // 「重複してたら先頭に持ってきて欲しいな~」って要望掲示板にこっそり書いたりしたけど相手にされないので自分で実装した。
  3390. var data = "item_id=" + watchId + "&token=" + token, replaced = true;
  3391. if (description) {
  3392. data += '&description='+ encodeURIComponent(description);
  3393. }
  3394.  
  3395. var _add = function(status, resp) {
  3396. var req = {
  3397. method: 'POST',
  3398. data: data,
  3399. url: url,
  3400. headers: {'Content-Type': 'application/x-www-form-urlencoded' }, // これを忘れて小一時間はまった
  3401. onload: function(resp) {
  3402. var result = JSON.parse(resp.responseText);
  3403. if (typeof callback === "function") callback(result.status, result, replaced);
  3404. }
  3405. };
  3406. GM_xmlhttpRequest(req);
  3407. };
  3408. // とりあえずマイリストにある場合はdeleteDefListItem()のcallbackで追加、ない場合は即時追加
  3409. if (!this.deleteDefListItem(watchId, _add)) {
  3410. replaced = false;
  3411. _add();
  3412. dispatchEvent('defMylistUpdate');
  3413. }
  3414. };
  3415.  
  3416. pt.addMylistItem = function(watchId, groupId, callback, description) {
  3417. var self = this;
  3418. var url = 'http://' + host + '/api/mylist/add';
  3419. var data = ['item_id=', watchId,
  3420. '&group_id=', groupId,
  3421. '&item_type=', 0, // video=0 seiga=5
  3422. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3423. '&token=', token
  3424. ].join('');
  3425. // 普通のマイリストのほうは重複しても「消してから追加」という処理を行っていない。
  3426. // とりあえずマイリストと違って登録の順番に意味があるのと、
  3427. // 古いのが押し出される心配がないため。
  3428. var _add = function() {
  3429. var req = {
  3430. method: 'POST',
  3431. data: data,
  3432. url: url,
  3433. headers: {'Content-Type': 'application/x-www-form-urlencoded' },
  3434. onload: function(resp) {
  3435. var result = JSON.parse(resp.responseText);
  3436. if (typeof callback === "function") callback(result.status, result);
  3437. if (result.status === 'ok') {
  3438. dispatchEvent('mylistUpdate', {action: 'add', groupId: groupId, watchId: watchId});
  3439. EventDispatcher.dispatch('onMylistItemAdded', groupId, watchId);
  3440. self.clearMylistCache(groupId);
  3441. }
  3442. },
  3443. error: function() {
  3444. Popup.alert('ネットワークエラー');
  3445. }
  3446. };
  3447. GM_xmlhttpRequest(req);
  3448. };
  3449. // 普通のマイリストに入れたら、とりあえずマイリストからは削除(≒移動)
  3450. if (!this.deleteDefListItem(watchId, _add)) _add();
  3451. };
  3452.  
  3453. pt.updateMylistItem = function(watchId, groupId, callback, description) {
  3454. var self = this;
  3455. this.loadMylist(groupId, function() {
  3456. var item = self.findMylistByWatchId(watchId, groupId);
  3457. if (!item) {
  3458. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3459. return;
  3460. }
  3461. var
  3462. itemId = item.item_id,
  3463. url = 'http://' + host + '/api/mylist/update',
  3464. data = ['item_id=', itemId,
  3465. '&group_id=', groupId,
  3466. '&item_type=', 0, // video=0 seiga=5
  3467. '&description=', (typeof description === 'string') ? encodeURIComponent(description) : '',
  3468. '&token=', token
  3469. ].join(''),
  3470. req = {
  3471. method: 'POST',
  3472. data: data,
  3473. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3474. url: url,
  3475. onload: function(resp) {
  3476. var result = JSON.parse(resp.responseText);
  3477. if (result.status === 'ok') {
  3478. if (typeof callback === "function") callback(result.status, result);
  3479. dispatchEvent('mylistUpdate', {action: 'update', groupId: groupId, watchId: watchId});
  3480. EventDispatcher.dispatch('onMylistItemUpdated', groupId, watchId);
  3481. }
  3482. },
  3483. error: function() {
  3484. Popup.alert('ネットワークエラー');
  3485. }
  3486. };
  3487.  
  3488. GM_xmlhttpRequest(req);
  3489. });
  3490. };
  3491.  
  3492.  
  3493. pt.deleteMylistItem = function(watchId, groupId, callback) {
  3494. var self = this;
  3495. this.loadMylist(groupId, function() {
  3496. var item = self.findMylistByWatchId(watchId, groupId);
  3497. if (!item) {
  3498. Popup.alert('マイリスト中に該当する動画がみつかりませんでした');
  3499. return;
  3500. }
  3501. var
  3502. item_id = item.item_id,
  3503. url = 'http://' + host + '/api/mylist/delete',
  3504. data = [
  3505. 'id_list[0][]=', item_id,
  3506. '&group_id=', groupId,
  3507. '&token=', token
  3508. ].join(''),
  3509. req = {
  3510. method: 'POST',
  3511. data: data,
  3512. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  3513. url: url,
  3514. onload: function(resp) {
  3515. var result = JSON.parse(resp.responseText);
  3516. if (result.status === 'ok') {
  3517. if (typeof callback === "function") callback(result.status, result);
  3518. dispatchEvent('mylistUpdate', {action: 'delete', groupId: groupId, watchId: watchId});
  3519. EventDispatcher.dispatch('onMylistItemDeleted', groupId, watchId);
  3520. }
  3521. },
  3522. error: function() {
  3523. Popup.alert('ネットワークエラー');
  3524. }
  3525. };
  3526.  
  3527. GM_xmlhttpRequest(req);
  3528. });
  3529. };
  3530.  
  3531.  
  3532. /**
  3533. * マイリスト登録パネルを返す
  3534. */
  3535. pt.getPanel = function(watchId, videoId) {
  3536. if (isNativeGM || host === location.host) {
  3537. return this.getNativePanel(watchId, videoId);
  3538. } else {
  3539. return this.getIframePanel(watchId, videoId);
  3540. }
  3541. };
  3542.  
  3543. pt.getNativePanel = function(watchId, videoId) {
  3544. var self = this;
  3545. var _watchId = watchId, _videoId = videoId || watchId;
  3546. var body = document.createElement('div');
  3547. var mylistListPopup = null;
  3548. body.className = 'mylistPopupPanel deflistSelected';
  3549. var nobr = document.createElement('nobr');
  3550. body.appendChild(nobr);
  3551.  
  3552. var extArea = document.createElement('span');
  3553.  
  3554.  
  3555. var isWatchPage = (window.PlayerApp) ? true : false;
  3556.  
  3557. var addDeflist = function(watchId, description) {
  3558. self.addDefListItem(watchId, function(status, result, replaced) {
  3559. self.reloadDefList();
  3560. if (status !== 'ok') {
  3561. Popup.alert('とりあえずマイリストへの登録に失敗: ' + result.error.description);
  3562. } else {
  3563. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  3564. Popup.show(
  3565. torimai +
  3566. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  3567. );
  3568. }
  3569. }, description);
  3570. };
  3571. var addMylist = function(watchId, mylistId, mylistName, description) {
  3572. self.addMylistItem(watchId, mylistId, function(status, result) {
  3573. self.reloadDefList();
  3574. if (status === 'ok') {
  3575. Popup.show( '<a href="/my/mylist/#/' + mylistId + '">' + mylistName + '</a>に登録しました');
  3576. } else {
  3577. Popup.alert(mylistName + 'への登録に失敗: ' + result.error.description);
  3578. }
  3579. }, description);
  3580. };
  3581. var setButtonStyleUpdating = function(btn) {
  3582. btn.style.opacity = 0.5;
  3583. btn.style.cursor = 'pointer';
  3584. btn.disabled = true;
  3585.  
  3586. window.setTimeout(function() {
  3587. btn.disabled = false;
  3588. btn.style.opacity = 1;
  3589. btn.style.cursor = 'pointer';
  3590. btn = null;
  3591. }, 1000);
  3592. };
  3593. var onMylistListClick = function(mylistId, mylistName, type) {
  3594. if (type === 'icon') {
  3595. if (window.WatchApp) {
  3596. if (mylistId === 'default') {
  3597. WatchController.showDeflist();
  3598. } else {
  3599. WatchController.showMylist(mylistId);
  3600. }
  3601. } else {
  3602. location.href = 'http://' + host + '/my/mylist/#/' + mylistId.replace('default','home');
  3603. }
  3604. return;
  3605. }
  3606. if (mylistId === 'default') {
  3607. addDeflist(_watchId);
  3608. } else {
  3609. addMylist(_watchId, mylistId, mylistName);
  3610. }
  3611. };
  3612.  
  3613. body.watchId = function(w, v) {
  3614. if (w) {
  3615. _watchId = w;
  3616. _videoId = v || w;
  3617. var isThreadId = (/^[0-9]+$/.test(w));
  3618.  
  3619. deleteDef.disabled = false;
  3620. if (self.findDeflistByWatchId(w)) {
  3621. deleteDef.style.display = '';
  3622. } else {
  3623. deleteDef.style.display = 'none';
  3624. }
  3625. if (!isWatchPage && isThreadId) {
  3626. tagBtn.style.display = 'none'; // スレッドIDから動画IDを取る手段がないためタグ取得が難しい
  3627. } else {
  3628. tagBtn.style.display = '';
  3629. }
  3630. if (newTabLink) {
  3631. newTabLink.href = 'http://nico.ms/' + _watchId; // QWatchに乗っ取られないようにnico.msをかます(せこい)
  3632. }
  3633. if (mylistListPopup) {
  3634. mylistListPopup.hide();
  3635. }
  3636. return body;
  3637. }
  3638. return _watchId;
  3639. };
  3640.  
  3641. body.show = function() {
  3642. body.style.display = '';
  3643. if (mylistListPopup) {
  3644. mylistListPopup.hide();
  3645. }
  3646. };
  3647. body.hide = function() {
  3648. body.style.display = 'none';
  3649. if (mylistListPopup) {
  3650. mylistListPopup.hide();
  3651. }
  3652. };
  3653.  
  3654. function createSelector() {
  3655. var sel = document.createElement('select');
  3656. var lastSelect = 0;
  3657.  
  3658. sel.className = 'mylistSelect';
  3659. var appendO = function(sel, text, value) {
  3660. var opt = document.createElement('option');
  3661. opt.appendChild(document.createTextNode(text));
  3662. opt.value = value;
  3663. sel.appendChild(opt);
  3664. return opt;
  3665. },
  3666. createOptions = function() {
  3667. for (var i = 0, len = mylistlist.length; i < len; i++) {
  3668. var mylist = mylistlist[i];
  3669. appendO(sel, (i + 1).toString(36) + ':' + mylist.name, mylist.id);
  3670. }
  3671. },
  3672. onSelect = function() {
  3673. // jQueryは全てのページにあるわけではないので気をつける。忘れると原宿が死ぬ
  3674. if (sel.selectedIndex === 0) {
  3675. body.className = body.className.replace('mylistSelected', 'deflistSelected');
  3676. } else {
  3677. lastSelect = sel.selectedIndex;
  3678. body.className = body.className.replace('deflistSelected', 'mylistSelected');
  3679. }
  3680. },
  3681. selectDeflist = function() {
  3682. sel.selectedIndex = 0;
  3683. onSelect();
  3684. },
  3685. onContextMenu = function(e) {
  3686. e.preventDefault();
  3687. e.stopPropagation();
  3688.  
  3689. if (lastSelect === 0) return;
  3690. if (sel.selectedIndex === 0) {
  3691. sel.selectedIndex = lastSelect;
  3692. } else {
  3693. sel.selectedIndex = 0;
  3694. }
  3695. onSelect();
  3696. };
  3697.  
  3698. appendO(sel, '0:とりマイ', 'default');
  3699. sel.selectedIndex = 0;
  3700. window.setTimeout(createOptions, initialized ? 0 : 3000);
  3701.  
  3702. sel.addEventListener('change', onSelect, false);
  3703. sel.addEventListener('contextmenu', onContextMenu, false);
  3704.  
  3705.  
  3706. body.addEventListener('dblclick', selectDeflist, false);
  3707. return sel;
  3708. }
  3709.  
  3710. function createSubmitButton() {
  3711. var btn = document.createElement('button');
  3712. btn.appendChild(document.createTextNode('my'));
  3713. btn.className = 'mylistAdd';
  3714. btn.title = 'マイリストに追加\n(ボタンを右クリックで詳細メニュー)';
  3715.  
  3716. var callMylistListPopup = function() {
  3717. if (!mylistListPopup) {
  3718. mylistListPopup = new MylistListPopup(mylistlist, onMylistListClick);
  3719. }
  3720. mylistListPopup.toggle(btn, _watchId);
  3721. };
  3722.  
  3723. btn.addEventListener('contextmenu', function(e) {
  3724. if (window.jQuery) {
  3725. e.preventDefault();
  3726. e.stopPropagation();
  3727. callMylistListPopup();
  3728. }
  3729. });
  3730.  
  3731. btn.addEventListener('click', function(e) {
  3732. var description = null;
  3733. if (e.shiftKey) {
  3734. description = prompt('マイリストコメントの入力');
  3735. if (!description) return;
  3736. }
  3737. setButtonStyleUpdating(btn);
  3738.  
  3739. var mylistId = sel.value, name = sel.options[sel.selectedIndex].textContent;
  3740. if (mylistId === 'default') {
  3741. addDeflist(_watchId, description);
  3742. } else {
  3743. addMylist(_watchId, mylistId, name, description);
  3744. }
  3745. } ,false);
  3746. return btn;
  3747. }
  3748.  
  3749. function createDeleteDeflistItemButton() {
  3750. var btn = document.createElement('button');
  3751. btn.appendChild(document.createTextNode('×'));
  3752. btn.className = 'deflistRemove';
  3753. btn.title = 'とりあえずマイリストから外す';
  3754.  
  3755. btn.addEventListener('click', function() {
  3756.  
  3757. setButtonStyleUpdating(btn);
  3758.  
  3759. self.deleteDefListItem(_watchId, function(status, result) {
  3760. self.reloadDefList();
  3761. btn.style.display = 'none';
  3762. if (status !== "ok") {
  3763. Popup.alert('とりあえずマイリストから削除に失敗: ' + result.error.description);
  3764. } else {
  3765. Popup.show('とりあえずマイリストから外しました');
  3766. }
  3767. });
  3768. } ,false);
  3769. return btn;
  3770. }
  3771.  
  3772. function createTagListButton() {
  3773. var btn = document.createElement('button');
  3774. btn.appendChild(document.createTextNode('tag'));
  3775. btn.className = 'tagGet';
  3776. btn.title = 'タグ取得';
  3777. btn.addEventListener('click', function(e) {
  3778. btn.disabled = true;
  3779.  
  3780. setButtonStyleUpdating(btn);
  3781.  
  3782. if (w.jQuery) {
  3783. var $btn = w.jQuery(btn), o = $btn.offset();
  3784. VideoTags.popupItems(_videoId, o.left, o.top + $btn.outerHeight());
  3785. } else {
  3786. VideoTags.popupItems(_videoId, e.pageX, e.pageY);
  3787. }
  3788. } ,false);
  3789. return btn;
  3790. }
  3791.  
  3792. function createNewTabLink() {
  3793. var a = document.createElement('a');
  3794. a.className = 'newTabLink';
  3795. a.target = '_blank';
  3796. a.title = 'この動画を新しいウィンドウで開く';
  3797. a.innerHTML = '▲';
  3798. return a;
  3799. }
  3800.  
  3801. var newTabLink = createNewTabLink();
  3802. if (w.WatchApp) {
  3803. nobr.appendChild(newTabLink);
  3804. }
  3805.  
  3806.  
  3807. var sel = createSelector();
  3808. var submit = createSubmitButton(sel);
  3809. nobr.appendChild(sel);
  3810. nobr.appendChild(submit);
  3811. if (w.jQuery) {
  3812. w.jQuery(sel).keydown(function(e) {
  3813. e.stopPropagation();
  3814. if (e.keyCode === 13) { // ENTER
  3815. submit.click();
  3816. }
  3817. });
  3818. }
  3819.  
  3820. var tagBtn = createTagListButton();
  3821. nobr.appendChild(tagBtn);
  3822.  
  3823. var deleteDef = createDeleteDeflistItemButton();
  3824. nobr.appendChild(deleteDef);
  3825.  
  3826.  
  3827.  
  3828. nobr.appendChild(extArea);
  3829.  
  3830. body.watchId(_watchId, _videoId);
  3831. return body;
  3832. };
  3833.  
  3834. // XHRでクロスドメインを超えられない場合はこちら
  3835. // 将来マイリストのポップアップウィンドウが廃止されたら使えない
  3836. // (マイページから強引に生成するか?)
  3837. pt.getIframePanel = function(watchId) {
  3838. var _watchId = watchId;
  3839. var body = document.createElement('iframe');
  3840. body.name = 'nicomylistaddDummy';
  3841. body.className = 'mylistPopupPanel';
  3842. body.setAttribute('style', 'width: 130px; height: 24px; z-index: 10000; border: 1px solid silver; padding: 0; margin: 0; overflow: hidden');
  3843.  
  3844. body.watchId = function(w) {
  3845. if (w) {
  3846. _watchId = w;
  3847. body.contentWindow.location.replace("http:/" + "/www.nicovideo.jp/mylist_add/video/" + w);
  3848. return body;
  3849. }
  3850. return _watchId;
  3851. };
  3852. if (watchId !== '') {
  3853. body.src = "http:/" + "/www.nicovideo.jp/mylist_add/video/" + _watchId;
  3854. }
  3855.  
  3856. // ダミーメソッド
  3857. body.show = function() {
  3858. body.style.display = '';
  3859. };
  3860. body.hide = function() {
  3861. body.style.display = 'none';
  3862. };
  3863.  
  3864.  
  3865. return body;
  3866. };
  3867.  
  3868. return new Mylist();
  3869. })();
  3870.  
  3871. var MylistListPopup = function() { this.initialize.apply(this, arguments); };
  3872. MylistListPopup.prototype = {
  3873. initialize: function(mylistList, onItemClick) {
  3874. this._mylistList = mylistList.concat();
  3875. this.initializeView(mylistList);
  3876. this._onItemClick = onItemClick;
  3877. },
  3878. initializeView: function() {
  3879. var $ = window.jQuery;
  3880. this._$view = $([
  3881. '<div class="mylistListPopup popupMenu">',
  3882. '<div class="listInner">',
  3883. '<ul></ul>',
  3884. '</div>',
  3885. '</div>',
  3886. ''].join(''));
  3887. this._$list = this._$view.find('ul');
  3888. this._$inner = this._$view.find('.listInner');
  3889.  
  3890.  
  3891. $('body').append(this._$view);
  3892.  
  3893. this.refresh();
  3894.  
  3895. this.initializeEvent(this._$view);
  3896. },
  3897. initializeEvent: function($view) {
  3898. $view.on('click', window.jQuery.proxy(function(e) {
  3899. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  3900. var target = e.target, $target = window.jQuery(target);
  3901. this.hide();
  3902. e.preventDefault();
  3903.  
  3904. var mylistId = $target.attr('data-mylist-id');
  3905. var mylistName = $target.attr('data-mylist-name');
  3906. if (!mylistId) { return; }
  3907. var type = target.className;
  3908.  
  3909. if (typeof this._onItemClick === 'function') {
  3910. this._onItemClick(mylistId, mylistName, type);
  3911. }
  3912. }, this));
  3913. var self = this, closeTimer = null;
  3914. $view.hover(
  3915. function() {
  3916. if (closeTimer) { window.clearTimeout(closeTimer); }
  3917. },
  3918. function() {
  3919. closeTimer = window.setTimeout(function() { self.hide(); }, 1000);
  3920. });
  3921.  
  3922. $view = null;
  3923. },
  3924. adjustColumnCount: function() {
  3925. this._$inner.css({
  3926. 'column-count': '',
  3927. 'max-height': ''
  3928. });
  3929. var height = this._$view.outerHeight(),
  3930. clientHeight = window.jQuery(window).innerHeight(),
  3931. threshold = clientHeight * 0.4;
  3932. if (threshold < height) {
  3933. var columns = parseInt( height / threshold, 10) + 1;
  3934. this._$inner.css({
  3935. 'column-count': columns,
  3936. 'max-height': clientHeight * 0.8
  3937. });
  3938. }
  3939. },
  3940. updateList: function(mylistList) {
  3941. this._mylistList = mylistList.concat();
  3942. this.refresh();
  3943. },
  3944. refresh: function() {
  3945. var mylistList = this._mylistList;
  3946. this._$list.empty();
  3947. for (var i = 0, len = mylistList.length; i < len; i++) {
  3948. var mylist = mylistList[i];
  3949. this.appendItem(mylist.id, mylist.name, mylist.icon_id);
  3950. }
  3951. this.appendItem('default', 'とりあえずマイリスト');
  3952.  
  3953. this.adjustColumnCount();
  3954. },
  3955. appendItem: function(id, name, icon_id) {
  3956. var $mylist = window.jQuery([
  3957. '<li class="folder', icon_id, '">',
  3958. '<span class="icon"></span>',
  3959. '<a href="my/mylist/#/', id.replace('default', 'home'), '" class="name">',
  3960. name,
  3961. '</a>',
  3962. '</li>',
  3963. ''].join(''));
  3964. $mylist.find('.icon, .name').attr({
  3965. 'data-mylist-id': id,
  3966. 'data-mylist-name': name
  3967. });
  3968.  
  3969. if (id === 'default') {
  3970. $mylist.addClass('deflist');
  3971. } else
  3972. if (id.indexOf('ext') === 0) {
  3973. $mylist.addClass('ext');
  3974. }
  3975. this._$list.append($mylist);
  3976. },
  3977. updateExist: function(watchId) {
  3978. if (!watchId) {
  3979. return;
  3980. }
  3981. this._$view.find('.exist').removeClass('exist');
  3982. this._$view.find('.name').each(function() {
  3983. var $this = window.jQuery(this), mylistId = $this.attr('data-mylist-id');
  3984. if (mylistId === 'default') { return; }
  3985. $this
  3986. .toggleClass('exist', window.WatchItLater.mylist.cache.hasItem(mylistId, watchId));
  3987. });
  3988.  
  3989. this._$view.find('.deflist .name')
  3990. .toggleClass('exist', window.WatchItLater.mylist.findDeflistByWatchId(watchId) !== null);
  3991. },
  3992. show: function(elm, watchId) {
  3993. this.adjustColumnCount();
  3994. this._$view.addClass('show active');
  3995.  
  3996. if (!elm) { return; }
  3997. var
  3998. $ = window.jQuery,
  3999. $elm = $(elm),
  4000. o = $elm.offset(),
  4001. $view = this._$view,
  4002. $window = $(window),
  4003. scrollLeft = $window.scrollLeft(),
  4004. scrollTop = $window.scrollTop();
  4005.  
  4006. $view.css({
  4007. top: Math.max(o.top - $view.outerHeight(), 0, scrollTop),
  4008. left: o.left
  4009. });
  4010. if ($view.offset().left + $view.outerWidth() > $window.innerWidth() + scrollLeft) {
  4011. $view.css({
  4012. left: Math.max(0, $window.innerWidth() + scrollLeft - $view.outerWidth())
  4013. });
  4014. }
  4015.  
  4016. this.updateExist(watchId);
  4017. window.jQuery('body').on('click', $.proxy(this._onBodyClick, this));
  4018. },
  4019. hide: function() {
  4020. var $view = this._$view
  4021. .removeClass('show');
  4022. window.setTimeout(function() {
  4023. $view.css({top: '', left: '', right: ''}).removeClass('active');
  4024. }, 500);
  4025. window.jQuery('body').off('click', this._onBodyClick);
  4026. },
  4027. toggle: function(elm, watchId) {
  4028. if (this._$view.hasClass('avtive')) {
  4029. this.hide();
  4030. } else {
  4031. this.show(elm, watchId);
  4032. }
  4033. },
  4034. _onBodyClick: function() {
  4035. this.hide();
  4036. }
  4037. };
  4038.  
  4039. window.WatchItLater.mylist.cache = (function() {
  4040. var CacheList = function() { this.initialize.apply(this, arguments); };
  4041. CacheList.prototype = {
  4042. initialize: function() {
  4043. this.reset();
  4044. },
  4045. reset: function() {
  4046. this._cacheList = {};
  4047. },
  4048. setCache: function(mylistId, items) {
  4049. if (!this.hasCache(mylistId)) {
  4050. this._cacheList[mylistId] = new MylistCache();
  4051. }
  4052. this._cacheList[mylistId].update(items);
  4053. },
  4054. hasCache: function(mylistId) {
  4055. if (this._cacheList[mylistId]) {
  4056. return true;
  4057. }
  4058. return false;
  4059. },
  4060. hasItem: function(mylistId, watchId) {
  4061. if (!this.hasCache(mylistId)) {
  4062. return false;
  4063. }
  4064. return this._cacheList[mylistId].hasItem(watchId);
  4065. },
  4066. addItem: function(mylistId, watchId) {
  4067. if (!this.hasCache(mylistId)) {
  4068. return false;
  4069. }
  4070. this._cacheList[mylistId].addItem(watchId);
  4071. },
  4072. removeItem: function(mylistId, watchId) {
  4073. if (!this.hasCache(mylistId)) {
  4074. return false;
  4075. }
  4076. this._cacheList[mylistId].removeItem(watchId);
  4077. },
  4078. count: function(mylistId) {
  4079. if (!this.hasCache(mylistId)) {
  4080. return NaN;
  4081. }
  4082. this._cacheList[mylistId].count();
  4083. },
  4084. toJSON: function() {
  4085. var cacheList = this._cacheList;
  4086. return this._cacheList;
  4087. },
  4088. parse: function(jsonString) {
  4089. var data;
  4090. try {
  4091. data = JSON.parse(jsonString);
  4092. } catch (e) {
  4093. data = {};
  4094. }
  4095. this.reset();
  4096. for (var mylistId in data) {
  4097. var mylistCache = data[mylistId];
  4098. this._cacheList[mylistId] = new MylistCache(mylistCache);
  4099. }
  4100. }
  4101. };
  4102.  
  4103. var MylistCache = function() { this.initialize.apply(this, arguments); };
  4104. MylistCache.prototype = {
  4105. initialize: function(mylistData) {
  4106. this._name = '';
  4107. if (mylistData) {
  4108. this.update(mylistData);
  4109. }
  4110. },
  4111. update: function(mylistData) {
  4112. this._cache = [];
  4113. this._hash = {};
  4114. var items = mylistData.items ? mylistData.items : mylistData;
  4115. for (var i = 0, len = items.length; i < len; i++) {
  4116. var
  4117. item = items[i],
  4118. watchId = typeof item.getId === 'function' ? item.getId() : item.id;
  4119. this._cache.push({id: watchId});
  4120. this._hash[watchId] = true;
  4121. }
  4122. if (mylistData.name) {
  4123. this._name = mylistData.name;
  4124. }
  4125. },
  4126. hasItem: function(watchId) {
  4127. return this._hash[watchId] === true;
  4128. },
  4129. addItem: function(watchId) {
  4130. if (this.hasItem(watchId)) {
  4131. return;
  4132. }
  4133. this._hash[watchId] = true;
  4134. this._cache.push({id: watchId});
  4135. },
  4136. removeItem: function(watchId) {
  4137. if (!this.hasItem(watchId)) {
  4138. return;
  4139. }
  4140. delete this._hash[watchId];
  4141. this._cache = $.grep(this._cache, function(item) {
  4142. return item.id !== watchId;
  4143. });
  4144. },
  4145. count: function() {
  4146. return this._cache.length;
  4147. },
  4148. toJSON: function() {
  4149. return {
  4150. name: this._name,
  4151. items: this._cache
  4152. };
  4153. },
  4154. parse: function(jsonString) {
  4155. var items;
  4156. try {
  4157. items = JSON.parse(jsonString);
  4158. } catch (e) {
  4159. items = [];
  4160. }
  4161. this.update(items);
  4162. }
  4163. };
  4164.  
  4165. var cacheList, noop = function() {};
  4166. var initialize = function() {
  4167. initialize = noop;
  4168. console.log('%cinitialize mylistCache', 'background: lightgreen;');
  4169. cacheList = new CacheList();
  4170.  
  4171. if (conf.enableLocalMylistCache) {
  4172. if (window.PlayerApp) {
  4173. $(window).on('beforeunload.watchItLater', function(e) {
  4174. window.localStorage.setItem('watchItLater_mylistCache', serialize());
  4175. });
  4176. }
  4177. var cacheData = window.localStorage.getItem('watchItLater_mylistCache');
  4178. if (cacheData) {
  4179. cacheList.parse(cacheData);
  4180. }
  4181. }
  4182. };
  4183. var hasCache = function(mylistId, watchId) {
  4184. initialize();
  4185. return cacheList.hasItem(mylistId, watchId);
  4186. };
  4187. var hasItem = function(mylistId, watchId) {
  4188. initialize();
  4189. return cacheList.hasItem(mylistId, watchId);
  4190. };
  4191. var addItem = function(mylistId, watchId) {
  4192. initialize();
  4193. cacheList.addItem(mylistId, watchId);
  4194. };
  4195. var removeItem = function(mylistId, watchId) {
  4196. initialize();
  4197. cacheList.removeItem(mylistId, watchId);
  4198. };
  4199. var setCache = function(mylistId, items) {
  4200. initialize();
  4201. cacheList.setCache(mylistId, items);
  4202. };
  4203. var serialize = function() {
  4204. initialize();
  4205. return JSON.stringify(cacheList);
  4206. };
  4207. var unserialize = function(json) {
  4208. initialize();
  4209. cacheList.parse(json);
  4210. };
  4211. var clearCache = function() {
  4212. window.localStorage.removeItem('watchItLater_mylistCache');
  4213. };
  4214.  
  4215.  
  4216. EventDispatcher.addEventListener('onMyMylistLoad', function(mylistId, list) {
  4217. setCache(mylistId, list || []);
  4218. });
  4219. EventDispatcher.addEventListener('onMylistItemAdded', function(mylistId, watchId) {
  4220. initialize();
  4221. cacheList.addItem(mylistId, watchId);
  4222. });
  4223. EventDispatcher.addEventListener('onMylistItemDeleted', function(mylistId, watchId) {
  4224. initialize();
  4225. cacheList.removeItem(mylistId, watchId);
  4226. });
  4227.  
  4228.  
  4229. return {
  4230. initialize: initialize,
  4231. hasItem: hasItem,
  4232. addItem: addItem,
  4233. setCache: setCache,
  4234. serialize: serialize,
  4235. unserialize: unserialize,
  4236. clearCache: clearCache
  4237. };
  4238. })();
  4239.  
  4240.  
  4241. var LocationHashParser = (function(conf, w) {
  4242. var self, dat = {};
  4243.  
  4244. function initialize() {
  4245. var hash = w.location.hash.toString();
  4246. var redirectedHash = window.sessionStorage.getItem('watchItLater_redirectedHash');
  4247. if (redirectedHash) {
  4248. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  4249. hash = redirectedHash;
  4250. window.sessionStorage.removeItem('watchItLater_redirectedHash');
  4251. }
  4252. try {
  4253. if (hash.indexOf('#json={') === 0) {
  4254. dat = JSON.parse(hash.substr(6));
  4255. }
  4256. } catch (e) {
  4257. try {
  4258. dat = JSON.parse(decodeURIComponent(hash.substr(6)));
  4259. } catch(ex) {
  4260. console.log(ex);
  4261. }
  4262. console.log(e);
  4263. }
  4264. }
  4265. function setValue(key, value) {
  4266. dat[key] = value;
  4267. }
  4268. function getValue(key) {
  4269. return dat[key];
  4270. }
  4271. function deleteValue(key) {
  4272. delete dat[key];
  4273. }
  4274. function updateHash() {
  4275. var loc = window.location.href.split('#')[0];
  4276. location.replace(loc + getHash());
  4277. }
  4278. function removeHash() {
  4279. if (location.hash.length <= 1) { return; }
  4280. var scrollTop = $(window).scrollTop();
  4281. var loc = window.location.href.split('#')[0];
  4282. location.replace(loc + '#');
  4283. $(window).scrollTop(scrollTop);
  4284. }
  4285. function getHash() {
  4286. var json = JSON.stringify(dat);
  4287. if (json === '{}') { return ''; }
  4288. return '#json=' + json;
  4289. }
  4290. function getUrl() {
  4291. var loc = window.location.href.split('#')[0];
  4292. return loc + getHash();
  4293. }
  4294. function clear() {
  4295. dat = {};
  4296. removeHash();
  4297. }
  4298.  
  4299. self = {
  4300. initialize: initialize,
  4301. setValue: setValue,
  4302. getValue: getValue,
  4303. deleteValue: deleteValue,
  4304. updateHash: updateHash,
  4305. removeHash: removeHash,
  4306. getHash: getHash,
  4307. getUrl: getUrl,
  4308. clear: clear
  4309. };
  4310. return self;
  4311. })(conf, w);
  4312.  
  4313. window.WatchItLater.loader.favMylists = (function() {
  4314. var lastUpdate = 0;
  4315. var favMylistList = [];
  4316. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4317. var $ = w.$;
  4318. /**
  4319. * お気に入りマイリストの取得。 jQueryのあるページでしか使えない
  4320. * マイページを無理矢理パースしてるので突然使えなくなるかも
  4321. */
  4322. var self = {
  4323. load: function(callback) {
  4324. if (!w.jQuery) return; //
  4325.  
  4326. function request(page) {
  4327. url = baseUrl + '?page=' + page;
  4328. GM_xmlhttpRequest({
  4329. url: url,
  4330. onload: function(resp) {
  4331. var $result = $(resp.responseText).find('#favMylist');
  4332.  
  4333. if ($result.length >= 1) {
  4334. updateMaxPage($result);
  4335.  
  4336. if (page === 1) { favMylistList = []; }
  4337.  
  4338. $result.find('.outer').each(function() {
  4339. favMylistList.push(readBlock(this));
  4340. });
  4341. }
  4342.  
  4343. if (page < maxPage) {
  4344. setTimeout(function() {
  4345. page++;
  4346. request(page);
  4347. }, 500);
  4348. } else {
  4349. sort();
  4350. do_callback();
  4351. }
  4352. }
  4353. });
  4354. }
  4355. function readBlock(elm) {
  4356. var
  4357. $elm = $(elm),
  4358. $a = $elm.find('h5 a'), $desc = $elm.find('.mylistDescription'),
  4359. iconType = $elm.find('.folderIcon').attr('class').split(' ')[1],
  4360. id = ($a.attr('href').split('/').reverse())[0],
  4361. $postTime = $elm.find('.postTime span'),
  4362. postTime = $.trim($postTime.text()),
  4363. postTimeData = $postTime.data(),
  4364. $videoLink = $elm.find('.videoTitle a'),
  4365. videoTitle = $videoLink.text(),
  4366. videoHref = $videoLink.attr('href'),
  4367. videoId = videoHref ? (videoHref.split('/').reverse()[0]) : '';
  4368. return {
  4369. id: id,
  4370. name: $a.text(),
  4371. description: $desc.text(),
  4372. iconType: iconType,
  4373. lastVideo: {
  4374. title: videoTitle,
  4375. videoId: videoId,
  4376. postedAt: postTime,
  4377. postTimeData: postTimeData
  4378. }
  4379. };
  4380. }
  4381.  
  4382. function updateMaxPage($result) {
  4383. var $paging = $result.find('.pagerWrap:first .pager:first a');
  4384. maxPage = Math.min(Math.max($paging.length, 1), 3);
  4385. }
  4386. function sort() {
  4387. favMylistList.sort(function(a, b) {
  4388. return (a.lastVideo.postedAt < b.lastVideo.postedAt) ? 1 : -1;
  4389. });
  4390. }
  4391. function do_callback() {
  4392. if (typeof callback === 'function') { callback(favMylistList); }
  4393. }
  4394.  
  4395. var now = Date.now();
  4396. if (now - lastUpdate < 3 * 60 * 1000) {
  4397. do_callback();
  4398. return;
  4399. }
  4400. lastUpdate = now;
  4401.  
  4402. var
  4403. baseUrl = 'http://' + host + '/my/fav/mylist',
  4404. url = baseUrl,
  4405. maxPage = 1;
  4406.  
  4407. request(1);
  4408.  
  4409. }
  4410. };
  4411. return self;
  4412. })();
  4413.  
  4414.  
  4415. window.WatchItLater.loader.favTags = (function(w) {
  4416. var lastUpdate = 0;
  4417. var favTagList = [], favTagTextList = [];
  4418. var host = location.host.replace(/^([\w\d]+)\./, 'www.');
  4419. var $ = w.$;
  4420. var pt = function(){};
  4421.  
  4422. var load = function(callback) {
  4423. if (!w.jQuery) return; //
  4424. var now = Date.now();
  4425. if (now - lastUpdate < 60 * 1000) {
  4426. if (typeof callback === 'function') { callback(favTagList); }
  4427. return;
  4428. }
  4429. lastUpdate = now;
  4430. var api = 'http://' + host + '/api/favtag/list?t=' + now;
  4431. $.ajax({
  4432. url: api,
  4433. timeout: 30000,
  4434. complete: function(result) {
  4435. if (result.status !== 200) {
  4436. return;
  4437. }
  4438. try {
  4439. var json = JSON.parse(result.responseText), items = json.favtag_items;
  4440. for (var i = 0, len = items.length; i < len; i++) {
  4441. var text = items[i]['tag'];
  4442. favTagList.push({href: '/tag/' + encodeURIComponent(text), name: items[i]['tag']});
  4443. favTagTextList.push(text);
  4444. }
  4445. EventDispatcher.dispatch('onFavTagsLoad', favTagTextList.concat());
  4446. if (typeof callback === 'function') { callback(favTagList); }
  4447. } catch (e) {
  4448. console.log('tag parse error!', e);
  4449. }
  4450. }
  4451. });
  4452. };
  4453.  
  4454. pt.load = load;
  4455. return pt;
  4456. })(w);
  4457.  
  4458.  
  4459. /**
  4460. * 左下に出るポップアップメッセージ
  4461. *
  4462. */
  4463. var Popup = (function(){
  4464. function Popup() {}
  4465.  
  4466. Popup.show = function(text) {
  4467. console.log('%c' + text, 'background: cyan;');
  4468. if (w.WatchApp) {
  4469. text = text.replace(/[\n]/, '<br />');
  4470. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4471. // Firefoxではflashの上に半透明要素を重ねられないのでとりあえず黒で塗りつぶす
  4472. '<span style="background: black;">' + text + '</span>'
  4473. );
  4474. }
  4475. };
  4476.  
  4477. Popup.alert = function(text) {
  4478. console.log('%c' + text, 'background: yellow;');
  4479. if (w.WatchApp) {
  4480. text = text.replace(/[\n]/, '<br />');
  4481. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.onData(
  4482. '<span style="background: black; color: red;">' + text + '</span>'
  4483. );
  4484. } else {
  4485. w.alert(text);
  4486. }
  4487. };
  4488.  
  4489. Popup.hide = function() {
  4490. if (w.WatchApp) {
  4491. w.WatchApp.ns.init.PopupMarqueeInitializer.popupMarqueeViewController.stop();
  4492. }
  4493. };
  4494. return Popup;
  4495. })();
  4496.  
  4497.  
  4498. var KeyMatch = (function() {
  4499. var self;
  4500.  
  4501. function create(def) {
  4502. var ch = def.char[0].toUpperCase();
  4503. return {
  4504. prop: {
  4505. char: ch,
  4506. code: typeof def.code === 'number' ? def.code : ch.charCodeAt(0),
  4507. shift: !!def.shift,
  4508. ctrl: !!def.ctrl,
  4509. alt: !!def.alt,
  4510. enable: !!def.enable
  4511. },
  4512. test: function(event) {
  4513. if (
  4514. this.prop.enable === true &&
  4515. this.prop.shift === event.shiftKey &&
  4516. this.prop.ctrl === event.ctrlKey &&
  4517. this.prop.alt === event.altKey &&
  4518. this.prop.code === event.which
  4519. ) {
  4520. event.preventDefault();
  4521. return true;
  4522. }
  4523. return false;
  4524. },
  4525. json: function() {
  4526. return JSON.stringify(this.prop);
  4527. }
  4528. };
  4529. }
  4530.  
  4531. self = {
  4532. create: create
  4533. };
  4534. return self;
  4535. })();
  4536.  
  4537. var TouchEventDispatcher = (function(target) {
  4538. var
  4539. self,
  4540. touchStartEvent = null,
  4541. touchEndEvent = null,
  4542. events = {
  4543. onflick: []
  4544. };
  4545. function dispatchEvent(name) {
  4546. var e = events[name];
  4547. for (var i =0, len = e.length; i < len; i++) {
  4548. e[i].apply(null, Array.prototype.slice.call(arguments, 1));
  4549. }
  4550. }
  4551.  
  4552. target.addEventListener('touchstart', function(e) {
  4553. touchStartEvent = e;
  4554. }, false);
  4555. target.addEventListener('touchcancel', function(e) {
  4556. touchStartEvent = null;
  4557. }, false);
  4558. target.addEventListener('touchend', function(e) {
  4559. touchEndEvent = e;
  4560. if (touchStartEvent !== null) {
  4561. var
  4562. sx = touchStartEvent.changedTouches[0].pageX, sy = touchStartEvent.changedTouches[0].pageY,
  4563. ex = touchEndEvent.changedTouches[0].pageX, ey = touchEndEvent.changedTouches[0].pageY,
  4564. dx = (sx - ex), dy = (sy - ey), len = Math.sqrt(dx * dx + dy * dy), s;
  4565. if (len > 150) {
  4566. s = dy / len;
  4567. var a = Math.abs(s), ss = Math.round(s);
  4568. if (a <= 0.3 || a >= 0.7) {
  4569. var d;
  4570. if (ss < 0) { d = 'down'; } else if (ss > 0) { d = 'up'; }
  4571. else if (dx < 0) { d = 'right';} else { d = 'left'; }
  4572. dispatchEvent('onflick', {
  4573. direction: d,
  4574. distance: len,
  4575. x: dx, y: dy,
  4576. startEvent: touchStartEvent,
  4577. endEvent: touchEndEvent
  4578. });
  4579. }
  4580. }
  4581. }
  4582. touchStartEvent = touchEndEvent = null;
  4583. }, false);
  4584.  
  4585. function onflick(callback) {
  4586. events.onflick.push(callback);
  4587. }
  4588.  
  4589. self = {
  4590. onflick: onflick
  4591. };
  4592. return self;
  4593. })(w.document);
  4594.  
  4595.  
  4596.  
  4597. /**
  4598. * リンクのマウスオーバーに仕込む処理
  4599. * ここの表示は再考の余地あり
  4600. */
  4601. var AnchorHoverPopup = (function(w, conf,EventDispatcher) {
  4602. var mylistPanel = Mylist.getPanel(''), hoverMenuDelay = conf.hoverMenuDelay * 1000;
  4603. mylistPanel.className += ' popup';
  4604. mylistPanel.style.display = 'none';
  4605. document.body.appendChild(mylistPanel);
  4606.  
  4607. EventDispatcher.addEventListener('on.config.hoverMenuDelay', function(delay) {
  4608. delay = parseFloat(delay, 10);
  4609. if (isNaN(delay)) { return; }
  4610. hoverMenuDelay = Math.abs(delay * 1000);
  4611. });
  4612.  
  4613. function showPanel(watchId, baseX, baseY, w_touch) {
  4614.  
  4615. var cn = mylistPanel.className.toString();
  4616. if (w_touch === true) {
  4617. cn = cn.replace(' w_touch', '') + ' w_touch';
  4618. } else {
  4619. if (cn.indexOf('w_touch') >= 0 && mylistPanel.style.display !== 'none') {
  4620. // フリック操作で表示したパネルが出ている間はそちらを優先し、なにもしない
  4621. return;
  4622. }
  4623. cn = cn.replace(' w_touch', '');
  4624.  
  4625. }
  4626. VideoTags.hidePopup();
  4627. if (mylistPanel.className !== cn) mylistPanel.className = cn;
  4628.  
  4629. mylistPanel.style.display = '';
  4630. mylistPanel.watchId(watchId);
  4631. mylistPanel.style.right = null;
  4632. mylistPanel.style.left = (baseX) + 'px';
  4633. mylistPanel.style.top = Math.max(baseY - mylistPanel.offsetHeight, 0, document.body.scrollTop, document.documentElement.scrollTop) + 'px';
  4634.  
  4635. if (mylistPanel.offsetLeft + mylistPanel.offsetWidth > document.body.clientWidth) {
  4636. mylistPanel.style.left = null;
  4637. mylistPanel.style.right = 0;
  4638. }
  4639.  
  4640. }
  4641.  
  4642.  
  4643. var videoReg = /(\?cc_video_id=|\?cc_id=|watch\/)([a-z0-9]+)/;
  4644. var excludeReg = /(news|live|seiga)\..*?nicovideo\.jp/;
  4645.  
  4646. function each(w, watchId) {
  4647.  
  4648. this.w_eventInit = false;
  4649.  
  4650. this.addEventListener('mouseover', function() {
  4651. var mx = 0, my = 0, self = this;
  4652. if (this.href) this.setAttribute('href', this.href.split('?')[0]);
  4653.  
  4654. self.w_mouse_in = true;
  4655. self.w_mouse_timer = null;
  4656. self.w_mouse_timer = setTimeout(function() {
  4657. self.w_mouse_timer = null;
  4658. if (!self.w_mouse_in) {
  4659. return;
  4660. }
  4661. var o;
  4662. if (w.jQuery) {
  4663. var $e = w.jQuery(self);
  4664. var t = $e.text();
  4665. o = t !== "" ? $e.offset() : $e.find('*').offset();
  4666. showPanel(watchId, o.left, o.top);
  4667. } else
  4668. if (self.getBoundingClientRect) {
  4669. o = (self.firstChild && self.firstChild.tagName === 'IMG') ? self.firstChild.getBoundingClientRect() : self.getBoundingClientRect();
  4670. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4671. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4672. showPanel(watchId, left + o.left, top + o.top);
  4673. } else {
  4674. showPanel(watchId, mx + 8, my + 8);
  4675. }
  4676. EventDispatcher.dispatch('mylistPanelOpen', mylistPanel, self, watchId);
  4677. }, hoverMenuDelay);
  4678.  
  4679. if (!this.w_eventInit) {
  4680. this.addEventListener('mouseout', function() {
  4681. self.w_mouse_in = false;
  4682. if (self.w_mouse_timer) {
  4683. clearTimeout(self.w_mouse_timer);
  4684. self.w_mouse_timer = null;
  4685. }
  4686. }, false);
  4687. if (!w.jQuery) {
  4688. this.addEventListener('mousemove', function(ev) {
  4689. mx = ev.pageX;
  4690. my = ev.pageY;
  4691. }, false);
  4692. }
  4693. this.w_eventInit = true;
  4694. }
  4695. }, false);
  4696. this.added = 1;
  4697. }
  4698.  
  4699. function bind(force, target) {
  4700. if (!conf.enableHoverPopup) { return; }
  4701.  
  4702. var a = Array.prototype.slice.apply(document.links), vreg = videoReg, ereg = excludeReg;
  4703. for (var i = 0, len = a.length; i < len; i++) {
  4704. var e = a[i];
  4705. var m, href= e.href;
  4706. if (
  4707. href &&
  4708. !e.added &&
  4709. (m = vreg.exec(href)) !== null &&
  4710. !ereg.test(href) &&
  4711. // e.className !== "itemEcoLink" &&
  4712. e.className !== "playlistSaveLink"
  4713. ) {
  4714. each.apply(e, [w, m[2]]);
  4715. }
  4716. }
  4717. }
  4718. function bindTouch() {
  4719. TouchEventDispatcher.onflick(function(e) {
  4720. var se = e.startEvent;
  4721. if (e.direction === 'right' && (se.target.tagName === 'A' || se.target.parentElement.tagName === 'A')) {
  4722. var
  4723. a = (se.target.tagName === 'A') ? e.startEvent.target : e.startEvent.target.parentElement,
  4724. href = a.href, vreg = videoReg, ereg = excludeReg, m, watchId;
  4725. if (
  4726. href &&
  4727. (m = vreg.exec(href)) !== null &&
  4728. !ereg.test(href) &&
  4729. // e.className !== "itemEcoLink" &&
  4730. true
  4731. ) {
  4732. watchId = m[2];
  4733. var o;
  4734. if (w.jQuery) {
  4735. var $a = w.jQuery(a);
  4736. var t = $a.text();
  4737. o = t !== "" ? $a.offset() : $a.find('*').offset();
  4738. showPanel(watchId, o.left, o.top, true);
  4739. } else {
  4740. o = (a.firstChild && a.firstChild.tagName === 'IMG') ? a.firstChild.getBoundingClientRect() : a.getBoundingClientRect();
  4741. var top = Math.max(w.document.documentElement.scrollTop, w.document.body.scrollTop),
  4742. left = Math.max(w.document.documentElement.scrollLeft, w.document.body.scrollLeft);
  4743. showPanel(watchId, left + o.left, top + o.top, true);
  4744. }
  4745. }
  4746. }
  4747. });
  4748. }
  4749.  
  4750. var lastUpdate = 0, linksCount = document.links.length,
  4751. bindLoop = function(nextTime) {
  4752. var now = Date.now();
  4753. var updateInterval = w.document.hasFocus() ? 3000 : 15000;
  4754. if (now - lastUpdate < updateInterval) {
  4755. var len = document.links.length;
  4756. if (linksCount === len) {
  4757. return;
  4758. }
  4759. linksCount = len;
  4760. }
  4761. bind();
  4762. lastUpdate = now;
  4763. };
  4764.  
  4765. var self = {
  4766. hidePopup: function() {
  4767. VideoTags.hidePopup();
  4768. mylistPanel.hide();
  4769. return this;
  4770. },
  4771. updateNow: function() {
  4772. bind();
  4773. lastUpdate -= 1500;
  4774. return this;
  4775. }
  4776. };
  4777.  
  4778.  
  4779. if (location.host === "ext.nicovideo.jp") {
  4780. bind();
  4781. } else {
  4782. var thumbnailReg = /\.smilevideo\.jp\/smile\?i=(\d+)/;
  4783. if (location.host === 'ch.nicovideo.jp' && w.jQuery) {
  4784. w.jQuery('.lazyimage, .thumb_video.thumb_114.wide img, .itemset li .image a .item').each(function() {
  4785. var $e = w.jQuery(this).text(' ');
  4786. var src = $e.attr('data-original') || $e.attr('src');
  4787. if (typeof src === 'string' && thumbnailReg.test(src)) {
  4788. each.apply(this, [w, 'so' + RegExp.$1]);
  4789. }
  4790. });
  4791. }
  4792. bindTouch();
  4793. bind();
  4794. setInterval(bindLoop, 500);
  4795. }
  4796. return self;
  4797. })(w, conf, EventDispatcher);
  4798. window.WatchItLater.popup = AnchorHoverPopup;
  4799.  
  4800.  
  4801. //===================================================
  4802. //===================================================
  4803. //===================================================
  4804.  
  4805.  
  4806. /**
  4807. * マイリスト登録のポップアップウィンドウを乗っ取る処理
  4808. *
  4809. * iframeの子ウィンドウ内に開かれた時に実行される
  4810. * クロスドメインを越えられない環境ではこっちを使うしかない
  4811. */
  4812. (function(){ // mylist window
  4813. if (w.location.href.indexOf('/mylist_add/') < 0 || w.name === 'nicomylistadd') return;
  4814.  
  4815. var $ = w.jQuery;
  4816. $('body,table,img,td').css({border:0, margin:0, padding:0, background: "transparent", overflow: 'hidden'});
  4817. $('#main_frm').css({background: '#fff', paddig: 0, borderRadius: 0}).addClass('mylistPopupPanel');
  4818.  
  4819. if ($('#edit_description').length < 1) {
  4820. $('#main_frm .font12:first').css({position: 'absolute', margin: 0, top: 0, left: 0, padding: 0, color: 'red', fontSize: '8pt'});
  4821. // ログインしてないぽい
  4822. return;
  4823. }
  4824.  
  4825. $('#box_success').css({position: 'absolute', top: 0, left: 0});
  4826. $('#box_success h1').css({color: 'black', fontSize: '8pt', padding: 0});
  4827. $('td').css({padding: 0});
  4828.  
  4829. // 「マイリストに登録しました」
  4830. // $('.mb8p4:last').show();
  4831. // $('.mb8p4:last h1').css({fontSize : "8pt"});
  4832.  
  4833. $('table:first').css({width: '200px'});
  4834. $('table:first td.main_frm_bg').css({height: '20px'});
  4835. $('table:first table:first').hide();
  4836.  
  4837. $('select')
  4838. .css({width: '64px',position: 'absolute', top:0, left:0, margin: 0})
  4839. .addClass('mylistSelect');
  4840. $('select')[0].selectedIndex = $('select')[0].options.length - 1;
  4841. $('#select_group option:last')[0].innerHTML = 'とりマイ';
  4842.  
  4843. // var submit = document.createElement("input");
  4844. // submit.className = 'mylistAdd';
  4845. // submit.type = "submit";
  4846. // submit.value = "マ";
  4847. // $(submit).css({position: 'absolute', top: 0, left: '64px'});
  4848. // $('select')[0].parentNode.appendChild(submit);
  4849.  
  4850.  
  4851. $('#edit_description').hide();
  4852.  
  4853. w.document.documentElement.scrollTop = 0;
  4854. w.document.documentElement.scrollLeft = 0;
  4855.  
  4856.  
  4857. $($.browser.safari ? 'body' : 'html').scrollTop(0);
  4858.  
  4859. w.window.close = function()
  4860. {
  4861. return;
  4862. };
  4863. w.window.alert = function()
  4864. {
  4865. w.document.write('<span style="position:absolute;top:0;left:0;font-size:8pt;color:red;">' +
  4866. arguments[0] + '</span>');
  4867. };
  4868. })();
  4869.  
  4870.  
  4871. //===================================================
  4872. //===================================================
  4873. //===================================================
  4874.  
  4875. window.WatchItLater.loader.videoArrayAPILoader = (function() {
  4876. var sessionId = 0;
  4877. var deferredList = {};
  4878. var cacheData = {};
  4879. var currentRequestIds = '', currentPromise;
  4880. var loaderFrame, loaderWindow;
  4881. var BASE_URL = 'http://i.nicovideo.jp/v3/video.array?v=';
  4882.  
  4883. //WatchItLater.loader.videoArrayAPILoader.load('sm9').then(function(info) { console.log(info); });
  4884.  
  4885. var load = function(watchId) {
  4886. var ids = [], result = {}, def = new $.Deferred(), timeoutTimer = null;
  4887.  
  4888. initialize();
  4889.  
  4890. $(typeof watchId !== 'string' ? watchId : [watchId]).each(function(i, id) {
  4891. if (cacheData[id]) {
  4892. result[id] = cacheData[id];
  4893. } else {
  4894. ids.push(id);
  4895. }
  4896. });
  4897. ids = window._.unique(ids);
  4898. if (ids.length < 1) {
  4899. window.setTimeout(function() { def.resolve(result); }, 0);
  4900. return def;
  4901. }
  4902. var _ids = JSON.stringify(ids);
  4903. var onSuccess = function(infoList) {
  4904. $(ids).each(function(i, id) {
  4905. result[id] = result[id] || infoList[id] || cacheData[id];
  4906. });
  4907.  
  4908. window.clearTimeout(timeoutTimer);
  4909. currentRequestIds = ''; currentPromise = null;
  4910. def.resolve(result);
  4911. def = null;
  4912. };
  4913. var onFail = function() {
  4914. window.clearTimeout(timeoutTimer);
  4915. console.log('%cVideoArrayAPILoader.onFail', 'color: red;');
  4916. currentRequestIds = ''; currentPromise = null;
  4917. if (def) {
  4918. def.reject();
  4919. }
  4920. def = null;
  4921. };
  4922.  
  4923. sessionId++;
  4924.  
  4925. timeoutTimer = window.setTimeout(onFail, 30 * 1000);
  4926.  
  4927. if (_ids === currentRequestIds) {
  4928. currentPromise.then(onSuccess, onFail);
  4929. return def;
  4930. }
  4931.  
  4932. currentRequestIds = _ids;
  4933. sendRequest(ids, sessionId).then(onSuccess, onFail);
  4934.  
  4935. return def.promise();
  4936. };
  4937.  
  4938. var sendRequest = function(ids, sessionId) {
  4939. var def = new $.Deferred();
  4940. currentPromise = def;
  4941. deferredList[sessionId] = def;
  4942. loaderWindow.location.replace(BASE_URL + ids.join(',') + '#' + sessionId);
  4943. return def.promise();
  4944. };
  4945.  
  4946. var parseVideoArray = function(xml) {
  4947. var $xml = $(xml), infoList = {};
  4948. $xml.find('video_info').each(function() {
  4949. var info = new parseVideoInfo($(this));
  4950. infoList[info.id] = cacheData[info.id] = cacheData[info.default_thread] = info;
  4951. if (info.threadIds && info.threadIds.length > 1) {
  4952. $(info.threadIds).each(function(i, threadId) {
  4953. infoList[threadId] = cacheData[threadId] = info;
  4954. });
  4955. }
  4956. });
  4957. return infoList;
  4958. };
  4959.  
  4960. var parseVideoInfo = function($xml) {
  4961. var info = {threadIds: []};
  4962. var elements = [
  4963. 'id', 'user_id', 'title', 'description', 'length_in_seconds',
  4964. 'thumbnail_url', 'first_retrieve', 'default_thread',
  4965. 'view_counter', 'mylist_counter'];
  4966.  
  4967. $(elements).each(function(i, elm) {
  4968. info[elm] = $xml.find(elm + ':first').text();
  4969. });
  4970.  
  4971. info['num_res'] = $xml.find('thread:first num_res').text();
  4972.  
  4973. var duration = parseInt(info['length_in_seconds'], 10);
  4974. info['length'] = parseInt(duration / 60, 10) + ':' + (100 + (duration % 60)).toString().substr(1);
  4975.  
  4976. info['first_retrieve_t'] = info['first_retrieve'];
  4977. info['first_retrieve'] =
  4978. window.WatchApp.ns.util.DateFormat.strftime(
  4979. '%Y-%m-%d %H:%M:%S',
  4980. new Date(info['first_retrieve'])
  4981. );
  4982.  
  4983. $xml.find('thread id, channel_thread id').each(function() {
  4984. info.threadIds.push(this.innerHTML);
  4985. });
  4986. return info;
  4987. };
  4988.  
  4989.  
  4990. var initialize = function() {
  4991. initialize = window._.noop;
  4992.  
  4993. loaderFrame = document.createElement('iframe');
  4994. loaderFrame.name = 'watchItLaterAPILoader';
  4995. loaderFrame.className = 'watchItLaterAPILoaderFrame';
  4996. document.body.appendChild(loaderFrame);
  4997.  
  4998. loaderWindow = loaderFrame.contentWindow;
  4999.  
  5000. EventDispatcher.addEventListener('onMessage', function(data, type) {
  5001. if (type !== 'VideoArrayAPILoader') { return; }
  5002. try {
  5003. var session = data.session, xml = data.xml;
  5004. //console.log('VideoArrayAPILoader.onMessage', data.session, data.xml);
  5005. if (deferredList[session]) {
  5006. deferredList[session].resolve(parseVideoArray(xml));
  5007. delete deferredList[session];
  5008. currentPromise = null;
  5009. }
  5010. } catch (e) {
  5011. console.log('message parse error', e);
  5012. if (deferredList[session]) {
  5013. deferredList[session].reject();
  5014. delete deferredList[session];
  5015. currentPromise = null;
  5016. }
  5017. }
  5018. });
  5019.  
  5020. };
  5021.  
  5022. // sample URL
  5023. // http://i.nicovideo.jp/v3/video.array?v=sm9,sm3393520,sm5909863,so23023492,1394173596
  5024. //initialize();
  5025. return {
  5026. load: load
  5027. };
  5028. })();
  5029.  
  5030.  
  5031.  
  5032.  
  5033. //===================================================
  5034. //===================================================
  5035. //===================================================
  5036.  
  5037. var _watchController = function(w) {
  5038. var WatchApp = w.WatchApp, _false = function() { return false; };
  5039. var
  5040. watch = (WatchApp && WatchApp.ns.init) || {},
  5041. watchInfoModel = (watch.CommonModelInitializer && WatchApp.ns.model.WatchInfoModel.getInstance()) || {},
  5042. nicoPlayer = (watch.PlayerInitializer && watch.PlayerInitializer.nicoPlayerConnector) || {},
  5043. videoExplorerController = watch.VideoExplorerInitializer.videoExplorerController,
  5044. videoExplorer = videoExplorerController.getVideoExplorer(),
  5045. videoExplorerContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  5046. $ = w.$, WatchJsApi = w.WatchJsApi;
  5047. return {
  5048. isZeroWatch: function() {
  5049. return (window.PlayerApp) ? true : false;
  5050. },
  5051. getWatchInfoModel: function() {
  5052. return watchInfoModel;
  5053. },
  5054. nicoSearch: function(word, search_type) {
  5055. if (!search_type) {
  5056. try {
  5057. var type = videoExplorerContentType.SEARCH;
  5058. search_type = videoExplorer.getContentList().getContent(type).getSearchType();
  5059. } catch(e) {
  5060. search_type = search_type || 'tag';
  5061. }
  5062. }
  5063. videoExplorerController.searchVideo(word, search_type);
  5064. AnchorHoverPopup.hidePopup();
  5065. },
  5066. showMylist: function(mylistId) {
  5067. videoExplorerController.showMylist(mylistId.toString());
  5068. },
  5069. clearDeflistCache: function() {
  5070. watch.VideoExplorerInitializer.deflistVideoAPILoader._cache.clear();
  5071. },
  5072. clearMylistCache: function(id) {
  5073. if (id) {
  5074. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.deleteElement(
  5075. 'http://riapi.nicovideo.jp/api/watch/mylistvideo?id=' + id.toString()
  5076. );
  5077. } else {
  5078. watch.VideoExplorerInitializer.mylistVideoAPILoader._cache.clear();
  5079. }
  5080. },
  5081. showDeflist: function() {
  5082. videoExplorerController.showDeflist();
  5083. },
  5084. changeScreenMode: function(mode) {
  5085. WatchJsApi.player.changePlayerScreenMode(mode);
  5086. setTimeout(function(){$(window).resize();}, 3000);
  5087. },
  5088. isFixedHeader: function() {
  5089. return !$('body').hasClass('nofix');
  5090. },
  5091. // ヘッダー追従かどうかを考慮したscrollTop
  5092. scrollTop: function(top, dur) {
  5093. var header = (this.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  5094.  
  5095. if (top !== undefined) {
  5096. return $(window).scrollTop(top - header, dur);
  5097. } else {
  5098. return $(window).scrollTop() + header;
  5099. }
  5100. },
  5101. scrollToVideoPlayer: function(force) {
  5102. // 縦解像度がタグ+プレイヤーより大きいならタグの開始位置、そうでないならプレイヤーの位置にスクロール
  5103. // ただし、該当部分が画面内に納まっている場合は、勝手にスクロールするとかえってうざいのでなにもしない
  5104. var $body = $('body'), isContentFix = $body.hasClass('content-fix');
  5105. $body.addClass('w_noHover').removeClass('content-fix');
  5106. var h = $('#playerContainer').outerHeight() + $('#videoTagContainer').outerHeight();
  5107. var top = $(window).height() >= h ? '#videoTagContainer, #playerContainer' : '#playerContainer';
  5108.  
  5109.  
  5110. if (force) {
  5111. // 要素が画面内に納まっている場合でも、その要素の位置までスクロール
  5112. WatchApp.ns.util.WindowUtil.scrollFit(top, 600);
  5113. } else {
  5114. // 要素が画面内に収まっている場合はスクロールしない
  5115. WatchApp.ns.util.WindowUtil.scrollFitMinimum(top, 600);
  5116. }
  5117. $(window).scrollLeft(0);
  5118. $body.toggleClass('content-fix', isContentFix);
  5119. setTimeout(function() {
  5120. $body.removeClass('w_noHover');
  5121. }, 800);
  5122. },
  5123. play: function() {
  5124. nicoPlayer.playVideo();
  5125. },
  5126. pause: function() {
  5127. nicoPlayer.stopVideo();
  5128. },
  5129. togglePlay: function() {
  5130. var status = $("#external_nicoplayer")[0].ext_getStatus();
  5131. if (status === 'playing') {
  5132. this.pause();
  5133. } else {
  5134. this.play();
  5135. }
  5136. },
  5137. isPlaying: function() {
  5138. var status = $("#external_nicoplayer")[0].ext_getStatus();
  5139. return status === 'playing';
  5140. },
  5141. nextVideo: function() {
  5142. return nicoPlayer.playNextVideo();
  5143. },
  5144. prevVideo: function() {
  5145. return nicoPlayer.playPreviousVideo();
  5146. },
  5147. vpos: function(v) {
  5148. if (typeof v === 'number') {
  5149. return nicoPlayer.seekVideo(v);
  5150. } else {
  5151. return nicoPlayer.getVpos();
  5152. }
  5153. },
  5154. openSearch: function() {
  5155. WatchApp.ns.init.VideoExplorerInitializer.expandButtonView.open();
  5156. // videoExplorer.openByCurrentCondition();
  5157. // videoExplorer.changeState(true);
  5158. },
  5159. closeSearch: function() {
  5160. videoExplorer.changeState(false);
  5161. videoExplorer.close();
  5162. },
  5163. openVideoOwnersVideo: function() {
  5164. if (this.isChannelVideo()) {
  5165. this.openChannelOwnersVideo();
  5166. } else {
  5167. this.openUpNushiVideo();
  5168. }
  5169. },
  5170. openUpNushiVideo: function() {
  5171. videoExplorerController.showOwnerVideo();
  5172. },
  5173. openChannelOwnersVideo: function() {
  5174. videoExplorerController.showMylist('-3');
  5175. },
  5176. openUserVideo: function(userId, userNick) {
  5177. videoExplorerController.showOtherUserVideos(userId, userNick);
  5178. },
  5179. openRecommend: function() {
  5180. var
  5181. type = videoExplorerContentType.RELATED_VIDEO,
  5182. open = function() {
  5183. var rel = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer._menu.getItemByContentType(type);
  5184. rel.select();
  5185. };
  5186. if (videoExplorer.isOpen()) {
  5187. open();
  5188. } else {
  5189. this.openSearch();
  5190. setTimeout(open, 500);
  5191. }
  5192. },
  5193. getVideoExplorerCurrentItems: function(format) {
  5194. var ac = videoExplorer._contentList.getActiveContent();
  5195. if (!ac || !ac.getItems) return [];
  5196. var items = ac.getItems();
  5197.  
  5198. if (!format) {
  5199. return items;
  5200. } else
  5201. if (format === 'playlist') {
  5202. var result = [];
  5203. for (var i = items.length - 1; i >= 0; i--) {
  5204. result.unshift(
  5205. videoExplorerController._item2playlistItem(items[i])
  5206. );
  5207. }
  5208. return result;
  5209. }
  5210. },
  5211. getWatchId: function() {// スレッドIDだったりsmXXXXだったり
  5212. return watchInfoModel.v;
  5213. },
  5214. getVideoId: function() {// smXXXXXX, soXXXXX など
  5215. return watchInfoModel.id;
  5216. },
  5217. getMyNick: function() {
  5218. return watch.CommonModelInitializer.viewerInfoModel.nickname;
  5219. },
  5220. getMyUserId: function() {
  5221. return watch.CommonModelInitializer.viewerInfoModel.userId;
  5222. },
  5223. getPlaylistItems: function() {
  5224. return watch.PlaylistInitializer.playlist.items || watch.PlaylistInitializer.playlist.currentItems;
  5225. },
  5226. setPlaylistItems: function(items, currentItem) {
  5227. var playlist = watch.PlaylistInitializer.playlist;
  5228. // var isAutoPlay = playlist.isContinuous();//.isAutoPlay();
  5229. playlist.reset(
  5230. items,
  5231. 'WatchItLater',
  5232. playlist.type,
  5233. playlist.option
  5234. );
  5235. if (currentItem) { playlist.playingItem = currentItem; }
  5236. else { playlist.playingItem = items[0]; }
  5237. // if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  5238. // playlist.disableContinuous();
  5239. // }
  5240. },
  5241. shufflePlaylist: function(target) {
  5242. var x = this.getPlaylistItems(), items = [], i, currentIndex = -1, currentItem = null;
  5243. if (target === 'right') {
  5244. for (i = 0; i < x.length;) {
  5245. if (x[0]._isPlaying) {
  5246. currentIndex = i;
  5247. currentItem = x.shift();
  5248. items.push(currentItem);
  5249. break;
  5250. } else {
  5251. items.push(x.shift());
  5252. }
  5253. }
  5254. }
  5255.  
  5256. x = x.map(function(a){return {weight:Math.random(), value:a};})
  5257. .sort(function(a, b){return a.weight - b.weight;})
  5258. .map(function(a){return a.value;});
  5259. for (i = 0; i < x.length; i++) {
  5260. if (x[i]._isPlaying) {
  5261. items.unshift(x[i]);
  5262. } else {
  5263. items.push(x[i]);
  5264. }
  5265. }
  5266. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView;
  5267. var left = pm.getLeftSideIndex();
  5268. this.setPlaylistItems(items, currentItem);
  5269. pv.scroll(left);
  5270. },
  5271. clearPlaylist: function(target) {
  5272. var x = this.getPlaylistItems(), items = [], i, currentItem = null;
  5273. if (target === 'left') {
  5274. for (i = x.length - 1; i >= 0; i--) {
  5275. items.unshift(x[i]);
  5276. if (x[i]._isPlaying) {
  5277. currentItem = x[i];
  5278. break;
  5279. }
  5280. }
  5281. } else
  5282. if (target === 'right') {
  5283. for (i = 0; i < x.length ; i++) {
  5284. items.push(x[i]);
  5285. if (x[i]._isPlaying) {
  5286. currentItem = x[i];
  5287. break;
  5288. }
  5289. }
  5290. }
  5291. else {
  5292. for (i = 0; i < x.length; i++) {
  5293. if (x[i]._isPlaying) {
  5294. currentItem = x[i];
  5295. items.unshift(x[i]);
  5296. }
  5297. }
  5298. }
  5299. this.setPlaylistItems(items, currentItem);
  5300. },
  5301. appendSearchResultToPlaylist: function(mode) {
  5302. var
  5303. items = this.getPlaylistItems(),
  5304. searchItems = this.getVideoExplorerCurrentItems('playlist'),
  5305. uniq = {}, i, playingIndex = 0, c, len, currentItem = null;
  5306. if (!searchItems || searchItems.length < 1) {
  5307. return;
  5308. }
  5309. for (i = 0, len = items.length; i < len; i++) {
  5310. uniq[items[i].id] = true;
  5311. if (items[i]._isPlaying) { playingIndex = i; currentItem = items[i]; }
  5312. }
  5313. if (mode === 'next') {
  5314. for (i = searchItems.length - 1; i >= 0; i--) {
  5315. c = searchItems[i];
  5316. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === undefined && items.splice(playingIndex + 1, 0, c);
  5317. }
  5318. } else {
  5319. for (i = 0, len = searchItems.length; i < len; i++) {
  5320. c = searchItems[i];
  5321. ("undefined" === typeof c.type || "video" === c.type) && uniq[c.id] === undefined && items.push(c);
  5322. }
  5323. }
  5324. this.setPlaylistItems(items, currentItem);
  5325. },
  5326. insertVideoToPlaylist: function(id) {
  5327. WatchItLater.VideoInfoLoader.load(id).then(function(info) {
  5328. var item = new WatchApp.ns.model.playlist.PlaylistItem(info);
  5329. watch.PlaylistInitializer.playlist.insertNextPlayingItem(item);
  5330. }, function(err) {
  5331. Popup.alert(err.message);
  5332. });
  5333. },
  5334. addDefMylist: function(description) {
  5335. var watchId = watchInfoModel.id;
  5336. setTimeout(function() {
  5337. Mylist.addDefListItem(watchId, function(status, result, replaced) {
  5338. Mylist.reloadDefList();
  5339. if (status !== "ok") {
  5340. Popup.alert('とりあえずマイリストの登録に失敗: ' + result.error.description);
  5341. } else {
  5342. var torimai = '<a href="/my/mylist">とりあえずマイリスト</a>';
  5343. Popup.show(
  5344. torimai +
  5345. (replaced ? 'の先頭に移動しました' : 'に登録しました')
  5346. );
  5347. }
  5348. }, description);
  5349. }, 0);
  5350. },
  5351. commentVisibility: function(v) {
  5352. if (v === 'toggle') {
  5353. return this.commentVisibility(!this.commentVisibility());
  5354. } else
  5355. if (typeof v === 'boolean') {
  5356. nicoPlayer.playerConfig.set({commentVisible: v});
  5357. return this;
  5358. } else {
  5359. var pc = nicoPlayer.playerConfig.get();
  5360. return pc.commentVisible;
  5361. }
  5362. },
  5363. deepenedComment: function(v) {
  5364. if (v === 'toggle') {
  5365. return this.deepenedComment(!this.deepenedComment());
  5366. } else
  5367. if (typeof v === 'boolean') {
  5368. nicoPlayer.playerConfig.set({deepenedComment: v});
  5369. return this;
  5370. } else {
  5371. var pc = nicoPlayer.playerConfig.get();
  5372. return pc.deepenedComment;
  5373. }
  5374. },
  5375. allowStageVideo: function(v) {
  5376. if (v === 'toggle') {
  5377. return this.allowStageVideo(!this.allowStageVideo());
  5378. } else
  5379. if (typeof v === 'boolean') {
  5380. nicoPlayer.playerConfig.set({allowStageVideo: v});
  5381. return this;
  5382. } else {
  5383. var pc = nicoPlayer.playerConfig.get();
  5384. return pc.allowStageVideo;
  5385. }
  5386. },
  5387. isStageVideoSupported: function() {
  5388. try {
  5389. var exp = w.document.getElementById('external_nicoplayer');
  5390. return exp.isStageVideoSupported();
  5391. } catch(e) {
  5392. console.log(e);
  5393. return false;
  5394. }
  5395. },
  5396. isStageVideoAvailable: function() {
  5397. try {
  5398. var exp = w.document.getElementById('external_nicoplayer');
  5399. return exp.isStageVideoAvailable();
  5400. } catch(e) {
  5401. console.log(e);
  5402. return false;
  5403. }
  5404. },
  5405. toggleStageVideo: function() {
  5406. if (!this.isStageVideoSupported()) {
  5407. Popup.alert('ハードウェアアクセラレーションを使用できない状態か、未対応の環境です');
  5408. return;
  5409. }
  5410. var isAllowed = this.allowStageVideo(), exp = $('#external_nicoplayer')[0];
  5411. exp.setIsForceUsingStageVideo(!isAllowed && conf.forceEnableStageVideo);
  5412. this.allowStageVideo(!isAllowed);
  5413. setTimeout($.proxy(function() {
  5414. isAllowed = this.allowStageVideo();
  5415. var isAvailable = this.isStageVideoAvailable();
  5416. Popup.show('ハードウェアアクセラレーション:' +
  5417. (isAllowed ? '設定ON' : '設定OFF') + ' / ' +
  5418. (isAvailable ? '使用可能' : '使用不能')
  5419. );
  5420. }, this), 100);
  5421. },
  5422. mute: function(v) {
  5423. var exp = w.document.getElementById('external_nicoplayer');
  5424.  
  5425. if (v === 'toggle') {
  5426. return this.mute(!this.mute());
  5427. } else
  5428. if (typeof v === 'boolean') {
  5429. exp.ext_setMute(v);
  5430. return this;
  5431. } else {
  5432. return exp.ext_isMute();
  5433. }
  5434. },
  5435. volume: function(v) {
  5436. var exp = w.document.getElementById('external_nicoplayer');
  5437. if (typeof v === 'string' && v.match(/^[+-]\d+$/)) {
  5438. this.volume(this.volume() + v * 1);
  5439. } else
  5440. if (typeof v === 'number' || (typeof v === 'string' && v.match(/^\d+$/))) {
  5441. exp.ext_setVolume(Math.max(0, Math.min(v * 1, 100)));
  5442. }
  5443. return exp.ext_getVolume();
  5444. },
  5445. isWide: function() {
  5446. var exp = w.document.getElementById('external_nicoplayer');
  5447. return exp.ext_isWide();
  5448. },
  5449. isPlaylistActive: function() {
  5450. return watch.PlaylistInitializer.playlist.getPlaybackMode() !== 'normal';
  5451. },
  5452. isPlaylistRandom: function() {
  5453. return watch.PlaylistInitializer.playlist.isShuffle();
  5454. },
  5455. isPlaylistContinuous: function() {
  5456. return watch.PlaylistInitializer.playlist.getPlaybackMode() === 'continuous';
  5457. },
  5458. getOwnerIcon: function() {
  5459. try {
  5460. return this.isChannelVideo() ? watchInfoModel.channelInfo.iconUrl : watchInfoModel.uploaderInfo.iconUrl;
  5461. } catch (e) {
  5462. return 'http://uni.res.nimg.jp/img/user/thumb/blank_s.jpg';
  5463. }
  5464. },
  5465. getOwnerName: function() {
  5466. try {
  5467. return this.isChannelVideo() ? watchInfoModel.channelInfo.name : watchInfoModel.uploaderInfo.nickname;
  5468. } catch (e) {
  5469. return '';
  5470. }
  5471. },
  5472. getOwnerId: function() {
  5473. try {
  5474. return this.isChannelVideo() ? watchInfoModel.channelInfo.id : watchInfoModel.uploaderInfo.id;
  5475. } catch (e) {
  5476. return '0';
  5477. }
  5478. },
  5479. getOwnerType: function() {
  5480. try {
  5481. return this.isChannelVideo() ? 'channel' : 'user';
  5482. } catch (e) {
  5483. return 'channel';
  5484. }
  5485. },
  5486. getOwnerPage: function() {
  5487. try {
  5488. if (this.isChannelVideo()) {
  5489. return $('#ch_prof').find('.symbol').attr('href');
  5490. } else {
  5491. return '/user/' + this.getOwnerId();
  5492. }
  5493. } catch (e) {
  5494. return '';
  5495. }
  5496. },
  5497. isFavoriteOwner: function() {
  5498. try {
  5499. return this.isChannelVideo() ?
  5500. !!(watchInfoModel.channelInfo && watchInfoModel.channelInfo.isFavorited) :
  5501. watchInfoModel.uploaderInfo.isFavorited;
  5502. } catch (e) {
  5503. return false;
  5504. }
  5505. },
  5506. isVideoPublic: function() { // 投稿動画一覧を公開しているか? 公開マイリストがあるかどうかとは別なのでややこしい
  5507. return this.isChannelVideo() ? true : watchInfoModel.uploaderInfo.isUserVideoPublic;
  5508. },
  5509. isChannelVideo: function() {
  5510. return watchInfoModel.isChannelVideo();
  5511. },
  5512. getOwnerInfo: function() {
  5513. return {
  5514. type: this.getOwnerType(),
  5515. name: this.getOwnerName(),
  5516. icon: this.getOwnerIcon(),
  5517. id: this.getOwnerId(),
  5518. page: this.getOwnerPage(),
  5519. isFavorite: this.isFavoriteOwner(),
  5520. isVideoPublic: this.isVideoPublic()
  5521. };
  5522. },
  5523. isSearchMode: function() {
  5524. return videoExplorer.isOpen(); ////return $('body').hasClass('videoExplorer');
  5525. },
  5526. isFullScreen: function() {
  5527. return $('body').hasClass('full_with_browser');
  5528. },
  5529. // フルスクリーンの時にタグとかプレイリストを表示する設定かどうか
  5530. isFullScreenContentAll: function() {
  5531. try {
  5532. var content = localStorage.BROWSER_FULL_OPTIONS;
  5533. if (typeof content !== 'string') return false;
  5534. var isAll = JSON.parse(content).content === 'all';
  5535. return isAll;
  5536. } catch(e) {
  5537. console.log('%cexception', 'background: red; color: white;', e);
  5538. return false;
  5539. }
  5540. },
  5541. isIchibaEmpty: function() {
  5542. return $('#ichibaMain') .find('.ichiba_mainitem').length < 1;
  5543. },
  5544. postComment: function(comment, command) {
  5545. comment = $.trim(comment);
  5546. if (comment.length <= 0) { return; }
  5547. if (!command) { command = ''; }
  5548.  
  5549. setTimeout(function() {
  5550. try {
  5551. var exp = w.document.getElementById('external_nicoplayer');
  5552. console.log('postComment: ', [comment, command]);
  5553. if (!exp.externalPostChat(comment, command)) {
  5554. Popup.alert('コメント投稿に失敗しました');
  5555. }
  5556. } catch(e) {
  5557. Popup.alert('コメント投稿に失敗しました');
  5558. }
  5559. }, 0);
  5560. },
  5561. // スレッドIDから動画IDに変換。出来なかった時はそのまま返す
  5562. getTid2Vid: function(watchId, callback) {
  5563. if (!watchId.match(/^[0-9]+$/)) {
  5564. return callback(watchId);
  5565. }
  5566. WatchItLater.VideoInfoLoader.load(watchId).then(function(info) {
  5567. callback(info.id);
  5568. },
  5569. function() {
  5570. callback(watchId);
  5571. });
  5572. }
  5573. };
  5574. }; // end _watchController
  5575.  
  5576. (function() {
  5577. window.WatchItLater.WatchController =
  5578. window.WatchController = {
  5579. isZeroWatch: function() { return window.PlayerApp ? true : false; },
  5580. isFullScreen: function() { return false; },
  5581. isSearchMode: function() { return false; },
  5582. getTid2Vid: function(threadId, callback) { return callback(threadId);}
  5583. };
  5584. })();
  5585.  
  5586.  
  5587.  
  5588. var Util = (function() {
  5589. var Cache = {
  5590. storage: {},
  5591. get: function(key) {
  5592. if (!this.storage[key]) {
  5593. console.log('no cache');
  5594. return false;
  5595. } else
  5596. if (this.storage[key].cachedUntil <= Date.now()){
  5597. console.log('cache timeout');
  5598. delete this.storage[key];
  5599. return false;
  5600. } else {
  5601. console.log('cache exist');
  5602. return this.storage[key].data;
  5603. }
  5604. },
  5605. set: function(key, data, cacheTimeMs) {
  5606. cacheTimeMs = cacheTimeMs || 1000 * 60 * 10;
  5607. console.log('set cache', key, cacheTimeMs);
  5608. this.storage[key] = {
  5609. data: data,
  5610. cachedUntil: Date.now() + cacheTimeMs
  5611. };
  5612. return data;
  5613. }
  5614. };
  5615. var Browser = {
  5616. isWebkit: function() {
  5617. return navigator.userAgent.toLowerCase().indexOf('webkit') >= 0;
  5618. },
  5619. isFx: function() {
  5620. return navigator.userAgent.toLowerCase().indexOf('firefox') >= 0;
  5621. }
  5622. };
  5623. var
  5624. isMetaKey = function(e) {
  5625. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return true; }
  5626. return false;
  5627. },
  5628. prevent = function(e) {
  5629. e.preventDefault(); e.stopPropagation();
  5630. },
  5631. scrollToVideoExplorer = function() {
  5632. if (!WatchController.isSearchMode()) { return; }
  5633. window.setTimeout(function() {
  5634. window.WatchApp.ns.util.WindowUtil.scrollFit($('#videoExplorer'));
  5635. }, 100);
  5636. };
  5637.  
  5638. var Closure = {
  5639. outScope: function(func) {
  5640. return new Function([
  5641. '(' + func.toString() + ').apply(this, arguments);'
  5642. ].join(''));
  5643. },
  5644. openVideoOwnersVideo: function() {
  5645. return function(e) {
  5646. if (isMetaKey(e)) { return; }
  5647. prevent(e);
  5648. WatchController.openVideoOwnersVideo();
  5649. scrollToVideoExplorer();
  5650. };
  5651. },
  5652. openVideoOwnersNicorepo: function() {
  5653. return function(e) {
  5654. if (isMetaKey(e)) { return; }
  5655. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  5656. return;
  5657. }
  5658. prevent(e);
  5659. //WatchController.showMylist(NicorepoVideo.REPO_OWNER);
  5660. WatchController.showMylist('repo-owner-' + WatchController.getOwnerId());
  5661. scrollToVideoExplorer();
  5662. };
  5663. },
  5664. openDefMylist: function() {
  5665. return function(e) {
  5666. if (isMetaKey(e)) { return; }
  5667. prevent(e);
  5668. WatchController.showDeflist();
  5669. scrollToVideoExplorer();
  5670. };
  5671. },
  5672. openMylist: function(id) {
  5673. return function(e) {
  5674. if (isMetaKey(e)) { return; }
  5675. prevent(e);
  5676. WatchController.showMylist(id);
  5677. scrollToVideoExplorer();
  5678. };
  5679. },
  5680. openNicoSearch: function(word, type) {
  5681. return function(e) {
  5682. if (isMetaKey(e)) { return; }
  5683. if (WatchController.isZeroWatch()) {
  5684. prevent(e);
  5685. WatchController.nicoSearch(word, type);
  5686. scrollToVideoExplorer();
  5687. }
  5688. };
  5689. },
  5690. seekVideo: function(vpos) {
  5691. return function(e) {
  5692. if (isMetaKey(e)) { return; }
  5693. prevent(e);
  5694. WatchController.vpos(vpos);
  5695. };
  5696. },
  5697. showLargeThumbnail: function(url) {
  5698. return function() {
  5699. WatchController.showLargeThumbnail(url);
  5700. };
  5701. },
  5702. commentPanelContextMenu: function() {
  5703. return function(a) {
  5704. a.preventDefault(); a.stopPropagation();
  5705. var c = this.commentListModel.getComment(this.parseResNo(jQuery(a.currentTarget)));
  5706. var WatchApp = null, WatchController = null;
  5707. if (c) {
  5708. var
  5709. $d = this.CommentContextMenu.$contextMenuContainer,
  5710. $e = jQuery("#playerCommentPanel"),
  5711. left = this.$commentTableHeaderOuter.position().left,
  5712. top = a.pageY - $e.offset().top,
  5713. f = Math.min($e.offset().top + $e.outerHeight(), jQuery(window).scrollTop() + jQuery(window).outerHeight());
  5714. if (f < a.pageY + $d.outerHeight()) top -= a.pageY + $d.outerHeight() - f;
  5715. this.CommentContextMenu.show(c, left, top);
  5716. }
  5717. };
  5718. }
  5719. };
  5720. var Deferred = {
  5721. wait: function(msec) {
  5722. return function() {
  5723. var args = Array.prototype.slice.call(arguments, 0);
  5724. var d = new $.Deferred();
  5725. setTimeout(function() {
  5726. d.resolve.apply(d, args);
  5727. }, msec);
  5728. return d.promise();
  5729. };
  5730. }
  5731. };
  5732.  
  5733.  
  5734. var self = {
  5735. Cache: Cache,
  5736. Closure: Closure,
  5737. Deferred: Deferred,
  5738. Browser: Browser,
  5739. here: function(func) { // えせヒアドキュメント
  5740. return func.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  5741. }
  5742. };
  5743. return self;
  5744. })();
  5745. window.WatchItLater.util = Util;
  5746.  
  5747. var NicoNews = (function() {
  5748. var WatchApp = null, watch = null, $ = null, WatchJsApi = null, initialized = false;
  5749. var $button = null, $history = null, $ul = null, deteru = {}, $textMarquee, $textMarqueeInner;
  5750. var isHover = false;
  5751.  
  5752. function onNewsUpdate(news) {
  5753. var type = news.data.type, $current = null,
  5754. newsText = $textMarqueeInner.find('.categoryOuter:last').text() +
  5755. $textMarqueeInner.find('.item .title, .item .header, .item .bannertext, .item .text').text(),
  5756. newsHref = $textMarqueeInner.find('a').attr('href');
  5757. if (deteru[newsHref]) {
  5758. $current = deteru[newsHref].remove();
  5759. } else {
  5760. $current = deteru[newsHref] = makeTopic(newsText, newsHref, type);
  5761. }
  5762. $ul.append($current);
  5763. $current.show(200, scrollToBottom);
  5764. }
  5765. function makeTopic(title, url, type) {
  5766. return $([
  5767. '<li style="display: none;">',
  5768. '<a href="', url , '" target="_blank" class="', type, ' title="', escape(title),'">', title, '</a>',
  5769. '</li>',
  5770. ''].join(''));
  5771. }
  5772. function scrollToBottom() {
  5773. if (!isHover) {
  5774. $history.animate({scrollTop: $('.newsHistory ul').innerHeight()}, 200);
  5775. }
  5776. }
  5777.  
  5778. var self = {
  5779. initialize: function(w) {
  5780. WatchApp = w.WatchApp;
  5781. if (!WatchApp || initialized) { return; }
  5782. watch = WatchApp.ns.init;
  5783. $ = w.$;
  5784. WatchJsApi = w.WatchJsApi;
  5785. $textMarquee = $('#textMarquee');
  5786. $textMarqueeInner = $textMarquee.find('.textMarqueeInner');
  5787.  
  5788. watch.TextMarqueeInitializer.textMarqueeViewController.scheduler.addEventListener(
  5789. 'schedule',
  5790. onNewsUpdate);
  5791.  
  5792. $button = $('<button class="openNewsHistory" title="ニコニコニュースの履歴を開く">▲</button>');
  5793. $history = $('<div class="newsHistory" style="display: none;"><ul></ul></div>');
  5794. $history.hover(
  5795. function() { isHover = true; },
  5796. function() { isHover = false; }
  5797. );
  5798. $ul = $history.find('ul');
  5799. $button.click(function() { self.toggle(); });
  5800.  
  5801. $textMarquee.append($button).append($history);
  5802. initialized = true;
  5803. },
  5804. open: function() {
  5805. $history.show(200, function() {
  5806. scrollToBottom();
  5807. WatchApp.ns.util.WindowUtil.scrollFitMinimum('.newsHistory', 200);
  5808. });
  5809. },
  5810. close: function() {
  5811. $history.hide(200);
  5812. isHover = false;
  5813. },
  5814. toggle: function() {
  5815. if ($history.is(':visible')) {
  5816. $button.text('▲');
  5817. this.close();
  5818. } else {
  5819. $button.text('▼');
  5820. this.open();
  5821. }
  5822. }
  5823. };
  5824. return self;
  5825. })();
  5826.  
  5827.  
  5828.  
  5829. /**
  5830. * マイリストや検索API互換形式のデータを返すやつ
  5831. */
  5832. var DummyMylist = function() { this.initialize.apply(this, arguments); };
  5833. DummyMylist.prototype = {
  5834. banner: '',
  5835. id: '-100',
  5836. sort: '4',
  5837. isDeflist: -1,
  5838. isWatchngCountFull: false,
  5839. isWatchngThisMylist: false,
  5840. itemCount: 0,
  5841. items: [],
  5842. rawData: {},
  5843. page: 1,
  5844. perPage: 32,
  5845. type: 2, // 2: MYLIST_VIDEO
  5846. //
  5847. // ver130726より新規追加 defineGetterのほうがいいかも
  5848.  
  5849. status: 'ok',
  5850. name: '',
  5851. description: '',
  5852. user_id: '',
  5853. user_nickname: 'ニコニコ動画',
  5854. default_sort: '1',
  5855. is_watching_this_mylist: false,
  5856. is_watching_count_full: false,
  5857. list: [],
  5858. // ここまで
  5859. initialize: function(param) {
  5860. this.rawData = {
  5861. status: 'ok',
  5862. list: [],
  5863. name: '総合ランキング',
  5864. description: '',
  5865. is_watching_count_full: false,
  5866. is_watching_this_mylist: false,
  5867. user_nickname: '',
  5868. user_id: '',
  5869. sort: '1'
  5870. };
  5871. this._baseCreateTime = Date.now();//new Date();
  5872. this.rawData.user_nickname = param.user_nickname || WatchController.getMyNick();
  5873. this.rawData.user_id = param.user_id || WatchController.getMyUserId();
  5874. this.rawData.name = param.name || this.rawData.name;
  5875. this.rawData.description = param.description || '';
  5876.  
  5877. this.type = param.type || WatchApp.ns.components.videoexplorer.model.ContentType.MYLIST_VIDEO;
  5878. this.sort = this.rawData.sort = param.sort || this.sort;
  5879. this.id = param.id || '-100';
  5880.  
  5881. this.status = this.rawData.status;
  5882. this.list = this.rawData.list;
  5883. this.name = this.rawData.name;
  5884. this.description = this.rawData.description;
  5885. this.default_sort = this.rawData.sort || this.sort;
  5886. this.user_nickname = this.rawData.user_nickname || this.user_nickname;
  5887. this.user_id = this.rawData.user_id;
  5888.  
  5889.  
  5890. },
  5891. setName: function(name) {
  5892. this.rawData.name = name;
  5893. },
  5894. getName: function() {
  5895. return this.rawData.name || '';
  5896. },
  5897. setPage: function(page) {
  5898. this.page = page;
  5899. this.items = this.rawData.list.slice(page * this.perPage - this.perPage, page * this.perPage);
  5900. },
  5901. push: function(item) {
  5902. if (!item.create_time) {
  5903. var tm = this._baseCreateTime - 60000 * this.itemCount;
  5904. item.create_time = tm;
  5905. }
  5906. this.rawData.list.push(item);
  5907. this.itemCount = this.rawData.list.length;
  5908. this.setPage(this.page);
  5909. },
  5910. unshift: function(item) {
  5911. if (!item.create_time) {
  5912. var tm = this._baseCreateTime + 60000 * this.itemCount;
  5913. item.create_time = tm;
  5914. }
  5915. this.rawData.list.unshift(item);
  5916. this.itemCount = this.rawData.list.length;
  5917. this.setPage(this.page);
  5918. },
  5919. slice: function(b, e) {
  5920. this.rawData.list = this.rawData.list.slice(b, e);
  5921. this.itemCount = this.rawData.list.length;
  5922. this.setPage(this.page);
  5923. },
  5924. sortItem: function(sortId, force) {
  5925. sortId = parseInt(sortId, 10);
  5926. if (!!!force && (sortId < 0 || sortId === parseInt(this.sort, 10)) ) { return; }
  5927. var sortKey = ([
  5928. 'create_time', 'create_time',
  5929. 'mylist_comment', 'mylist_comment',
  5930. 'title', 'title',
  5931. 'first_retrieve', 'first_retrieve',
  5932. 'view_counter', 'view_counter',
  5933. 'thread_update_time', 'thread_update_time',
  5934. 'num_res', 'num_res',
  5935. 'mylist_counter', 'mylist_counter',
  5936. 'length_seconds', 'length_seconds'
  5937. ])[sortId],
  5938. order = (sortId % 2 === 0) ? 'asc' : 'desc';
  5939.  
  5940. if (!sortKey) { return; }
  5941. var compare= {
  5942. asc: function(a, b) { return (a[sortKey] > b[sortKey] ) ? 1 : -1; },
  5943. desc: function(a, b) { return (a[sortKey] < b[sortKey] ) ? 1 : -1; },
  5944. iasc: function(a, b) { return (a[sortKey]*1 > b[sortKey]*1) ? 1 : -1; },
  5945. idesc: function(a, b) { return (a[sortKey]*1 < b[sortKey]*1) ? 1 : -1; }
  5946. };
  5947. // 偶数がascで奇数がdescかと思ったら特に統一されてなかった
  5948. if (
  5949. sortKey === 'first_retrieve' ||
  5950. sortKey === 'thread_update_time'
  5951. ) {
  5952. order = (sortId % 2 === 1) ? 'asc' : 'desc';
  5953. } else
  5954. // 数値系は偶数がdesc
  5955. if (sortKey === 'view_counter' ||
  5956. sortKey === 'num_res' ||
  5957. sortKey === 'mylist_counter' ||
  5958. sortKey === 'length_seconds'
  5959. ) {
  5960. order = (sortId % 2 === 1) ? 'iasc' : 'idesc';
  5961. }
  5962. this.sort = this.rawData.sort = sortId.toString();
  5963. this.rawData.list.sort(compare[order]);
  5964. this.items = this.rawData.list.slice(0, 32);
  5965. this.list = this.rawData.list.slice(0);
  5966. }
  5967. };
  5968.  
  5969. var DummyMylistVideo = function() { this.initialize.apply(this, arguments); };
  5970. DummyMylistVideo.prototype = {
  5971. id: 0,
  5972. title: '',
  5973. length: 0,
  5974. view_counter: 0,
  5975. num_res: 0,
  5976. mylist_counter: 0,
  5977. description_short: '',
  5978. first_retrieve: null,
  5979. thumbnail_url: null,
  5980. mylist_comment: '',
  5981. create_time: null,
  5982. type: 0, //'video',
  5983. _info: {},
  5984.  
  5985.  
  5986. initialize: function(info) {
  5987. this._info = info._info || this;
  5988. this.id = info.id;
  5989. this.length = info.length;
  5990. this.mylist_counter = info.mylist_counter || 0;
  5991. this.view_counter = info.view_counter || 0;
  5992. this.num_res = info.num_res || 0;
  5993. this.first_retrieve = info.first_retrieve || '2000-01-01 00:00:00';
  5994. this.create_time = info.create_time || null;
  5995. this.thumbnail_url = info.thumbnail_url || 'http://res.nimg.jp/img/common/video_deleted_ja-jp.jpg' /* 「視聴できません」 */;
  5996. this.title = info.title || '';
  5997. this.type = info.type || 'video';
  5998. this.description_short = info.description_short;
  5999. this.length = info.length || '00:00';
  6000. this.length_seconds = parseInt(info.length_seconds || 0, 10);
  6001. this.mylist_comment = info.mylist_comment || '';
  6002. this.type = info.type || WatchApp.ns.components.videoexplorer.model.ContentItemType.VIDEO;
  6003.  
  6004. if (this.length_seconds === 0 && this.length && this.length.indexOf(':') >= 0) {
  6005. var sp = this.length.split(':');
  6006. this.length_seconds = sp[0] * 60 + sp[1] * 1;
  6007. } else
  6008. if (this.length === '00:00' && this.length_seconds > 0) {
  6009. this.length = parseInt(this.length_seconds / 60, 10) + ':' + (this.length_seconds % 60);
  6010. }
  6011. },
  6012. getType: function() { return this.type; },
  6013. getInfo: function() { return this; }, // 手抜き
  6014. getName: function() { return this.title; },
  6015. getId: function() { return this.id; },
  6016. getDescription: function() { return this.description_short; },
  6017.  
  6018.  
  6019. length_seconds: 0, // TODO:
  6020. thread_update_time: '2000-01-01 00:00:00' // TODO: 「コメントが新しい順でソート」に必要?
  6021. };
  6022.  
  6023. // 参考:
  6024. // http://looooooooop.blog35.fc2.com/blog-entry-1146.html
  6025. // http://toxy.hatenablog.jp/entry/2013/07/25/200645
  6026. // http://ch.nicovideo.jp/pita/blomaga/ar297860
  6027. // http://search.nicovideo.jp/docs/api/ma9.html
  6028. var NewNicoSearch = function() { this.initialize.apply(this, arguments); };
  6029. NewNicoSearch.API_BASE_URL = 'http://api.search.nicovideo.jp/api/';
  6030. NewNicoSearch.PAGE_BASE_URL = 'http://search.nicovideo.jp/video/';
  6031. NewNicoSearch.prototype = {
  6032. _u: '', // 24h, 1w, 1m, ft 期間指定
  6033. _ftfrom: '', // YYYY-MM-DD
  6034. _ftto: '', // YYYY-MM-DD
  6035. _l: '', // short long
  6036. _m: false, // true=音楽ダウンロード
  6037. _sort: '', // last_comment_time, last_comment_time_asc,
  6038. // view_counter, view_counter_asc,
  6039. // comment_counter, comment_counter_asc,
  6040. // mylist_counter, mylist_counter_asc,
  6041. // upload_time, upload_time_asc,
  6042. // length_seconds, length_seconds_asc
  6043. _size: 32, // 一ページの件数 maxは100
  6044. _issuer: 'watch-it-later',
  6045. _base_url: NewNicoSearch.API_BASE_URL,
  6046. initialize: function(params) {
  6047.  
  6048. },
  6049. load: function(params, callback) {
  6050. var url = this._base_url;
  6051. var data = {};
  6052. data.query = params.query || 'Qwatch';
  6053. data.service = params.service || ['video']; // video video_tag
  6054. data.search = params.search || ['title', 'tags', 'description'];
  6055. data.join = params.join || [
  6056. // TODO:投稿者IDを取得する方法がないか?
  6057. 'cmsid', 'title', 'description', 'thumbnail_url', 'start_time',
  6058. 'view_counter', 'comment_counter', 'mylist_counter', 'length_seconds', 'last_res_body'
  6059. // 'user_id', 'channel_id', 'main_community_id', 'ss_adlut'
  6060. ];
  6061. data.filters = params.filters || [{}];
  6062. data.sort_by = params.sort_by || 'start_time';
  6063. data.order = params.order || 'desc';
  6064. data.timeout = params.timeout || 10000;
  6065. data.issuer = params.issuer || 'watch-it-later';
  6066. data.reason = params.reason || 'video-explorer'; // 'watchItLater';
  6067. data.size = params.size || 32;
  6068. data.from = params.from || 0;
  6069.  
  6070. if (params.sort_by === '_hot') { // 人気順ソートのパラメータ
  6071. data.hot_field = params.hot_field;
  6072. data.hot_from = params.hot_from;
  6073. data.hot_to = params.hot_to;
  6074. }
  6075.  
  6076. var cache_key = JSON.stringify({url: url, data: data}), cache = Util.Cache.get(cache_key);
  6077. if (cache) {
  6078. setTimeout(function() { callback(null, cache); }, 0);
  6079. return;
  6080. }
  6081.  
  6082. $.ajax({
  6083. url: url,
  6084. type: 'POST',
  6085. data: JSON.stringify(data),
  6086. timeout: 30000,
  6087. complete: function(result) {
  6088. console.log('result', result);
  6089. if (result.status !== 200) {
  6090. callback('fail', 'HTTP status:' + result.status);
  6091. return;
  6092. }
  6093. var data;
  6094. try {
  6095. var lines = result.responseText.split('\n'), head = JSON.parse(lines[0]);
  6096. if (head.values[0].total > 0) {
  6097. data = [head];
  6098. for (var i = 1, len = lines.length; i < len - 1; i++) {
  6099. data.push(JSON.parse(lines[i]));
  6100. }
  6101. } else {
  6102. data = [head, JSON.parse(lines[1]), {type: 'hits', values: []}, JSON.parse(lines[2])];
  6103. }
  6104. Util.Cache.set(cache_key, data);
  6105. } catch(e) {
  6106. console.log('Exception: ', e, result);
  6107. callback('fail', 'JSON syntax');
  6108. return;
  6109. }
  6110. callback(null, data);
  6111. },
  6112. error: function(req, status, thrown) {
  6113. if (status === 'parsererror') {
  6114. return;
  6115. }
  6116. console.log('%c ajax error: ' + status, 'background: red', arguments);
  6117. callback('fail', status);
  6118. }
  6119. });
  6120. }
  6121. };
  6122.  
  6123.  
  6124.  
  6125. /**
  6126. * niconico新検索の検索結果を既存の検索API互換形式に変換して返すやつ
  6127. */
  6128. var NewNicoSearchWrapper = function() { this.initialize.apply(this, arguments); };
  6129. NewNicoSearchWrapper.prototype = {
  6130. _search: null,
  6131. sortTable: {f: 'start_time', v: 'view_counter', r: 'comment_counter', m: 'mylist_counter', l: 'length_seconds',
  6132. '_hot': '_hot', // 人気が高い順
  6133. '_explore': '_explore', // 新着優先
  6134. '_popular': '_popular' // 並び順指定なし
  6135. },
  6136. initialize: function(params) {
  6137. this._search = params.search;
  6138. },
  6139. _buildSearchQuery: function(params) {
  6140. var query = {filters: []};
  6141. var sortTable = this.sortTable;
  6142. query.query = params.searchWord;
  6143. query.search = params.searchType === 'tag' ? ['tags'] : ['tags', 'title', 'description'];
  6144. query.sort_by = params.sort && sortTable[params.sort] ? sortTable[params.sort] : 'last_comment_time';
  6145. query.order = params.order === 'd' ? 'desc' : 'asc';
  6146. query.size = params.size || 32;
  6147. query.from = params.page ? Math.max(parseInt(params.page, 10) - 1, 0) * query.size : 0;
  6148.  
  6149. var n = new Date();
  6150. var now = n.getTime();
  6151. switch (params.u) {
  6152. case '1h':
  6153. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 1 * 60 * 60 * 1000)));
  6154. break;
  6155. case '24h': case '1d':
  6156. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 1 * 24 * 60 * 60 * 1000)));
  6157. break;
  6158. case '1w': case '7d':
  6159. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 7 * 24 * 60 * 60 * 1000)));
  6160. break;
  6161. case '1m':
  6162. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 30 * 24 * 60 * 60 * 1000)));
  6163. break;
  6164. case '3m':
  6165. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 90 * 24 * 60 * 60 * 1000)));
  6166. break;
  6167. case '6m':
  6168. query.filters.push(this._buildStartTimeRangeFilter(new Date(now - 180 * 24 * 60 * 60 * 1000)));
  6169. break;
  6170. default:
  6171. break;
  6172. }
  6173.  
  6174. if (query.sort_by === '_hot') {
  6175. // 人気が高い順ソート
  6176. (function() {
  6177. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6178. query.hot_field = 'mylist_counter';
  6179. query.hot_from = format(new Date(now - 1 * 24 * 60 * 60 * 1000));
  6180. query.hot_to = format(n);
  6181.  
  6182. query.order = 'desc';
  6183. })();
  6184. }
  6185.  
  6186. if (typeof params.userId === 'string' && params.userId.match(/^\d+$/)) {
  6187. query.filters.push({type: 'equal', field: 'user_id', value: params.userId});
  6188. }
  6189. if (typeof params.channelId === 'string' && params.channelId.match(/^\d+$/)) {
  6190. query.filters.push({type: 'equal', field: 'channel_id', value: params.channelId});
  6191. }
  6192.  
  6193. if (params.l === 'short') { // 5分以内
  6194. query.filters.push(this._buildLengthSecondsRangeFilter(0, 60 * 5));
  6195. } else
  6196. if (params.l === 'long' ) { // 20分以上
  6197. query.filters.push(this._buildLengthSecondsRangeFilter(60 * 20));
  6198. }
  6199.  
  6200. if (params.m === true) { // 音楽ダウンロード
  6201. query.filters.push({type: 'equal', field: 'music_download', value: true});
  6202. }
  6203.  
  6204. // TODO: これの調査 → {field: 'ss_adult', type: 'equal', value: false}
  6205.  
  6206. return query;
  6207. },
  6208. _buildStartTimeRangeFilter: function(from, to) {
  6209. var format = function(date) { return WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', date); };
  6210. var range = {field: 'start_time', type: 'range', include_lower: true, };
  6211. range.from = format(from);
  6212. if (to) range.to = format(to);
  6213. return range;
  6214. },
  6215. _buildLengthSecondsRangeFilter: function(from, to) {
  6216. var range = {field: 'length_seconds', type: 'range'};
  6217. if (to) { // xxx ~ xxx
  6218. range.from = Math.min(from, to);
  6219. range.to = Math.max(from, to);
  6220. range.include_lower = range.include_upper = true;
  6221. } else { // xxx以上
  6222. range.from = from;
  6223. range.include_lower = true;
  6224. }
  6225. return range;
  6226. },
  6227. load: function(params, callback) {
  6228. var query = this._buildSearchQuery(params);
  6229. this._search.load(query, $.proxy(function(err, result) {
  6230. this.onLoad(err, result, params, query, callback);
  6231. }, this));
  6232. },
  6233. onLoad: function(err, result, params, query, callback) {
  6234. if (err) {
  6235. console.log('load fail', err, result);
  6236. callback('fail', {message: '通信に失敗しました1'});
  6237. return;
  6238. }
  6239. var searchResult;
  6240. searchResult = {
  6241. status: 'ok',
  6242. count: result[0].values[0].total,
  6243. page: params.page,
  6244. list: []
  6245. };
  6246. var pushItems = function(items) {
  6247. var len = items.length;
  6248. for (var i = 0; i < len; i++) {
  6249. var item = items[i], description = item.description ? item.description.replace(/<.*?>/g, '') : '';
  6250. searchResult.list.push({
  6251. id: item.cmsid,
  6252. type: 0, // 0 = VIDEO,
  6253. length: item.length_seconds ?
  6254. Math.floor(item.length_seconds / 60) + ':' + (item.length_seconds % 60 + 100).toString().substr(1) : '',
  6255. mylist_counter: item.mylist_counter,
  6256. view_counter: item.view_counter,
  6257. num_res: item.comment_counter,
  6258. first_retrieve: item.start_time,
  6259. create_time: item.start_time,
  6260. thumbnail_url: item.thumbnail_url,
  6261. title: item.title,
  6262. description_short: description.substr(0, 150),
  6263. description_full: description,
  6264. length_seconds: item.length_seconds,
  6265. last_res_body: item.last_res_body
  6266. // channel_id: item.channel_id,
  6267. // main_community_id: item.main_community_id
  6268. });
  6269. }
  6270. };
  6271. for (var i = 1; i < result.length; i++) {
  6272. if (result[i].type === 'hits' && result[i].endofstream) { break; }
  6273. if (result[i].type === 'hits' && result[i].values) {
  6274. pushItems(result[i].values);
  6275. }
  6276. }
  6277. callback(null, searchResult);
  6278. }
  6279. };
  6280.  
  6281. // sug.search.nicovideo.jpはリアルタイムの入力補完用? で、関連タグはhttp://api.search.nicovideo.jp/api/tag/ っぽい
  6282. var NicoSearchSuggest = function() { this.initialize.apply(this, arguments); };
  6283. NicoSearchSuggest.API_BASE_URL = 'http://sug.search.nicovideo.jp/'; //'/suggestion/complete';
  6284. NicoSearchSuggest.prototype = {
  6285. _base_url: NicoSearchSuggest.API_BASE_URL,
  6286. initialize: function(params) {
  6287. },
  6288. load: function(word, callback) {
  6289. if (typeof word !== 'string' || word.length <= 0) {
  6290. throw new Error('wordが設定されてない!');
  6291. }
  6292. var url = this._base_url + 'suggestion/complete',
  6293. cache_key = JSON.stringify({url: url, word: word}),
  6294. cache_time = 60 * 1000 * 1,
  6295. cache = Util.Cache.get(cache_key);
  6296. if (cache) {
  6297. setTimeout(function() { callback(null, cache); }, 0);
  6298. return;
  6299. }
  6300. $.ajax({
  6301. url: url,
  6302. type: 'POST',
  6303. data: word,
  6304. timeout: 30000,
  6305. complete: function(result) {
  6306. if (result.status !== 200) {
  6307. callback('fail', 'HTTP status:' + result.status);
  6308. return;
  6309. }
  6310. var data;
  6311. try {
  6312. data = JSON.parse(result.responseText);
  6313. } catch(e) {
  6314. console.log('Exception: ', e, result);
  6315. callback('fail', 'JSON syntax');
  6316. }
  6317. Util.Cache.set(cache_key, data, cache_time);
  6318. callback(null, data);
  6319. },
  6320. error: function(req, status, thrown) {
  6321. if (status === 'parsererror') {
  6322. return;
  6323. }
  6324. callback('fail', status);
  6325. }
  6326. });
  6327. }
  6328. };
  6329.  
  6330. var NicoSearchRelatedTag = function() { this.initialize.apply(this, arguments); };
  6331. NicoSearchRelatedTag.API_BASE_URL = 'http://api.search.nicovideo.jp/';
  6332. NicoSearchRelatedTag.prototype = {
  6333. _base_url: NicoSearchRelatedTag.API_BASE_URL,
  6334. initialize: function(params) {
  6335. },
  6336. load: function(word, callback) {
  6337. var url = this._base_url + 'api/tag/',
  6338. cache_key = JSON.stringify({url: url, word: word}),
  6339. cache_time = 60 * 1000 * 10,
  6340. cache = Util.Cache.get(cache_key);
  6341. if (cache) {
  6342. setTimeout(function() { callback(null, cache); }, 0);
  6343. return;
  6344. }
  6345. var query = {query: word, service: ['tag_video'], from: 0, size: 100, timeout: 10000, reason: 'user'};
  6346. $.ajax({
  6347. url: url,
  6348. type: 'POST',
  6349. data: JSON.stringify(query),
  6350. timeout: 30000,
  6351. complete: function(result) {
  6352. if (result.status !== 200) {
  6353. callback('fail', 'HTTP status:' + result.status);
  6354. return;
  6355. }
  6356. var data;
  6357. try {
  6358. var lines = result.responseText.split('\n');
  6359. data = JSON.parse(lines[0]);
  6360. } catch(e) {
  6361. console.log('Exception: ', e, result);
  6362. callback('fail', 'JSON syntax');
  6363. return;
  6364. }
  6365. Util.Cache.set(cache_key, data, cache_time);
  6366. callback(null, data);
  6367. },
  6368. error: function(req, status, thrown) {
  6369. if (status === 'parsererror') {
  6370. return;
  6371. }
  6372. callback('fail', status);
  6373. }
  6374. });
  6375. }
  6376. };
  6377.  
  6378.  
  6379. var VideoInfoLoader = function() { this.initialize.apply(this, arguments); };
  6380. VideoInfoLoader.BASE_URL = "http://riapi.nicovideo.jp/api/search/tag";
  6381. VideoInfoLoader.prototype = {
  6382. initialize: function(params) {
  6383. },
  6384. load: function(id, callback) {
  6385. var def = new $.Deferred;
  6386.  
  6387. var cache_key = JSON.stringify({'VideoInfoLoaderCache': id}), cacheData = Util.Cache.get(cache_key);
  6388. if (cacheData) {
  6389. return def.resolve(cacheData);
  6390. }
  6391.  
  6392. if (id.toString().match(/^\d+$/)) { // watchId
  6393. WatchApp.ns.init.PlaylistInitializer.videoInfoAPILoader.load(
  6394. [id],
  6395. function(err, resp) {
  6396. if (err !== null) {
  6397. return def.reject({message: '通信に失敗しました(1)', status: 'fail'});
  6398. }
  6399. if (resp.items && resp.items[id] && resp.items[id].id) {
  6400. if (typeof callback === 'function') { callback(null, resp.items[id]); }
  6401. return def.resolve(Util.Cache.set(cache_key, resp.items[id]));
  6402. }
  6403. var err = {message: '動画が見つかりませんでした(1): ' + id, status: 'fail'};
  6404. if (typeof callback === 'function') { callback(err, null); }
  6405. return def.reject(err);
  6406. }
  6407. );
  6408. return def.promise();
  6409. }
  6410.  
  6411. // タグ検索APIの「もしかして: xxx」を使って動画情報を取得する
  6412. WatchApp.ns.util.HTTPUtil.loadXDomainAPI({ // videoId
  6413. url: VideoInfoLoader.BASE_URL,
  6414. type: 'GET',
  6415. dataType: 'json',
  6416. xhrFields: {
  6417. withCredentials: true
  6418. },
  6419. data: {
  6420. words: 'watch/' + id,
  6421. sort: 'f',
  6422. order: 'd',
  6423. page: '1',
  6424. mode: 'watch'
  6425. },
  6426. success: function(result) {
  6427. if (result.suggest_video && result.suggest_video.id) {
  6428. if (typeof callback === 'function') { callback(null, result.suggest_video); }
  6429. def.resolve(Util.Cache.set(cache_key, result.suggest_video));
  6430. } else {
  6431. var err = {message: '動画が見つかりませんでした(2): ' + id, status: 'fail'};
  6432. if (typeof callback === 'function') { callback(err, null); }
  6433. def.reject(err);
  6434. }
  6435. },
  6436. error: function(resp) {
  6437. var err = {message: '通信に失敗しました(2)', status: 'fail'};
  6438. if (typeof callback === 'function') { callback(err, null); }
  6439. def.reject(err);
  6440. }
  6441. });
  6442. return def.promise();
  6443. }
  6444. };
  6445. WatchItLater.VideoInfoLoader = new VideoInfoLoader({});
  6446.  
  6447. var RelatedVideo = function() { this.initialize.apply(this, arguments); }
  6448. RelatedVideo.prototype = {
  6449. initialize: function(params) {
  6450. },
  6451. load: function(watchId) {
  6452. var def = new $.Deferred;
  6453. window.WatchApp.ns.init.VideoExplorerInitializer.relatedVideoAPILoader.load(
  6454. {'video_id': watchId},
  6455. function(err, result) {
  6456. if (err !== null) {
  6457. return def.reject({message: '通信に失敗しました(1)', status: 'fail', err: err});
  6458. }
  6459. return def.resolve(result);
  6460. }
  6461. );
  6462. return def.promise();
  6463. }
  6464. };
  6465. WatchItLater.RelatedVideo = new RelatedVideo({});
  6466.  
  6467. /**
  6468. * 動画視聴履歴をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  6469. */
  6470. var VideoWatchHistory = (function(w, Util){
  6471. function load(callback) {
  6472. var watch, $, myNick, myId, url;
  6473. try{
  6474. watch = w.WatchApp.ns.init;
  6475. $ = w.$; url = '/my/history';
  6476. myNick = WatchController.getMyNick(); myId = WatchController.getMyUserId();
  6477. } catch (e) {
  6478. console.log(e);
  6479. throw { message: 'エラーが発生しました', status: 'fail'};
  6480. }
  6481.  
  6482. var CACHE_KEY = 'videohistory', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6483. if (cacheData) {
  6484. setTimeout(function() {callback(cacheData);}, 0);
  6485. return;
  6486. }
  6487.  
  6488. var result = new DummyMylist({
  6489. id: '-1',
  6490. sort: '1',
  6491. name: myNick + 'の視聴履歴',
  6492. user_id: myId,
  6493. user_name: 'ニコニコ動画'
  6494. });
  6495. GM_xmlhttpRequest({
  6496. url: url,
  6497. onload: function(resp) {
  6498. var $dom = $(resp.responseText), $list = $dom.find('#historyList');
  6499. $list.find('.outer').each(function() {
  6500. var
  6501. $item = $(this), $meta = $item.find('.metadata'), $title = $item.find('.section h5 a'),
  6502. id = $title.attr('href').split('/').reverse()[0], title = $title.text(),
  6503. duration = $item.find('.videoTime').text(),
  6504. viewCnt = $meta.find('.play') .text().split(':')[1].replace(/,/g, ''),
  6505. resCnt = $meta.find('.comment').text().split(':')[1].replace(/,/g, ''),
  6506. mylistCnt = $meta.find('.mylist') .text().split(':')[1].replace(/,/g, ''),
  6507. postedAt = '20' + $meta.find('.posttime').text().replace(/(年|月)/g, '-').replace(/(日| *投稿)/g, ''),
  6508. thumbnail = $item.find('.thumbContainer a .video').attr('src');
  6509.  
  6510. var item = new DummyMylistVideo({
  6511. id: id,
  6512. length: duration,
  6513. mylist_counter: mylistCnt,
  6514. view_counter: viewCnt,
  6515. num_res: resCnt,
  6516. first_retrieve: postedAt,
  6517. thumbnail_url: thumbnail,
  6518. title: title,
  6519. _info: {first_retrieve: postedAt},
  6520. description_short: $item.find('.section .posttime span').text()
  6521. });
  6522. result.push(item);
  6523. });
  6524. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6525. },
  6526. onerror: function() {
  6527. Popup.alert('視聴履歴の取得に失敗しました');
  6528. }
  6529. });
  6530.  
  6531. }
  6532. var self = {
  6533. load : load
  6534. };
  6535. return self;
  6536. })(w, Util);
  6537.  
  6538.  
  6539.  
  6540. var VideoRecommendations = (function(w, Util){
  6541. var histories = {};
  6542. function request(callback) {
  6543. var watch, $, url, myNick, myId;
  6544. try{
  6545. watch = w.WatchApp.ns.init;
  6546. $ = w.$;
  6547. url = '/recommendations';
  6548. myNick = WatchController.getMyNick();
  6549. myId = WatchController.getMyUserId();
  6550. } catch (e) {
  6551. console.log(e);
  6552. throw { message: 'エラーが発生しました', status: 'fail'};
  6553. }
  6554. var CACHE_KEY = 'recommend', CACHE_TIME = 1000 * 60 * 1, cacheData = Util.Cache.get(CACHE_KEY);
  6555. if (cacheData) {
  6556. setTimeout(function() {callback(cacheData); }, 0);
  6557. return;
  6558. }
  6559.  
  6560. var result = new DummyMylist({
  6561. id: '-2',
  6562. sort: '1',
  6563. name: 'あなたにオススメの動画'
  6564. });
  6565. GM_xmlhttpRequest({
  6566. url: url,
  6567. onload: function(resp) {
  6568. var text = resp.responseText, lines = text.split(/[\r\n]/), found = false, data, i, len;
  6569. for (i = 0, len = lines.length; i < len; i++) {
  6570. var line = lines[i];
  6571. if (line.indexOf('var Nico_RecommendationsParams') >= 0 &&
  6572. lines[i + 5] && lines[i + 5].indexOf('first_data') >= 0) {
  6573. data = JSON.parse(lines[i + 5].replace(/^.*?:/, ''));
  6574. if (data && data.videos) {
  6575. found = true;
  6576. break;
  6577. }
  6578. }
  6579. }
  6580. if (!found) {
  6581. throw { message: '取得に失敗しました', status: 'fail'};
  6582. }
  6583.  
  6584. for (i = 0, len = data.videos.length; i < len; i++) {
  6585. var video = data.videos[i];
  6586. if (histories[video.id]) {
  6587. delete histories[video.id];
  6588. }
  6589. var item = new DummyMylistVideo({
  6590. id: video.id,
  6591. length: video.length,
  6592. mylist_counter: video.mylist_counter,
  6593. view_counter: video.view_counter,
  6594. num_res: video.num_res,
  6595. first_retrieve: video.first_retrieve,
  6596. thumbnail_url: video.thumbnail_url,
  6597. title: video.title_short,
  6598. _info: video,
  6599. description_short: '関連タグ: ' + video.recommend_tag
  6600. });
  6601. histories[video.id] = item;
  6602. }
  6603. for (var v in histories) {
  6604. result.unshift(histories[v]);
  6605. }
  6606. result.slice(0, 128);
  6607. callback(Util.Cache.set(CACHE_KEY, result, CACHE_TIME));
  6608. },
  6609. onerror: function() {
  6610. throw { message: '取得に失敗しました', status: 'fail'};
  6611. }
  6612. });
  6613.  
  6614. }
  6615. function load(callback, param) {
  6616. request(function(result) {
  6617. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6618. result.setPage(viewPage);
  6619. callback(result);
  6620. });
  6621. }
  6622. var self = {
  6623. load : load
  6624. };
  6625. return self;
  6626. })(w, Util);
  6627.  
  6628.  
  6629. var NicorepoVideo = (function(w, Util) {
  6630. if (!window.PlayerApp) return {};
  6631.  
  6632. var CACHE_TIME = 1000 * 60 * 10;
  6633. var WatchApp = w.WatchApp;
  6634.  
  6635. var getNicorepoTitle = function(type, param) {
  6636. var base = '【ニコレポ】';
  6637. if (type === 'all') {
  6638. return base + 'すべての動画';
  6639. } else
  6640. if (type === 'chcom') {
  6641. return base + 'お気に入りチャンネル&コミュニティの動画';
  6642. } else
  6643. if (type === 'mylist') {
  6644. return base + 'お気に入りマイリストの動画';
  6645. } else
  6646. if (type === 'owner') {
  6647. return WatchController.getOwnerName() + 'のニコレポ';
  6648. }
  6649. return base + 'お気に入りユーザーの動画';
  6650. };
  6651.  
  6652. var parseItemList = function($dom) {
  6653. var $list = $dom.find('.timeline');
  6654. return $list.find([
  6655. '.log-user-mylist-add',
  6656. '.log-user-uad-advertise',
  6657. '.log-user-video-upload',
  6658. '.log-user-video-review',
  6659. '.log-mylist-added-video',
  6660. '.log-community-video-upload',
  6661. '.log-user-video-round-number-of-view-counter',
  6662. '.log-user-video-round-number-of-mylist-counter'
  6663. ].join(', '));
  6664. };
  6665.  
  6666. var ownerReg = /\/(community|user|channel)\/((co|ch)?\d+)\??/;
  6667. var parseNicorepoItem = function(src) {
  6668. var
  6669. $item = $(src), $title = $item.find('.log-content .log-target-info a'),
  6670. id = $title.attr('href').split('/').reverse()[0].replace(/\?.*$/, ''), title = $title.text(),
  6671. duration = '--:--',
  6672. viewCnt = '-',
  6673. resCnt = '-',
  6674. mylistCnt = '-',
  6675. postedAt = WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M:%S', new Date($item.find('.log-footer-date time').attr('datetime'))),
  6676. thumbnail = $item.find('.log-target-thumbnail .video').attr('data-src'),
  6677. description_short = $.trim($item.find('.log-body').text()).replace(/(しました|されました)。/g, ''),
  6678. $owner = $item.find('.author-user, .author-community'),
  6679. ownerPage = $owner.attr('href'),
  6680. ownerMatch = ownerReg.exec(ownerPage),
  6681. ownerName = $owner.text(),
  6682. ownerId = (ownerMatch !== null && ownerMatch.length >= 3) ? ownerMatch[2] : null,
  6683. ownerIcon = $item.find('.log-author img').attr('data-src'),
  6684. mylistComment = $item.find('.log-content .log-subdetails').text()
  6685. ;
  6686.  
  6687. $item.removeClass('log').removeClass('passive').removeClass('first');
  6688. if (src.className === 'log-mylist-added-video') {
  6689. ownerName = $item.find('.log-body a:first').text();
  6690. ownerPage = $item.find('.log-body a:last').attr('href');
  6691. }
  6692.  
  6693. var item = new DummyMylistVideo({
  6694. id: id,
  6695. length: duration,
  6696. mylist_counter: mylistCnt,
  6697. view_counter: viewCnt,
  6698. num_res: resCnt,
  6699. first_retrieve: postedAt,
  6700. thumbnail_url: thumbnail,
  6701. mylist_comment: mylistComment,
  6702. title: title,
  6703. _info: {
  6704. first_retrieve: postedAt,
  6705. nicorepo_className: src.className,
  6706. nicorepo_log: [window._.escape(description_short)],
  6707. nicorepo_owner: {
  6708. id: ownerId,
  6709. icon: ownerIcon,
  6710. page: ownerPage,
  6711. name: ownerName
  6712. }
  6713. },
  6714. description_short: description_short
  6715. });
  6716. return item;
  6717. };
  6718.  
  6719. var loadPage = function(baseUrl, result, nextLink, type) {
  6720. var def = new $.Deferred();
  6721. if (nextLink === null) {
  6722. return def.resolve(baseUrl, result, null, null);
  6723. }
  6724. var url = baseUrl;
  6725. if (type === 'offset') {
  6726. url += nextLink ? ('&offset=' + nextLink) : '';
  6727. } else {
  6728. url += nextLink ? ('&last_timeline=' + nextLink) : '';
  6729. }
  6730. console.log('load Url=', url);
  6731.  
  6732. $.ajax({
  6733. url: url,
  6734. timeout: 30000
  6735. }).then(
  6736. function(resp) {
  6737. var $dom = $(resp),
  6738. $nextPageLink = $dom.find('.next-page-link'),
  6739. hasNextPage = $nextPageLink.length > 0;
  6740.  
  6741. parseItemList($dom).each(function() {
  6742. result.push(parseNicorepoItem(this));
  6743. });
  6744.  
  6745. var nextLinkReg = /(last_timeline|offset)=(\d+)/;
  6746. if (hasNextPage) {
  6747. var href = $nextPageLink.attr('href');
  6748. if (nextLinkReg.test(href)) {
  6749. def.resolve(baseUrl, result, RegExp.$2, RegExp.$1);
  6750. } else {
  6751. def.resolve(baseUrl, result, null, null);
  6752. }
  6753. } else {
  6754. def.resolve(baseUrl, result, null, null);
  6755. }
  6756. },
  6757. function() {
  6758. def.reject();
  6759. });
  6760.  
  6761. return def.promise();
  6762. };
  6763.  
  6764. var pipeRequest = function(baseUrl, result, maxPages, callback) {
  6765. var def = new $.Deferred(), p = def.promise();
  6766.  
  6767. for (var i = maxPages; i >= 0; i--) {
  6768. p = p.then(loadPage);
  6769. if (i > 0) p = p.then(Util.Deferred.wait(300));
  6770. }
  6771.  
  6772. p.then(
  6773. function() {
  6774. var uniq = {}, uniq_items = [];
  6775. for (var i = result.rawData.list.length - 1; i >= 0; i--) {
  6776. var item = result.rawData.list[i], id = item.id, mc = item.mylist_comment;
  6777. if (uniq[id + mc]) {
  6778. uniq[id + mc]._info.nicorepo_log.push(item.first_retrieve + ' ' + item._info.nicorepo_log[0].replace(/^.*?さん(の|が)動画(が|を) ?/, ''));
  6779. } else {
  6780. uniq[id + mc] = item;
  6781. }
  6782. }
  6783. for (var v in uniq) {
  6784. uniq_items.unshift(uniq[v]);
  6785. }
  6786. result.rawData.list = uniq_items;
  6787. callback(result);
  6788. }
  6789. );
  6790. def.resolve(baseUrl, result, '', '');
  6791.  
  6792. };
  6793.  
  6794. var request = function(param) {
  6795. var url, nickname, userId, type, baseUrl;
  6796. var def = new $.Deferred;
  6797. try {
  6798. url = '';
  6799. nickname = param.nickname || WatchController.getMyNick();
  6800. userId = param.userId || WatchController.getMyUserId();
  6801. type = param.type || 'user';
  6802. baseUrl = '/my/top/' + type + '?innerPage=1&mode=next_page';
  6803. if (param.userId) {
  6804. baseUrl = '/user/'+ param.userId +'/top?innerPage=1&mode=next_page';
  6805. }
  6806. } catch (e) {
  6807. console.log(e);
  6808. return def.reject({message: 'エラーが発生しました', status: 'fail'});
  6809. }
  6810.  
  6811. var cacheData = Util.Cache.get(baseUrl);
  6812. if (cacheData) {
  6813. return def.resolve(cacheData);
  6814. }
  6815.  
  6816. var
  6817. result = new DummyMylist({
  6818. id: '-10',
  6819. sort: '1',
  6820. default_sort: '1',
  6821. name: getNicorepoTitle(type, param),
  6822. user_id: type === 'owner' ? WatchController.getOwnerId() : userId,
  6823. user_nickname: type === 'owner' ? WatchController.getOwnerName() : nickname
  6824. });
  6825.  
  6826. pipeRequest(baseUrl, result, 2, function(result) {
  6827. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  6828. });
  6829.  
  6830. return def.promise();
  6831. };
  6832.  
  6833. var load = function(callback, param) {
  6834. return request(param)
  6835. .then(function(result) {
  6836. var viewPage = (param && typeof param.page === 'number') ? param.page : 1;
  6837. result.sortItem(param.sort || 1, true);
  6838. result.setPage(viewPage);
  6839. if (typeof result === 'function') { callback(result); }
  6840. return this.done(result);
  6841. }, function() {
  6842. return this.fail({message: 'ニコレポの取得に失敗しました', status: 'fail'});
  6843. });
  6844. };
  6845.  
  6846. var self = {
  6847. load: load,
  6848. REPO_ALL: -10,
  6849. REPO_USER: -11,
  6850. REPO_CHCOM: -12,
  6851. REPO_MYLIST: -13,
  6852. REPO_OWNER: -14,
  6853. loadAll: function(callback, p) {
  6854. p = p || {};
  6855. p.type = 'all';
  6856. return self.load(callback, p);
  6857. },
  6858. loadUser: function(callback, p) {
  6859. p = p || {};
  6860. p.type = 'user';
  6861. return self.load(callback, p);
  6862. },
  6863. loadChCom: function(callback, p) {
  6864. p = p || {};
  6865. p.type = 'chcom';
  6866. return self.load(callback, p);
  6867. },
  6868. loadMylist: function(callback, p) {
  6869. p = p || {};
  6870. p.type = 'mylist';
  6871. return self.load(callback, p);
  6872. },
  6873. loadOwner: function(callback, p) {
  6874. p = p || {};
  6875. p.type = 'owner';
  6876. p.userId = WatchController.getOwnerId();
  6877. return self.load(callback, p);
  6878. }
  6879. };
  6880. WatchItLater.NicorepoVideo = self;
  6881.  
  6882. return self;
  6883. })(w, Util);
  6884.  
  6885.  
  6886.  
  6887. /**
  6888. * ランキングのRSSをマイリストAPIと互換のある形式に変換することで、ダミーマイリストとして表示してしまう作戦
  6889. */
  6890. var VideoRanking = (function(w, Util) {
  6891. if (!window.PlayerApp) return {};
  6892. var $ = w.jQuery;
  6893.  
  6894. var
  6895. genreIdTable = {
  6896. all: -100,
  6897. g_ent2: -110,
  6898. ent: -111,
  6899. music: -112,
  6900. sing: -113,
  6901. play: -114,
  6902. dance: -115,
  6903. vocaloid: -116,
  6904. nicoindies: -117,
  6905. g_life2: -120,
  6906. animal: -121,
  6907. cooking: -122,
  6908. nature: -123,
  6909. travel: -124,
  6910. sport: -125,
  6911. lecture: -126,
  6912. drive: -127,
  6913. history: -128,
  6914. g_politics: -130,
  6915. g_tech: -140,
  6916. science: -141,
  6917. tech: -142,
  6918. handcraft: -143,
  6919. make: -144,
  6920. g_culture2: -150,
  6921. anime: -151,
  6922. game: -152,
  6923. toho: -153,
  6924. imas: -154,
  6925. radio: -155,
  6926. draw: -156,
  6927. g_other: -160,
  6928. are: -161,
  6929. diary: -162,
  6930. other: -163
  6931. // r18: -170
  6932. },
  6933. genreNameTable = {
  6934. all: 'カテゴリ合算',
  6935. g_ent2: 'エンタメ・音楽',
  6936. ent: 'エンターテイメント',
  6937. music: '音楽',
  6938. sing: '歌ってみた',
  6939. play: '演奏してみた',
  6940. dance: '踊ってみた',
  6941. vocaloid: 'VOCALOID',
  6942. nicoindies: 'ニコニコインディーズ',
  6943. g_life2: '生活・一般・スポ',
  6944. animal: '動物',
  6945. cooking: '料理',
  6946. nature: '自然',
  6947. travel: '旅行',
  6948. sport: 'スポーツ',
  6949. lecture: 'ニコニコ動画講座',
  6950. drive: '車載動画',
  6951. history: '歴史',
  6952. g_politics: '政治',
  6953. g_tech: '科学・技術',
  6954. science: '科学',
  6955. tech: 'ニコニコ技術部',
  6956. handcraft: 'ニコニコ手芸部',
  6957. make: '作ってみた',
  6958. g_culture2: 'アニメ・ゲーム・絵',
  6959. anime: 'アニメ',
  6960. game: 'ゲーム',
  6961. toho: '東方',
  6962. imas: 'アイドルマスター',
  6963. radio: 'ラジオ',
  6964. draw: '描いてみた',
  6965. g_other: 'その他',
  6966. are: '例のアレ',
  6967. diary: '日記',
  6968. other: 'その他',
  6969. r18: 'R-18'
  6970. },
  6971. termIdTable = {
  6972. 'hourly': 0,
  6973. 'daily': -1000,
  6974. 'weekly': -2000,
  6975. 'monthly': -3000,
  6976. 'total': -4000
  6977. },
  6978. termNameTable = {
  6979. 'hourly': '(毎時)',
  6980. 'daily': '(24時間)',
  6981. 'weekly': '(週間)',
  6982. 'monthly': '(月間)',
  6983. 'total': '(合計)'
  6984. },
  6985. idTermTable = {},
  6986. idGenreTable = {}
  6987. ;
  6988. if (conf.debugMode) { genreIdTable['r18'] = -170; }
  6989. for (var genre in genreIdTable) { idGenreTable[genreIdTable[genre]] = genre;}
  6990. for (var term in termIdTable ) { idTermTable [termIdTable [term ]] = term; }
  6991.  
  6992. /**
  6993. * ニコニコ動画ランキングのRSSをマイリストAPI互換のデータ形式に変換
  6994. */
  6995. var rss2mylist = function(xml) {
  6996. var
  6997. $x = $(xml),
  6998. title = $x.find('channel title:first').text(),
  6999. $items = $x.find('channel item'),
  7000. result = new DummyMylist({
  7001. name: title,
  7002. id: '-100'
  7003. });
  7004. $items.each(function() {
  7005. var video = parseRssItem($(this));
  7006. var item = new DummyMylistVideo({
  7007. id: video.id,
  7008. length: video.duration,
  7009. mylist_counter: video.mylistCnt,
  7010. view_counter: video.viewCnt,
  7011. num_res: video.resCnt,
  7012. first_retrieve: video.postedAt,
  7013. thumbnail_url: video.thumbnail,
  7014. title: video.title.replace(/^.*?第(\d+)位/, '第000$1位').replace(/^第\d+(\d{3})位/, '第$1位'),
  7015. _info: {first_retrieve: video.postedAt},
  7016. description_short: video.description.substring(0, 50)
  7017. });
  7018. result.push(item);
  7019. });
  7020. return result;
  7021. };
  7022.  
  7023. var parseRssItem = function($item) {
  7024. var
  7025. desc_cdata = $item.find('description').text(),
  7026. $desc = $('<div>' + desc_cdata + '</div>');
  7027. return {
  7028. title : $item.find('title') .text(),
  7029. id : $item.find('guid') .text().split('/').reverse()[0],
  7030. duration : $desc.find('.nico-info-length') .text(),
  7031. viewCnt : $desc.find('.nico-info-total-view') .text().replace(/,/g, ''),
  7032. resCnt : $desc.find('.nico-info-total-res') .text().replace(/,/g, ''),
  7033. mylistCnt : $desc.find('.nico-info-total-mylist').text().replace(/,/g, ''),
  7034. postedAt : $desc.find('.nico-info-date') .text()
  7035. .replace(/(年|月)/g, '-')
  7036. .replace(/:/g, ':')
  7037. .replace(/(日)/g, ''),
  7038. description : $desc.find('.nico-description') .text(),
  7039. thumbnail : $desc.find('.nico-thumbnail img').attr('src')
  7040. };
  7041. };
  7042.  
  7043. var pipeRequest = function(baseUrl, result, page, maxPage) {
  7044. var def = new $.Deferred(), p = def.promise();
  7045.  
  7046. var getPipe = function(result, url, page) {
  7047. return function() {
  7048. console.log('load RSS', url, page);
  7049. return $.ajax({
  7050. url: url,
  7051. timeout: 30000,
  7052. data: {rss: '2.0', lang: 'ja-jp', page: page},
  7053. beforeSend: function(xhr) {
  7054. xhr.setRequestHeader('X-Requested-With', {toString: function(){ return ''; }});
  7055. }
  7056. }).then(function(resp) {
  7057. var res = rss2mylist(resp);
  7058. for (var i = 0, len = res.rawData.list.length; i < len; i++) {
  7059. result.push(res.rawData.list[i]);
  7060. }
  7061. });
  7062. };
  7063. };
  7064.  
  7065. for (var i = page; i <= maxPage; i++) {
  7066. p = p.then(getPipe(result, baseUrl, i));
  7067. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  7068. }
  7069.  
  7070. def.resolve();
  7071.  
  7072. return p;
  7073. };
  7074.  
  7075. var CACHE_TIME = 1000 * 60 * 30;
  7076. var request = function(baseUrl, page, maxPage) {
  7077. var def = new $.Deferred();
  7078. var cacheData = Util.Cache.get(baseUrl);
  7079. if (cacheData) {
  7080. return def.resolve(cacheData);
  7081. }
  7082.  
  7083. var result = new DummyMylist({
  7084. name: '総合ランキング',
  7085. id: '-100'
  7086. });
  7087.  
  7088. pipeRequest(baseUrl, result, page, maxPage).then(
  7089. function() {
  7090. def.resolve(Util.Cache.set(baseUrl, result, CACHE_TIME));
  7091. },
  7092. function() {
  7093. def.reject();
  7094. });
  7095.  
  7096. return def.promise();
  7097. };
  7098.  
  7099. var parseParam = function(param) {
  7100. var
  7101. id = parseInt(param.id || -100, 10),
  7102. genreId = getGenreId(id),
  7103. termId = getTermId(id),
  7104. category = idGenreTable[genreId] || 'all', type = 'fav', term = 'daily', lang= 'ja-jp',
  7105. viewPage = (param && typeof param.page === 'number') ? param.page : 1,
  7106. genreName = genreNameTable[category] || genreNameTable['all'],
  7107. maxRssPage = 1, sort = param.sort || '4';
  7108.  
  7109. term = idTermTable[termId] || idTermTable[0];
  7110. maxRssPage = (category === 'all' && term !== 'hourly') ? 3 : 1;
  7111. return {
  7112. genreId: genreId,
  7113. genreName: genreName,
  7114. category: category,
  7115. type: type,
  7116. term: term,
  7117. lang: lang,
  7118. viewPage: viewPage,
  7119. sort: sort,
  7120. maxRssPage: maxRssPage,
  7121. baseUrl:
  7122. '/ranking/'+ type +'/'+ term + '/'+ category //+'?rss=2.0&lang=' + lang
  7123. };
  7124. };
  7125.  
  7126. var loadRanking = function(param) {
  7127. var p = parseParam(param);
  7128. return request(p.baseUrl, 1, p.maxRssPage)
  7129. .then(function(result) {
  7130. result.name = p.genreName;
  7131. result.setPage(p.viewPage);
  7132.  
  7133. this.done(result);
  7134. });
  7135. };
  7136.  
  7137. var load = function(onload, param) {
  7138. var p = parseParam(param);
  7139. return request(p.baseUrl, 1, p.maxRssPage)
  7140. .then(function(result) {
  7141. result.name = p.genreName;
  7142. result.setPage(p.viewPage);
  7143.  
  7144. if (typeof onload === 'function') {
  7145. onload(result);
  7146. }
  7147. return this.done(result);
  7148. }, function() {
  7149. return this.fail({message: 'ランキングの取得に失敗しました', status: 'fail'});
  7150. });
  7151. };
  7152.  
  7153. var getTermId = function(t) {
  7154. if (typeof t === 'string') {
  7155. return termIdTable[t] || 0;
  7156. } else
  7157. if (typeof t === 'number'){
  7158. return (t - (t % 1000)) % 10000;
  7159. }
  7160. return 0;
  7161. };
  7162. var getGenreId = function(g, term) {
  7163. if (typeof g === 'string') {
  7164. return (genreIdTable[g] || 0) + getTermId(term);
  7165. } else
  7166. if (typeof g === 'number'){
  7167. return g % 1000;
  7168. } else {
  7169. return genreIdTable;
  7170. }
  7171. };
  7172. var getGenreName = function(g) {
  7173. if (typeof g === 'number' || (typeof g === 'string' && g.match(/^-?[0-9]+$/))) {
  7174. g = g % 1000;
  7175. var genre = idGenreTable[g];
  7176. return genreNameTable[genre];
  7177. } else
  7178. if (typeof g === 'string') {
  7179. return genreNameTable[g];
  7180. } else {
  7181. return genreNameTable;
  7182. }
  7183. };
  7184. var getCategory = function(g) {
  7185. if (typeof g === 'number') {
  7186. g = g % 1000;
  7187. return idGenreTable[g - (g %10)];
  7188. } else
  7189. if (typeof g === 'string') {
  7190. g = genreIdTable[g];
  7191. return idGenreTable[g - (g %10)];
  7192. } else {
  7193. return 'all';
  7194. }
  7195. };
  7196.  
  7197. var self = {
  7198. load: load,
  7199. getTermId: getTermId,
  7200. getGenreId: getGenreId,
  7201. getGenreName: getGenreName,
  7202. getCategory: getCategory
  7203. };
  7204. WatchItLater.VideoRanking = self;
  7205. return self;
  7206. })(w, Util);
  7207.  
  7208.  
  7209.  
  7210. /**
  7211. * チャンネル動画一覧をマイリストAPIと互換のある形式で返すことで、ダミーマイリストとして表示してしまう作戦
  7212. */
  7213. var ChannelVideoList = (function(w, Util){
  7214. if (!window.PlayerApp) return {};
  7215. var
  7216. CACHE_TIME = 1000 * 60 * 1, MAX_PAGE = 3,
  7217. getPipe = function(baseUrl, result, page) {
  7218. return function(hasPage) {
  7219. var def = new $.Deferred();
  7220. if (!hasPage) return def.resolve(hasPage);
  7221. var url = baseUrl + '?page=' + page;
  7222. console.log('load page', url);
  7223.  
  7224. $.ajax({url: url, timeout: 30000}).then(function(resp) {
  7225. var hasNextPage = parseItems(resp, result);
  7226. def.resolve(hasNextPage);
  7227. }, function(err) {
  7228. def.reject(err);
  7229. });
  7230. return def.promise();
  7231. };
  7232. },
  7233. pipeRequest = function(baseUrl, result) {
  7234. var def = new $.Deferred(), p = def.promise();
  7235.  
  7236. var maxPage = MAX_PAGE;
  7237. for (var i = 1; i <= maxPage; i++) {
  7238. p = p.then(getPipe(baseUrl, result, i));
  7239. if (i < maxPage) { p = p.then(Util.Deferred.wait(300)); }
  7240. }
  7241.  
  7242. p.then(function() {
  7243. this.done(result);
  7244. });
  7245.  
  7246. def.resolve(true);
  7247. return p;
  7248. },
  7249. load = function(callback, params) {
  7250. var myId, url, id, ownerName, def = new $.Deferred();
  7251. try{
  7252. id = params.id.toString().replace(/^ch/, '');
  7253. ownerName = params.ownerName;
  7254. url = 'http://ch.nicovideo.jp/channel/ch'+ id + '/video';
  7255. myId = WatchController.getMyUserId();
  7256. } catch (e) {
  7257. console.log(e);
  7258. throw { message: 'エラーが発生しました', status: 'fail'};
  7259. }
  7260.  
  7261. var CACHE_KEY = 'ch-' + id, cacheData = Util.Cache.get(CACHE_KEY);
  7262. if (cacheData) {
  7263. if (typeof callback === 'function') {
  7264. setTimeout(function() { callback(cacheData); } , 0);
  7265. }
  7266. return def.resolve(cacheData);
  7267. }
  7268.  
  7269.  
  7270. var result = new DummyMylist({
  7271. id: 'ch' + id,
  7272. sort: '1',
  7273. name: ownerName + 'の動画',
  7274. user_id: myId,
  7275. user_name: 'ニコニコ動画'
  7276. });
  7277.  
  7278. pipeRequest(url, result).then(function() {
  7279. Util.Cache.set(CACHE_KEY, result, CACHE_TIME);
  7280. if (typeof callback === 'function') callback(result);
  7281. def.resolve(result);
  7282. });
  7283.  
  7284. return def.promise();
  7285. },
  7286. parseItems = function(html, result) {
  7287. var $html = $(html), $list = $html.find('.contents_list .item');
  7288. var hasNextPage = false;
  7289. $list.each(function() {
  7290. var $item = $(this);
  7291. var id = $item.find('.title a').attr('href').split('/').reverse()[0];
  7292. var $counts = $item.find('.counts'), first_retrieve = $item.find('.time var').attr('title');
  7293. w.$item = $item;
  7294. result.push(new DummyMylistVideo({
  7295. id: id,
  7296. length: $item.find('.length').text(),
  7297. mylist_counter: $counts.find('.mylist var').text().split(',').join(''),
  7298. view_counter: $counts.find('.view var').text().split(',').join(''),
  7299. num_res: $counts.find('.comment var').text().split(',').join(''),
  7300. first_retrieve: first_retrieve,
  7301. thumbnail_url: $item.find('.lazyimage').data('original'),
  7302. title: $item.find('.title').text().trim(),
  7303. _info: {first_retrieve: first_retrieve, is_channel: true},
  7304. description_short: $item.find('.description').text().trim()
  7305. }));
  7306. });
  7307. if ($html.find('.pager .next:not(.disabled)').length > 0) {
  7308. hasNextPage = true;
  7309. }
  7310. return hasNextPage;
  7311. },
  7312. loadOwnerVideo = function(callback) {
  7313. if (!WatchController.isChannelVideo()) {
  7314. throw {message: 'チャンネル情報の取得に失敗しました', status: 'fail'};
  7315. }
  7316. var params = {
  7317. id: WatchController.getOwnerId(),
  7318. ownerName: WatchController.getOwnerName()
  7319. };
  7320. var def = new $.Deferred();
  7321. load(callback, params).then(function(result) {
  7322. if (typeof callback === 'function') callback(result);
  7323. def.resolve(result);
  7324. }, function() {
  7325. def.reject({message: 'チャンネル動画の取得に失敗しました', status: 'fail'});
  7326. });
  7327. return def.promise();
  7328. };
  7329.  
  7330. var self = {
  7331. load: load,
  7332. loadOwnerVideo: loadOwnerVideo
  7333. };
  7334. WatchItLater.ChannelVideo = self;
  7335. return self;
  7336. })(w, Util);
  7337.  
  7338.  
  7339.  
  7340. var niconicodoRedirect = function() {
  7341. // www.nicovideo.jp/stampを watchにパラメータを中継するためのクッションページとして使う。
  7342. // watchと同じドメインならどこでもいいけど、ここはDBアクセスもなさそうな静的ページため採用
  7343. var hash = location.hash.toString();
  7344. if (hash.indexOf('#json={') !== 0) {
  7345. return;
  7346. }
  7347. console.log('%cNiconicodo redirect', 'background: lightgreen;');
  7348.  
  7349. LocationHashParser.initialize();
  7350. var blankWatchId = 'sm20353707';
  7351. var redirectWatchId = LocationHashParser.getValue('redirectWatchId');
  7352. // 見た目が残念なので消す
  7353. document.body.innerHTML = '';
  7354.  
  7355. window.sessionStorage.setItem('watchItLater_redirectedHash', location.hash);
  7356. var redirectTo = '/watch/' + (redirectWatchId ? redirectWatchId : blankWatchId);
  7357. location.replace(redirectTo);
  7358. };
  7359.  
  7360.  
  7361. /**
  7362. * GINZAwatch上でのあれこれ
  7363. * 無計画に増築中
  7364. *
  7365. * watch.jsを解析すればわかる
  7366. *
  7367. */
  7368. var ZeroFunc = function(w) { // Zero Watch
  7369. var
  7370. video_id = '', watch_id = '',
  7371. // WatchApp = w.WatchApp, WatchJsApi = w.WatchJsApi,
  7372. isTouchActive = false,
  7373. console = conf.debugMode ? window.console : {log: _.noop, trace: _.noop, time: _.noop, timeEnd: _.noop},
  7374. watch = WatchApp.ns.init,
  7375. watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  7376.  
  7377. console.log('%cGinza', 'background: lightgreen;');
  7378.  
  7379. /**
  7380. * ゆっくり再生(スロー再生)メニュー
  7381. */
  7382. var Yukkuri = (function($, conf, w) {
  7383. var self, $content = null, $button = null, timer = null, cnt = 0, isActive = false;
  7384.  
  7385. function createDom() {
  7386. $content = $('<div id="yukkuriPanel" />');
  7387. $button = $('<button>yu</button>').addClass('yukkuriButton').attr({title: 'ゆっくり(スロー再生)'});
  7388. $button.click(function() {
  7389. toggleActive();
  7390. });
  7391. $content.append($button);
  7392.  
  7393. $('body').append($content);
  7394. }
  7395.  
  7396. function show() {
  7397. if ($content === null) {
  7398. createDom();
  7399. }
  7400. updateView();
  7401. $content.show();
  7402. }
  7403. function hide() {
  7404. $content.hide();
  7405. }
  7406. function updateView() {
  7407. $button.toggleClass('active', isActive);
  7408. }
  7409.  
  7410. function start() {
  7411. if (timer !== null) {
  7412. clearInterval(timer);
  7413. }
  7414. isActive = true;
  7415. updateView();
  7416. timer = setInterval(function() {
  7417. var v = cnt++ % 4;
  7418. if (v === 0) {
  7419. WatchController.play();
  7420. } else
  7421. if (v === 1) {
  7422. WatchController.pause();
  7423. }
  7424. }, 20);
  7425. }
  7426. function stop() {
  7427. if (timer !== null) {
  7428. clearInterval(timer);
  7429. timer = null;
  7430. }
  7431. isActive = false;
  7432. updateView();
  7433. WatchController.pause();
  7434. }
  7435.  
  7436. function toggleActive() {
  7437. if (isActive) {
  7438. stop();
  7439. } else {
  7440. start();
  7441. }
  7442. return isActive;
  7443. }
  7444.  
  7445. self = {
  7446. show: show,
  7447. hide: hide,
  7448. start: start,
  7449. stop: stop
  7450. };
  7451. return self;
  7452. })($, conf, w);
  7453.  
  7454. function onWindowResizeEnd() {
  7455. setTimeout(function() {
  7456. EventDispatcher.dispatch('onWindowResizeEnd');
  7457. }, 1000);
  7458. }
  7459.  
  7460. /**
  7461. * デフォルトの市場貼付ボタンはなぜかページの一番上までスクロールするという意地悪な仕様だが、
  7462. * こっちはがんばって見やすい位置に調整して開く
  7463. */
  7464. function ichibaSearch(word, shopCode) {
  7465. var wait = 10, opened = false;
  7466. //shopCode = shopCode || 'az'; // az = amazon
  7467. var search = function() {
  7468. if ($('#ichibaConsole').is(':visible')) {
  7469. setTimeout(function() {
  7470. w.WatchApp.ns.util.WindowUtil.scrollFitMinimum('#ichibaConsole', 300);
  7471. }, 1000);
  7472. if (!word) {
  7473. return;
  7474. }
  7475. if ($('#ichiba_search_form_query').is(':visible')) {
  7476. $('#ichiba_search_form_query').val(word);
  7477. w.ichiba.search(shopCode, 0, 'all');
  7478. setTimeout(function() {$('#ichiba_search_form_query').focus();}, 1000);
  7479. } else {
  7480. if (!opened) {
  7481. if(shopCode) { w.ichiba.showRelatedTagItems(shopCode, 0, 'all'); }
  7482. opened = true;
  7483. }
  7484. if (wait-- > 0) setTimeout(search, 1000);
  7485. }
  7486. } else {
  7487. if (wait-- > 0) setTimeout(search, 1000);
  7488. }
  7489. };
  7490. search();
  7491. w.ichiba.showConsole();
  7492. }
  7493. WatchController.ichibaSearch = ichibaSearch;
  7494.  
  7495. function initVideoCounter() {
  7496. var
  7497. playerAreaConnector = watch.PlayerInitializer.playerAreaConnector,
  7498. counter = {mylistCount: 0, viewCount: 0, commentCount: 0},
  7499. blinkItem = function($elm) {
  7500. $elm.removeClass('animateBlink').addClass('blink');
  7501. setTimeout(function() {
  7502. $elm.addClass('animateBlink').removeClass('blink');
  7503. $elm = null;
  7504. }, 500);
  7505. };
  7506. var setVideoCounter = function(watchId, title) {
  7507. var $tpl = $(
  7508. '<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>'
  7509. );
  7510. assignVideoCountToDom($tpl, counter);
  7511.  
  7512. if ((conf.popupViewCounter === 'always') ||
  7513. (conf.popupViewCounter === 'full' && $('body').hasClass('full_with_browser'))
  7514. ) {
  7515. Popup.show(
  7516. $('<div/>')
  7517. .append(
  7518. $('<a/>')
  7519. .text(window._.unescape(title))
  7520. .attr('href', 'http://nico.ms/' + watchId)
  7521. )
  7522. .html() +
  7523. '<br/><span style="margin-left:10px; font-size: 90%;">'+ $tpl.html() + '</span>'
  7524. );
  7525. }
  7526. $('#trueBrowserFullShield').html([
  7527. '<img class="ownerIcon" src="', WatchController.getOwnerIcon(), '">',
  7528. '<div class="title">', title, '</div>',
  7529. '<p class="postedAt">',$('.videoPostedAt:last').text(), '</p>',
  7530. '<p class="videoCounter">', $tpl.html(), '</p>',
  7531. ''].join(''))
  7532. .toggleClass('favorite', WatchController.isFavoriteOwner())
  7533. .find('img').attr('title', WatchController.getOwnerName());
  7534.  
  7535. if (conf.headerViewCounter) {
  7536. var vc = $('#videoCounter');
  7537. if (vc.length < 1) {
  7538. var li = $('<li></li>')[0];
  7539. li.id = 'videoCounter';
  7540. $('#siteHeaderLeftMenu').after(li);
  7541. vc = $('#videoCounter');
  7542. }
  7543. vc.empty().append($tpl);
  7544. }
  7545. };
  7546.  
  7547. playerAreaConnector.addEventListener('onWatchCountUpdated', function(c) {
  7548. var diff = c - counter.viewCount;
  7549. if (diff === 0) return;
  7550. counter.viewCount = c;
  7551. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'viewCount', diff);
  7552. });
  7553. playerAreaConnector.addEventListener('onCommentCountUpdated', function(c) {
  7554. var diff = c - counter.commentCount;
  7555. if (diff === 0) return;
  7556. counter.commentCount = c;
  7557. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'commentCount', diff);
  7558. });
  7559. playerAreaConnector.addEventListener('onMylistCountUpdated', function(c) {
  7560. var diff = c - counter.mylistCount;
  7561. if (diff === 0) return;
  7562. counter.mylistCount = c;
  7563. EventDispatcher.dispatch('onVideoCountUpdated', counter, 'mylistCount', diff);
  7564. });
  7565.  
  7566. EventDispatcher.addEventListener('onWatchInfoReset', function(watchInfoModel){
  7567. counter.mylistCount = watchInfoModel.mylistCount;
  7568. counter.viewCount = watchInfoModel.viewCount;
  7569. counter.commentCount = watchInfoModel.commentCount;
  7570.  
  7571. setVideoCounter(watchInfoModel.v, watchInfoModel.title);
  7572. });
  7573. EventDispatcher.addEventListener('onVideoCountUpdated', function(c, type, diff) {
  7574. var $target = $('.sidePanel .videoInfo, #trueBrowserFullShield, #videoCounter');
  7575. assignVideoCountToDom($target, c);
  7576. $target.find('.' + type + 'Diff').text(diff).toggleClass('down', diff < 0);
  7577. blinkItem($target.find('.' + type + ', .' + type + 'Diff'));
  7578. });
  7579.  
  7580. } //
  7581.  
  7582. var isFirst = true;
  7583. function onVideoInitialized() {
  7584. watch = WatchApp.ns.init;
  7585. AnchorHoverPopup.hidePopup().updateNow();
  7586. tagv = watch.TagInitializer.tagViewController;
  7587. WatchCounter.add();
  7588.  
  7589. if (isFirst) {
  7590. if (conf.autoPlayIfWindowActive === 'yes' && w.document.hasFocus()) {
  7591. // ウィンドウがアクティブの時だけ自動再生する。 複数タブ開いてるときは便利
  7592. setTimeout(function() { WatchController.play(); }, 2000);
  7593. }
  7594.  
  7595. if (isFirst && conf.commentVisibility !== 'visible') {
  7596. if (conf.commentVisibility === 'hidden') {
  7597. console.log('comment off');
  7598. WatchController.commentVisibility(false);
  7599. } else {
  7600. console.log('last state', conf.lastCommentVisibility);
  7601. WatchController.commentVisibility(conf.lastCommentVisibility === 'visible');
  7602. }
  7603. }
  7604. EventDispatcher.dispatch('onFirstVideoInitialized');
  7605. }
  7606.  
  7607. EventDispatcher.dispatch('onVideoInitialized', isFirst);
  7608. isFirst = false;
  7609. } //
  7610.  
  7611. function onVideoChangeStatusUpdated(isChanging) {
  7612. AnchorHoverPopup.hidePopup();
  7613. if (isChanging) {
  7614. $('.sidePanel .sideVideoInfo').removeClass('show');
  7615. }
  7616. if ((conf.enableAutoPlaybackContinue || conf.debugMode) && watch.PlayerInitializer.noUserOperationController.autoPlaybackModel._isAutoPlayback) {
  7617. watch.PlayerInitializer.noUserOperationController.autoPlaybackModel.setCount(0);
  7618. }
  7619. EventDispatcher.dispatch('onVideoChangeStatusUpdated', isChanging);
  7620. }
  7621.  
  7622. var $sideInfoPanelTemplate = $([
  7623. '<div class="sideVideoInfoInner">',
  7624.  
  7625. '<div class="videoTitleContainer"><h3 class="videoTitle"></h3></div>',
  7626. '<div class="videoOwnerInfoContainer">',
  7627. '<div class="channelIconContainer"><a target="_blank" class="channelIconLink">',
  7628. '<img class="channelIcon"></a>',
  7629. '<span class="channelName">提供: ',
  7630. '<a class="showOtherVideos" target="_blank"><span class="channelNameInner"></span></a></span>',
  7631. '</span>',
  7632. '</div>',
  7633. '<div class="userIconContainer"><a target="_blank" class="userIconLink">',
  7634. '<img class="userIcon"></a>',
  7635. '<span class="userName">投稿者: ',
  7636. '<span class="userNameInner notPublic"></span>',
  7637. '<span class="isPublic"><a class="showOtherVideos"><span class="userNameInner"></span></a></span>',
  7638. '</span>',
  7639. '</div>',
  7640. '</div>',
  7641. '<div class="videoInfo">',
  7642. '<span class="videoPostedAt"></span>',
  7643. '<ul class="videoStats">',
  7644. '<li style="position: relative;">再生: <span class="viewCountDiff videoCountDiff"></span><span class="videoCount viewCount"></span></li>',
  7645. '<li style="position: relative;">コメント: <span class="commentCountDiff videoCountDiff"></span><span class="videoCount commentCount"></span></li>',
  7646. '<li style="position: relative;">マイリスト: <span class="mylistCountDiff videoCountDiff"></span><span class="videoCount mylistCount"></span></li>',
  7647. '</ul>',
  7648. '</div>',
  7649.  
  7650.  
  7651. '<div class="videoThumbnailContainer" style="display: none;">',
  7652. '<img class="videoThumbnailImage">',
  7653. '</div>',
  7654. '<div class="videoDetails">',
  7655. '<div class="videoDescription">',
  7656. '<div class="videoDescriptionInner">',
  7657. '</div>',
  7658. '</div>',
  7659. '</div>',
  7660. '</div>',
  7661. ''].join(''));
  7662.  
  7663.  
  7664. // - 左パネル乗っ取る
  7665. function initLeftPanel($, conf, w) {
  7666.  
  7667. var $tab = $([
  7668. '<ul id="leftPanelTabContainer">',
  7669. '<li class="tab ichiba" data-selection="ichiba" >市場</li>',
  7670. '<li class="tab videoInfo" data-selection="videoInfo">情報</li>',
  7671. '</ul>'].join(''));
  7672.  
  7673. var
  7674. $sidePanel = $('<div id="leftPanel" />').addClass('sidePanel'),
  7675. $infoPanel = $('<div/>').attr({'id': 'leftVideoInfo', 'class': 'sideVideoInfo sidePanelInner'}),
  7676. $ichibaPanel = $('<div/>').attr({'id': 'leftIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7677. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel);
  7678. $('#playerTabWrapper').after($sidePanel);
  7679.  
  7680. var
  7681. onTabSelect = function(e) {
  7682. e.preventDefault();
  7683. AnchorHoverPopup.hidePopup();
  7684. var selection = $(e.target).attr('data-selection');
  7685. if (typeof selection === 'string') {
  7686. conf.setValue('lastLeftTab', selection);
  7687. changeTab(selection);
  7688. }
  7689. },
  7690. changeTab = function(selection) {
  7691. $sidePanel.removeClass('videoInfo ichiba').addClass(selection);
  7692. if (selection === 'ichiba') {
  7693. resetIchiba(false);
  7694. }
  7695. },
  7696. lastIchibaVideoId = '',
  7697. resetIchiba = function(force) {
  7698. var videoId = watchInfoModel.id;
  7699. if (lastIchibaVideoId === videoId && !force) {
  7700. return;
  7701. }
  7702. lastIchibaVideoId = videoId;
  7703. resetSideIchibaPanel($ichibaPanel, true);
  7704. },
  7705. resetScroll = function() {
  7706. $(this).animate({scrollTop: 0}, 600);
  7707. };
  7708.  
  7709. $infoPanel .on('dblclick', resetScroll);
  7710. $ichibaPanel.on('dblclick', resetScroll);
  7711.  
  7712. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7713. changeTab(conf.lastLeftTab);
  7714.  
  7715. var refreshPanel = function(isFirst) {
  7716. if (isFirst) { return; }
  7717.  
  7718. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  7719. if ($ichibaPanel.is(':visible')) {
  7720. resetIchiba(true);
  7721. }
  7722. };
  7723. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  7724. refreshPanel();
  7725.  
  7726. } // end of initLeftPanel
  7727.  
  7728. function initRightPanel($, conf, w) {
  7729. var $rightPanel = $('#playerTabWrapper').addClass('sidePanel');
  7730. initRightPanelVerticalTab($rightPanel);
  7731. initRightPanelHorizontalTab($, conf, w);
  7732. var $playerTabWrapper = $rightPanel, wideCss = null;
  7733. var
  7734. createWideCommentPanelCss = function (targetWidth) {
  7735. var px = targetWidth - $rightPanel.outerWidth();
  7736. var elms = [
  7737. '#playerTabWrapper', //'#playerTabWrapper',
  7738. '#commentDefaultHeader',
  7739. '#playerCommentPanel .commentTable',
  7740. '#playerCommentPanel .commentTable .commentTableContainer'
  7741. ];
  7742. var css = [
  7743. 'body.videoExplorer #content.w_adjusted #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7744. 'body:not(.full_with_browser) .w_wide #playerTabWrapper { width: ', targetWidth,'px; }\n',
  7745. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }\n', // 960 + 140
  7746. 'body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }\n\n' // 1186 + 140
  7747. ];
  7748. for (var v in elms) {
  7749. var $e = $(elms[v]), newWidth = $e.width() + px;
  7750. css.push([
  7751. '.w_wide #playerTabWrapper ', elms[v],
  7752. ' , body.videoExplorer #content.w_adjusted ',
  7753. elms[v], '\n{ width: ', newWidth,'px !important; }\n\n'
  7754. ].join(''));
  7755. }
  7756. wideCss = addStyle(css.join(''), 'wideCommentPanelCss');
  7757. console.log(css.join(''));
  7758. },
  7759. toggleWide = function(v) {
  7760. $('#content').toggleClass('w_wide', v);
  7761. EventDispatcher.dispatch('onWindowResizeEnd');
  7762. };
  7763.  
  7764. var wideCommentPanelCss = Util.here(function() {/*
  7765. body.videoExplorer #content.w_adjusted #playerTabWrapper { width: 420px; }
  7766. body:not(.full_with_browser) .w_wide #playerTabWrapper { width: 420px; }
  7767.  
  7768. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea { width: 1100px; }
  7769. body:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea.size_normal { width: 1326px; }
  7770.  
  7771. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerTabWrapper,
  7772. body.videoExplorer #content.w_adjusted #playerTabWrapper
  7773. { width: 420px !important; }
  7774.  
  7775. body:not(.full_with_browser) .w_wide #playerTabWrapper #commentDefaultHeader,
  7776. body.videoExplorer #content.w_adjusted #commentDefaultHeader
  7777. { width: 408px !important; }
  7778.  
  7779. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable,
  7780. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable
  7781. { width: 406px !important; }
  7782.  
  7783. body:not(.full_with_browser) .w_wide #playerTabWrapper #playerCommentPanel .commentTable .commentTableContainer,
  7784. body.videoExplorer #content.w_adjusted #playerCommentPanel .commentTable .commentTableContainer
  7785. { width: 406px !important; }
  7786. */});
  7787. addStyle(wideCommentPanelCss, 'wideCommentPanelCss');
  7788.  
  7789. EventDispatcher.addEventListener('on.config.wideCommentPanel', toggleWide);
  7790. toggleWide(!!conf.wideCommentPanel);
  7791.  
  7792. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  7793.  
  7794. //EventDispatcher.dispatch('onWindowResizeEnd');
  7795. //createWideCommentPanelCss(420);
  7796.  
  7797. var $div = $([
  7798. '<div id="sharedNgSettingContainer" style="display: none;">NG共有: ',
  7799. '<select id="sharedNgSetting">',
  7800. '<option value="HIGH">高</option>',
  7801. '<option value="MIDDLE">中</option>',
  7802. '<option value="LOW">低</option>',
  7803. '<option value="NONE">無</option>',
  7804. '</select>',
  7805. '</div>',
  7806. ''].join('')), $ngs = $div.find('select');
  7807.  
  7808. $ngs
  7809. .val(watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get().ngScoringFilteringLevel)
  7810. .on('change', function() {
  7811. var val = this.value;
  7812. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({ngScoringFilteringLevel: this.value});
  7813. });
  7814. $('#commentDefaultHeader').append($div);
  7815.  
  7816. EventDispatcher.addEventListener('on.config.enableSharedNgSetting', function(newValue, oldValue) {
  7817. if (newValue) {
  7818. $div.show();
  7819. } else {
  7820. $div.hide();
  7821. }
  7822. });
  7823. if (conf.enableSharedNgSetting) { $div.show(); }
  7824. });
  7825.  
  7826. if (conf.removeCommentPanelHoverEvent) {
  7827. $("#commentDefault").find(".commentTableContainerInner") .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7828. $('#playerCommentPanel .section .commentTable .commentTableContainer') .off('mouseover').off('mouseenter').off('mouseleave').off('mouseout');
  7829. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7830. .off('contextmenu')
  7831. .on('contextmenu', '.cell',
  7832. $.proxy(Util.Closure.commentPanelContextMenu(), watch.PlayerInitializer.commentPanelViewController.commentContent)
  7833. );
  7834. }
  7835. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function(isChanging) {
  7836. if (isChanging) {
  7837. watch.PlayerInitializer.commentPanelViewController.commentContent.$commentTableContainer
  7838. .find('.cell').off();
  7839. }
  7840. });
  7841. } // end initRightPanel
  7842.  
  7843. function initRightPanelHorizontalTab($, conf, w) {
  7844. } //
  7845.  
  7846. function initRightPanelVerticalTab($sidePanel) {
  7847. if (!conf.rightPanelJack) { return; }
  7848.  
  7849. var $tab = $([
  7850. '<ul id="sidePanelTabContainer">',
  7851. '<li class="tab comment" data-selection="w_comment" >コメント</li>',
  7852. '<li class="tab videoInfo" data-selection="w_videoInfo">動画情報</li>',
  7853. '<li class="tab ichiba" data-selection="w_ichiba" >ニコニコ市場</li>',
  7854. '<li class="tab review" data-selection="w_review" >レビュー</li>',
  7855. '</ul>'].join(''));
  7856.  
  7857. var $infoPanel = $('<div/>').attr({'id': 'rightVideoInfo', 'class': 'sideVideoInfo sidePanelInner'});
  7858. var $ichibaPanel = $('<div/>').attr({'id': 'rightIchibaPanel', 'class': 'sideIchibaPanel sidePanelInner'});
  7859. var $reviewPanel = $('<div/>').attr({'id': 'rightReviewPanel', 'class': 'sideReviewPanel sidePanelInner'});
  7860. $sidePanel.append($tab).append($infoPanel).append($ichibaPanel).append($reviewPanel);
  7861.  
  7862. var
  7863. onTabSelect = function(e) {
  7864. e.preventDefault();
  7865. AnchorHoverPopup.hidePopup();
  7866. var selection = $(e.target).attr('data-selection');
  7867. if (typeof selection === 'string') {
  7868. if (WatchController.isSearchMode()) {
  7869. conf.setValue('lastRightTabInExplorer', selection);
  7870. } else {
  7871. conf.setValue('lastRightTab', selection);
  7872. }
  7873. changeTab(selection);
  7874. }
  7875. },
  7876. $videoReview = $('#videoReview'),
  7877. toggleReview = function(f) {
  7878. if (f) {
  7879. $reviewPanel.append($videoReview);
  7880. } else {
  7881. $('#playerBottomAd').after($videoReview);
  7882. }
  7883. },
  7884. changeTab = function(selection) {
  7885. if ($sidePanel.hasClass('w_review') && selection !== 'w_review') {
  7886. toggleReview(false);
  7887. }
  7888. $sidePanel.removeClass('w_videoInfo w_comment w_ichiba w_review').addClass(selection);
  7889. if (selection === 'w_ichiba') {
  7890. resetIchiba(false);
  7891. } else
  7892. if (selection === 'w_review') {
  7893. toggleReview(true);
  7894. } else
  7895. if (selection === 'w_comment') {
  7896. setTimeout(function() {
  7897. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  7898. }, 500);
  7899. }
  7900. return changeTab;
  7901. },
  7902. lastIchibaVideoId = '', resetIchiba = function(force) {
  7903. var videoId = watchInfoModel.id;
  7904. if (lastIchibaVideoId === videoId && !force) {
  7905. return;
  7906. }
  7907. lastIchibaVideoId = videoId;
  7908. resetSideIchibaPanel($ichibaPanel, true);
  7909. },
  7910. resetScroll = function() {
  7911. $(this).animate({scrollTop: 0}, 600);
  7912. };
  7913.  
  7914. $infoPanel .on('dblclick', resetScroll);
  7915. $ichibaPanel.on('dblclick', resetScroll);
  7916. $reviewPanel.on('dblclick', resetScroll);
  7917.  
  7918. $tab.on('click', onTabSelect).on('touchend', onTabSelect);
  7919. changeTab(conf.lastRightTab);
  7920.  
  7921. EventDispatcher.addEventListener('onVideoExplorerOpening', function() {
  7922. changeTab('w_comment');
  7923. });
  7924. EventDispatcher.addEventListener('onVideoExplorerClosing', function() {
  7925. changeTab(conf.lastRightTab);
  7926. });
  7927.  
  7928. var onOuterResize = function() {
  7929. var $body = $('body'), $right = $('#playerTabWrapper');
  7930. if (WatchController.isSearchMode() || $body.hasClass('full_with_browser')) { return; }
  7931. var w = $('#external_nicoplayer').outerWidth(), margin = 124;
  7932. w += $right.is(':visible') ? $right.outerWidth() : 0;
  7933. $('#sidePanelTabContainer').toggleClass('left', (window.innerWidth - w - margin < 0));
  7934. };
  7935. EventDispatcher.addEventListener('onWindowResizeEnd', onOuterResize);
  7936. EventDispatcher.addEventListener('onPlayerAlignmentAreaResize', onOuterResize);
  7937.  
  7938. var refreshPanel = function(isFirst) {
  7939.  
  7940. window.setTimeout(function() {
  7941. $sidePanel
  7942. .toggleClass('reviewEmpty', $('#videoReview').find('.stream').length < 1)
  7943. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  7944. }, 2000);
  7945.  
  7946. if (isFirst) { return; }
  7947.  
  7948. sidePanelRefresh($infoPanel, $ichibaPanel, $sidePanel, $sideInfoPanelTemplate.clone());
  7949. if ($ichibaPanel.is(':visible')) {
  7950. resetIchiba(true);
  7951. }
  7952. };
  7953. EventDispatcher.addEventListener('onVideoInitialized', refreshPanel);
  7954.  
  7955. refreshPanel();
  7956.  
  7957. } // end of initRightPanelVerticalTab
  7958.  
  7959.  
  7960. function assignVideoCountToDom($tpl, count) {
  7961. var addComma = WatchApp.ns.util.StringUtil.addComma;
  7962. $tpl
  7963. .find('.viewCount' ).text(addComma(count.viewCount )).end()
  7964. .find('.commentCount').text(addComma(count.commentCount)).end()
  7965. .find('.mylistCount' ).text(addComma(count.mylistCount ));
  7966. return $tpl;
  7967. } //
  7968.  
  7969. function sidePanelRefresh($sideInfoPanel, $ichibaPanel, $sidePanel, $template) {
  7970. var isFavorite = WatchController.isFavoriteOwner();
  7971. //var h = $sideInfoPanel.innerHeight() - 100;
  7972.  
  7973. $template.find('.videoTitle').html(watchInfoModel.title);
  7974.  
  7975. assignVideoCountToDom($template, watchInfoModel);
  7976. $template.find('.videoPostedAt').text($('.videoPostedAt:last').text());
  7977.  
  7978. var $videoDescription = $template.find('.videoDescription');
  7979.  
  7980. $videoDescription.find('.videoDescriptionInner').append(create$videoDescription(watchInfoModel.description));
  7981.  
  7982. var $userIconContainer = $template.find('.userIconContainer');
  7983. var $channelIconContainer = $template.find('.channelIconContainer');
  7984.  
  7985. var info = WatchController.getOwnerInfo();
  7986. if (info.type === 'channel') {
  7987. if (info.id && info.id !== '0') {
  7988. $channelIconContainer
  7989. .find('.channelIcon')
  7990. .attr({'src': info.icon}).end()
  7991. .find('.channelIconLink')
  7992. .attr({'href': info.page})
  7993. .on('click', Util.Closure.openVideoOwnersVideo()).end()
  7994. .find('.channelNameInner')
  7995. .text(info.name).end()
  7996. .find('.showOtherVideos')
  7997. .attr({'href': info.page})
  7998. .on('click', Util.Closure.openVideoOwnersVideo());
  7999. }
  8000. $userIconContainer.remove();
  8001. } else {
  8002. if (info.id && info.id !== '0') { // ユーザーが退会してたりすると情報が無いのでチェックしてから
  8003. $userIconContainer
  8004. .find('.userIcon')
  8005. .attr({'src': info.icon}).end()
  8006. .find('.userIconLink')
  8007. .attr({'href': info.page})
  8008. .on('click', Util.Closure.openVideoOwnersNicorepo()).end()
  8009. .find('.userNameInner')
  8010. .text(info.name).end()
  8011. .find('.showOtherVideos')
  8012. .attr({'href': info.page + '/video'})
  8013. .on('click', Util.Closure.openVideoOwnersVideo() ).end()
  8014. .toggleClass('isUserVideoPublic', info.isVideoPublic);
  8015. $channelIconContainer.remove();
  8016. } else {
  8017. $userIconContainer.remove();
  8018. $channelIconContainer.remove();
  8019. }
  8020. }
  8021.  
  8022. $sideInfoPanel.find('*').unbind();
  8023.  
  8024. $sidePanel
  8025. .toggleClass('ichibaEmpty', WatchController.isIchibaEmpty());
  8026.  
  8027. $sideInfoPanel
  8028. .empty()
  8029. .scrollTop(0)
  8030. .toggleClass('isFavorite', isFavorite)
  8031. .toggleClass('isChannel', WatchController.isChannelVideo())
  8032. .append($template);
  8033.  
  8034. window.setTimeout(function() {
  8035. $sideInfoPanel.addClass('show');
  8036. $sideInfoPanel = $ichibaPanel = $sidePanel = $template =
  8037. $videoDescription = $userIconContainer =
  8038. $channelIconContainer = null;
  8039. }, 100);
  8040.  
  8041. } // end of sidePanelRefresh
  8042.  
  8043. /**
  8044. * 説明文中の動画リンク類を加工
  8045. */
  8046. function decorateVideoDescriptionLink($description) {
  8047. var watchLinks = [], watchIds = [];
  8048. var videoReg = /\/watch\/((sm|nm|so|)\d+)$/;
  8049. var seigaReg = /seiga\/im(\d+)/;
  8050. $description.find('a').each(function() {
  8051. var url = this.href, text, $this = $(this);
  8052. if (videoReg.test(url)) {
  8053. var watchId = RegExp.$1;
  8054. var $nextButton = $([
  8055. '<div class="nextPlayButton" title="次に再生" onclick="WatchItLater.WatchController.insertVideoToPlaylist(\'', watchId, '\')">次に再生</div>',
  8056. ''].join(''));
  8057. $this.after($nextButton);
  8058.  
  8059. watchLinks.push({id: watchId, $target: $nextButton});
  8060. watchIds.push(watchId);
  8061. } else if (seigaReg.test(url)) {
  8062. var illustId = RegExp.$1;
  8063. var $thumbnail = $([
  8064. '<div class="descriptionThumbnail illust">',
  8065. '<img src="http://lohas.nicoseiga.jp/thumb/',
  8066. illustId,
  8067. 'z" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  8068. '</div>',
  8069. ''].join(''));
  8070. $this.after($thumbnail);
  8071. }
  8072. });
  8073.  
  8074. if (conf.enableDescriptionThumbnail && watchIds.length > 0) {
  8075. var ac = function(s) {
  8076. s = parseInt(s, 10);
  8077. s = s < 1 ? '-' : s;
  8078. return '<span class="count">' + WatchApp.ns.util.StringUtil.addComma(s) + '</span>';
  8079. };
  8080. var onWatchIdInfoReady = function(result) {
  8081. $(watchLinks).each(function(i, watchLink) {
  8082. var id = watchLink.id, $target = watchLink.$target;
  8083. if (result[id]) {
  8084. var info = result[id];
  8085. var $thmb = $([
  8086. '<div class="descriptionThumbnail video">',
  8087. '<img src="', info.thumbnail_url, '" onclick="WatchItLater.WatchController.showLargeThumbnail(this.src);">',
  8088. '<span class="uploadAt">', info.first_retrieve ,' 投稿</span>',
  8089. '<p>', info.title, ' (', info.length, ')</p>',
  8090. '<div class="counterContainer">',
  8091. '<span class="view">再生: ', ac(info.view_counter) ,'</span> ',
  8092. '<span class="comment">コメ: ', ac(info.num_res) ,'</span> ',
  8093. '<span class="mylist">マイ: ', ac(info.mylist_counter),'</span>',
  8094. '</div>',
  8095. '</div>'].join(''));
  8096. $target.after($thmb);
  8097. }
  8098. $target = watchLink = null;
  8099. });
  8100. watchIds = watchLinks = null;
  8101. };
  8102. var onWatchIdInfoFail = function() {
  8103. watchIds = watchLinks = null;
  8104. };
  8105. window.setTimeout(function() {
  8106. window.WatchItLater.loader.videoArrayAPILoader.load(watchIds).then(onWatchIdInfoReady, onWatchIdInfoFail);
  8107. }, 1000);
  8108. } else {
  8109. watchIds = watchLinks = null;
  8110. }
  8111. $description = null;
  8112. }
  8113.  
  8114. /**
  8115. * 動画説明文のクリックイベント類を割り当てる
  8116. */
  8117. function bindDescriptionEvents($description) {
  8118. $description.on('click', function(e) {
  8119. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8120.  
  8121. var elm = e.target;
  8122. if (elm.tagName !== 'A') { return; }
  8123. if (elm.className === 'otherSite') return;
  8124.  
  8125. var $elm = $(elm);
  8126.  
  8127. if (elm.textContent.indexOf('mylist/') === 0) {
  8128. e.preventDefault(); e.stopPropagation();
  8129. var mylistId = elm.textContent.split('/').reverse()[0];
  8130.  
  8131. WatchController.showMylist(mylistId);
  8132. } else
  8133. if (elm.className === 'seekTime') {
  8134. e.preventDefault(); e.stopPropagation();
  8135. var data = $elm.attr('data-seekTime').split(":"),
  8136. vpos = (data[0] * 60 + parseInt(data[1], 10)) * 1000;
  8137. WatchController.vpos(vpos);
  8138. }
  8139. });
  8140. $description.find('.watch').unbind('click');
  8141. $description = null;
  8142. }
  8143.  
  8144. function create$videoDescription(html) {
  8145. var linkmatch = /<a.*?<\/a>/, links = [], n;
  8146. html = html.split('<br />').join(' <br /> ');
  8147. while ((n = linkmatch.exec(html)) !== null) {
  8148. links.push(n);
  8149. html = html.replace(n, ' <!----> ');
  8150. }
  8151.  
  8152. // (htttp://example.com) -> ( htttp://example.com ) にして、 閉じカッコがリンクされるのを抑止
  8153. html = html.replace(/\((https?:\/\/[\x21-\x3b\x3d-\x7e]+)\)/gi, '( $1 )');
  8154. html = html.replace(/(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi, '<a href="$1" target="_blank" class="otherSite">$1</a>');
  8155. for (var i = 0, len = links.length; i < len; i++) {
  8156. html = html.replace(' <!----> ', links[i]);
  8157. }
  8158. html = html.split(' <br /> ').join('<br />');
  8159. var $description = $('<p class="videoDescription description">' + html + '</p>');
  8160.  
  8161. bindDescriptionEvents($description);
  8162. decorateVideoDescriptionLink($description);
  8163. return $description;
  8164. } //
  8165.  
  8166. function resetSideIchibaPanel($ichibaPanel, force) {
  8167.  
  8168. $ichibaPanel.scrollTop(0).find('*').unbind().empty();
  8169. var $inner = $('<div class="ichibaPanelInner" />');
  8170. var $header = $('<div class="ichibaPanelHeader"><p class="logo">ニコニコ市場出張所</p></div>');
  8171. $inner.append($header);
  8172.  
  8173.  
  8174. var items = [];
  8175. $('#ichibaMain').find('.ichiba_mainitem>div').each(function() {
  8176. var $item = $(this).clone().attr('id', null);
  8177. var $dl = $('<dl class="ichiba_mainitem" />').append($item);
  8178. $item.find('.thumbnail span').css({fontSize: ''});
  8179. // 誤クリックしやすいのでサムネはリンクを外す
  8180. $item.find('.thumbnail a img, .blomagaThumbnail, .blomagaText')
  8181. .parent().attr('href', null).attr('style', null).css({'text-decoration': 'none'});
  8182. $item.find('a').attr('onclick', null);
  8183. items.push($dl);
  8184. $inner.append($dl);
  8185. });
  8186. if (items.length > 0) {
  8187. for (var i = items.length -1; i >= 0; i--) {
  8188. $inner.find('#watch' + i + '_mq').attr('id', null).addClass('ichibaMarquee');
  8189. }
  8190. }
  8191. $inner.find('.nicoru').remove();
  8192.  
  8193. var $footer = $('<div class="ichibaPanelFooter"></div>');
  8194.  
  8195. var $addIchiba = $('<button class="addIchiba">商品を貼る</button>');
  8196. $addIchiba.click(function() {
  8197. AnchorHoverPopup.hidePopup();
  8198. ichibaSearch();
  8199. });
  8200. $footer.append($addIchiba);
  8201.  
  8202. var $reloadIchiba = $('<button class="reloadIchiba">リロード</button>');
  8203. $reloadIchiba.click(function() {
  8204. resetSideIchibaPanel($ichibaPanel, true);
  8205. $ichibaPanel = null;
  8206. });
  8207. $footer.append($reloadIchiba);
  8208.  
  8209. $inner.append($footer);
  8210. $inner.hide();
  8211. $ichibaPanel.append($inner);
  8212. $inner.fadeIn();
  8213. $inner = $header = $footer = $addIchiba = $reloadIchiba = null;
  8214. } //
  8215.  
  8216. function initHidariue() {
  8217. // 再生終了時に勝手にメニューが開閉するのを止める
  8218. window.WatchApp.ns.init.PlayerInitializer.videoendViewController.videoHeaderViewController = {openVideoMenu: function(){}, closeVideoMenu: function() {}};
  8219. var hidariue = null;
  8220. var resetHidariue = function() {
  8221. // var dt = new Date();
  8222. // if (dt.getMonth() < 1 && dt.getDate() <=1) {
  8223. // $('#videoMenuTopList').append('<li style="font-size:50%"> \ │ /<br>  /‾\   /‾‾‾‾‾‾‾‾‾<br>─( ゜ ∀ ゜ )< しんねんしんねん!<br>  \_/   \_________<br> / │ \</li>');
  8224. // }
  8225. if (!conf.hidariue) { return; }
  8226. if (!hidariue) {
  8227. $('#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>');
  8228. hidariue = $('#hidariue')[0];
  8229. }
  8230. hidariue.src = 'http://res.nimg.jp/img/base/head/icon/nico/' +
  8231. (1000 + Math.floor(Math.random() * 1000)).toString().substr(1) + '.gif';
  8232. };
  8233. EventDispatcher.addEventListener('onVideoInitialized', resetHidariue);
  8234. } //
  8235.  
  8236.  
  8237. var VideoExplorerToggleMenu = function(title, titleLink) {
  8238. this.initializeBaseDom(title, titleLink);
  8239. };
  8240. WatchApp.mixin(VideoExplorerToggleMenu.prototype, {
  8241. initializeBaseDom: function(title, titleLink) {
  8242.  
  8243. this._$toggle = $('<li style="display:none;" class="toggleVideoExplorerMenu watchItLaterMenu"></li>');
  8244. this._$menu = $('<li class="slideMenu"><ul></ul></li>');
  8245. this._$list = this._$menu.find('ul');
  8246.  
  8247. var $a = $('<a/>').text(title).attr('href', titleLink);
  8248. this._$toggle.append($a);
  8249.  
  8250. this._initializeToggleEvent();
  8251. this._initializeItemEvent();
  8252. },
  8253. _initializeToggleEvent: function() {
  8254. this._$toggle.on('click', $.proxy(function(e) {
  8255. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8256. e.preventDefault(); e.stopPropagation();
  8257.  
  8258. var isVisible = this._$menu.hasClass('open');
  8259. this._$toggle.addClass('opening');
  8260. this._$menu.toggleClass('open', !isVisible);
  8261.  
  8262. window.setTimeout($.proxy(function() {
  8263. this._$toggle.toggleClass('open', !isVisible);
  8264. this._$toggle.removeClass('opening');
  8265. }, this), 500);
  8266. }, this));
  8267. },
  8268. _initializeItemEvent: function() {
  8269. this._$menu.on('click', function(e) {
  8270. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) { return; }
  8271.  
  8272. var elm = e.target;
  8273. if (elm.tagName !== 'A') { return; }
  8274. var $elm = $(elm);
  8275. var type = $elm.attr('data-menu-type');
  8276.  
  8277. if (type === 'mylist') {
  8278. e.preventDefault(); e.stopPropagation();
  8279. var mylistId = $elm.attr('data-mylist-id');
  8280. WatchController.showMylist(mylistId);
  8281. } else
  8282. if (type === 'deflist') {
  8283. e.preventDefault(); e.stopPropagation();
  8284. WatchController.showDeflist();
  8285. } else
  8286. if (type === 'tag') {
  8287. e.preventDefault(); e.stopPropagation();
  8288. var tag = $elm.attr('data-search-tag');
  8289. WatchController.nicoSearch(tag, 'tag');
  8290. }
  8291. });
  8292. },
  8293. attach: function() {
  8294. $('.videoExplorerMenu').find('ul:first li:first').after(this._$menu).after(this._$toggle);
  8295. },
  8296. detach: function() {
  8297. this._$toggle.detach();
  8298. this._$menu.detach();
  8299. },
  8300. add$listItem: function($item) {
  8301. this._$list.append($item);
  8302. },
  8303. addItem: function(name, title, attr) {
  8304. var
  8305. $a = $('<a/>')
  8306. .attr(attr)
  8307. .text(title),
  8308. $li = $('<li/>').addClass(iconType);
  8309. $li.append($a);
  8310. this._$list.append($li);
  8311. return $a;
  8312. },
  8313. addMylistItem: function(name, mylistId, title, iconType) {
  8314. var
  8315. $a = $('<a/>')
  8316. .text(name)
  8317. .attr({
  8318. href: '/mylist/' + mylistId,
  8319. title: title,
  8320. 'data-menu-type': 'mylist',
  8321. 'data-mylist-id': mylistId,
  8322. }),
  8323. $li = $('<li/>').addClass(iconType || '');
  8324. $li.append($a);
  8325. this._$list.append($li);
  8326. return $a;
  8327. },
  8328. show: function() {
  8329. this._$toggle.fadeIn(500);
  8330. }
  8331. });
  8332.  
  8333. WatchItLater.videoExplorerMenu = {};
  8334. WatchItLater.videoExplorerMenu.favMylists = (function() {
  8335. var toggleMenu;
  8336.  
  8337. var initialize = function() {
  8338. initialize = window._.noop;
  8339. console.log('%cinitialize WatchItLater.videoExplorerMenu.favMylists', 'background: lightgreen;');
  8340.  
  8341. toggleMenu = new VideoExplorerToggleMenu('お気に入りマイリスト', '/my/fav/mylist');
  8342.  
  8343. window.WatchItLater.loader.favMylists.load(function(mylists) {
  8344. if (mylists.length < 1) {
  8345. return;
  8346. }
  8347. for (var i = 0, len = mylists.length; i < len; i++) {
  8348. var mylist = mylists[i], lastVideo = mylist.lastVideo, $li = $('<li/>'),
  8349. title = [
  8350. '/mylist/', mylist.id, '\n',
  8351. mylist.description, '\n',
  8352. '最新動画: ', lastVideo.title, '\n',
  8353. '投稿日時: ', lastVideo.postedAt, '\n',
  8354. ''].join('');
  8355. toggleMenu.addMylistItem(
  8356. mylist.name,
  8357. mylist.id,
  8358. title,
  8359. mylist.iconType
  8360. );
  8361. }
  8362. toggleMenu.show();
  8363. });
  8364. };
  8365.  
  8366. return {
  8367. attach: function() {
  8368. initialize();
  8369. toggleMenu.attach();
  8370. },
  8371. detach: function() {
  8372. if (!toggleMenu) {
  8373. return;
  8374. }
  8375. console.log(toggleMenu);
  8376. toggleMenu.detach();
  8377. }
  8378. };
  8379. })();
  8380.  
  8381. WatchItLater.videoExplorerMenu.favTags = (function() {
  8382. var toggleMenu;
  8383.  
  8384. var initialize = function() {
  8385. initialize = window._.noop;
  8386. console.log('%cinitialize WatchItLater.videoExplorerMenu.favTags', 'background: lightgreen;');
  8387.  
  8388. toggleMenu = new VideoExplorerToggleMenu('お気に入りタグ', '/my/fav/tag');
  8389.  
  8390. window.WatchItLater.loader.favTags.load(function(tags) {
  8391. if (tags.length < 1) {
  8392. $toggle.remove();
  8393. return;
  8394. }
  8395. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  8396. for (var i = 0, len = tags.length; i < len; i++) {
  8397. var tag = tags[i], $li = $('<li/>'),
  8398. $a = $('<a/>')
  8399. .attr({
  8400. href: '/tag/' + encodeURIComponent(tag.name + ' ' + conf.defaultSearchOption) + sortOrder,
  8401. 'data-menu-type': 'tag',
  8402. 'data-search-tag': tag.name
  8403. })
  8404. .text(tag.name);
  8405. toggleMenu.add$listItem($li.append($a));
  8406. }
  8407. toggleMenu.show();
  8408. });
  8409. };
  8410.  
  8411. return {
  8412. attach: function() {
  8413. initialize();
  8414. toggleMenu.attach();
  8415. },
  8416. detach: function() {
  8417. if (!toggleMenu) {
  8418. return;
  8419. }
  8420. toggleMenu.detach();
  8421. }
  8422. };
  8423. })();
  8424.  
  8425.  
  8426. WatchItLater.videoExplorerMenu.myShortcuts = (function() {
  8427. var toggleMenu;
  8428.  
  8429. var initialize = function() {
  8430. initialize = window._.noop;
  8431. console.log('%cinitialize WatchItLater.videoExplorerMenu.myShortcuts', 'background: lightgreen;');
  8432.  
  8433. toggleMenu = new VideoExplorerToggleMenu('マイショートカット', '/my/mylist');
  8434. toggleMenu.attach();
  8435.  
  8436. window.WatchItLater.mylist.loadMylistList(function(mylistList) {
  8437. toggleMenu.add$listItem(
  8438. $('<li/>').append(
  8439. $('<a/>')
  8440. .addClass('defMylist')
  8441. .attr({href: '/my/mylist', 'data-menu-type': 'deflist'})
  8442. .text('とりあえずマイリスト')
  8443. ));
  8444. var items = [
  8445. {id: -1, href: '/my/history', name: '視聴履歴'},
  8446. {id: -2, href: '/recommendations', name: 'あなたにオススメの動画'},
  8447. {id: NicorepoVideo.REPO_ALL, href: '/my/top/all', name: '【ニコレポ】すべての動画'},
  8448. {id: NicorepoVideo.REPO_USER, href: '/my/top/user', name: '【ニコレポ】お気に入りユーザー'},
  8449. {id: NicorepoVideo.REPO_CHCOM, href: '/my/top/chcom', name: '【ニコレポ】チャンネル&コミュニティ'},
  8450. {id: NicorepoVideo.REPO_MYLIST, href: '/my/top/mylist', name: '【ニコレポ】お気に入りマイリスト'}
  8451. ];
  8452. for (var v in items) {
  8453. var item = items[v];
  8454. toggleMenu
  8455. .addMylistItem(item.name, item.id)
  8456. .addClass('defMylist')
  8457. .attr({
  8458. href: item.href
  8459. });
  8460. }
  8461. for (var i = 0, len = mylistList.length; i < len; i++) {
  8462. var mylist = mylistList[i];
  8463. toggleMenu.addMylistItem(mylist.name, mylist.id, '', 'folder' + mylist.icon_id);
  8464. }
  8465.  
  8466. toggleMenu.show();
  8467. });
  8468. };
  8469.  
  8470. return {
  8471. attach: function() {
  8472. initialize();
  8473. toggleMenu.attach();
  8474. },
  8475. detach: function() {
  8476. if (!toggleMenu) {
  8477. return;
  8478. }
  8479. toggleMenu.detach();
  8480. }
  8481. };
  8482. })();
  8483.  
  8484. WatchItLater.videoExplorerMenu.videoRanking = (function() {
  8485. var toggleMenu;
  8486.  
  8487. var VideoRankingToggleMenu = function(title, titleLink) {
  8488. WatchApp.extend(this, VideoRankingToggleMenu, VideoExplorerToggleMenu, [title, titleLink]);
  8489.  
  8490. this._$menu.addClass('videoRankingList');
  8491. this.initializeCategoryToggleEvents();
  8492. };
  8493. WatchApp.mixin(VideoRankingToggleMenu.prototype, {
  8494. initializeCategoryToggleEvents: function() {
  8495. this._$menu.on('click', '.rankingCategoryToggle', function(e) {
  8496. e.preventDefault(); e.stopPropagation();
  8497.  
  8498. var $target = $(e.currentTarget), category = $target.attr('data-category');
  8499. var $popup = $target.closest('.slideMenu');
  8500. var isClose = $popup.find('li.' + category).toggleClass('categoryClose').hasClass('categoryClose');
  8501.  
  8502. conf.setValue('rankingCategory_' + category + '_Close', isClose);
  8503. });
  8504. },
  8505. addRankingItem: function($, genre, id, name, category, term) {
  8506. var $a =
  8507. $('<a/>')
  8508. .attr({
  8509. href: '/ranking/fav/' + term + '/' + genre,
  8510. 'data-menu-type': 'mylist',
  8511. 'data-mylist-id': id
  8512. })
  8513. .text(name)
  8514. .addClass(genre);
  8515. var $li = $('<li/>');
  8516.  
  8517. if (genre === category) {
  8518. $li.addClass('isCategory'); // nameと同じならカテゴリランキング、違うならジャンルランキング
  8519. if (genre !== 'all' && genre !== 'g_politics' && genre !== 'r18') {
  8520. var $button = $([
  8521. '<button class="rankingCategoryToggle">',
  8522. '<span class="open" title="サブカテゴリを開く">▼</span>',
  8523. '<span class="close" title="サブカテゴリを閉じる">▲</span>',
  8524. '</button>'
  8525. ].join(''));
  8526. $button.attr('data-category', category);
  8527. $li.append($button);
  8528. }
  8529. }
  8530. var isClose = conf.getValue('rankingCategory_' + category + '_Close');
  8531. $li
  8532. .toggleClass('categoryClose', isClose)
  8533. .attr({'data-genre': genre, 'data-category': category})
  8534. .addClass(category).addClass(genre)
  8535. .append($a);
  8536.  
  8537. this._$list.append($li);
  8538. return $a;
  8539. }
  8540. });
  8541.  
  8542.  
  8543. var initialize = function() {
  8544. initialize = window._.noop;
  8545. console.log('%cinitialize WatchItLater.videoExplorerMenu.videoRanking', 'background: lightgreen;');
  8546.  
  8547. toggleMenu = new VideoRankingToggleMenu('動画ランキング', '/ranking');
  8548. toggleMenu.attach();
  8549.  
  8550. // TODO: マジックナンバーを
  8551. toggleMenu.addRankingItem($, 'all', -100, 'カテゴリ合算(毎時)', 'all', 'hourly');
  8552. toggleMenu.addRankingItem($, 'all', -1100, 'カテゴリ合算(24時間)', 'all', 'daily');
  8553. // toggleMenu.addRankingItem($, 'all', -4100, 'カテゴリ合算(合計)', 'all', 'total');
  8554.  
  8555. var genreId = VideoRanking.getGenreId();
  8556. for (var genre in genreId) {
  8557. if (genre === 'all') { continue; }
  8558. var id = genreId[genre], name = VideoRanking.getGenreName(genre), category = VideoRanking.getCategory(id);
  8559. toggleMenu.addRankingItem($, genre, id, name, category, 'hourly');
  8560. }
  8561.  
  8562. window.setTimeout(function() { toggleMenu.show(); }, 100);
  8563. };
  8564.  
  8565. return {
  8566. attach: function() {
  8567. initialize();
  8568. toggleMenu.attach();
  8569. },
  8570. detach: function() {
  8571. if (!toggleMenu) {
  8572. return;
  8573. }
  8574. toggleMenu.detach();
  8575. }
  8576. };
  8577. })();
  8578.  
  8579.  
  8580. var WatchingVideoView = function() { this.initialize.apply(this, arguments); };
  8581. WatchingVideoView.prototype = {
  8582. _params: null,
  8583. _$view: null,
  8584. _watchInfoModel: null,
  8585. _type: null,
  8586. initialize: function(params) {
  8587. this._content = params.content;
  8588. this._watchInfoModel = params.watchInfoModel;
  8589. this._$view = params.$view;
  8590. this._mylistController = params.mylistController;
  8591. this._type = params.type;
  8592.  
  8593. this._$title = this._$view.find('.title');
  8594. this._$thumb = this._$view.find('.thumbnail');
  8595. this._$add = this._$view.find('.add');
  8596. this._$remove = this._$view.find('.remove');
  8597.  
  8598. this._$add .on('click', $.proxy(this._onAddClick, this));
  8599. this._$remove.on('click', $.proxy(this._onRemoveClick, this));
  8600.  
  8601. EventDispatcher.addEventListener('onWatchInfoReset', $.proxy(this.onVideoChange, this));
  8602. },
  8603. getView: function() {
  8604. return this._$view;
  8605. },
  8606. detach: function() {
  8607. this._$view.detach();
  8608. },
  8609. update: function() {
  8610. $('.videoExplorerBody').toggleClass('containsWatchingVideo', this._content.containsWatchId());
  8611. },
  8612. onVideoChange: function() {
  8613. this._$title.html(this._watchInfoModel.title);
  8614. this._$thumb
  8615. .attr('src', this._watchInfoModel.thumbnail)
  8616. .off('click').on('click', Util.Closure.showLargeThumbnail(this._watchInfoModel.thumbnail));
  8617. if (this._content.isActive()) {
  8618. this.update();
  8619. }
  8620. },
  8621. _setIsUpdating: function() {
  8622. this._$view.addClass('updating');
  8623. setTimeout($.proxy(this._clearIsUpdating, this), 3000);
  8624. },
  8625. _clearIsUpdating: function() {
  8626. this._$view.removeClass('updating');
  8627. },
  8628. _getIsUpdating: function() {
  8629. return this._$view.hasClass('updating');
  8630. },
  8631. _onAddClick: function() {
  8632. var watchId = WatchController.getWatchId();
  8633. this._setIsUpdating();
  8634. if (this._type === 'deflist') {
  8635. this._mylistController.addDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8636. } else {
  8637. var mylistId = this._content.getMylistId();
  8638. this._mylistController.addMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8639. }
  8640. },
  8641. _onRemoveClick: function() {
  8642. var watchId = WatchController.getWatchId();
  8643. this._setIsUpdating();
  8644. if (this._type === 'deflist') {
  8645. this._mylistController.deleteDefListItem(watchId, $.proxy(this._onMylistUpdate, this));
  8646. } else {
  8647. var mylistId = this._content.getMylistId();
  8648. this._mylistController.deleteMylistItem (watchId, mylistId, $.proxy(this._onMylistUpdate, this));
  8649. }
  8650. },
  8651. _onMylistUpdate: function(status, result) {
  8652. if (status === 'ok') {
  8653. if (this._type === 'deflist') {
  8654. WatchController.clearDeflistCache();
  8655. }
  8656. } else {
  8657. Popup.alert('更新に失敗: ' + result.error.description);
  8658. }
  8659. this._content.setFilter(null);
  8660. setTimeout(
  8661. $.proxy(function() {
  8662. //this._content.changeState({page: 1});
  8663. this.contentRefresh();
  8664. this._clearIsUpdating();
  8665. }, this), 500);
  8666. },
  8667. contentRefresh: function() {
  8668. var params = this._content.getParams();
  8669. params.page = 1;
  8670. this._content.changeState(params);
  8671. this._content.refresh({page: 1});
  8672. }
  8673. }; // end WatchingVideoView.prototype
  8674.  
  8675.  
  8676. var GrepOptionView = function() { this.initialize.apply(this, arguments); };
  8677. GrepOptionView.prototype = {
  8678. _params: null,
  8679. _$view: null,
  8680. initialize: function(params) {
  8681. this._content = params.content;
  8682. this._$view = params.$view;
  8683. this._$form = this._$view.find('form');
  8684. this._$input = this._$view.find('.grepInput').attr('list', params.listName);
  8685. this._$community = this._$view.find('.community');
  8686. this._$alive = this._$view.find('.alive');
  8687. this._$invert = this._$view.find('.invert');
  8688. this._$checkboxes = this._$view.find('input[type=checkbox]');
  8689.  
  8690. this._not = false;
  8691.  
  8692. this._$view.toggleClass('debug', !!conf.debugMode);
  8693.  
  8694. this._$list = $('<datalist />').attr('id', params.listName);
  8695. $('body').append(this._$list);
  8696.  
  8697. this._$form.on('submit', $.proxy(this._onFormSubmit, this));
  8698. this._$checkboxes.on('click', $.proxy(this._onCheckClick, this));
  8699.  
  8700. this._$input.on('click', $.proxy(function(e) {
  8701. e.stopPropagation();
  8702. }, this)) .on('focus', $.proxy(function(e) {
  8703. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  8704. }, this));
  8705.  
  8706. this._$view .on('click', $.proxy(function(e) {
  8707. this._$input.focus();
  8708. }, this));
  8709. },
  8710. getView: function() {
  8711. return this._$view;
  8712. },
  8713. detach: function() {
  8714. this._$view.detach();
  8715. },
  8716. clear: function() {
  8717. this._$input.val('');
  8718. this._$checkboxes.prop('checked', false);
  8719. this._$view.removeClass('active');
  8720. },
  8721. update: function() {
  8722. var list = this._content.getRawList();
  8723. var tmp = [];
  8724. for (var i = list.length -1; i >= 0; i--) {
  8725. tmp.push('<option>');
  8726. tmp.push(list[i].title); // 既にエスケープされてる
  8727. tmp.push('</option>');
  8728. }
  8729. this._$list.html(tmp.join(''));
  8730.  
  8731. if (this._getWord().length > 0) {
  8732. window.setTimeout($.proxy(function() { this._$input.focus(); }, this), 100);
  8733. }
  8734. },
  8735. _isActive: function() {
  8736. return (this._$input.val().length > 0 ||
  8737. !!this._$community.prop('checked') ||
  8738. !!this._$alive .prop('checked'));
  8739. },
  8740. _getWord: function() {
  8741. return $.trim(this._$input.val());
  8742. },
  8743. _onCheckClick: function(e) {
  8744. e.stopPropagation();
  8745. this._submit();
  8746. },
  8747. _onFormSubmit: function(e) {
  8748. e.preventDefault();
  8749. e.stopPropagation();
  8750. this._submit();
  8751. },
  8752. _submit: function() {
  8753. var isActive = this._isActive();
  8754. this._$view.toggleClass('active', isActive);
  8755.  
  8756. if (isActive) {
  8757. this._content.setFilter(this._getFilter());
  8758. } else {
  8759. this._content.setFilter(null);
  8760. }
  8761.  
  8762. this.contentRefresh();
  8763. },
  8764. contentRefresh: function() {
  8765. var params = this._content.getParams();
  8766. params.page = 1;
  8767. this._content.changeState(params);
  8768. this._content.refresh({page: 1});
  8769. },
  8770. _getFilter: function() {
  8771. var to_h = function(str) {
  8772. return str.replace(/[A-Za-z0-9]/g, function(s) {
  8773. return String.fromCharCode(s.charCodeAt(0) - 65248);
  8774. }).toLowerCase();
  8775. };
  8776. var word = to_h(this._getWord());
  8777. var communityReg = /^so|^\d+$/;
  8778. var wordFilter = word.length > 0;
  8779. var communityFilter = !!this._$community.prop('checked');
  8780. var aliveFilter = !!this._$alive.prop('checked');
  8781. var isInvert = !!this._$invert.prop('checked');
  8782.  
  8783.  
  8784. var isCommunity = function(item) {
  8785. return communityReg.test(item.id);
  8786. };
  8787. var isMatch = function(item) {
  8788. var title = item.title;
  8789. var desc = item.description_full || item.description_short || '';
  8790. var mc = item.mylist_comment || '';
  8791. var text = to_h([title, desc, mc].join('\n'));
  8792.  
  8793. return text.indexOf(word) >= 0;
  8794. };
  8795. var isAlive = function(item) {
  8796. var thumbnail = item.thumbnail_url || '';
  8797. if (thumbnail.indexOf('http://res.nimg.jp/img/common/video_deleted') < 0) {
  8798. return true;
  8799. }
  8800. return false;
  8801. };
  8802.  
  8803. var grepFilter = function(item) {
  8804. var result = true, i = func.length, f;
  8805. while (--i >= 0 && (result || isInvert)) {
  8806. f = func[i];
  8807. result &= f(item);
  8808. }
  8809. return isInvert ? !result : result;
  8810. };
  8811.  
  8812. var func = [], f;
  8813. if (wordFilter) { func.push(isMatch); }
  8814. if (communityFilter) { func.push(isCommunity); }
  8815. if (aliveFilter) { func.push(isAlive); }
  8816.  
  8817. if (func.length < 1) { return null; }
  8818.  
  8819. return grepFilter;
  8820. }
  8821. }; // end GrepOptionView.prototype
  8822.  
  8823. function initMylistContent($, conf, w) {
  8824. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  8825. var ContentView = WatchApp.ns.components.videoexplorer.view.content.MylistVideoContentView;
  8826. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  8827. var explorer = vec.getVideoExplorer();
  8828. var myUserId = WatchController.getMyUserId();
  8829. var content = explorer.getContentList().getContent(ContentType.MYLIST_VIDEO);
  8830. var loader = content._mylistVideoAPILoader;
  8831. var pager = content._pager;
  8832. var watchingVideoView = new WatchingVideoView({
  8833. content: content,
  8834. watchInfoModel: watchInfoModel,
  8835. mylistController: Mylist,
  8836. type: 'mylist',
  8837. $view: $([
  8838. '<div class="watchingVideo">',
  8839. '<img class="thumbnail">',
  8840. '<p class="title"></p>',
  8841. '<span class="contains" >この動画はリストに登録されています</span>',
  8842. '<span class="not_contains">この動画はリストにありません</span>',
  8843. '<span class="edit">',
  8844. '<button class="add" >登録</button>',
  8845. '<button class="remove">外す</button>',
  8846. '</span>',
  8847. '</div>',
  8848. ''].join(''))
  8849. });
  8850.  
  8851. var grepOptionView = new GrepOptionView({
  8852. content: content,
  8853. listName: 'suggestMylistTitle',
  8854. $view: $([
  8855. '<div class="grepOption">',
  8856. '<form>',
  8857. '<input type="search" class="grepInput" autocomplete="on" placeholder="タイトル・説明文で絞り込む(G)" accesskey="g">',
  8858. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  8859. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  8860. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  8861. '</form>',
  8862. '</div>',
  8863. ].join(''))
  8864. });
  8865.  
  8866.  
  8867. pager._pageItemCount = conf.searchPageItemCount;
  8868. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  8869. pager._pageItemCount = v;
  8870. });
  8871.  
  8872. content._isOwnerNicorepo = false;
  8873. content._isRanking = false;
  8874. content.getIsMine = $.proxy(function() {
  8875. return parseInt(this.getUserId(), 10) === parseInt(myUserId, 10) && parseInt(this.getMylistId(), 10) > 0;
  8876. }, content);
  8877. content.getIsDummy = $.proxy(function() {
  8878. var id = this.getMylistId();
  8879. return parseInt(id, 10) <= 0 || id.toString().indexOf('repo') === 0;
  8880. }, content);
  8881. content.getIsOwnerNicorepo = $.proxy(function() { return this._isOwnerNicorepo; }, content);
  8882. content.getIsRanking = $.proxy(function() { return this._isRanking; }, content);
  8883.  
  8884. // grep対応のための拡張
  8885. content._rawList = [];
  8886. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  8887. content._filter = null;
  8888. content.setFilter = $.proxy(function(filter) {
  8889. this._filter = filter;
  8890. }, content);
  8891. content.getFilter = $.proxy(function() { return this._filter; }, content);
  8892.  
  8893. content.clear_org = content.clear;
  8894. content.clear = $.proxy(function() {
  8895. this.setFilter(null);
  8896. this.clear_org();
  8897. grepOptionView.clear();
  8898. }, content);
  8899.  
  8900. content.getNickname = $.proxy(function() {
  8901. if (this._nickname && this._nickname.length > 0) {
  8902. return this._nickname;
  8903. }
  8904. return 'no-name';
  8905. }, content);
  8906.  
  8907. content.onLoad_org = content.onLoad;
  8908. content.onLoad = $.proxy(function(err, result) {
  8909. this._isOwnerNicorepo = result.isOwnerNicorepo;
  8910. this._isRanking = result.isRanking;
  8911.  
  8912. var filter = this.getFilter();
  8913. if (err === null && result.list && result.list.length) {
  8914. if (!result.rawList) result.rawList = result.list.concat();
  8915. if (filter) {
  8916. var list = [];
  8917.  
  8918. for (var i = result.rawList.length - 1; i >= 0; i--) {
  8919. var item = result.rawList[i];
  8920. if (item.title && filter(item)) {
  8921. list.unshift(item);
  8922. }
  8923. }
  8924. result.list = list;
  8925. } else {
  8926. result.list = result.rawList.concat();
  8927. }
  8928. } else
  8929. if (result.rawList) {
  8930. result.list = result.rawList.concat();
  8931. }
  8932.  
  8933. this._rawList = result.rawList || [];
  8934. this.onLoad_org(err, result);
  8935. if (this.getIsMine()) {
  8936. EventDispatcher.dispatch('onMyMylistLoad', this.getMylistId(), {
  8937. name: this.getName(),
  8938. items: result.rawList
  8939. });
  8940. }
  8941. }, content);
  8942.  
  8943. content.containsWatchId = $.proxy(function(watchId) {
  8944. var list = this.getRawList();
  8945. if (!watchId) { watchId = WatchController.getWatchId(); }
  8946.  
  8947. for (var i = list.length - 1; i >= 0; i--) {
  8948. if (list[i].id === watchId) { return true; }
  8949. }
  8950. return false;
  8951. }, content);
  8952.  
  8953. loader.load_org = loader.load;
  8954. loader.load = $.proxy(function(params, callback) {
  8955. var isOwnerNicorepo = false, isRanking = false;
  8956. var id = params.id;
  8957. if (typeof id === 'string' && id.indexOf('repo-owner-') === 0) {
  8958. id = NicorepoVideo.REPO_OWNER;
  8959. }
  8960.  
  8961. var applyFilter = function(err, result) {
  8962. result.isOwnerNicorepo = isOwnerNicorepo;
  8963. result.isRanking = isRanking;
  8964. callback(err, result);
  8965. };
  8966. if (id < 0) {
  8967. var timeoutTimer = null;
  8968. var onload = function(result) {
  8969. window.clearTimeout(timeoutTimer);
  8970. // 投稿者ニコレポが0件で、投稿動画一覧を公開していたらそっちを開くタイマーをセット
  8971. if (result.list.length < 1 &&
  8972. parseInt(id, 10) === NicorepoVideo.REPO_OWNER &&
  8973. WatchController.isVideoPublic()) {
  8974. window.setTimeout(function() {
  8975. WatchController.openVideoOwnersVideo();
  8976. }, 500);
  8977. }
  8978. applyFilter(null, result);
  8979. };
  8980. var onerror = function(result) {
  8981. window.clearTimeout(timeoutTimer);
  8982. callback('error', result);
  8983. };
  8984. timeoutTimer = window.setTimeout(function() {
  8985. onload = onerror = window._.noop;
  8986. onerror({message: '通信がタイムアウトしました:' + id, status: 'fail'});
  8987. }, 30 * 1000);
  8988.  
  8989. // マイリストIDに負の数字(通常ないはず)が来たら乗っ取るサイン
  8990. // そもそもマイリストIDはstringのようなので数字にこだわる必要なかったかも
  8991. //
  8992. try {
  8993. if (typeof VideoRanking.getGenreName(id) === 'string') {
  8994. isRanking = true;
  8995. VideoRanking.load(null, {id: id}).then(onload, onerror);
  8996. return;
  8997. }
  8998. // TODO: マジックナンバーを
  8999. switch (parseInt(id, 10)) {
  9000. case -1:
  9001. VideoWatchHistory.load(onload);
  9002. break;
  9003. case -2:
  9004. VideoRecommendations.load(onload);
  9005. break;
  9006. case -3:
  9007. ChannelVideoList.loadOwnerVideo(null).then(onload, onerror);
  9008. break;
  9009. case NicorepoVideo.REPO_ALL:
  9010. NicorepoVideo.loadAll() .then(onload, onerror);
  9011. break;
  9012. case NicorepoVideo.REPO_USER:
  9013. NicorepoVideo.loadUser() .then(onload, onerror);
  9014. break;
  9015. case NicorepoVideo.REPO_CHCOM:
  9016. NicorepoVideo.loadChCom() .then(onload, onerror);
  9017. break;
  9018. case NicorepoVideo.REPO_MYLIST:
  9019. NicorepoVideo.loadMylist().then(onload, onerror);
  9020. break;
  9021. case NicorepoVideo.REPO_OWNER:
  9022. isOwnerNicorepo = true;
  9023. NicorepoVideo.loadOwner() .then(onload, onerror);
  9024. break;
  9025. default:
  9026. throw {message: '未定義のIDです:' + id, status: 'fail'};
  9027. }
  9028. } catch(e) {
  9029. // TODO: ここのエラーをちゃんと投げる
  9030. if (e.message && e.status) {
  9031. onerror({
  9032. status: e.status,
  9033. message: e.message
  9034. });
  9035. } else {
  9036. console.log(e); console.trace();
  9037. onerror({message: 'エラーが発生しました:' + id, status: 'fail'});
  9038. }
  9039. }
  9040. } else {
  9041. this.load_org(params, applyFilter);
  9042. }
  9043. }, loader);
  9044.  
  9045.  
  9046. var __css__ = Util.here(function() {/*
  9047. #videoExplorer .watchingVideo { display: none; }
  9048. #videoExplorer .watchingVideo .title { display: none; }
  9049. #videoExplorer .watchingVideo.updating * {
  9050. cursor: wait; opacity: 0.5;
  9051. }
  9052. #videoExplorer .watchingVideo button {
  9053. padding: 2px 12px; margin: 12px 24px;
  9054. }
  9055. #videoExplorer .isMine .watchingVideo {
  9056. display: block; background: #f4f4f4; border: 1px solid #ccc;
  9057. margin: auto; width: 500px; min-height: 48px; padding: 16px;
  9058. }
  9059.  
  9060. #videoExplorer .watchingVideo .thumbnail {
  9061. float: left; width: 72px; margin-right: 24px; cursor: pointer;
  9062. }
  9063.  
  9064.  
  9065. #videoExplorer .watchingVideo .title {
  9066. font-weight: bolder;
  9067. }
  9068. #videoExplorer .watchingVideo .title:before { content: ''; }
  9069. #videoExplorer .watchingVideo .title:after { content: ' '; }
  9070.  
  9071. #videoExplorer .watchingVideo .contains { display: none; }
  9072. #videoExplorer .containsWatchingVideo .watchingVideo .contains { display: inline; }
  9073. #videoExplorer .watchingVideo .not_contains { display: inline; }
  9074. #videoExplorer .containsWatchingVideo .watchingVideo .not_contains { display: none; }
  9075.  
  9076. #videoExplorer .watchingVideo .edit { display: none; }
  9077. #videoExplorer .isMine .watchingVideo .edit { display: inline-block; }
  9078.  
  9079. #videoExplorer .watchingVideo .add { display: inline-block; }
  9080. #videoExplorer .containsWatchingVideo .watchingVideo .add { display: none; }
  9081.  
  9082. #videoExplorer .watchingVideo .remove{ display: none; }
  9083. #videoExplorer .containsWatchingVideo .watchingVideo .remove{ display: inline-block; }
  9084.  
  9085. .isMine .editFavorite {
  9086. display: none; {* 自分のマイリストにはお気に入り登録ボタンを出さない *}
  9087. }
  9088. .watchingVideo button {
  9089. cursor: pointer;
  9090. }
  9091.  
  9092. .grepOption {
  9093. padding: 16px;
  9094. width: 500px;
  9095. margin: 16px auto;
  9096. background: #f4f4f4; border: 1px solid #ccc;
  9097. }
  9098. .grepOption .grepInput {
  9099. font-size: 120%;
  9100. width: 100%;
  9101. }
  9102.  
  9103. .grepOption .filter {
  9104. display: block; margin: 8px;
  9105. }
  9106. .grepOption .filter:hover {
  9107. background: #ccc;
  9108. }
  9109. .grepOption .filter.invertFilter {
  9110. display: none;
  9111. }
  9112. .grepOption.active .filter.invertFilter {
  9113. display: block; text-align: right;
  9114. }
  9115.  
  9116.  
  9117. */});
  9118. addStyle(__css__, 'mylistContentCss');
  9119.  
  9120. var MylistDetailView = WatchApp.ns.components.videoexplorer.view.content.parts.MylistDetailView;
  9121. MylistDetailView.prototype.update_org = MylistDetailView.prototype.update;
  9122. MylistDetailView.prototype.update = function(id, name, description, count) {
  9123. this.update_org(id, name, description, count);
  9124. if (id.toString().match(/repo-owner-(\d+)/)) {
  9125. this._$name.attr('href', '/user/' + RegExp.$1);
  9126. } else
  9127. if (parseInt(id, 10) <= 0) {
  9128. this._$name.attr('href', '');
  9129. }
  9130. };
  9131.  
  9132. var
  9133. overrideContentView = function(proto, watchingVideoView, grepOptionView) {
  9134. var updateCssClass = function(content) {
  9135. $('.videoExplorerBody')
  9136. .toggleClass('dummyMylist', content.getIsDummy())
  9137. .toggleClass('isMine', content.getIsMine())
  9138. .toggleClass('ownerNicorepo', content.getIsOwnerNicorepo())
  9139. .toggleClass('ranking', content.getIsRanking())
  9140. ;
  9141. };
  9142.  
  9143. proto.detach_org = proto.detach;
  9144. proto.detach = function() {
  9145. this.detach_org();
  9146. watchingVideoView.detach();
  9147. grepOptionView.detach();
  9148. };
  9149.  
  9150. proto.onUpdate_org = proto.onUpdate;
  9151. proto.onUpdate = function() {
  9152. this.onUpdate_org();
  9153. updateCssClass(this._content);
  9154. watchingVideoView.update();
  9155. grepOptionView.update();
  9156. this._$content.find('.mylistSortOrder').before(watchingVideoView.getView());
  9157. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9158. };
  9159.  
  9160. proto.onError_org = proto.onError;
  9161. proto.onError = function() {
  9162. this.onError_org();
  9163. updateCssClass(this._content);
  9164. watchingVideoView.update();
  9165. grepOptionView.update();
  9166. this._$content.find('.mylistSortOrder').before(grepOptionView.getView());
  9167. };
  9168.  
  9169. };
  9170.  
  9171. overrideContentView(ContentView.prototype, watchingVideoView, grepOptionView);
  9172. } // end initMylistContent
  9173.  
  9174. function initDeflistContent($, conf, w) {
  9175. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  9176. var ContentView = WatchApp.ns.components.videoexplorer.view.content.DeflistVideoContentView;
  9177. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  9178. var explorer = vec.getVideoExplorer();
  9179. var content = explorer.getContentList().getContent(ContentType.DEFLIST_VIDEO);
  9180. var loader = content._deflistVideoAPILoader;
  9181. var pager = content._pager;
  9182. var watchingVideoView = new WatchingVideoView({
  9183. content: content,
  9184. watchInfoModel: watchInfoModel,
  9185. mylistController: Mylist,
  9186. type: 'deflist',
  9187. $view: $([
  9188. '<div class="watchingVideo">',
  9189. '<img class="thumbnail">',
  9190. '<p class="title"></p>',
  9191. '<span class="contains" >この動画はリストに登録されています</span>',
  9192. '<span class="not_contains">この動画はリストにありません</span>',
  9193. '<span class="edit">',
  9194. '<button class="add" >登録</button>',
  9195. '<button class="remove">外す</button>',
  9196. '</span>',
  9197. '</div>',
  9198. ''].join(''))
  9199. });
  9200. var grepOptionView = new GrepOptionView({
  9201. content: content,
  9202. listName: 'suggestDeflistTitle',
  9203. $view: $([
  9204. '<div class="grepOption">',
  9205. '<form>',
  9206. '<input type="search" class="grepInput" autocomplete="on" placeholder="とりマイをタイトル・説明文で絞り込む(G)" accesskey="g">',
  9207. '<label class="communityFilter filter"><input type="checkbox" class="community">チャンネル・コミュニティ・マイメモリーのみ</label>',
  9208. '<label class="aliveFilter filter"><input type="checkbox" class="alive">生存動画のみ</label>',
  9209. '<label class="invertFilter filter"><input type="checkbox" class="invert">絞り込みの反転</label>',
  9210. '</form>',
  9211. '</div>',
  9212. ].join(''))
  9213. });
  9214.  
  9215.  
  9216.  
  9217. pager._pageItemCount = conf.searchPageItemCount;
  9218. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  9219. pager._pageItemCount = v;
  9220. });
  9221.  
  9222.  
  9223. content.changeState_org = content.changeState;
  9224. content.changeState = $.proxy(function(params, callback) {
  9225. console.log('deflist refresh! ', params, callback);
  9226. if (!this.isActive()) {
  9227. WatchController.clearDeflistCache();
  9228. }
  9229. this.changeState_org(params, callback);
  9230. }, content);
  9231.  
  9232. content.getIsMine = function() { return true; };
  9233.  
  9234. // grep対応のための拡張
  9235. content._rawList = [];
  9236. content.getRawList = $.proxy(function() { return this._rawList; }, content);
  9237. content._filter = null;
  9238. content.setFilter = $.proxy(function(filter) {
  9239. this._filter = filter;
  9240. }, content);
  9241. content.getFilter = $.proxy(function() { return this._filter; }, content);
  9242.  
  9243. content.onLoad_org = content.onLoad;
  9244. content.onLoad = $.proxy(function(err, result) {
  9245. var filter = this.getFilter();
  9246. if (err === null && result.list && result.list.length) {
  9247. if (!result.rawList) result.rawList = result.list.concat();
  9248. if (filter) {
  9249. var list = [];
  9250.  
  9251. for (var i = result.rawList.length - 1; i >= 0; i--) {
  9252. var item = result.rawList[i];
  9253. if (item.title && filter(item)) {
  9254. list.unshift(item);
  9255. }
  9256. }
  9257. result.list = list;
  9258. } else {
  9259. result.list = result.rawList.concat();
  9260. }
  9261. } else
  9262. if (result.rawList) {
  9263. result.list = result.rawList.concat();
  9264. }
  9265. this._rawList = result.rawList || [];
  9266.  
  9267. this.onLoad_org(err, result);
  9268. }, content);
  9269.  
  9270. content.clear_org = content.clear;
  9271. content.clear = $.proxy(function() {
  9272. this.setFilter(null);
  9273. this.clear_org();
  9274. grepOptionView.clear();
  9275. }, content);
  9276.  
  9277. content.containsWatchId = $.proxy(function(watchId) {
  9278. var list = this.getRawList();
  9279. if (!watchId) { watchId = WatchController.getWatchId(); }
  9280.  
  9281. for (var i = list.length - 1; i >= 0; i--) {
  9282. if (list[i].id === watchId) { return true; }
  9283. }
  9284. return false;
  9285. }, content);
  9286.  
  9287. var
  9288. overrideContentView = function(proto, watchingVideoView) {
  9289. var updateCssClass = function(content) {
  9290. $('.videoExplorerBody').toggleClass('isMine', true);
  9291. };
  9292.  
  9293. proto.detach_org = proto.detach;
  9294. proto.detach = function() {
  9295. this.detach_org();
  9296. watchingVideoView.detach();
  9297. grepOptionView.detach();
  9298. };
  9299.  
  9300. proto.onUpdate_org = proto.onUpdate;
  9301. proto.onUpdate = function() {
  9302. this.onUpdate_org();
  9303. updateCssClass(this._content);
  9304. watchingVideoView.update();
  9305. grepOptionView.update();
  9306. this._$content.find('.deflistSortOrder').before(watchingVideoView.getView());
  9307. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9308. };
  9309.  
  9310. proto.onError_org = proto.onError;
  9311. proto.onError = function() {
  9312. this.onError_org();
  9313. updateCssClass(this._content);
  9314. watchingVideoView.update();
  9315. grepOptionView.update();
  9316. this._$content.find('.deflistSortOrder').before(grepOptionView.getView());
  9317. };
  9318.  
  9319. };
  9320.  
  9321. overrideContentView(ContentView.prototype, watchingVideoView);
  9322. } // end initDeflistContent
  9323.  
  9324.  
  9325.  
  9326.  
  9327. function showLargeThumbnail(baseUrl) {
  9328. var largeUrl = baseUrl, size;
  9329. if (baseUrl.indexOf('smilevideo.jp') >= 0) {
  9330. largeUrl = baseUrl + '.L';
  9331. size = 'width: 360px; height: 270px;max-height: 500px;';
  9332. } else {
  9333. largeUrl = baseUrl.replace(/z$/, 'l');
  9334. size = 'width: 360px; max-height: 500px;';
  9335. }
  9336. var
  9337. html = [
  9338. '<div onmousedown="if (event.button == 0) { $(\'#popupMarquee\').removeClass(\'show\'); event.preventDefault(); }" style="background:#000;">',
  9339. '<img src="', largeUrl, '" style="', size, ' z-index: 3; position: absolute; display: none;" onload="this.style.display = \'\';">',
  9340. '<img src="', baseUrl, '" style="', size, ' z-index: 2;">',
  9341. '</div>',
  9342. ''].join('');
  9343. Popup.show(html);
  9344. } //
  9345. WatchController.showLargeThumbnail = showLargeThumbnail;
  9346.  
  9347. function onVideoStopped() {
  9348. EventDispatcher.dispatch('onVideoStopped');
  9349. }
  9350.  
  9351. function onVideoEnded() {
  9352. EventDispatcher.dispatch('onVideoEnded');
  9353. }
  9354.  
  9355. var videoExplorerOpenCount = 0;
  9356. function onVideoExplorerOpened(params) {
  9357. window.console.timeEnd('onVideoExplorerOpen');
  9358. var target = params.target, contentList = params.contentList, content = params.content;
  9359. if (videoExplorerOpenCount++ === 0) {
  9360. EventDispatcher.dispatch('onFirstVideoExplorerOpened', content);
  9361. }
  9362. EventDispatcher.dispatch('onVideoExplorerOpened', content);
  9363.  
  9364. AnchorHoverPopup.hidePopup().updateNow();
  9365. }
  9366.  
  9367. function onVideoExplorerOpening(params) {
  9368. window.console.time('onVideoExplorerOpen');
  9369. var target = params.target, contentList = params.contentList, content = params.content;
  9370. if (videoExplorerOpenCount === 0) {
  9371. EventDispatcher.dispatch('onFirstVideoExplorerOpening', params);
  9372. }
  9373. EventDispatcher.dispatch('onVideoExplorerOpening', params);
  9374. }
  9375.  
  9376. function onVideoExplorerClosing(params) {
  9377. var target = params.target, contentList = params.contentList, content = params.content;
  9378. EventDispatcher.dispatch('onVideoExplorerClosing', content);
  9379. }
  9380.  
  9381. function onVideoExplorerClosed(params) {
  9382. var target = params.target, contentList = params.contentList, content = params.content;
  9383. AnchorHoverPopup.hidePopup().updateNow();
  9384. EventDispatcher.dispatch('onVideoExplorerClosed', content);
  9385. setTimeout(function() {
  9386. watch.PlaylistInitializer.playlistView.resetView();
  9387. }, 1000);
  9388. }
  9389.  
  9390. function onVideoExplorerRefreshStart(params) {
  9391. window.console.time('videoExplorerRefresh');
  9392. var target = params.target, contentList = params.contentList, content = params.content;
  9393. var
  9394. ContentType = WatchApp.ns.components.videoexplorer.model.ContentType,
  9395. type = content.getType(),
  9396. $ve = $('#videoExplorer')
  9397. .removeClass('w_user').removeClass('w_upload').removeClass('w_mylist')
  9398. .removeClass('w_deflist').removeClass('w_related').removeClass('w_search'),
  9399. $body = $ve.find('.videoExplorerBody')
  9400. .removeClass('isMine').removeClass('dummyMylist')
  9401. .removeClass('isRanking').removeClass('isOwnerNicorepo'),
  9402. className = 'w_user';
  9403. switch (type) {
  9404. case ContentType.USER_VIDEO:
  9405. className = 'w_user';
  9406. break;
  9407. case ContentType.UPLOADED_VIDEO:
  9408. className = 'w_uploaded';
  9409. break;
  9410. case ContentType.MYLIST_VIDEO:
  9411. className = 'w_mylist';
  9412. break;
  9413. case ContentType.DEFLIST_VIDEO:
  9414. className = 'w_deflist';
  9415. break;
  9416. case ContentType.RELATED_VIDEO:
  9417. className = 'w_related';
  9418. break;
  9419. case ContentType.SEARCH:
  9420. className = 'w_search';
  9421. break;
  9422. }
  9423. $ve.addClass(className);
  9424.  
  9425. EventDispatcher.dispatch('onVideoExplorerRefreshStart', content);
  9426. }
  9427. function onVideoExplorerRefreshEnd(params) {
  9428. window.console.timeEnd('videoExplorerRefresh');
  9429. var target = params.target, contentList = params.contentList, content = params.content;
  9430. EventDispatcher.dispatch('onVideoExplorerRefreshEnd', content);
  9431. }
  9432. function onVideoExplorerChangePage(params) {
  9433. var target = params.target, contentList = params.contentList, content = params.content;
  9434. EventDispatcher.dispatch('onVideoExplorerChangePage', content);
  9435. }
  9436.  
  9437. /**
  9438. * 検索中の動画サイズを無理矢理でっかくするよ。
  9439. */
  9440. var videoExplorerStyle = null, lastAvailableWidth = 0, lastBottomHeight = 0;
  9441. function adjustVideoExplorerSize(force) {
  9442. if (force !== true && (!conf.videoExplorerHack || !WatchController.isSearchMode())) { return; }
  9443. $('#videoExplorer, #content, #bottomContentTabContainer').toggleClass('w_adjusted', conf.videoExplorerHack);
  9444. var
  9445. rightAreaWidth = $('.videoExplorerBody').outerWidth(), // 592
  9446. availableWidth = Math.max($(window).innerWidth() - rightAreaWidth, 300),
  9447. commentInputHeight = $('#playerContainer').hasClass('oldTypeCommentInput') ? 30 : 0,
  9448. controlPanelHeight = $('#playerContainer').hasClass('controll_panel') ? 46 : 0;
  9449.  
  9450. var
  9451. defPlayerWidth = 300, otherPluginsHeight = 0,
  9452. defPlayerHeight = (defPlayerWidth - 32) * 9 / 16 + 10,
  9453. ratio = availableWidth / defPlayerWidth , availableHeight = defPlayerHeight * ratio + commentInputHeight + controlPanelHeight,
  9454. xdiff = (availableWidth - defPlayerWidth /*- 20 */), windowHeight = $(window).innerHeight(),
  9455. bottomHeight = windowHeight - availableHeight - (WatchController.isFixedHeader() ? $('#siteHeader').outerHeight() : 0) - otherPluginsHeight;
  9456.  
  9457. if (ratio < 1 || availableWidth <= 0 || bottomHeight <= 0 || (lastAvailableWidth === availableWidth && lastBottomHeight === bottomHeight)) { return; }
  9458.  
  9459. var seekbarWidth = 675, scaleX = (availableWidth) / seekbarWidth;
  9460.  
  9461. lastAvailableWidth = availableWidth;
  9462. lastBottomHeight = bottomHeight;
  9463.  
  9464. // コメントパネル召喚
  9465. var commentPanelWidth = 420;
  9466. var dynamic_css = [//'<style type="text/css" id="explorerHack">',
  9467. 'body.videoExplorer #content.w_adjusted #playerContainerWrapper, \n',
  9468. 'body.videoExplorer #content.w_adjusted #playerAlignmentArea, \n',
  9469. 'body.videoExplorer #content.w_adjusted #playerContainer, \n',
  9470. 'body.videoExplorer #content.w_adjusted #nicoplayerContainer ,\n',
  9471. 'body.videoExplorer #content.w_adjusted #external_nicoplayer \n',
  9472. '{',
  9473. 'width: ', availableWidth, 'px !important; height: ', availableHeight, 'px !important;padding: 0; margin: 0; ',
  9474. '}\n',
  9475. 'body.videoExplorer #content.w_adjusted .videoExplorerMenu { ',
  9476. 'position: absolute; width: 300px;',
  9477. 'margin-top: ', availableHeight, 'px !important; left: ', (xdiff - 2), 'px; ',
  9478. 'max-height: ', bottomHeight + 'px; overflow-y: auto; overflow-x: hidden; height: auto;',
  9479. '}\n',
  9480. 'body.videoExplorer #videoExplorer.w_adjusted { ',
  9481. 'margin-left: ', availableWidth, 'px !important;',
  9482. 'min-height: ', (windowHeight + 200) ,'px !important;',
  9483. 'overflow-x: hidden;',
  9484. ' }\n',
  9485. 'body.videoExplorer #content.w_adjusted #playlist { margin-left: ', xdiff, 'px; }\n',
  9486.  
  9487. 'body.videoExplorer #content.w_adjusted #nicoHeatMap {',
  9488. '-webkit-transform: scaleX(', availableWidth / 100, ');',
  9489. 'transform: scaleX(', availableWidth / 100, ');',
  9490. '}\n',
  9491. 'body.videoExplorer #content.w_adjusted #nicoHeatMapContainer {',
  9492. 'width: ', availableWidth, 'px;',
  9493. '}\n',
  9494. 'body.videoExplorer #content.w_adjusted #smart_music_kiosk {',
  9495. '-webkit-transform: scaleX(', scaleX, ');',
  9496. 'left: ', ((availableWidth - seekbarWidth) / 2) ,'px !important;',
  9497. '}\n',
  9498. 'body.videoExplorer #content.w_adjusted #songrium_logo_mini {',
  9499. 'left: ', (availableWidth + 5) ,'px !important;',
  9500. '}\n',
  9501. 'body.videoExplorer #content.w_adjusted #inspire_category {',
  9502. 'left: ', (availableWidth + 32) ,'px !important;',
  9503. '}\n',
  9504.  
  9505.  
  9506. 'body.videoExplorer #content.w_adjusted #leftPanel {',
  9507. ' display: block !important; top: ', (availableHeight + otherPluginsHeight - 1), 'px; max-height: ', bottomHeight, 'px; width: ', (xdiff - 4 + 1), 'px; left: 0;',
  9508. ' height:', bottomHeight, 'px; display: block; border-radius: 0;',
  9509. '}',
  9510. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo, body.size_small.no_setting_panel.videoExplorer #content.w_adjusted #leftPanel .sideIchibaPanel {',
  9511. 'width: ', Math.max((xdiff - 4), 130), 'px; border-radius: 0;',
  9512. '}',
  9513.  
  9514. ((xdiff >= 400) ?
  9515. [
  9516. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoTitleContainer{',
  9517. 'margin-left: 158px; border-radius: 0 0 ;background: #ddd; border: solid; border-color: #ccc; border-width: 1px 1px 0;',
  9518. '}',
  9519. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoThumbnailContainer{',
  9520. 'position: absolute; max-width: 150px; top: 0; ',
  9521. '}',
  9522. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoInfo{',
  9523. 'background: #ddd; margin-left: 158px; border-radius: 0 0; border: solid; border-color: #ccc; border-width: 0 1px 1px;',
  9524. '}',
  9525. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoDetails{',
  9526. 'margin-left: 158px; height: 100%; ',
  9527. '}',
  9528. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .videoOwnerInfoContainer{',
  9529. 'position: absolute; width: 150px; top: 0; border: 1px solid #ccc; margin: 0;',
  9530. '}',
  9531. 'body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .userIconContainer, body.videoExplorer #content.w_adjusted #leftPanel .sideVideoInfo .ch_profile{',
  9532. 'background: #ddd; max-width: 150px; float: none; border-radius: 0;',
  9533. '}',
  9534. 'body.videoExplorer:not(.content-fix) .w_adjusted .videoDetails, ',
  9535. 'body.videoExplorer:not(.content-fix) .w_adjusted #videoExplorerMenu {',
  9536. // タグ領域三行分 bodyのスクロール位置をタグの場所にしてる時でもパネルは文章の末端までスクロールできるようにするための細工
  9537. // (四行以上あるときは表示しきれないが)
  9538. 'padding-bottom: 72px; ',
  9539. '}',
  9540. ].join('') :
  9541. (
  9542. (xdiff >= 154) ?
  9543. ['body.videoExplorer #content.w_adjusted #leftPanel #leftPanelTabContainer { padding: 4px 2px 3px 2px; }'].join('') :
  9544. ['body.videoExplorer #content.w_adjusted #leftPanel { display: none !important;}'].join('')
  9545. )
  9546. ),
  9547. 'body.videoExplorer #bottomContentTabContainer.w_adjusted .videoExplorerFooterAdsContainer { width: 520px; }\n',
  9548.  
  9549. 'body.videoExplorer #content.w_adjusted #playerTabWrapper {',
  9550. 'height: ', (availableHeight), 'px !important;',
  9551. '}',
  9552.  
  9553. 'body.videoExplorer #content.w_adjusted #playerTabWrapper .sidePanelInner {',
  9554. 'height: ', (availableHeight - 2), 'px;',
  9555. '}',
  9556. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active {',
  9557. 'right: -',(commentPanelWidth - 2), 'px !important; top: 0 !important; background: transparent; border: 0; margin-top: 0px;',
  9558. '}',
  9559. 'body.videoExplorer #content.w_adjusted #playerTabWrapper.w_active #playerCommentPanel {',
  9560. 'display: block; background: #dfdfdf; border: 1px solid;',
  9561. '}',
  9562. ''].join('');
  9563.  
  9564. if (videoExplorerStyle) {
  9565. videoExplorerStyle.innerHTML = dynamic_css;
  9566. } else {
  9567. videoExplorerStyle = addStyle(dynamic_css, 'videoExplorerStyle');
  9568. }
  9569.  
  9570. } // end adjustVideoExplorerSize
  9571.  
  9572. function setupVideoExplorerStaticCss() {
  9573. var __css__ = Util.here(function() {/*
  9574. body.videoExplorerOpening {
  9575. overflow-y: scroll;
  9576. }
  9577. body.videoExplorerOpening .videoExplorerMenu {
  9578. {* display: none; *}
  9579. }
  9580. body.videoExplorerOpening #playerTabWrapper {
  9581. visibility: hidden;
  9582. }
  9583. #videoExplorer {
  9584. transition: margin-left 0.4s ease 0.4s; overflow-x: hidden;
  9585. }
  9586. #videoExplorer.w_adjusted .videoExplorerBody, #videoExplorer.w_adjusted .videoExplorerContent .contentItemList {
  9587. width: 592px; padding-left: 0; min-width: 592px; max-width: auto;
  9588. }
  9589. #videoExplorer.w_adjusted .searchBox {
  9590. width: 574px;\
  9591. }
  9592. #videoExplorer.w_adjusted .videoExplorerContent {
  9593. width: 592px;
  9594. }
  9595. #videoExplorer.w_adjusted .videoExplorerBody .resultContentsWrap {
  9596. width: 592px; padding: 16px 0px;
  9597. }
  9598. #videoExplorer.w_adjusted .videoExplorerMenu, #content .videoExplorerMenu:not(.initialized) { display: none; }
  9599. .videoExplorerMenu {
  9600. transition: margin-top 0.4s ease 0.4s; {*, left 0.4s ease-in-out*};
  9601. }
  9602. #leftPanel {
  9603. {* 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;*}
  9604. transition: width 0.4s ease 0.4s, height 0.4s ease 0.4s, left 0.4s ease 0.4s;
  9605. }
  9606. #content.w_adjusted #playlist {
  9607. min-width: 592px;
  9608. }
  9609. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li, body.videoExplorer #content.w_adjusted #videoExplorerExpand {
  9610. height: 26px;
  9611. }
  9612. #content.w_adjusted .videoExplorerMenu:not(.w_touch) .itemList>li>a,body.videoExplorer #content.w_adjusted #videoExplorerExpand a{
  9613. line-height: 26px; font-size: 100%;
  9614. }
  9615. .errorMessage {
  9616. max-height: 0; line-height: 30px; overflow: hidden; text-align: center; color: #f88; cursor: pointer;
  9617. transition: max-height 0.8s ease;
  9618. }
  9619. .videoErrorOccurred .errorMessage {
  9620. max-height: 100px;
  9621. }
  9622.  
  9623. .w_adjusted .videoExplorerMenu .itemList li .arrow {
  9624. top: 8px;
  9625. }
  9626.  
  9627.  
  9628. .w_adjusted .videoExplorerMenu .closeVideoExplorer {
  9629. width: 300px; position: relative; padding: 2px 10px; background: #f5f5f5;
  9630. }
  9631. .w_adjusted .videoExplorerMenu .closeVideoExplorer:hover {
  9632. background: #dbdbdb;
  9633. }
  9634. .w_adjusted .videoExplorerMenu .closeVideoExplorer a{
  9635. display: block;line-height: 26px; {*color: #CC0000;*}
  9636. }
  9637.  
  9638. #searchResultNavigation > ul > li a:after, #content.w_adjusted #videoExplorerExpand a#closeSearchResultExplorer:after {
  9639. top: 8px;
  9640. }
  9641.  
  9642.  
  9643.  
  9644.  
  9645. #content.w_adjusted #playerContainerWrapper, #content.w_adjusted #playlist { box-shadow: none; }
  9646. #content.w_adjusted #videoExplorerExpand .arrow { display: none; }
  9647.  
  9648. body.videoExplorer #footer.w_adjusted {
  9649. display: none;
  9650. }
  9651. .w_adjusted .uadTagRelated .default .itemList .item .videoTitleContainer {
  9652. width: 130px;
  9653. text-align: center;
  9654. }
  9655. .w_adjusted .uadTagRelated .uadTagRelated {
  9656. margin-bottom: 30px;
  9657. }
  9658. .w_adjusted .uadTagRelated .itemList .item,
  9659. .w_adjusted .uadTagRelated .emptyItem,
  9660. .w_adjusted .uadTagRelated .default .landing {
  9661. width: 130px; margin: 0 10px 0 8px;
  9662. }
  9663. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper .itemImage {
  9664. width: 130px; height: auto; top: 0; left: 0;
  9665. }
  9666. .w_adjusted .uadTagRelated .default .itemList .item .imageContainer .itemImageWrapper {
  9667. width: 130px; height: 100px;
  9668. }
  9669. .w_adjusted .uadTagRelated .emptyItem .emptyMessageContainer {
  9670. width: 130px; height: 100px;
  9671. }
  9672. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link,
  9673. .w_adjusted .videoExplorerContent .column1 .videoInformationOuter .link .title {
  9674. display: inline;
  9675. }
  9676.  
  9677. #videoExplorer.w_adjusted .videoExplorerContent .column1 .commentBlank {
  9678. width: 96%;
  9679. }
  9680. #videoExplorer.w_adjusted .videoExplorerContent .column4 .commentBlank {
  9681. width: 24%;
  9682. }
  9683. .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item .createdTime .submit
  9684. {
  9685. display: none !important;
  9686. }
  9687. .nicorepoResult .column4 .videoInformation {
  9688. display: none;
  9689. }
  9690.  
  9691. #videoExplorer .pager { margin-right: 20px; }
  9692. #videoExplorer .contentItemList { clear: both; }
  9693.  
  9694. body.videoExplorer #content.w_adjusted #playerContainerWrapper { overflow: visible; }
  9695. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContent { padding: 20px 0px; }
  9696. body.videoExplorer #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9697. { margin-left: 0; padding: 20px 340px 20px 0px; }
  9698. body.videoExplorer.playlist #videoExplorer.w_adjusted .videoExplorerContentWrapper
  9699. { margin-left: 0; padding: 164px 340px 20px 0px; }
  9700.  
  9701. {* 謎のスペーサー *}
  9702. {*body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:first *}
  9703. {* body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:not(.videoExplorerMenuInner) { display: none; } *}
  9704. body.videoExplorer #content.w_adjusted .videoExplorerMenu>div:nth-child(1) { display: none; }
  9705.  
  9706. body.videoExplorer #content.w_adjusted .videoExplorerMenu
  9707. { width: 300px; }
  9708.  
  9709. body.videoExplorer #content.w_adjusted .videoExplorerMenuInner
  9710. { position: static !important; top: 0 !important; left: 0 !important; }
  9711.  
  9712. body.videoExplorer #bottomContentTabContainer.w_adjusted { {*background: #ccc;*} }
  9713. body:not(.videoExplorer) .videoExplorerMenu { display: none; }
  9714.  
  9715. body.videoExplorer #content.w_adjusted #nicoplayerContainer {
  9716. z-index: 100;
  9717. }
  9718. body.videoExplorer #content.w_adjusted #playerTabWrapper {
  9719. top: 0px !important; background: #dfdfdf; border-radius: 4px;
  9720. z-index: 99;
  9721. transition: right 0.3s ease-out;
  9722. }
  9723. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column1 .item {
  9724. margin-left: 8px;
  9725. }
  9726. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList.column4 .item {
  9727. width: 130px; margin-left: 8px; margin-right: 10px;
  9728. }
  9729. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .videoInformationOuter {
  9730. width: 444px;
  9731. }
  9732. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .column1 .nicorepoResult .videoInformationOuter {
  9733. width: auto;
  9734. }
  9735. #videoExplorer.w_adjusted .contentItemList .folder .column1 .description,
  9736. #videoExplorer.w_adjusted .suggestVideo .folder .column1 .description,
  9737. #videoExplorer.w_adjusted .descriptionShort {
  9738. width: 440px;
  9739. }
  9740. .w_adjusted .column1 .createdTime.at {
  9741. width: 130px; text-align: center;
  9742. }
  9743. .w_adjusted .createdTime {
  9744. white-space: nowrap; {*background: #fff;*}
  9745. }
  9746. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .contentItemList .folder .container,
  9747. #videoExplorer.w_adjusted .videoExplorerBody .videoExplorerContent .suggestVideo .folder .container {
  9748. background-position: -15px -270px; width: 130px; height: 100px;
  9749. }
  9750.  
  9751. body.size_small.no_setting_panel.videoExplorer #content #videoExplorerExpand { {*「閉じる」ボタン *}
  9752. position: static; top: auto; left: auto; margin-top: 0;',
  9753. }
  9754. body.videoExplorer #content.w_adjusted #playerTabWrapper #playerCommentPanel {
  9755. display: none;
  9756. }
  9757.  
  9758. .videoExplorerMenu .item:hover {
  9759. background: #dbdbdb; text-decoration: underline;
  9760. }
  9761. .videoExplorerMenu .item {
  9762. position: relative; border-bottom: 1px solid #CCCCCC; background: #f2f2f2;
  9763. text-decoration: none; cursor: pointer;
  9764. }
  9765. .videoExplorerMenu .item .arrow {
  9766. display: block; position: absolute; top: 14px; right: 12px; width: 9px; height: 12px;
  9767. background: url("http://res.nimg.jp/img/watch_q9/video_explorer/icon_normal.png") no-repeat 0 0;
  9768. }
  9769. .videoExplorerMenu .item .text {
  9770. position: relative; width: 100%; height: 100%; display: block; text-align: left;
  9771. text-decoration: none; padding: 0 12px; color: #000; box-sizing: border-box;
  9772. line-height: 26px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box;
  9773. }
  9774. .videoExplorerMenu .itemList>li:first {
  9775. position: relative;
  9776. }
  9777.  
  9778. #videoExplorer .videoExplorerMenu .closeVideoExplorer, #videoExplorer .quickSearchInput {
  9779. display: none;
  9780. }
  9781.  
  9782. #videoExplorer .videoExplorerBody .videoExplorerContent .contentItemList.column1 .video .column1 .videoInformationOuter .title
  9783. {
  9784. white-space: normal;
  9785. }
  9786.  
  9787. .column1 .balloon .mylistComment {
  9788. display: none; {* バルーンの中にマイリストコメントとか意味不明すぎる *}
  9789. }
  9790.  
  9791. .videoExplorer #playlist {
  9792. transition: margin-left 0.4s ease 0.4s;
  9793. }
  9794.  
  9795. #playerAlignmentArea .toggleCommentPanel {
  9796. display: none;
  9797. position: absolute;
  9798. right: -119px;
  9799. bottom: 70px;
  9800. width: 100px;
  9801. height: 30px;
  9802. cursor: pointer;
  9803. outline: none;
  9804. background: #ccc;
  9805. border-radius: 16px 16px 0 0;
  9806. border: solid 1px black;
  9807. -webkit-transform: rotate(90deg); -webkit-transform-origin: 0 0 0;
  9808. transform: rotate(90deg); transform-origin: 0 0 0;
  9809. transition: 0.4s ease-in-out;
  9810. -webkit-transition: 0.4s ease-in-out;
  9811. }
  9812. #playerAlignmentArea .toggleCommentPanel::-moz-focus-inner {
  9813. border: 0px;
  9814. }
  9815. body.videoExplorer .w_adjusted #playerAlignmentArea .toggleCommentPanel { display: block; }
  9816. #playerAlignmentArea .toggleCommentPanel:before {
  9817. content: '↑ ';
  9818. }
  9819. #playerAlignmentArea .toggleCommentPanel:hover {
  9820. right: -129px;
  9821. }
  9822. #playerAlignmentArea .toggleCommentPanel.w_active {
  9823. right: -418px;
  9824. bottom: -29px;
  9825. border-radius: 0 0 16px 16px;
  9826. z-index: 10000;
  9827. -webkit-transform: rotate(360deg);
  9828. transform: rotate(360deg);
  9829. transition: none;
  9830. -webkit-transition: 0.4s ease-in-out;
  9831. {*transition: 0.4s ease-in-out;*}
  9832. }
  9833. #playerAlignmentArea .toggleCommentPanel.w_active:hover {
  9834. {*box-shadow: 2px 2px 2px #888;*}
  9835. right: -418px;
  9836. }
  9837. #playerAlignmentArea .toggleCommentPanel.w_active:before {
  9838. content: '← ';
  9839. }
  9840.  
  9841. .videoExplorerOpening .videoExplorerBody .videoExplorerConfig {
  9842. display: none;
  9843. }
  9844. .videoExplorerBody .videoExplorerConfig {
  9845. cursor: pointer;
  9846. width: 80px; margin-left: -36px; white-space: nowrap;
  9847. border-radius: 0 32px 0 0; border: solid 1px #666; border-width: 1px 1px 0;
  9848. color: #fff; background: #aaa;
  9849. }
  9850. #videoExplorer.w_adjusted .videoExplorerConfig .open,
  9851. #videoExplorer:not(.w_adjusted) .videoExplorerConfig .close {
  9852. display: none;
  9853. }
  9854. .videoExplorerConfig::-moz-focus-inner { border: 0px; }
  9855.  
  9856. .videoExplorer #playerContainer.appli_panel #appliPanel {
  9857. width: auto !important; background: #333;
  9858. }
  9859. .videoExplorerContent {
  9860. background: #fff;
  9861. }
  9862. */});
  9863. return addStyle(__css__, 'videoExplorerStyleStatic');
  9864. } // end setupVideoExplorerStaticCss
  9865.  
  9866. function initAutoComplete($searchInput) {
  9867. var
  9868. $suggestList = $('<datalist id="quickSearchSuggestList"></datalist>'),
  9869. wordSuggest = '',
  9870. favTagsSuggest = '',
  9871. loading = false,
  9872. val = '',
  9873. suggestLoader = new NicoSearchSuggest({}),
  9874. update = _.debounce(function() {
  9875. if (loading) {
  9876. return;
  9877. }
  9878. var value = $searchInput.val();
  9879. if (value.length >= 1 && val !== value) {
  9880. val = $searchInput.val();
  9881. loading = true;
  9882. suggestLoader.load(val, onSuggestLoaded);
  9883. } else {
  9884. loading = false;
  9885. }
  9886. }, 300),
  9887. onSuggestLoaded = function(err, result) {
  9888. if (err) {
  9889. return;
  9890. }
  9891. if (result.candidates) {
  9892. console.log(result.candidates);
  9893. var candidates = result.candidates;
  9894. var options = [];
  9895. for (var i = candidates.length - 1; i >= 0; i--) {
  9896. options.unshift(['<option value="', candidates[i], '"></option>'].join(''));
  9897. }
  9898. wordSuggest = options.join('');
  9899. refresh();
  9900. }
  9901. loading = false;
  9902. },
  9903. refresh = function() {
  9904. $suggestList.html(wordSuggest + favTagsSuggest);
  9905. },
  9906. bind = function($elm) {
  9907. $elm
  9908. .on('focus', update)
  9909. .on('keydown', update)
  9910. .on('keyup', update)
  9911. .on('keypress', update)
  9912. .on('click', update)
  9913. .on('mousedown', update)
  9914. .on('mouseup', update)
  9915. .attr({'autocomplete': 'on', 'list': 'quickSearchSuggestList', 'placeholder': '検索ワードを入力(Q)'});
  9916. // try {
  9917. // //$elm.attr('type', 'search');
  9918. // //$elm[0].setAttribute('type', 'search');//.attr('type', 'search');
  9919. // } catch (e) {
  9920. // console.log(e);
  9921. // }
  9922. };
  9923.  
  9924. EventDispatcher.addEventListener('onFavTagsLoad', function(tags) {
  9925. var options = [];
  9926. for (var i = tags.length - 1; i >= 0; i--) {
  9927. options.unshift(['<option value="', tags[i], '"></option>'].join(''));
  9928. }
  9929. favTagsSuggest = options.join('');
  9930. refresh();
  9931. });
  9932. $('body').append($suggestList);
  9933.  
  9934. bind($searchInput);
  9935. } //
  9936.  
  9937. function initVideoExplorer($, conf, w) {
  9938. setupVideoExplorerStaticCss();
  9939.  
  9940. var
  9941. _vp = WatchApp.ns.components.videoexplorer,
  9942. initializer = watch.VideoExplorerInitializer,
  9943. controller = initializer.videoExplorerController,
  9944. explorer = controller.getVideoExplorer(),
  9945. explorerConfig = _vp.config.VideoExplorerConfig,
  9946. menu = explorer.getMenu(),
  9947. ContentItemType = _vp.model.ContentItemType,
  9948. ContentType = _vp.model.ContentType,
  9949. watchPageRouter = WatchApp.ns.model.state.WatchPageRouter.getInstance(),
  9950. playerConnector = watch.PlayerInitializer.nicoPlayerConnector,
  9951. searchType = 'tag',
  9952. $menu = $('.videoExplorerMenu'),
  9953. $searchInput = $('<input class="quickSearchInput" type="search" name="q" accesskey="q" required="required" />')
  9954. .attr({'title': '検索ワードを入力', 'placeholder': '検索ワードを入力(Q)'}),
  9955. $closeExplorer = $('<div class="closeVideoExplorer"><a href="javascript:;">▲ 画面を戻す</a></div>'),
  9956. $inputForm = $('<form action="javascript:void(0);" />').append($searchInput),
  9957. $toggleCommentPanel = $('<button class="toggleCommentPanel">コメント</button>');
  9958.  
  9959. // init search menu
  9960. $searchInput.on('keyup', function(e) {
  9961. $('.searchText input').val(this.value);
  9962. }).on('click', function(e) {
  9963. e.stopPropagation();
  9964. });
  9965. $inputForm.on('submit', function(e) {
  9966. //e.preventDefault();
  9967. var val = $.trim($searchInput.val());
  9968. if (val.length > 0) {
  9969. if (val.match(/(sm|nm|so)\d+/)) {
  9970. WatchController.nicoSearch(val, 'tag');
  9971. } else {
  9972. WatchController.nicoSearch(val, 'keyword');
  9973. }
  9974. }
  9975. });
  9976. EventDispatcher.addEventListener('onSearchStart', function(word, type) {
  9977. searchType = type.replace(/^.*\./, '');
  9978. $searchInput.val(word);
  9979. });
  9980. initAutoComplete($searchInput);
  9981.  
  9982. $closeExplorer.find('a').on('click', function() {
  9983. WatchController.closeSearch();
  9984. });
  9985.  
  9986. // メニュー拡張
  9987. var
  9988. detachMenuItems = function() {
  9989. WatchItLater.videoExplorerMenu.myShortcuts .detach();
  9990. WatchItLater.videoExplorerMenu.videoRanking.detach();
  9991. WatchItLater.videoExplorerMenu.favTags .detach();
  9992. WatchItLater.videoExplorerMenu.favMylists .detach();
  9993.  
  9994. $inputForm.detach();
  9995. $closeExplorer.detach();
  9996. },
  9997. attachMenuItems = function() {
  9998. if (conf.enableFavTags ) { WatchItLater.videoExplorerMenu.favTags.attach(); }
  9999. if (conf.enableFavMylists) { WatchItLater.videoExplorerMenu.favMylists.attach(); }
  10000. WatchItLater.videoExplorerMenu.videoRanking.attach();
  10001. WatchItLater.videoExplorerMenu.myShortcuts.attach();
  10002.  
  10003.  
  10004. $('.videoExplorerMenu')
  10005. .find('.itemList>li:first').append($inputForm)
  10006. .end().find('.errorMessage').after($closeExplorer);
  10007. };
  10008. controller._refreshMenu_org = controller._refreshMenu;
  10009. controller._refreshMenu = $.proxy(function() {
  10010. detachMenuItems();
  10011. this._refreshMenu_org();
  10012. attachMenuItems();
  10013. }, controller);
  10014. controller.showDeflist_org = controller.showDeflist;
  10015. controller.showMylist_org = controller.showMylist;
  10016. controller.showOtherUserVideos_org = controller.showOtherUserVideos;
  10017. controller.showOwnerVideo_org = controller.showOwnerVideo;
  10018. controller.searchVideo_org = controller.searchVideo;
  10019. controller.showDeflist = $.proxy(function() {
  10020. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10021. location.href = "/my/mylist";
  10022. return;
  10023. }
  10024. this.showDeflist_org();
  10025. }, controller);
  10026. controller.showMylist = $.proxy(function(id) {
  10027. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10028. location.href = "/mylist/" + id;
  10029. return;
  10030. }
  10031. this.showMylist_org(id);
  10032. }, controller);
  10033. controller.showOtherUserVideos = $.proxy(function(id, name) {
  10034. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10035. location.href = "/user/" + id;
  10036. return;
  10037. }
  10038. this.showOtherUserVideos_org(id, name);
  10039. }, controller);
  10040. controller.showOwnerVideo = $.proxy(function() {
  10041. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10042. location.href = "/user/" + WatchController.getOwnerId();
  10043. return;
  10044. }
  10045. this.showOwnerVideo_org();
  10046. }, controller);
  10047. controller.searchVideo = $.proxy(function(word, type) {
  10048. if (conf.disableVideoExplorer && !WatchController.isSearchMode()) {
  10049. var sortOrder = '?sort=' + conf.searchSortType + '&order=' + conf.searchSortOrder;
  10050. location.href = (type === 'tag' ? 'tag' : 'search') + "/" + encodeURIComponent(word) + sortOrder;
  10051. return;
  10052. }
  10053. this.searchVideo_org(word, type);
  10054. }, controller);
  10055.  
  10056. EventDispatcher.addEventListener('onBottomContentTabViewReset', function(name) {
  10057. if (name === 'outline') {
  10058. WatchController.closeSearch();
  10059. }
  10060. });
  10061.  
  10062.  
  10063. EventDispatcher.addEventListener('on.config.enableFavTags',
  10064. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  10065. EventDispatcher.addEventListener('on.config.enableFavMylists',
  10066. function() { if (WatchController.isSearchMode()) { controller._refreshMenu(); }});
  10067.  
  10068. EventDispatcher.addEventListener('onVideoExplorerOpened', function(content) {
  10069. setTimeout(function() {
  10070. if (conf.videoExplorerHack) {
  10071. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  10072. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  10073. }
  10074. }, 100);
  10075.  
  10076. $('body').removeClass('videoExplorerOpening');
  10077. $('.videoExplorerMenu').addClass('initialized');
  10078. });
  10079. EventDispatcher.addEventListener('onVideoExplorerRefreshEnd', function(content) {
  10080. if (content.getType() === ContentType.USER_VIDEO) {
  10081. var items = content.getItems();
  10082. if (items.length === 1 && items[0].getContentItemType() !== ContentItemType.VIDEO) {
  10083. // ユーザーの投稿動画一覧が公開マイリスト一つだけだったら自動でそれを開く
  10084. items[0].stepIn();
  10085. return;
  10086. }
  10087. }
  10088. });
  10089. EventDispatcher.addEventListener('onVideoExplorerRefreshStart', function(content) {
  10090. window.WatchApp.ns.util.WindowUtil.scrollFit('#videoExplorer');
  10091. });
  10092. EventDispatcher.addEventListener('onVideoExplorerOpening', function(content) {
  10093. $('body').addClass('videoExplorerOpening');
  10094. adjustVideoExplorerSize(true);
  10095. });
  10096. EventDispatcher.addEventListener('onVideoExplorerClosing', function(content) {
  10097. });
  10098.  
  10099. EventDispatcher.addEventListener('onBeforeVideoExplorerMenuClear', function() {
  10100. detachMenuItems();
  10101. });
  10102.  
  10103. EventDispatcher.addEventListener('onUpdateSettingPanelVisible', function(isVisible, panel) {
  10104. if (isVisible && WatchController.isSearchMode()) {
  10105. setTimeout(function() {
  10106. WatchController.closeSearch();
  10107. setTimeout(function() {
  10108. playerConnector.updateSettingsPanelVisible(true, panel);
  10109. }, 800);
  10110. }, 100);
  10111. }
  10112. });
  10113.  
  10114. EventDispatcher.addEventListener('onFirstVideoExplorerOpened', function() {
  10115. window.console.time('onFirstVideoExplorerOpen');
  10116. EventDispatcher.addEventListener('onWindowResizeEnd', adjustVideoExplorerSize);
  10117. EventDispatcher.addEventListener('onVideoInitialized', adjustVideoExplorerSize);
  10118. window.console.timeEnd('onFirstVideoExplorerOpen');
  10119. });
  10120.  
  10121. var duration_match = /^([0-9]+):([0-9]+)/;
  10122. controller._item2playlistItem = function (item) {
  10123. // 動画長が入るようにする
  10124. var length_seconds = 0, len = item.getLength ? item.getLength() : '', m;
  10125. if (typeof len === 'string' && (m = duration_match.exec(len)) !== null) {
  10126. length_seconds = m[1] * 60 + m[2] * 1;
  10127. }
  10128. return new WatchApp.ns.model.playlist.PlaylistItem({
  10129. id : item.getId(),
  10130. title : item.getTitle(),
  10131. thumbnail_url : item.getThumbnailUrl(),
  10132. view_counter : item.getViewCounter(),
  10133. num_res : item.getNumRes(),
  10134. mylist_counter: item.getMylistCounter(),
  10135. mylist_comment: item.getMylistComment(),
  10136. first_retrieve: item.getFirstRetrieve(),
  10137. ads_counter : item.getUadCounter(),
  10138. length_seconds: length_seconds
  10139. });
  10140. };
  10141.  
  10142. initVideoExplorerItemContent();
  10143.  
  10144. $('#playerAlignmentArea').append($toggleCommentPanel);
  10145. $toggleCommentPanel.on('click', function() {
  10146. AnchorHoverPopup.hidePopup();
  10147. $('#playerTabWrapper').toggleClass('w_active');
  10148. $toggleCommentPanel.toggleClass('w_active', $('#playerTabWrapper').hasClass('w_active'));
  10149. setTimeout(function() {
  10150. watch.PlayerInitializer.commentPanelViewController.contentManager.activeContent().refresh();
  10151. }, 1000);
  10152. }).on('mouseover', function() {
  10153. AnchorHoverPopup.hidePopup();
  10154. });
  10155.  
  10156. var toggleVideoExplorerHack = function(v) {
  10157. $('#videoExplorer, #content, #footer, #bottomContentTabContainer').toggleClass('w_adjusted', v);
  10158. if (v) {
  10159. $('#content').append($('.videoExplorerMenu'));
  10160. if (WatchController.isSearchMode()) {
  10161. playerConnector.updatePlayerConfig({playerViewSize: ''}); // ノーマル画面モード
  10162. adjustVideoExplorerSize(true);
  10163. }
  10164. } else {
  10165. $('.videoExplorerContentWrapper').before($('.videoExplorerMenu'));
  10166. setTimeout(function() {
  10167. if (WatchController.isSearchMode()) {
  10168. playerConnector.updatePlayerConfig({playerViewSize: 'small'});
  10169. WatchApp.ns.util.WindowUtil.shake();
  10170. }
  10171. }, 1000);
  10172. }
  10173. };
  10174. EventDispatcher.addEventListener('on.config.videoExplorerHack', toggleVideoExplorerHack);
  10175. toggleVideoExplorerHack(conf.videoExplorerHack);
  10176.  
  10177.  
  10178. watchPageRouter._prepareState_org = watchPageRouter._prepareState;
  10179. watchPageRouter._prepareState = $.proxy(function(state) {
  10180. if (
  10181. conf.videoExplorerHack &&
  10182. WatchController.isSearchMode() &&
  10183. state.getVideoId() !== this._currentState.getVideoId()
  10184. ) {
  10185. state.prepare({
  10186. video: {id: this._watchInfoModel.v}
  10187. });
  10188. return state;
  10189. } else {
  10190. return this._prepareState_org(state);
  10191. }
  10192. }, watchPageRouter);
  10193. window.WatchApp.ns.model.state.WatchPageState.prototype.isVideoStateChange =
  10194. function(state) {
  10195. return this.getVideoId() !== state.getVideoId();
  10196. };
  10197.  
  10198.  
  10199. } // end initVideoExplorer
  10200.  
  10201. function initVideoExplorerItemContent() {
  10202.  
  10203. // 動画情報表示のテンプレートを拡張
  10204. var
  10205. overrideItemTemplate = function() {
  10206. var menu =
  10207. '<div class="thumbnailHoverMenu">' +
  10208. '<button class="showLargeThumbnail" onclick="WatchItLater.onShowLargeThumbnailClick(this);" title="大きいサムネイルを表示">+</button>' +
  10209. '<button class="deleteFromMyMylist" onclick="WatchItLater.onDeleteFromMyMylistClick(this);">マイリスト外す</button>' +
  10210. '</div>', $menu = $(menu);
  10211.  
  10212. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html());
  10213. $template.find('.column1 .thumbnailContainer').append($menu).end()
  10214. .find('.column4 .balloon').before($menu.clone()).end()
  10215. .find('.column4 .balloon').remove().end()
  10216. .find('.descriptionShort')
  10217. .after($('<p class="itemMylistComment mylistComment"/>')).end()
  10218. .find('.createdTime').after($('<div class="nicorepoOwnerIconContainer"><a target="_blank"><img /></a></div>'));
  10219. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.videoItemTemplate').html($template.html());
  10220. $template = $menu = null;
  10221.  
  10222. },
  10223. onDeleteFromMyMylistClick = function(elm) {
  10224. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  10225. var contentList = WatchApp.ns.init.VideoExplorerInitializer.videoExplorer.getContentList();
  10226. var
  10227. $videoItem = $(elm).parent().parent(),
  10228. watchId = $videoItem.find('.link').attr('href').split('/').reverse()[0],
  10229. ac = contentList.getActiveContent(),
  10230. type = contentList.getActiveContentType(),
  10231. onUpdate = function(status, result) {
  10232. if (status !== "ok") {
  10233. Popup.alert('削除に失敗: ' + result.error.description);
  10234. } else {
  10235. $videoItem.parent().animate({opacity: 0.3}, 500);
  10236. }
  10237. };
  10238.  
  10239. if (type === ContentType.MYLIST_VIDEO) {
  10240. if (!ac.getIsMine()) { return; }
  10241. Mylist.deleteMylistItem(watchId, ac.getMylistId(), onUpdate, 0);
  10242. } else
  10243. if (type === ContentType.DEFLIST_VIDEO) {
  10244. Mylist.deleteDefListItem(watchId, onUpdate, 0);
  10245. }
  10246. },
  10247. onShowLargeThumbnailClick = function (elm) {
  10248. var
  10249. $videoItem = $(elm).parent().parent(),
  10250. src = $videoItem.find('.thumbnail').attr('src');
  10251. if (!src) { return; }
  10252. showLargeThumbnail(src);
  10253. };
  10254. overrideItemTemplate();
  10255. WatchItLater.onDeleteFromMyMylistClick = onDeleteFromMyMylistClick;
  10256. WatchItLater.onShowLargeThumbnailClick = onShowLargeThumbnailClick;
  10257.  
  10258. // 動画情報表示の拡張
  10259. var ItemView = WatchApp.ns.components.videoexplorer.view.content.item.AbstractVideoContentItemView;
  10260. ItemView.prototype._setView_org = ItemView.prototype._setView;
  10261.  
  10262. ItemView.prototype.update_org = ItemView.prototype.update;
  10263. ItemView.prototype.update = function() {
  10264. // 動画情報表示をゴリゴリいじる場所
  10265. var item = this._item, $item = this._$item;
  10266. this.update_org(item);
  10267.  
  10268. this._$item.find('.deleteFromMyMylist').data('watchId', this._item.getId());
  10269. if (item._mylistComment) { // マイリストコメント
  10270. $item.find('.itemMylistComment').css({display: 'block'});
  10271. } else {
  10272. $item.find('.itemMylistComment').remove();
  10273. }
  10274.  
  10275. this._$item.find('.thumbnailContainer')
  10276. .css('background-image', 'url(' + this._$thumbnail.attr('src') + ')');
  10277.  
  10278. if (item._seed && item._seed._info) {
  10279. var info = item._seed._info;
  10280. if (info.nicorepo_owner) { // ニコレポ
  10281. $item.addClass(info.nicorepo_className).addClass('nicorepoResult');
  10282. var owner = info.nicorepo_owner;
  10283. var $iconContainer = $item.find('.nicorepoOwnerIconContainer'), $icon = $iconContainer.find('img'), $link = $iconContainer.find('a');
  10284. $icon.attr('src', owner.icon);
  10285. $link.attr({'href': owner.page, 'data-ownerid': owner.id, 'title': owner.name + ' さん'});
  10286. if (info.nicorepo_className.indexOf('log-user-') >= 0) {
  10287. $link.attr(
  10288. 'onclick',
  10289. 'if (arguments[0].button > 0) return; arguments[0].preventDefault();' +
  10290. 'WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController.showOtherUserVideos(this.dataset.ownerid, this.title);' +
  10291. 'WatchApp.ns.util.WindowUtil.scrollFit($("#videoExplorer"));'
  10292. );
  10293. }
  10294. if (info.nicorepo_log.length > 1) {
  10295. $item.find('.descriptionShort').html(info.nicorepo_log.join('<br>'));
  10296. }
  10297. // ニコレポは再生数が取れないので-で埋める
  10298. this._$viewCount .html('-');
  10299. this._$commentCount.html('-');
  10300. this._$mylistCount .html('-');
  10301. $iconContainer = $icon = $link = null;
  10302. }
  10303. }
  10304. if (item._seed && typeof item._seed.description_full === 'string' && item._seed.description_full.length > 150) {
  10305. this._$descriptionShort.attr('title', item._seed.description_full);
  10306. }
  10307. $item = null;
  10308.  
  10309. };
  10310. ItemView = null;
  10311.  
  10312. } // end initVideoExplorerItemContent
  10313.  
  10314.  
  10315.  
  10316. var lastVideoOwnerJson = '';
  10317. function onWatchInfoReset(watchInfoModel) {
  10318. $('body').toggleClass('w_channel', watchInfoModel.isChannelVideo());
  10319. EventDispatcher.dispatch('onWatchInfoReset', watchInfoModel);
  10320. var owner = WatchController.getOwnerInfo(), owner_json = JSON.stringify(owner);
  10321. if (lastVideoOwnerJson.length > 0 && lastVideoOwnerJson !== owner_json) {
  10322. EventDispatcher.dispatch('onVideoOwnerChanged', owner);
  10323. }
  10324. lastVideoOwnerJson = owner_json;
  10325. }
  10326.  
  10327. function onScreenModeChange(sc) {
  10328. setTimeout(function() {
  10329. EventDispatcher.dispatch('onScreenModeChange', sc);
  10330. }, 500);
  10331. }
  10332.  
  10333. function initMylistPanel($, conf, w) {
  10334. var iframe = Mylist.getPanel('');
  10335. iframe.id = "mylist_add_frame";
  10336. iframe.className += " fixed";
  10337. w.document.body.appendChild(iframe);
  10338. iframe.hide(); // ページの初期化が終わるまでは表示しない
  10339.  
  10340. var $iframe = $(iframe);
  10341. $iframe.find('.mylistSelect').attr('accesskey', ':');
  10342.  
  10343. var toggleMylistMenuInFull = function(v) {
  10344. $('.mylistPopupPanel')
  10345. .toggleClass('hideInFull', v === 'hide')
  10346. .toggleClass('hideAllInFull', v === 'hideAll');
  10347. };
  10348. EventDispatcher.addEventListener('on.config.hideMenuInFull', toggleMylistMenuInFull);
  10349. toggleMylistMenuInFull(conf.hideMenuInFull);
  10350.  
  10351. var setMylistPanelPosition = function(v) {
  10352. $iframe
  10353. .toggleClass('left', v.indexOf('left') >= 0)
  10354. .toggleClass('top', v.indexOf('top') >= 0);
  10355. setTimeout(function() {
  10356. $('#yukkuriPanel')
  10357. .toggleClass('mylistPanelLeft', v.indexOf('left') >= 0 && v.indexOf('top') < 0);
  10358. }, 500);
  10359. };
  10360. EventDispatcher.addEventListener('on.config.mylistPanelPosition', setMylistPanelPosition);
  10361. if (conf.mylistPanelPosition !== '') setMylistPanelPosition(conf.mylistPanelPosition);
  10362.  
  10363. var $footer = $('#footer'), $window = $(window);
  10364. var toggleMylistPanelStyle = function() {
  10365. if ($footer.is(':visible')) {
  10366. $iframe.toggleClass('black', $window.scrollTop() + $window.innerHeight() - $footer.offset().top >= 0);
  10367. } else {
  10368. $iframe.removeClass('black');
  10369. }
  10370. };
  10371.  
  10372. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10373. toggleMylistPanelStyle();
  10374. var newVideoId = watchInfoModel.id;
  10375. var newWatchId = watchInfoModel.v;
  10376. iframe.watchId(newVideoId, newWatchId);
  10377. if (isFirst) iframe.show();
  10378. });
  10379. EventDispatcher.addEventListener('onScrollEnd', toggleMylistPanelStyle);
  10380. EventDispatcher.addEventListener('onWindowResizeEnd', toggleMylistPanelStyle);
  10381. EventDispatcher.addEventListener('onScreenModeChange', toggleMylistPanelStyle);
  10382. EventDispatcher.addEventListener('on.config.hidePlaylist', toggleMylistPanelStyle);
  10383. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', toggleMylistPanelStyle);
  10384. } //
  10385.  
  10386. function initScreenMode() {
  10387. EventDispatcher.addEventListener('onVideoInitialized', function(isFirst) {
  10388. if (conf.autoBrowserFull) {
  10389. setTimeout(function() {
  10390. if ($('body').hasClass('up_marquee') && conf.disableAutoBrowserFullIfNicowari) {
  10391. // ユーザーニコ割があるときは自動全画面にしない
  10392. return;
  10393. }
  10394. if (WatchController.isSearchMode()) { // TODO: localStorageに直接アクセスすんな
  10395. var settingSize = (localStorage["PLAYER_SETTINGS.LAST_PLAYER_SIZE"] === '"normal"') ? 'normal' : 'medium';
  10396. WatchController.changeScreenMode(settingSize);
  10397. }
  10398. WatchController.changeScreenMode('browserFull');
  10399. onWindowResizeEnd(); // TODO:;;;;
  10400. }, 100);
  10401. } else {
  10402. if (conf.autoOpenSearch && !WatchController.isSearchMode() && !$('body').hasClass('full_with_browser')) {
  10403. WatchController.openSearch();
  10404. }
  10405. if (conf.autoScrollToPlayer) {
  10406. // 初回のみ、プレイヤーが画面内に納まっていてもタグの位置まで自動スクロールさせる。(ファーストビューを固定するため)
  10407. // 二回目以降は説明文や検索結果からの遷移なので、必要最小限の動きにとどめる
  10408. if (!WatchController.isSearchMode() || isFirst) {
  10409. WatchController.scrollToVideoPlayer(isFirst);
  10410. }
  10411. }
  10412. }
  10413. });
  10414.  
  10415.  
  10416. var lastPlayerConfig = null, lastScreenMode = '';
  10417.  
  10418. function hideIfNeed() {
  10419. if (conf.controllerVisibilityInFull === 'hidden') {
  10420. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set({oldTypeCommentInput: true, oldTypeControlPanel: false});
  10421. $('body').addClass('hideCommentInput');
  10422. } else {
  10423. var $w = $(window), iw = $w.innerWidth(), ih = $w.innerHeight();
  10424. var controllerH = 46, inputH = 30;
  10425. }
  10426. }
  10427.  
  10428. function restoreVisibility() {
  10429. if (lastPlayerConfig !== null) {
  10430. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10431. {oldTypeCommentInput: !!lastPlayerConfig.oldTypeCommentInput, oldTypeControlPanel: !!lastPlayerConfig.oldTypeControlPanel}
  10432. );
  10433. $(window).resize();
  10434. }
  10435. $('body').removeClass('hideCommentInput');
  10436. }
  10437.  
  10438. function toggleTrueBrowserFull(v) {
  10439. v = (typeof v === 'boolean') ? v : !$('body').hasClass('trueBrowserFull');
  10440. $('body').toggleClass('trueBrowserFull', v).toggleClass('full_and_mini', v);
  10441. conf.setValue('enableTrueBrowserFull', v);
  10442. if (!v) {
  10443. watch.PlaylistInitializer.playlistView.resetView();
  10444. }
  10445. return v;
  10446. }
  10447.  
  10448. function initShield() {
  10449. var shield = $('<div id="trueBrowserFullShield" />');
  10450. shield.click(function(e) {
  10451. e.stopPropagation();
  10452. toggleTrueBrowserFull();
  10453. });
  10454. $('#external_nicoplayer').after(shield);
  10455. shield = null;
  10456. }
  10457. initShield();
  10458.  
  10459. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  10460. var mode = sc.mode;
  10461. $('body').removeClass('w_fullScreenMenu');
  10462. if (mode === 'browserFull' && lastScreenMode !== mode) {
  10463. lastPlayerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig.get();
  10464. conf.setValue('lastControlPanelPosition', lastPlayerConfig.oldTypeControlPanel ? 'bottom' : 'over');
  10465. //$('body').toggleClass('w_fullWithPlaylist', WatchController.isFullScreenContentAll());
  10466. hideIfNeed();
  10467. if (conf.enableTrueBrowserFull) toggleTrueBrowserFull(conf.enableTrueBrowserFull);
  10468. } else
  10469. if (lastScreenMode === 'browserFull' && mode !== 'browserFull') {
  10470. conf.setValue('lastControlPanelPosition', '');
  10471. $('#playerContainerSlideArea').css({height: ''}); // wall bug fix
  10472. restoreVisibility();
  10473. }
  10474. lastScreenMode = mode;
  10475. });
  10476.  
  10477. $(window).on('beforeunload.watchItLater', function(e) {
  10478. conf.setValue('lastControlPanelPosition', '');
  10479. restoreVisibility();
  10480. });
  10481.  
  10482. var wheelCounter = 0, wheelTimer = null;
  10483. EventDispatcher.addEventListener('onWheelNoButton', function(e, delta) {
  10484. if (!conf.enableFullScreenMenu) return;
  10485. if ((e.target.tagName !== 'OBJECT' && e.target.tagName !== 'HTML') ||
  10486. !WatchController.isFullScreen()) return;
  10487. if (wheelTimer) {
  10488. wheelCounter += delta;
  10489. } else {
  10490. wheelCounter = 0;
  10491. wheelTimer = setTimeout(function() {//
  10492. wheelTimer = null;
  10493. if (Math.abs(wheelCounter) > 3) {
  10494. EventDispatcher
  10495. .dispatch('onToggleFullScreenMenu',
  10496. $('body').toggleClass('w_fullScreenMenu', wheelCounter < 0).hasClass('w_fullScreenMenu')
  10497. );
  10498. AnchorHoverPopup.hidePopup();
  10499. }
  10500. }, 500);
  10501. }
  10502. });
  10503.  
  10504. TouchEventDispatcher.onflick(function(e) {
  10505. if (!conf.enableFullScreenMenu) return;
  10506. if ((e.direction !=='up' && e.direction !=='down') || e.startEvent.target.tagName !== 'OBJECT' || !WatchController.isFullScreen()) return;
  10507. if (wheelTimer) {
  10508. clearTimeout(wheelTimer);
  10509. wheelTimer = null;
  10510. }
  10511. EventDispatcher
  10512. .dispatch('onToggleFullScreenMenu',
  10513. $('body').toggleClass('w_fullScreenMenu', e.direction === 'down').hasClass('w_fullScreenMenu')
  10514. );
  10515. AnchorHoverPopup.hidePopup();
  10516. });
  10517.  
  10518. var $fullScreenMenuContainer = $('<div id="fullScreenMenuContainer"/>');
  10519. var $fullScreenModeSwitch = $([
  10520. '<button class="fullScreenModeSwitch button">',
  10521. '画面モード: ',
  10522. '<span class="modeStatus mode_normal">標準</span>',
  10523. '<span class="modeStatus mode_noborder">最大化 </span>',
  10524. '</button>'
  10525. ].join('')).attr('title', '全画面時の表示切り替え').click(toggleTrueBrowserFull);
  10526. var $toggleStageVideo = $([
  10527. '<button class="stageVideoSwitch button">',
  10528. 'アクセラレーション: ',
  10529. '<span class="modeStatus mode_off">OFF</span>',
  10530. '<span class="modeStatus mode_on">ON</span>',
  10531. '</button>'
  10532. ].join('')).attr('title', 'ハードウェアアクセラレーションのON/OFF').click(function() { WatchController.toggleStageVideo(); });
  10533. var $toggleSetting = $([
  10534. '<button class="toggleSetting button">',
  10535. '</button>'
  10536. ].join('')).text('⛭設定').attr('title', '設定パネルを開閉します').click(function() { ConfigPanel.toggle();});
  10537. $fullScreenMenuContainer.append($fullScreenModeSwitch).append($toggleStageVideo).append($toggleSetting);
  10538. $('#nicoplayerContainerInner').append($fullScreenMenuContainer);
  10539.  
  10540.  
  10541. if (conf.lastControlPanelPosition === 'bottom' || conf.lastControlPanelPosition === 'over') {
  10542. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  10543. console.log('restore oldTypeControlPanel ? ', conf.lastControlPanelPosition === 'bottom');
  10544. watch.PlayerInitializer.nicoPlayerConnector.playerConfig.set(
  10545. {oldTypeControlPanel: conf.lastControlPanelPosition === 'bottom'}
  10546. );
  10547. });
  10548. }
  10549.  
  10550. } // end initScreenMode()
  10551.  
  10552.  
  10553.  
  10554.  
  10555. function initPlaylist($, conf, w) {
  10556. var
  10557. playlist = watch.PlaylistInitializer.playlist,
  10558. blankVideoId = 'sm20353707', blankVideoUrl = 'http://www.nicovideo.jp/watch/' + blankVideoId + '?',
  10559. redirectPageUrl = 'http://www.nicovideo.jp/stamp',
  10560. items = {},
  10561. toCenter = function() { // 表示位置調整
  10562. var
  10563. pm = WatchApp.ns.view.playlist.PlaylistManager,
  10564. pv = watch.PlaylistInitializer.playlistView,
  10565. pl = playlist,
  10566. current = pl.getPlayingIndex(),
  10567. cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()),
  10568. center = Math.round(cols / 2);
  10569.  
  10570. if (cols < 1) { return; }
  10571. var currentLeft = pm.getLeftSideIndex();
  10572. pv.scroll(Math.max(0, current - center + 1));
  10573. },
  10574. scroll = function(d) {
  10575. var isEffectEnabled = watch.PlaylistInitializer.playlistView.isEffectEnabled;
  10576. var left = WatchApp.ns.view.playlist.PlaylistManager.getLeftSideIndex();
  10577. watch.PlaylistInitializer.playlistView.isEffectEnabled = false;
  10578. watch.PlaylistInitializer.playlistView.scroll(Math.max(0, left + d));
  10579. watch.PlaylistInitializer.playlistView.isEffectEnabled = isEffectEnabled;
  10580. };
  10581.  
  10582. playlist.isAutoPlay = playlist.isContinuous; // 互換用
  10583. playlist.enableAutoPlay = playlist.enableContinuous;
  10584. playlist.disableAutoPlay = playlist.disableContinuous;
  10585.  
  10586. $('#playlist').find('.playlistInformation').on('dblclick.watchItLater', function(e) {
  10587. e.preventDefault();
  10588. e.stopPropagation();
  10589. toCenter();
  10590. });
  10591.  
  10592. EventDispatcher.addEventListener('onVideoInitialized', function() {
  10593. var pm = WatchApp.ns.view.playlist.PlaylistManager, pv = watch.PlaylistInitializer.playlistView, pl = watch.PlaylistInitializer.playlist;
  10594. var current = pl.getPlayingIndex(), cols = Math.floor($('#playlistContainerInner').innerWidth() / pm.getItemWidth()), center = Math.floor(cols / 2);
  10595. if (pm.getLeftSideIndex() + cols <= pl.getNextPlayingIndex()) { toCenter(); }
  10596. });
  10597.  
  10598. $('#playlistContainer .prevArrow, #playlistContainer .nextArrow').on('mousewheel.watchItLater', function(e, delta) {
  10599. if (WatchController.isFullScreen()) { return; }
  10600. e.preventDefault();
  10601. e.stopPropagation();
  10602. scroll(delta *-1);
  10603. }).attr('title', 'ホイールで左右にスクロール');
  10604.  
  10605. // フルスクリーン中はプレイリストのどこでもスクロールできたほうがいいね
  10606. $('#playlist').on('mousewheel.watchItLater', function(e, delta) {
  10607. if (WatchController.isFullScreen() || WatchController.isSearchMode() || $('#footer').hasClass('noBottom')) {
  10608. e.preventDefault();
  10609. e.stopPropagation();
  10610. scroll(delta *-1);
  10611. }
  10612. });
  10613.  
  10614. EventDispatcher.addEventListener('onWheelAndButton', function(e, delta, button) {
  10615. if (WatchController.isFullScreen()) { return; }
  10616. if ($('#playlist').hasClass('dragging')) {
  10617. e.preventDefault();
  10618. scroll(delta *-1);
  10619. }
  10620. });
  10621.  
  10622. var
  10623. updatePos = function() {
  10624. if (
  10625. conf.hashPlaylistMode === 2 || (conf.hashPlaylistMode === 1 && WatchController.isPlaylistActive())) {
  10626. LocationHashParser.setValue('playlist', exportPlaylist());
  10627. LocationHashParser.updateHash();
  10628. }
  10629. if (conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') {
  10630. setTimeout(function() {
  10631. w[conf.storagePlaylistMode].setItem('watchItLater_playlist', JSON.stringify(exportPlaylist()));
  10632. }, 0);
  10633. }
  10634.  
  10635. var pos = Math.max((playlist.getPlayingIndex() + 1), 1) + '/' + Math.max(playlist.getItems().length, 1);
  10636. $('.generationMessage').text(pos + " - \n" + $('.generationMessage').text().replace(/^.*\n/, ''));
  10637. },
  10638. resetView = function() {
  10639. watch.PlaylistInitializer.playlistView.resetView();
  10640. },
  10641. exportPlaylist = function(option, type, continuous, shuffle) {
  10642. var
  10643. items = playlist.currentItems,
  10644. list = [],
  10645. current = 0,
  10646. len = conf.debugMode ? Math.min(600, items.length) : Math.min(300, items.length);
  10647.  
  10648. for (var i = 0; i < len; i++) {
  10649. var item = items[i];
  10650. if (item._isPlaying) current = i;
  10651. list.push([
  10652. item.id,
  10653. parseInt(item.mylistCounter, 10).toString(36),
  10654. parseInt(item.viewCounter, 10).toString(36),
  10655. parseInt(item.numRes, 10).toString(36),
  10656. (item.thumbnailUrl ? parseInt(item.thumbnailUrl.split('?i=')[1], 10).toString(36) : 'c490r'),
  10657. ].join(',') + ':' + item.title
  10658. );
  10659. }
  10660. return {
  10661. a: (typeof continuous === 'boolean') ? continuous : WatchController.isPlaylistContinuous(),
  10662. r: (typeof shuffle === 'boolean') ? shuffle : WatchController.isPlaylistRandom(),
  10663. o: option || playlist.option,
  10664. t: type || playlist.type,
  10665. i: list,
  10666. c: current
  10667. };
  10668. },
  10669. importPlaylist = function(list) {
  10670. var PlaylistItem = WatchApp.ns.model.playlist.PlaylistItem, newItems = [], uniq = {}, currentIndex = -1;
  10671.  
  10672. WatchController.clearPlaylist();
  10673. var currentItem = playlist.currentItems[0];
  10674. if (!currentItem) {
  10675. var wm = watchInfoModel;
  10676. currentItem = new PlaylistItem({
  10677. id: wm.v,
  10678. title: wm.title,
  10679. mylist_counter: wm.mylistCount,
  10680. view_counter: wm.viewCount,
  10681. num_res: wm.commentCount,
  10682. thumbnail_url: wm.thumbnail,
  10683. first_retriee: wm.postedAt
  10684. });
  10685. }
  10686.  
  10687. for (var i = 0, len = list.i.length; i < len; i++) {
  10688. var
  10689. dat = list.i[i],
  10690. c = dat.split(':')[0].split(','),
  10691. title = dat.replace(/^.*:/, ''),
  10692. id = c[0],
  10693. thumbnailId = parseInt(c[4], 36);
  10694.  
  10695. if (uniq[id] || typeof id !== 'string') { continue; }
  10696. uniq[id] = true;
  10697. if (id === watchInfoModel.v) {
  10698. currentIndex = i;
  10699. newItems.push(currentItem);
  10700. } else {
  10701. var item = new PlaylistItem({
  10702. id: id,
  10703. title: title.replace('<', '&lt;').replace('>', '&gt;'), // ないはずだけど一応
  10704. mylist_counter: parseInt(c[1], 36),
  10705. view_counter: parseInt(c[2], 36),
  10706. num_res: parseInt(c[3], 36),
  10707. thumbnail_url: 'http://tn-skr' + ((thumbnailId % 4) + 1) + '.smilevideo.jp/smile?i=' + thumbnailId,
  10708. first_retrieve: null
  10709. });
  10710. newItems.push(item);
  10711. }
  10712. }
  10713. // 復元するリストの中に現在の動画がなかった
  10714. if (currentIndex === -1) {
  10715. if (typeof list.c === 'number') {
  10716. if (list.c < newItems.length) {
  10717. currentIndex = list.c + 1;
  10718. newItems.splice(currentIndex, 0, currentItem);
  10719. } else {
  10720. currentIndex = list.length;
  10721. newItems.push(currentItem);
  10722. }
  10723. } else {
  10724. newItems.unshift(currentItem);
  10725. currentIndex = 0;
  10726. }
  10727. }
  10728.  
  10729. var isAutoPlay = playlist.isContinuous();//isAutoPlay();
  10730. playlist.reset(newItems, 'WatchItLater', list.t, list.o);
  10731. if (!isAutoPlay) { // 本家側の更新でリセット時に勝手に自動再生がONになるようになったので、リセット前の状態を復元する
  10732. playlist.disableContinuous();
  10733. }
  10734. if (currentIndex >= 0) { playlist.playingItem = newItems[currentIndex]; }
  10735. if (list.a) { playlist.enableContinuous(); }
  10736. if (list.r) {
  10737. if (watchInfoModel.id === blankVideoId) {
  10738. setTimeout(function() {
  10739. WatchController.shufflePlaylist();
  10740. }, 3000);
  10741. } else {
  10742. playlist.enableContinuous();
  10743. }
  10744. }
  10745. },
  10746. $dialog = null, $savelink = null, $continuous, $shuffle,
  10747. openSaveDialog = function() {
  10748. function resetLink() {
  10749. var playlist = exportPlaylist(null, null, $continuous.is(':checked'), $shuffle.is(':checked'));
  10750. playlist.o = playlist.o || [];
  10751. playlist.o.name = $savelink.text();
  10752. playlist.t = 'mylist';
  10753. LocationHashParser.setValue('playlist', playlist);
  10754. if (!playlist.r) {
  10755. LocationHashParser.setValue('redirectWatchId', watchInfoModel.id);
  10756. } else {
  10757. LocationHashParser.deleteValue('redirectWatchId');
  10758. }
  10759. $savelink
  10760. //.attr('href', blankVideoUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10761. .attr('href', redirectPageUrl + LocationHashParser.getHash().replace(/\?/g, ''))
  10762. .unbind();
  10763. }
  10764. function closeDialog() {
  10765. $dialog.removeClass('show');
  10766. }
  10767.  
  10768. if (!$dialog) {
  10769. $dialog = $('<div id="playlistSaveDialog" />');
  10770. $dialog.append($([
  10771. '<div class="shadow"></div>',
  10772. '<div class="formWindow"><div class="formWindowInner">',
  10773. '<h3>プレイリスト保存用リンク(実験中)</h3>',
  10774. '<p class="link"><a target="_blank" class="playlistSaveLink">保存用リンク</a><button class="editButton">編集</button></p>',
  10775. '<label><input type="checkbox" class="continuous">開始時に連続再生をONにする</label><br>',
  10776. '<label><input type="checkbox" class="shuffle">開始時にリストをシャッフルする</label>',
  10777. '<p class="desc">リンクを右クリックしてコピーやブックマークする事で、現在のプレイリストを保存する事ができます。</p>',
  10778. '<button class="closeButton">閉じる</button>',
  10779. '</div></div>',
  10780. ''].join('')));
  10781. $savelink = $dialog.find('a').attr('added', 1);
  10782. $continuous = $dialog.find('.continuous');
  10783. $shuffle = $dialog.find('.shuffle');
  10784. $dialog.find('.shadow').on('click', closeDialog);
  10785. $dialog.find('.editButton').on('click', function() {
  10786. var newTitle = prompt('タイトルを編集', $savelink.text());
  10787. if (newTitle) {
  10788. $savelink.text(newTitle);
  10789. resetLink();
  10790. }
  10791. });
  10792. $continuous.on('click', resetLink);
  10793. $shuffle .on('click', resetLink);
  10794. $dialog.find('.closeButton').on('click', closeDialog);
  10795.  
  10796. $('body').append($dialog);
  10797. }
  10798. $savelink.text(
  10799. $('#playlist .generationMessage')
  10800. .text()
  10801. .replace(/^.*?\n/, '')
  10802. .replace(/^.*「/, '')
  10803. .replace(/」.*?$/, '')
  10804. .replace(/ *- \d{4}-\d\d-\d\d \d\d:\d\d$/, '') +
  10805. ' - ' + WatchApp.ns.util.DateFormat.strftime('%Y-%m-%d %H:%M', new Date())
  10806. );
  10807. $continuous.attr('checked', WatchController.isPlaylistActive());
  10808. $shuffle .attr('checked', WatchController.isPlaylistRandom());
  10809. resetLink();
  10810. $dialog.addClass('show');
  10811. },
  10812. PlaylistMenu = (function($, conf, w, playlist) {
  10813. var $popup = null, $generationMessage = $('#playlist').find('.generationMessage'), self;
  10814.  
  10815. var
  10816. enableContinuous = function() {
  10817. playlist.enableContinuous();
  10818. },
  10819. createDom = function() {
  10820. $popup = $('<div class="playlistMenuPopup popupMenu"/>')
  10821. .addClass('pop')
  10822. .toggleClass('w_touch', isTouchActive);
  10823. var $ul = $('<ul/>');
  10824. $popup.click(function() {
  10825. self.hide();
  10826. });
  10827. var $shuffle = $('<li>シャッフル: 全体</li>').click(function(e) {
  10828. WatchController.shufflePlaylist();
  10829. enableContinuous();
  10830. });
  10831. $ul.append($shuffle);
  10832. var $shuffleR = $('<li>シャッフル: 右</li>').click(function(e) {
  10833. WatchController.shufflePlaylist('right');
  10834. enableContinuous();
  10835. });
  10836. $ul.append($shuffleR);
  10837.  
  10838. var $next = $('<li>検索結果を追加: 次に再生</li>').click(function() {
  10839. WatchController.appendSearchResultToPlaylist('next');
  10840. enableContinuous();
  10841. });
  10842. $ul.append($next);
  10843.  
  10844. var $insert = $('<li>検索結果を追加: 末尾</li>').click(function() {
  10845. WatchController.appendSearchResultToPlaylist();
  10846. enableContinuous();
  10847. });
  10848. $ul.append($insert);
  10849.  
  10850. var $clear = $('<li>リストを消去: 全体</li>').click(function() {
  10851. WatchController.clearPlaylist();
  10852. //watch.PlaylistInitializer.playlist.setPlaybackMode('normal');
  10853. });
  10854. $ul.append($clear);
  10855.  
  10856. var $clearLeft = $('<li>リストを消去: 左</li>').click(function() {
  10857. WatchController.clearPlaylist('left');
  10858. });
  10859. $ul.append($clearLeft);
  10860. var $clearRight = $('<li>リストを消去: 右</li>').click(function() {
  10861. WatchController.clearPlaylist('right');
  10862. });
  10863. $ul.append($clearRight);
  10864.  
  10865. var $saver = $('<li>リストを保存(実験中)</li>').click(function() {
  10866. openSaveDialog();
  10867. });
  10868.  
  10869. $ul.append($saver);
  10870. $popup.append($ul);
  10871. $('body').append($popup);
  10872. },
  10873. show = function() {
  10874. if ($popup === null) { createDom(); }
  10875. var offset = $generationMessage.offset(), $window = $(window) , pageBottom = $window.scrollTop() + $window.innerHeight();
  10876. $popup.css({
  10877. left: offset.left,
  10878. top: Math.min(offset.top + 24, pageBottom - $popup.outerHeight())
  10879. }).show();
  10880. },
  10881. hide = function() {
  10882. if ($popup) { $popup.hide(); }
  10883. },
  10884. toggle = function() {
  10885. if ($popup === null || !$popup.is(':visible')) {
  10886. show();
  10887. } else {
  10888. hide();
  10889. }
  10890. };
  10891.  
  10892. $generationMessage.click(function(e) {
  10893. e.preventDefault();
  10894. self.toggle();
  10895. });
  10896.  
  10897. $('body').on('click.watchItLater', function(e) {
  10898. var tagName = e.target.tagName, className = e.target.className;
  10899. if (className !== 'generationMessage') {
  10900. self.hide();
  10901. }
  10902. });
  10903. self = {
  10904. show: show,
  10905. hide: hide,
  10906. toggle: toggle
  10907. };
  10908. return self;
  10909. })($, conf, w, playlist);
  10910.  
  10911.  
  10912. var hashlist = LocationHashParser.getValue('playlist');
  10913. if (hashlist && hashlist.i && hashlist.i.length > 0) {
  10914. try {
  10915. console.log('restore playlist!!');
  10916. importPlaylist(hashlist);
  10917. if (conf.hashPlaylistMode < 1) {
  10918. LocationHashParser.removeHash();
  10919. }
  10920. setTimeout(function() { resetView(); } , 3000);
  10921. } catch (e) {
  10922. console.log(e);
  10923. console.trace();
  10924. }
  10925. } else
  10926. if ((conf.storagePlaylistMode === 'sessionStorage' || conf.storagePlaylistMode === 'localStorage') && w[conf.storagePlaylistMode] && !playlist.isContinuous()) {
  10927. try {
  10928. console.log('restore playlist:' + conf.storagePlaylistMode);
  10929. var list = JSON.parse(w[conf.storagePlaylistMode].getItem('watchItLater_playlist'));
  10930. if (list !== null) { importPlaylist(list); }
  10931. setTimeout(function() { resetView(); } , 3000);
  10932. } catch (e) {
  10933. console.log('プレイリストの復元に失敗!', e);
  10934. }
  10935. } else {
  10936. updatePos();
  10937. }
  10938.  
  10939.  
  10940. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  10941. if ($('body').hasClass('full_with_browser')) {
  10942. // フル画面時プレイリストを閉じる
  10943. if (conf.autoClosePlaylistInFull) { $('#content').find('.browserFullPlaylistClose:visible').click(); }
  10944. }
  10945. });
  10946.  
  10947. EventDispatcher.addEventListener('onVideoExplorerOpened', function() {
  10948. // 2013/09/26 本家側で開閉を記録するようになった -> 2014/03/03 また記憶しなくなった
  10949.  
  10950. // 通常画面でプレイリストを表示にしてるなら、開いた状態をデフォルトにする
  10951. if (conf.hidePlaylistInVideoExplorer === false) {
  10952. playlist.open();
  10953. //$('#playlist').find('.browserFullOption a:visible').click();
  10954. }
  10955. });
  10956. $('#playlist .browserFullOption a').on('click', function() {
  10957. if (WatchController.isSearchMode()) {
  10958. conf.setValue('hidePlaylistInVideoExplorer', !conf.hidePlaylistInVideoExplorer);
  10959. }
  10960. });
  10961.  
  10962. EventDispatcher.addEventListener('on.config.hashPlaylistMode', function(v) {
  10963. if (v === 0) {
  10964. LocationHashParser.deleteValue('playlist');
  10965. LocationHashParser.removeHash();
  10966. } else
  10967. if (v === 1 || v === 2) {
  10968. var msg = [
  10969. '【警告】「プレイリストが消えないモード」は実験中の機能です。',
  10970. '',
  10971. 'この機能を使うと、ページをリロードしたりブックマークしてもプレイリストが消えなくなりますが、',
  10972. 'データを力技で保持するため、ページのURLがものすごく長く(※)なります。',
  10973. '',
  10974. 'そのため、ブラウザのパフォーマンスが低下したり、未知の不具合が発生する可能性があります。',
  10975. 'それでもこの機能を使ってみたい!という方だけ「OK」を押してください。',
  10976. '',
  10977. '※ 数千~数万文字くらい!',
  10978. ''].join('\n');
  10979. if (confirm(msg)) {
  10980. LocationHashParser.setValue('playlist', exportPlaylist());
  10981. LocationHashParser.updateHash();
  10982. } else {
  10983. conf.setValue('hashPlaylistMode', 0);
  10984. ConfigPanel.refresh();
  10985. }
  10986. }
  10987. });
  10988.  
  10989. $('#playlist .browserFullOption').on('click.bugfix', resetView);
  10990.  
  10991. $('.generationMessage, .prevArrow, .nextArrow, .playbackOption').on('mouseover', function() {
  10992. AnchorHoverPopup.hidePopup();
  10993. });
  10994.  
  10995. playlist.addEventListener('changePlaybackMode', function(mode) {
  10996. console.log('changePlaybackMode', mode, conf.hashPlaylistMode);
  10997. if (mode === 'normal' && conf.hashPlaylistMode < 2) {
  10998. LocationHashParser.removeHash();
  10999. } else {
  11000. updatePos();
  11001. }
  11002. });
  11003.  
  11004. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11005. updatePos();
  11006. EventDispatcher.addEventListener('onScreenModeChange', function(sc) {
  11007. resetView();
  11008. });
  11009. EventDispatcher.addEventListener('onWatchInfoReset', function() {
  11010. updatePos();
  11011. });
  11012. playlist.addEventListener('reset', function() {
  11013. EventDispatcher.dispatch('onPlaylistReset');
  11014. updatePos();
  11015. });
  11016. playlist.addEventListener('update', function() {
  11017. EventDispatcher.dispatch('onPlaylistUpdate');
  11018. updatePos();
  11019. });
  11020. });
  11021.  
  11022. var togglePlaylistDisplay = function(v) {
  11023. var $playlist = $('#playlist');
  11024. if (!v) {
  11025. $playlist.addClass('w_show').removeClass('w_closing');
  11026. } else {
  11027. $playlist.addClass('w_closing');
  11028. setTimeout(function() { $playlist.removeClass('w_show');}, 500);
  11029. }
  11030. };
  11031. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  11032. togglePlaylistDisplay(conf.hidePlaylist);
  11033.  
  11034. // プレイリスト消えないモードの時はプレイリストを勝手におすすめに置き換える機能をキャンセル
  11035. (function() {
  11036. var ld = WatchApp.ns.init.VideoExplorerInitializer.videoExplorerController._videoExplorerPlaylistResetArgumentsLoader;
  11037. ld.load_org = ld.load;
  11038. ld.load = $.proxy(function(a, b, c) {
  11039. if (conf.storagePlaylistMode !== '') {
  11040. return;
  11041. }
  11042. this.load_org(a, b, c);
  11043. }, ld);
  11044. ld = null;
  11045. })();
  11046.  
  11047. } // end initPlaylist
  11048.  
  11049.  
  11050. function initPageHeader($, conf, w) {
  11051. $('.videoDetailExpand h2').addClass('videoDetailToggleButton');
  11052. } // end initPageHeader
  11053.  
  11054.  
  11055. function initVideoTagContainer($, conf, w) {
  11056. var $videoHeaderTagEditLinkArea = null, $toggleTagEditText = null, baseTagHeight = 72, currentHeight = 72;
  11057. var tagListView = watch.TagInitializer.tagViewController.tagListView, $videoHeader = $('.videoHeaderOuter');
  11058.  
  11059. tagListView.getCurrentDefaultHeight_org = tagListView.getCurrentDefaultHeight;
  11060. tagListView.getCurrentDefaultHeight = function() {
  11061. if ($('body').hasClass('full_with_browser')) {
  11062. return tagListView.getCurrentDefaultHeight_org();
  11063. }
  11064. return currentHeight;
  11065. };
  11066.  
  11067. $videoHeaderTagEditLinkArea = $('.toggleTagEditInner .videoHeaderTagEditLinkArea');
  11068. $('.toggleTagEdit').append($videoHeaderTagEditLinkArea);
  11069. $toggleTagEditText = $('<span class="toggleText">' + $('.toggleTagEditInner').text() + '</span>');
  11070. $('.toggleTagEditInner').empty().append($toggleTagEditText).append($videoHeaderTagEditLinkArea);
  11071.  
  11072. var onTagReset = function() {
  11073. try {
  11074. // タグが2行以下だったら自動的に狭くする処理
  11075. if (!conf.enableAutoTagContainerHeight) { return; }
  11076. currentHeight = Math.min(baseTagHeight, $('#videoTagContainer').find('.tagInner').innerHeight());
  11077.  
  11078. if (baseTagHeight !== currentHeight) {
  11079. var $toggle = $('#videoTagContainer').find('.toggleTagEdit');
  11080. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  11081.  
  11082. if (currentHeight < 36) { // 1行以下の時
  11083. $videoHeader.addClass('tag1Line');
  11084. } else {
  11085. if (currentHeight <= 60) { // 2行以下の時
  11086. $videoHeader.addClass('tag2Lines');
  11087. }
  11088. }
  11089. watch.TagInitializer.tagViewController.tagListView.fit();
  11090. } else {
  11091. $videoHeader.removeClass('tag1Line').removeClass('tag2Lines');
  11092. watch.TagInitializer.tagViewController.tagListView.fit();
  11093. }
  11094. } catch (e) {
  11095. console.log(e);
  11096. }
  11097. };
  11098.  
  11099. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11100. EventDispatcher.addEventListener('onVideoInitialized', onTagReset);
  11101. });
  11102. watch.TagInitializer.tagList.addEventListener('reset', onTagReset);
  11103. window.setTimeout(onTagReset, 1000);
  11104.  
  11105. $videoHeaderTagEditLinkArea = $toggleTagEditText = null;
  11106.  
  11107.  
  11108. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org = WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived;
  11109. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived = function(a) {
  11110. //console.log('onTagDataReceived', a);
  11111. if (conf.disableTagReload) {
  11112. return;
  11113. }
  11114. WatchApp.ns.model.player.NicoPlayerConnector.onTagDataReceived_org(a);
  11115. };
  11116.  
  11117.  
  11118. } // end initVideoTagContainer
  11119.  
  11120.  
  11121.  
  11122. function initVideoReview($, conf, w) {
  11123. var __css__ = Util.here(function() {/*
  11124. .sidePanel #videoReview { margin: 0 auto; }
  11125. #outline.w_compact #videoReview { width: 300px; }
  11126. #outline.w_compact textarea.newVideoReview { width: 277px; }
  11127. #outline.w_compact #videoReviewHead { width: 283px; }
  11128. #outline.w_compact #videoReview .stream { width: 300px; }
  11129. #outline.w_compact #videoReview .inner { width: 300px; }
  11130. #outline.w_compact .commentContent { width: 278px; }
  11131. #outline.w_compact .commentContentBody { width: 232px; }
  11132. .sidePanel.w_review #videoReview { width: 308px; }
  11133. .sidePanel.w_review textarea.newVideoReview { width: 286px; }
  11134. .sidePanel.w_review #videoReviewHead { width: 291px; }
  11135. .sidePanel.w_review #videoReview .stream { width: 308px; }
  11136. .sidePanel.w_review #videoReview .inner { width: 308px; }
  11137. .sidePanel.w_review .commentContent { width: 286px; }
  11138. .sidePanel.w_review .commentContentBody { width: 240px; }
  11139. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview { width: 400px; }
  11140. body:not(.full_with_browser) .w_wide .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11141. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReviewHead { width: 383px; }
  11142. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .stream { width: 400px; }
  11143. body:not(.full_with_browser) .w_wide .sidePanel.w_review #videoReview .inner { width: 400px; }
  11144. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContent { width: 378px; }
  11145. body:not(.full_with_browser) .w_wide .sidePanel.w_review .commentContentBody { width: 332px; }
  11146. body.videoExplorer .sidePanel.w_review #videoReview { width: 400px; }
  11147. body.videoExplorer .sidePanel.w_review textarea.newVideoReview { width: 377px; }
  11148. body.videoExplorer .sidePanel.w_review #videoReviewHead { width: 383px; }
  11149. body.videoExplorer .sidePanel.w_review #videoReview .stream { width: 400px; }
  11150. body.videoExplorer .sidePanel.w_review #videoReview .inner { width: 400px; }
  11151. body.videoExplorer .sidePanel.w_review .commentContent { width: 378px; }
  11152. body.videoExplorer .sidePanel.w_review .commentContentBody { width: 332px; }
  11153.  
  11154. body:not(.videoExplorer) .sidePanel .commentUserProfile, body:not(.videoExplorer) .sidePanel .panelTrigger {
  11155. display: none !important;
  11156. }
  11157. body.videoExplorer .sidePanel .commentUserProfile {
  11158. position: fixed;
  11159. top: 36px !important;
  11160. left: auto !important;
  11161. right: 0 !important;
  11162. z-index: 11000;
  11163. }
  11164.  
  11165. .sidePanel .getMoreReviewComment {
  11166. margin-bottom: 256px;
  11167. }
  11168. */});
  11169. var reviewCss = addStyle(__css__, 'videoReviewCss');
  11170.  
  11171. /*
  11172. EventDispatcher.addEventListener('onFirstVideoInitialized', function() { setTimeout(function() {
  11173. var elms = [
  11174. '#videoReview',
  11175. 'textarea.newVideoReview',
  11176. '#videoReviewHead',
  11177. '#videoReview .stream',
  11178. '#videoReview .inner',
  11179. '.commentContent',
  11180. '.commentContentBody'
  11181. ];
  11182. var css = [], $baseElement = $('#videoReview');
  11183. var makeCss = function (targetWidth, preSel) {
  11184. var px = targetWidth - $baseElement.outerWidth();
  11185. for (var v in elms) {
  11186. var $e = $(elms[v]), newWidth = $e.width() + px;
  11187. css.push([
  11188. preSel, elms[v], ' { width: ', newWidth,'px; }\n'
  11189. ].join(''));
  11190. }
  11191. };
  11192. makeCss(300, '#outline.w_compact ');
  11193. makeCss(308, '.sidePanel.w_review ');
  11194. makeCss(400, '.sidePanel.w_review.w_wide ');
  11195. makeCss(400, 'body.videoExplorer .sidePanel.w_review ');
  11196. console.log(css.join(''));
  11197. var reviewCss = addStyle(css.join(''), 'videoReviewCss');
  11198. }, 3000);});
  11199. */
  11200. } // end initVideoReview
  11201.  
  11202. function initNews() {
  11203. var stopNicoNewsPolling = function() {
  11204. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemDispatcher.stop();
  11205. window.WatchApp.ns.init.TextMarqueeInitializer.textMarqueeItemList.list.length = 0;
  11206. };
  11207. var toggleNoNews = function() {
  11208. $('#content').toggleClass('noNews', conf.hideNicoNews || conf.customPlayerSize !== '');
  11209. if ($('#content').hasClass('noNews')) {
  11210. stopNicoNewsPolling();
  11211. }
  11212. };
  11213.  
  11214. EventDispatcher.addEventListener('on.config.hideNicoNews', toggleNoNews);
  11215. EventDispatcher.addEventListener('on.config.customPlayerSize', toggleNoNews);
  11216.  
  11217. toggleNoNews();
  11218.  
  11219. if (conf.enableNewsHistory) { NicoNews.initialize(w); }
  11220. if (conf.hideNewsInFull) { $('body').addClass('hideNewsInFull'); }
  11221. } //
  11222.  
  11223.  
  11224. function initEvents() {
  11225. var pac = watch.PlayerInitializer.playerAreaConnector;
  11226.  
  11227. pac.addEventListener("onVideoInitialized", onVideoInitialized);
  11228. pac.addEventListener("onVideoEnded", onVideoEnded);
  11229. pac.addEventListener("onVideoStopped", onVideoStopped);
  11230. // pac.addEventListener('onSystemMessageFatalErrorSended', onSystemMessageFatalErrorSended);
  11231. // watch.WatchInitializer.watchModel.addEventListener('error', function() {console.log(arguments);});
  11232.  
  11233. pac.addEventListener('updateSettingsPanelVisible', function(isVisible, panel) {
  11234. EventDispatcher.dispatch('onUpdateSettingPanelVisible', isVisible, panel);
  11235. });
  11236.  
  11237. watchInfoModel.addEventListener('reset', onWatchInfoReset);
  11238. watchInfoModel.addEventListener('beforeReset', function() {
  11239. window.console.time('watchInfoModelReset');
  11240. EventDispatcher.dispatch('onWatchInfoBeforeReset');
  11241. });
  11242. watchInfoModel.addEventListener('afterReset', function() {
  11243. window.console.timeEnd('watchInfoModelReset');
  11244. EventDispatcher.dispatch('onWatchInfoAfterReset');
  11245. });
  11246. watch.PlayerInitializer.playerScreenMode.addEventListener('change', onScreenModeChange);
  11247.  
  11248. var explorer = watch.VideoExplorerInitializer.videoExplorer;
  11249. explorer.addEventListener('openStart', onVideoExplorerOpening);
  11250. explorer.addEventListener('openEnd', onVideoExplorerOpened);
  11251. explorer.addEventListener('closeStart', onVideoExplorerClosing);
  11252. explorer.addEventListener('closeEnd', onVideoExplorerClosed);
  11253. explorer.addEventListener('refreshStart', onVideoExplorerRefreshStart);
  11254. explorer.addEventListener('refreshEnd', onVideoExplorerRefreshEnd);
  11255. explorer.addEventListener('changeCurrentPage', onVideoExplorerChangePage); //
  11256.  
  11257.  
  11258. $('body').dblclick(function(e){
  11259. var tagName = e.target.tagName, cls = e.target.className || '';
  11260. if (tagName === 'SELECT' || tagName === 'INPUT' || tagName === 'BUTTON' || cls.match(/mylistPopupPanel/)) {
  11261. return;
  11262. }
  11263. if (!WatchController.isFullScreen()) {
  11264. AnchorHoverPopup.hidePopup();
  11265. if (conf.doubleClickScroll) {
  11266. e.preventDefault();
  11267. EventDispatcher.dispatch('onScrollReset');
  11268. WatchController.scrollToVideoPlayer(true);
  11269. }
  11270. }
  11271. });
  11272.  
  11273. var bottomContentTabView = WatchApp.ns.view.BottomContentTabView.getInstance();
  11274. bottomContentTabView.addEventListener('changeContent', function(name) {
  11275. EventDispatcher.dispatch('onBottomContentTabViewReset', name);
  11276. });
  11277.  
  11278.  
  11279. Mylist.onDefMylistUpdate(function() {
  11280. //WatchController.clearDeflistCache();
  11281. });
  11282. Mylist.onMylistUpdate(function(info) {
  11283. WatchController.clearMylistCache(info.groupId);
  11284. });
  11285.  
  11286. $(window).on('beforeunload.watchItLater', function(e) {
  11287. conf.setValue('lastCommentVisibility', WatchController.commentVisibility() ? 'visible' : 'hidden');
  11288. }).on('resize', window._.debounce(function() {
  11289. AnchorHoverPopup.hidePopup();
  11290. EventDispatcher.dispatch('onWindowResizeEnd');
  11291. }, 1000));
  11292.  
  11293. //$(document).on('scroll', WatchApp.ns.event.EventDispatcher.throttle(function() {
  11294. $(document).on('scroll', function() {
  11295. if (document.body.style.pointerEvents !== 'none') {
  11296. document.body.style.pointerEvents = 'none';
  11297. }
  11298. });
  11299. $(document).on('scroll', window._.debounce(function() {
  11300. document.body.style.pointerEvents = '';
  11301. EventDispatcher.dispatch('onScrollEnd');
  11302. }, 500));
  11303.  
  11304. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  11305. pac.addEventListener('onVideoChangeStatusUpdated', onVideoChangeStatusUpdated);
  11306. });
  11307.  
  11308. window.addEventListener('message', function(event) {
  11309. if (event.origin.indexOf('nicovideo.jp') < 0) return;
  11310. try {
  11311. var data = JSON.parse(event.data);
  11312. if (data.id !== 'WatchItLater') { return; }
  11313.  
  11314. EventDispatcher.dispatch('onMessage', data.body, data.type);
  11315.  
  11316. } catch (e) {
  11317. console.log(
  11318. '%cError: window.onMessage - ',
  11319. 'color: red; background: yellow',
  11320. e, event.origin, event.data);
  11321. }
  11322. });
  11323. } //
  11324.  
  11325. function initAdditionalButtons() {
  11326.  
  11327. var createPlaylistToggle = function() {
  11328. var $playlistToggle = $('<button title="プレイリスト表示/非表示" class="playlistToggle">プレイリスト</button>');
  11329.  
  11330. $playlistToggle.on('click', function() {
  11331. AnchorHoverPopup.hidePopup();
  11332. conf.setValue('hidePlaylist', !!!conf.hidePlaylist);
  11333. });
  11334.  
  11335. var togglePlaylistDisplay = function(v) {
  11336. $playlistToggle.toggleClass('w_show', !v);
  11337. };
  11338.  
  11339. EventDispatcher.addEventListener('on.config.hidePlaylist', togglePlaylistDisplay);
  11340. togglePlaylistDisplay(conf.hidePlaylist);
  11341.  
  11342. return $playlistToggle;
  11343. };
  11344. var createOpenExplorer = function() {
  11345. return $('<button class="openVideoExplorer">検索▼</button>').on('click', function() {
  11346. WatchController.openSearch();
  11347. if (!$('body').hasClass('content-fix')) {
  11348. WatchController.scrollToVideoPlayer(true);
  11349. }
  11350. });
  11351. };
  11352. var $div = $('<div class="bottomAccessContainer"/>').append(createPlaylistToggle()).append(createOpenExplorer());
  11353.  
  11354.  
  11355. var $headerMenu = $('<li class="watchItLaterSettingMenu"><a href="javascript:;" title="WatchItLaterの設定">WatchItLater設定</a></li>');
  11356. $('#siteHeaderRightMenuFix').after($headerMenu);
  11357.  
  11358. $('#outline .outer').before($div);
  11359. var $container = $('<div class="bottomConfButtonContainer" />'), $conf = $('<button title="WatchItLaterの設定">設定</button>');
  11360. var $explorerConf = $('<button><span class="open">`・ω・´</span><span class="close">´・ω・`</span></button>');
  11361. var toggleConf = function(e) {
  11362. e.stopPropagation();
  11363. AnchorHoverPopup.hidePopup();
  11364. ConfigPanel.toggle();
  11365. };
  11366. $container.append($conf);
  11367. $conf.addClass('openConfButton');
  11368. $conf.on('click', toggleConf);//.attr('accesskey', 'p');
  11369. $('#outline .outer').before($container);
  11370. $headerMenu.find('a').on('click', toggleConf);//.attr('accesskey', 'p');
  11371.  
  11372.  
  11373. $('.videoExplorerBody').append($explorerConf);
  11374. $explorerConf
  11375. .on('click',
  11376. function() { WatchItLater.config.set('videoExplorerHack', !WatchItLater.config.get('videoExplorerHack')); })
  11377. .addClass('videoExplorerConfig');
  11378.  
  11379. var $body = $('body'), $window = $(window);
  11380. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  11381. if (WatchController.isSearchMode() || WatchController.isFullScreen()) { return; }
  11382. var w = $div.outerWidth(), threshold = ($(window).innerWidth() - 960) / 2;
  11383. $('#outline').toggleClass('under960', w > threshold && !$('#footer').hasClass('noBottom'));
  11384. });
  11385. } // end initAdditionalButtons
  11386.  
  11387.  
  11388. function initSearchContent($, conf, w) {
  11389. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11390. var SearchSortOrder = WatchApp.ns.components.videoexplorer.model.SearchSortOrder;
  11391. var View = WatchApp.ns.components.videoexplorer.view.content.SearchContentView;
  11392. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11393. var explorer = vec.getVideoExplorer();
  11394. var content = explorer.getContentList().getContent(ContentType.SEARCH);
  11395. var relatedTag = new NicoSearchRelatedTag({});
  11396. var newSearch = new NewNicoSearch({});
  11397. var newSearchWrapper = new NewNicoSearchWrapper({search: newSearch});
  11398. var pager = content._pager;
  11399. var __css__ = Util.here(function() {/*
  11400. .newSearchOption {
  11401. text-align: center; margin-bottom: 16px; padding: 8px;
  11402. background: #eee;
  11403. display: none;
  11404. }
  11405. .newSearchOption select, .newSearchOption label{
  11406. margin-right: 32px;
  11407. }
  11408. .newSearchOption .reset{
  11409. cursor: pointer; background: #eee;
  11410. }
  11411. .newSearchOption p{
  11412. margin: 8px;
  11413. }
  11414. .newSearchOption .ownerName {
  11415. }
  11416. .w_sugoiSearch .newSearchOption {
  11417. display: block;
  11418. }
  11419.  
  11420. .relatedTagList {
  11421. }
  11422. .relatedTagList p{
  11423. display: inline-block; margin: 4px;
  11424. }
  11425. .relatedTagList li, .relatedTagList ul {
  11426. display: inline;
  11427. margin: 0 8px 0 0;
  11428. list-style: none;
  11429. word-break: break-all;
  11430. }
  11431. .relatedTagList li {
  11432. background: #f4f4f4; padding: 2px 4px;
  11433. border: solid 1px #999;
  11434. border-radius: 4px;
  11435. line-height: 180%;
  11436. }
  11437. .relatedTagList li:hover {
  11438. }
  11439. .relatedTagList li:hover a{
  11440. text-decoration: none;
  11441. }
  11442.  
  11443. .sugoiOption {
  11444. display: none;
  11445. }
  11446. .w_sugoiSearch .sugoiOption {
  11447. display: block; background: #eef;
  11448. }
  11449. .w_sugoiSearch optgroup.sugoiOption {
  11450. font-weight: bolder;
  11451. }
  11452.  
  11453. */});
  11454. addStyle(__css__, 'searchContent');
  11455.  
  11456. // 動画表示のテンプレート拡張
  11457. var $template = $('<div/>').html(watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html());
  11458. $template.find('.searchSortOrder')
  11459. .append([
  11460. '<optgroup label="新検索専用" class="sugoiOption">',
  11461. '<option value="sort=_hot&amp;order=d"" class="sugoiOption">人気が高い順</option>',
  11462. '<option value="sort=_popular&amp;order=d" class="sugoiOption">並び順指定なし</option>',
  11463. '<option value="sort=_explore&amp;order=d" class="sugoiOption">新着優先</option>',
  11464. '</optgroup>'
  11465. ].join(''));
  11466. watch.VideoExplorerInitializer.videoExplorerView._contentListView._$view.find('.searchContentTemplate').html($template.html());
  11467. $template = null;
  11468.  
  11469.  
  11470.  
  11471. var RelatedTagView = function() { this.initialize.apply(this, arguments); };
  11472. RelatedTagView.prototype = {
  11473. _$view: null,
  11474. _relatedTag: null,
  11475. initialize: function(params) {
  11476. this._relatedTag = params.relatedTag;
  11477. this._$view = params.$view;
  11478. this._$list = this._$view.find('ul');
  11479. },
  11480. getView: function() {
  11481. return this._$view;
  11482. },
  11483. detach: function() {
  11484. this._$view.detach();
  11485. },
  11486. update: function(candidates) {
  11487. if (!candidates || candidates.length < 1) {
  11488. this.detach();
  11489. return;
  11490. }
  11491. if (candidates.length > 10) {
  11492. candidates = candidates
  11493. .map(function(a){return {weight:Math.random(), value:a};})
  11494. .sort(function(a, b){return a.weight - b.weight;})
  11495. .map(function(a){return a.value;});
  11496. }
  11497. var $ul = this._$list.empty();
  11498. for (var i = 0, len = Math.min(10, candidates.length); i < len; i++) {
  11499. $ul.append(this._create$tag(candidates[i].tag));
  11500. }
  11501. },
  11502. clear: function() {
  11503. this._$list.empty();
  11504. },
  11505. _create$tag: function(text) {
  11506. var
  11507. $a = $('<a/>')
  11508. .html(text)
  11509. .attr('href', 'http://search.nicovideo.jp/video/tag/' + encodeURIComponent(text))
  11510. .on('click', Util.Closure.openNicoSearch(text)),
  11511. $tag = $('<li/>').append($a);
  11512. return $tag;
  11513. }
  11514. };
  11515.  
  11516. var NewSearchOptionView = function() { this.initialize.apply(this, arguments); };
  11517. NewSearchOptionView.prototype = {
  11518. _content: null,
  11519. _$view: null,
  11520. _$startTimeRange: null,
  11521. _$lengthSecondsRange: null,
  11522. initialize: function(params) {
  11523. this._content = params.content;
  11524. this._$view = params.$view;
  11525. this._$startTimeRange = this._$view.find('.startTimeRange');
  11526. this._$lengthSecondsRange = this._$view.find('.lengthSecondsRange');
  11527. this._$musicDlFilter = this._$view.find('.musicDlFilter');
  11528. this._$ownerFilter = this._$view.find('.ownerFilter');
  11529. this._$ownerName = this._$view.find('.ownerName');
  11530. this._$resetButton = this._$view.find('.reset');
  11531.  
  11532. this._$startTimeRange .val(params.startTimeRange || '');
  11533. this._$lengthSecondsRange.val(params.lengthSecondsRange || '');
  11534. this._$musicDlFilter .attr('checked', !!params.musicDlFilter);
  11535.  
  11536. this._$startTimeRange .on('change', $.proxy(this._onStartTimeRangeSelect , this));
  11537. this._$lengthSecondsRange.on('change', $.proxy(this._onLengthSecondsRangeSelect, this));
  11538. this._$musicDlFilter .on('click', $.proxy(this._onMusicDlFilterChange , this));
  11539. this._$ownerFilter .on('click', $.proxy(this._onOwnerFilterChange , this));
  11540. this._$resetButton .on('click', $.proxy(this.reset , this));
  11541.  
  11542. EventDispatcher.addEventListener('onVideoOwnerChanged', $.proxy(this.onVideoOwnerChange, this));
  11543. this._$ownerName.text(WatchController.getOwnerName());
  11544. },
  11545. getView: function() {
  11546. return this._$view;
  11547. },
  11548. detach: function() {
  11549. this._$view.detach();
  11550. },
  11551. update: function() {
  11552. },
  11553. onVideoOwnerChange: function(ownerInfo) {
  11554. this._content.setOwnerFilter(false);
  11555. this._$ownerFilter.prop('checked', false);
  11556. this._$ownerName.text(ownerInfo.name);
  11557. },
  11558. _onStartTimeRangeSelect: function() {
  11559. this._content.setStartTimeRange(this._$startTimeRange.val());
  11560. this.contentRefresh();
  11561. },
  11562. _onLengthSecondsRangeSelect: function() {
  11563. this._content.setLengthSecondsRange(this._$lengthSecondsRange.val());
  11564. this.contentRefresh();
  11565. },
  11566. _onMusicDlFilterChange: function() {
  11567. this._content.setMusicDlFilter(!!this._$musicDlFilter.prop('checked'));
  11568. this.contentRefresh();
  11569. },
  11570. _onOwnerFilterChange: function() {
  11571. this._content.setOwnerFilter(!!this._$ownerFilter.prop('checked'));
  11572. this.contentRefresh();
  11573. },
  11574. contentRefresh: function() {
  11575. var params = this._content.getParams();
  11576. params.page = 1;
  11577. this._content.changeState(params);
  11578. this._content.refresh({page: 1});
  11579. },
  11580. refresh: function() {
  11581. //console.log('refresh!', this._content.getOwnerFilter(), this._content.getMusicDlFilter(false));
  11582. this._$startTimeRange .val(this._content.getStartTimeRange('') || '');
  11583. this._$lengthSecondsRange.val(this._content.getLengthSecondsRange('') || '');
  11584. this._$musicDlFilter .prop('checked', !!this._content.getMusicDlFilter(false));
  11585. this._$ownerFilter .prop('checked', !!this._content.getOwnerFilter());
  11586. },
  11587. reset: function() {
  11588. var v = this._$startTimeRange.val() + this._$lengthSecondsRange.val();
  11589. if (v !== '') {
  11590. this._content.setStartTimeRange('');
  11591. this._content.setLengthSecondsRange('');
  11592. this._content.setMusicDlFilter(false);
  11593. this._$startTimeRange.val('');
  11594. this._$lengthSecondsRange.val('');
  11595. this._$musicDlFilter.prop('checked', false);
  11596. this._content.changeState({ page: 1 });
  11597. //this._content.refresh({ page: 1 });
  11598. }
  11599. }
  11600. };
  11601.  
  11602. var relatedTagView = new RelatedTagView({
  11603. relatedTag: relatedTag,
  11604. $view: $('<div class="relatedTagList"><p>関連タグ: </p><ul></ul></div>')
  11605. });
  11606. var newSearchOptionView = new NewSearchOptionView({
  11607. content: content,
  11608. startTimeRange: conf.searchStartTimeRange,
  11609. lengthSecondsRange: conf.searchLengthSecondsRange,
  11610. musicDlFilter: conf.searchMusicDlFilter,
  11611. $view: $([
  11612. '<div class="newSearchOption">',
  11613. '<span>投稿日時: </span>',
  11614. '<select class="startTimeRange" name="u">',
  11615. '<option selected="selected" value="" >指定なし</option>',
  11616. '<option value="24h">24時間以内</option>',
  11617. '<option value="1w" >1週間以内</option>',
  11618. '<option value="1m" >1ヶ月(30日)以内</option>',
  11619. '<option value="3m" >3ヶ月(90日)以内</option>',
  11620. '<option value="6m" >6ヶ月(180日)以内</option>',
  11621. '</select>',
  11622. '<span>再生時間: </span>',
  11623. '<select class="lengthSecondsRange" name="l">',
  11624. '<option selected="selected" value="" >指定なし</option>',
  11625. '<option value="short">5分以内</option>',
  11626. '<option value="long" >20分以上</option>',
  11627. '</select>',
  11628. '<p>',
  11629. '<label>',
  11630. '<input type="checkbox" name="m" class="musicDlFilter">音楽DL対応のみ</input>',
  11631. '</label>',
  11632. '<label>',
  11633. '<input type="checkbox" name="owner" class="ownerFilter"><span class="ownerName">この投稿者</span>&nbsp;の動画のみ</input>',
  11634. '</label>',
  11635. '</p>',
  11636. '</div>',
  11637. ''].join(''))
  11638. });
  11639.  
  11640.  
  11641.  
  11642. content._originalWord = '';
  11643. content.changeState_org = content.changeState;
  11644. content.changeState = $.proxy(function(params, callback) {
  11645. var word = WatchApp.get(params, 'searchWord', 'string', '');
  11646. var type = WatchApp.get(params, 'searchType', 'string', this.getSearchType());
  11647. if (typeof word === 'string' && word.length > 0) {
  11648. this._originalWord = word;
  11649.  
  11650. if (conf.defaultSearchOption && conf.defaultSearchOption !== '') {
  11651. if (word.indexOf(conf.defaultSearchOption) < 0 && !word.match(/(sm|nm|so)\d+/)) {
  11652. params.searchWord += " " + conf.defaultSearchOption;
  11653. }
  11654. }
  11655. }
  11656. AnchorHoverPopup.hidePopup();
  11657.  
  11658. EventDispatcher.dispatch('onSearchStart', this._originalWord, type);
  11659. this.changeState_org(params, callback);
  11660. }, content);
  11661.  
  11662. // ニコニコ新検索エンジンを使うための布石
  11663. content._searchEngineType = conf.searchEngine;
  11664. content._lastSearchEngineType = conf.searchEngine;
  11665. content.setSearchEngineType = $.proxy(function(type) {
  11666. this._searchEngineType = type;
  11667. this.updateSearchPageItemCount();
  11668. }, content);
  11669. content.updateSearchPageItemCount = $.proxy(function() {
  11670. this._pager._pageItemCount = this._searchEngineType === 'sugoi' ? conf.searchPageItemCount : 32;
  11671. }, content);
  11672. content.getSearchEngineType = $.proxy(function() {
  11673. return this._searchEngineType === 'sugoi' ? 'sugoi' : 'normal';
  11674. }, content);
  11675. content.setLastSearchEngineType = $.proxy(function(type) { this._lastSearchEngineType = type; }, content);
  11676. content.getLastSearchEngineType = $.proxy(function() { return this._lastSearchEngineType; }, content);
  11677. content._newSearchWrapper = newSearchWrapper;
  11678.  
  11679. content._startTimeRange = conf.searchStartTimeRange;
  11680. content._lengthSecondsRange = conf.searchLengthSecondsRange;
  11681. content._musicDlFilter = conf.searchMusicDlFilter;
  11682. content._ownerFilter = false;
  11683.  
  11684. content.getStartTimeRange = $.proxy(function() { return this._startTimeRange; }, content);
  11685. content.getLengthSecondsRange = $.proxy(function() { return this._lengthSecondsRange; }, content);
  11686. content.getMusicDlFilter = $.proxy(function() { return this._musicDlFilter; }, content);
  11687. content.getOwnerFilter = $.proxy(function() { return this._ownerFilter; }, content);
  11688. content.setStartTimeRange = $.proxy(function(value) {
  11689. this._startTimeRange = value;
  11690. conf.setValue('searchStartTimeRange', value);
  11691. }, content);
  11692. content.setLengthSecondsRange = $.proxy(function(value) {
  11693. this._lengthSecondsRange = value;
  11694. conf.setValue('searchLengthSecondsRange',value);
  11695. }, content);
  11696. content.setMusicDlFilter = $.proxy(function(value) {
  11697. this._musicDlFilter = !!value;
  11698. conf.setValue('searchMusicDlFilter', !!value);
  11699. }, content);
  11700. content.setOwnerFilter = $.proxy(function(value) {
  11701. this._ownerFilter = !!value;
  11702. }, content);
  11703.  
  11704. // 新検索独自のソート順への対応
  11705. content._searchSortOrder._flush_org = content._searchSortOrder._flush;
  11706. content._searchSortOrder._flush = $.proxy(function() {
  11707. var sort = this._sort[WatchApp.ns.components.videoexplorer.model.SearchType.KEYWORD];
  11708. if (sort === '_hot' || sort === '_popular' || sort === '_explore') { // 新検索にしかないパラメータは保存しない
  11709. return;
  11710. }
  11711. this._flush_org();
  11712. }, content._searchSortOrder);
  11713.  
  11714.  
  11715. EventDispatcher.addEventListener('on.config.searchPageItemCount', function() {
  11716. content.updateSearchPageItemCount();
  11717. });
  11718.  
  11719. content.getParams_org = content.getParams;
  11720. content.getParams = $.proxy(function() {
  11721. var params = this.getParams_org();
  11722. params = $.extend(true, {
  11723. l: this.getLengthSecondsRange(),
  11724. u: this.getStartTimeRange(),
  11725. m: this.getMusicDlFilter(),
  11726. size: this._pager._pageItemCount
  11727. }, params);
  11728. if (this.getOwnerFilter()) {
  11729. if (WatchController.isChannelVideo()) {
  11730. params.channelId = WatchController.getOwnerId();
  11731. } else {
  11732. params.userId = WatchController.getOwnerId();
  11733. }
  11734. }
  11735. return params;
  11736. }, content);
  11737.  
  11738. // タグ検索だけ毎回ソート順がデフォルトにリセットされるようになったので、
  11739. // デフォルト値を書き換えるという力技で対抗
  11740. SearchSortOrder.TAG_DEFAULT_SORT = conf.searchSortType;
  11741. SearchSortOrder.TAG_DEFAULT_ORDER = conf.searchSortOrder;
  11742. content._searchSortOrder.getSortFromCookie = function() { return conf.searchSortType; };
  11743. content._searchSortOrder.getOrderFromCookie = function() { return conf.searchSortOrder; };
  11744.  
  11745. content.load_org = content.load;
  11746. content.load = $.proxy(function(params, callback) {
  11747. var word = this.getSearchWord();
  11748. if (this.getSearchEngineType() !== 'sugoi' || word.length <= 0 || word.match(/(sm|nm|so)\d+/)) {
  11749. // 新検索ではもしかして~が取得できないため、検索ワードに動画IDっぽい文字列が含まれてる場合は旧タグ検索を使う。
  11750. this.setLastSearchEngineType('normal');
  11751. params.sort = 'n';
  11752. params.order = 'd';
  11753. this.load_org(params, callback);
  11754. } else {
  11755. this.setLastSearchEngineType('sugoi');
  11756. params = this.getParams();
  11757.  
  11758.  
  11759. this._newSearchWrapper.load(params, function(err, result) {
  11760. console.log('%cNewNicoSearchWrapper result', 'color: green;', result);
  11761. callback(err, result);
  11762. });
  11763. }
  11764. }, content);
  11765. content.setSearchEngineType(conf.searchEngine);
  11766.  
  11767. EventDispatcher.addEventListener('on.config.searchEngine', function(type) {
  11768. content.setSearchEngineType(type);
  11769. });
  11770.  
  11771.  
  11772. var
  11773. overrideSearchSortOrder = function(proto) { // ソート順を記憶するためのフック
  11774. proto.getSort_org = proto.getSort;
  11775. proto.getSort = function() {
  11776. var sort = conf.searchSortType;
  11777. if ((sort === '_hot' || sort === '_popular' || sort === '_explore') && content.getLastSearchEngineType() !== 'sugoi') {
  11778. // 通常検索で新検索にしかないソート順だったらデフォルトのnを返す
  11779. return 'n';
  11780. }
  11781. return conf.searchSortType;
  11782. };
  11783.  
  11784. proto.setSort_org = proto.setSort;
  11785. proto.setSort = function(type, sort) {
  11786. conf.setValue('searchSortType', sort);
  11787. SearchSortOrder.TAG_DEFAULT_SORT = sort;
  11788. this.setSort_org(type, sort);
  11789. };
  11790.  
  11791. proto.getOrder_org = proto.getOrder;
  11792. proto.getOrder = function() {
  11793. return conf.searchSortOrder;
  11794. };
  11795.  
  11796. proto.setOrder_org = proto.setOrder;
  11797. proto.setOrder = function(type, order) {
  11798. if (content.getLastSearchEngineType() === 'sugoi') { // 新検索の時だけソート順を記憶
  11799. SearchSortOrder.TAG_DEFAULT_ORDER = order;
  11800. conf.setValue('searchSortOrder', order);
  11801. }
  11802. this.setOrder_org(type, order);
  11803. };
  11804. },
  11805. overrideSearchContentView = function(proto, relatedTag) {
  11806. proto._updateRelatedTag = function() {
  11807. if (!conf.enableRelatedTag) { return; }
  11808. var word = this._content._originalWord;
  11809. relatedTagView.clear();
  11810.  
  11811. if (typeof word === 'string' && word.length > 0) {
  11812. this._$header.append(relatedTagView.getView());
  11813. relatedTag.load(word, function(err, result) {
  11814. console.log('SearchContentView._updateRelatedTag', err, result);
  11815. if (err) {
  11816. console.log('load suggest fail', err, result);
  11817. } else {
  11818. relatedTagView.update(result.values);
  11819. }
  11820. });
  11821. }
  11822. };
  11823.  
  11824. proto.detach_org = proto.detach;
  11825. proto.detach = function() {
  11826. this.detach_org();
  11827. newSearchOptionView.detach();
  11828. relatedTagView.detach();
  11829. };
  11830.  
  11831. proto.onUpdate_org = proto.onUpdate;
  11832. proto.onUpdate = function() {
  11833. this.onUpdate_org();
  11834. this._$content.find('.searchBox').after(newSearchOptionView.getView());
  11835. this._updateRelatedTag();
  11836. var engine = this._content.getLastSearchEngineType();
  11837. newSearchOptionView.refresh();
  11838. $('.videoExplorerBody')
  11839. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  11840. .toggleClass('w_normalSearch', engine !== 'sugoi');
  11841. };
  11842.  
  11843. proto.onError_org = proto.onError;
  11844. proto.onError = function() {
  11845. this.onError_org();
  11846. this._$header.append(newSearchOptionView.getView());
  11847. this._updateRelatedTag();
  11848. var engine = this._content.getLastSearchEngineType();
  11849. $('.videoExplorerBody')
  11850. .toggleClass('w_sugoiSearch', engine === 'sugoi')
  11851. .toggleClass('w_normalSearch', engine !== 'sugoi');
  11852. };
  11853.  
  11854. };
  11855.  
  11856. overrideSearchSortOrder(SearchSortOrder.prototype);
  11857. overrideSearchContentView(View.prototype, relatedTag);
  11858.  
  11859. } // end initSearchContent
  11860.  
  11861. function initUserVideoContent($, conf, w) {
  11862. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11863. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11864. var explorer = vec.getVideoExplorer();
  11865. var content = explorer.getContentList().getContent(ContentType.USER_VIDEO);
  11866. var pager = content._pager;
  11867.  
  11868. pager._pageItemCount = conf.searchPageItemCount;
  11869. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  11870. pager._pageItemCount = v;
  11871. });
  11872. }
  11873.  
  11874. function initUploadedVideoContent($, conf, w) {
  11875. var ContentType = WatchApp.ns.components.videoexplorer.model.ContentType;
  11876. var vec = watch.VideoExplorerInitializer.videoExplorerController;
  11877. var explorer = vec.getVideoExplorer();
  11878. var content = explorer.getContentList().getContent(ContentType.UPLOADED_VIDEO);
  11879. var pager = content._pager;
  11880.  
  11881. pager._pageItemCount = conf.searchPageItemCount;
  11882. EventDispatcher.addEventListener('on.config.searchPageItemCount', function(v) {
  11883. pager._pageItemCount = v;
  11884. });
  11885. }
  11886.  
  11887.  
  11888. var isSquareCssInitialized = false;
  11889. function initSquareThumbnail() {
  11890. var isSquare = true;// !!conf.squareThumbnail;
  11891. if (isSquare && !isSquareCssInitialized) {
  11892. var __css__ = Util.here(function() {/*
  11893. {* 元のCSSを打ち消すためにやや冗長 *}
  11894. #videoExplorer .noImage, #videoExplorer.w_adjusted .item .thumbnail {
  11895. display: none !important;
  11896. }
  11897. #videoExplorer .thumbnailContainer {
  11898. background-size: contain;
  11899. background-repeat: no-repeat;
  11900. background-position: center center;
  11901. }
  11902. #videoExplorer.w_adjusted .item .thumbnailContainer {
  11903. width: 130px; height: 100px;
  11904. {*max-width: 130px; height: auto; top: 0; left: 0;*} margin-right: 8px;
  11905. }
  11906.  
  11907. #videoExplorer.w_adjusted .uadFrame {
  11908. width: 130px; height: 100px;
  11909. background-size: 100% 100%;
  11910. }
  11911. #videoExplorer.w_adjusted .uadTagRelated .default .itemList .item .imageContainer {
  11912. width: 130px; height: 100px;
  11913. }
  11914. */});
  11915.  
  11916. addStyle(__css__, 'squareThumbnailCss');
  11917. isSquareCssInitialized = true;
  11918. }
  11919. $('#videoExplorer').toggleClass('squareThumbnail', isSquare);
  11920. } //
  11921.  
  11922. function initPageBottom($, conf, w) {
  11923. function updateHideVideoExplorerExpand(v) {
  11924. $('#content, #outline').toggleClass('w_hideSearchExpand', v === true);
  11925. }
  11926. function updateIchibaVisibility(v) {
  11927. $('#outline').toggleClass('noIchiba', v === 'hidden');
  11928. }
  11929. function updateReviewVisibility(v) {
  11930. $('#outline').toggleClass('noReview', v === 'hidden');
  11931. }
  11932. function updateBottomContentsVisibility(v) {
  11933. $('#bottomContentTabContainer, #footer').toggleClass('noBottom', v === 'hidden');
  11934. }
  11935.  
  11936. EventDispatcher.addEventListener('on.config.hideVideoExplorerExpand', updateHideVideoExplorerExpand);
  11937. EventDispatcher.addEventListener('on.config.ichibaVisibility', updateIchibaVisibility);
  11938. EventDispatcher.addEventListener('on.config.reviewVisibility', updateReviewVisibility);
  11939. EventDispatcher.addEventListener('on.config.bottomContentsVisibility', updateBottomContentsVisibility);
  11940. if (conf.hideVideoExplorerExpand === true) { updateHideVideoExplorerExpand(true); }
  11941. if (conf.ichibaVisibility !== 'visible') { updateIchibaVisibility(conf.ichibaVisibility); }
  11942. if (conf.reviewVisibility !== 'visible') { updateReviewVisibility(conf.reviewVisibility); }
  11943. if (conf.bottomContentsVisibility !== 'visible') { updateBottomContentsVisibility(conf.bottomContentsVisibility); }
  11944.  
  11945. var $bottomToggle = $('<div class="toggleBottom"><div class="openBottom">▽</div><div class="closeBottom">△</div></div>');
  11946. $bottomToggle.on('click', function() {
  11947. var v = conf.bottomContentsVisibility;
  11948. conf.setValue('bottomContentsVisibility', v === 'hidden' ? 'visible' : 'hidden');
  11949. //ConfigPanel.refresh();
  11950. }).attr('title', '市場・レビューの開閉');
  11951. $('#footer').append($bottomToggle);
  11952. } //
  11953.  
  11954.  
  11955.  
  11956. function initShortcutKey() {
  11957. var list = [
  11958. {name: 'shortcutTogglePlay', exec: function(e) {
  11959. WatchController.togglePlay();
  11960. }},
  11961. {name: 'shortcutDefMylist', exec: function(e) {
  11962. WatchController.addDefMylist();
  11963. }},
  11964. {name: 'shortcutMylist', exec: function(e) {
  11965. $('#mylist_add_frame').find('.mylistAdd').click();
  11966. }},
  11967. {name: 'shortcutOpenDefMylist', exec: function(e) {
  11968. WatchController.showDeflist();
  11969. WatchController.scrollToVideoPlayer(true);
  11970. }},
  11971. {name: 'shortcutOpenSearch', exec: function(e) {
  11972. WatchController.openSearch();
  11973. if (!$('body').hasClass('content-fix')) {
  11974. WatchController.scrollToVideoPlayer(true);
  11975. }
  11976. }},
  11977. {name: 'shortcutOpenRecommend', exec: function(e) {
  11978. WatchController.openRecommend();
  11979. if (!$('body').hasClass('content-fix')) {
  11980. WatchController.scrollToVideoPlayer(true);
  11981. }
  11982. }},
  11983. {name: 'shortcutScrollToNicoPlayer', exec: function(e) {
  11984. WatchController.scrollToVideoPlayer(true);
  11985. }},
  11986. {name: 'shortcutCommentVisibility', exec: function(e) {
  11987. WatchController.commentVisibility('toggle');
  11988. }},
  11989. {name: 'shortcutShowOtherVideo', exec: function(e) {
  11990. WatchController.openVideoOwnersVideo();
  11991. }},
  11992. {name: 'shortcutMute', exec: function(e) {
  11993. WatchController.mute('toggle');
  11994. }},
  11995. {name: 'shortcutDeepenedComment', exec: function(e) {
  11996. WatchController.deepenedComment('toggle');
  11997. }},
  11998. {name: 'shortcutToggleStageVideo', exec: function(e) {
  11999. WatchController.toggleStageVideo();
  12000. }},
  12001. {name: 'shortcutInvisibleInput', exec: function(e) {
  12002. $('.invisibleCommentInput').focus();
  12003. }}
  12004. ];
  12005. for (var v in list) {
  12006. var n = list[v].name;
  12007. list[v].keyMatch = KeyMatch.create(conf[n]);
  12008. }
  12009.  
  12010. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12011. for (var v in list) {
  12012. var n = list[v].name;
  12013. if (n === name) {
  12014. list[v].keyMatch = KeyMatch.create(newValue);
  12015. }
  12016. }
  12017. });
  12018.  
  12019. $('body').on('keydown.watchItLater', function(e) {
  12020. // 一部のキーボードについているMusic Key(正式名称不明)に対応 Chromeしか拾えない?
  12021. if (e.keyCode === 178) { // 停止
  12022. WatchController.togglePlay();
  12023. } else
  12024. if (e.keyCode === 179) { // 一時停止
  12025. WatchController.togglePlay();
  12026. } else
  12027. if (e.keyCode === 177) { // 前の曲
  12028. if (WatchController.vpos() > 2000) {
  12029. WatchController.vpos(0);
  12030. } else {
  12031. WatchController.prevVideo();
  12032. }
  12033. } else
  12034. if (e.keyCode === 176) { // 次の曲
  12035. WatchController.nextVideo();
  12036. }
  12037. if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
  12038. return;
  12039. }
  12040. // 全画面時はFlashにフォーカスがなくてもショートカットキーが効くようにする
  12041.  
  12042. for (var v in list) {
  12043. var n = list[v].name;
  12044. if (list[v].keyMatch.test(e)) {
  12045. list[v].exec(e);
  12046. }
  12047. }
  12048. });
  12049. } //
  12050.  
  12051. function initNicoS($, conf, w) {
  12052. WatchJsApi.nicos.addEventListener('nicoSJump', function(e) {
  12053. if (conf.ignoreJumpCommand) {
  12054. e.cancel();
  12055. Popup.show('「@ジャンプ」コマンドをキャンセルしました');
  12056. }
  12057. });
  12058. var seekCount = 0;
  12059. WatchApp.ns.model.player.NicoSClientConnector.addEventListener('nicoSSeek', function(e) {
  12060. seekCount++;
  12061. if (conf.nicoSSeekCount < 0) return;
  12062. if (seekCount > conf.nicoSSeekCount) {
  12063. e.cancel();
  12064. }
  12065. });
  12066. // 動画が切り替わったか、最後まで視聴したらカウンターリセット
  12067. EventDispatcher.addEventListener('onVideoInitialized', function() {
  12068. seekCount = 0;
  12069. });
  12070. EventDispatcher.addEventListener('onVideoEnded', function() {
  12071. seekCount = 0;
  12072. });
  12073. } //
  12074.  
  12075. function initMouse() {
  12076. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12077. if (name === 'mouseClickWheelVolume') {
  12078. if (oldValue === 0) {
  12079. initWheelWatch();
  12080. } else
  12081. if (newValue === 0) {
  12082. $(document)
  12083. .unbind('mousewheel.watchItLaterWheelWatch')
  12084. .unbind('mousedown.watchItLaterWheelWatch')
  12085. .unbind('mouseup.watchItLaterWheelWatch');
  12086. }
  12087. }
  12088. });
  12089.  
  12090. function initWheelWatch() {
  12091. var leftDown = false, rightDown = false, isVolumeChanged = false;
  12092. var event = {
  12093. cancel: false,
  12094. reset: function() { this.cancel = false; return this; },
  12095. preventDefault: function() { this.cancel = true;}
  12096. };
  12097. $(document).on('mousewheel.watchItLaterWheelWatch', function(e, delta) {
  12098. var button = -1;
  12099. // TODO: マジックナンバーを
  12100. if (typeof e.buttons === 'number') { // firefox
  12101. button = e.buttons;
  12102. } else { // chrome
  12103. if (leftDown) { button = 1; }
  12104. else
  12105. if (rightDown) { button = 2; }
  12106. }
  12107. if (button < 1) {
  12108. EventDispatcher._dispatch('onWheelNoButton', e, delta);
  12109. return;
  12110. }
  12111. EventDispatcher.dispatch('onWheelAndButton', event.reset(), delta, button);
  12112. if (event.cancel) {
  12113. e.preventDefault();
  12114. return;
  12115. }
  12116. if (conf.mouseClickWheelVolume !== button) {
  12117. return;
  12118. }
  12119.  
  12120. var v = WatchController.volume(), r;
  12121. isVolumeChanged = true;
  12122. // 音量を下げる時は「うわ音でけぇ!」
  12123. // 音量を上げる時は「ちょっと聞こえにくいな」…というパターンが多いので、変化の比率が異なる
  12124. if (delta > 0) {
  12125. v = Math.max(v, 1);
  12126. r = (v < 5) ? 1.3 : 1.1;
  12127. v = WatchController.volume(v * r);
  12128. } else {
  12129. v = WatchController.volume(Math.floor(v / 1.2));
  12130. }
  12131. e.preventDefault();
  12132. }).on('mousedown.watchItLaterWheelWatch', function(e) { // chromeはホイールイベントでe.buttonsが取れないため
  12133. if (e.which === 1) leftDown = true;
  12134. if (e.which === 3) rightDown = true;
  12135. }).on('mouseup.watchItLaterWheelWatch', function(e) {
  12136. if (e.which === 1) leftDown = false;
  12137. if (e.which === 3) rightDown = false;
  12138. }).on('contextmenu.watchItLaterWheelWatch', function(e) {
  12139. if (isVolumeChanged) {
  12140. e.preventDefault();
  12141. }
  12142. isVolumeChanged = false;
  12143. });
  12144. }
  12145. window.setTimeout(function() { initWheelWatch(); }, 5000);
  12146. } // end initMouse
  12147.  
  12148. function initTouch() {
  12149. var touchInitialized = false;
  12150. TouchEventDispatcher.onflick(function(e) {
  12151. var se = e.startEvent;
  12152. if (!conf.enableQTouch) {return; }
  12153. if (e.direction === 'right') {
  12154. if (se.target.id === 'playerTabWrapper') {
  12155. $(se.target).addClass('w_active');
  12156. }
  12157. if (!touchInitialized) {
  12158. $('#mylist_add_frame, #leftPanelTabContainer, .videoExplorerMenu, #playerTabWrapper').addClass('w_touch');
  12159. $('.userProfile, .resultPagination, #searchResultContainer select, .playlistMenuPopup').addClass('w_touch');
  12160. isTouchActive = true;
  12161. touchInitialized = true;
  12162. }
  12163. } else
  12164. if (e.direction === 'left') {
  12165. if (se.target.tagName === 'DIV' &&
  12166. $.contains('#playerTabWrapper', se.target)) {
  12167. $('#playerTabWrapper').removeClass('w_active');
  12168. }
  12169. }
  12170. });
  12171. } //
  12172.  
  12173. function initOtherCss() {
  12174. var __dynamic_css_template__ = Util.here(function() {/*
  12175. .full_with_browser.w_fullScreenMenu #nicoHeatMap {
  12176. transform: scaleX($scale); -webkit-transform: scaleX($scale); display: block;
  12177. }
  12178. */});
  12179. var exStyle = null;
  12180. var updateDynamicCss = function() {
  12181. var css = __dynamic_css_template__;
  12182. var innerWidth = $('body').innerWidth();
  12183. css = css.split('$scale').join($('body').innerWidth() / 100);
  12184. if (exStyle) {
  12185. exStyle.innerHTML = css;
  12186. return exStyle;
  12187. } else {
  12188. return addStyle(css, 'expression');
  12189. }
  12190. };
  12191. exStyle = updateDynamicCss();
  12192.  
  12193. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12194. updateDynamicCss();
  12195. });
  12196.  
  12197. var __gpuLayer__ = (function() {/*
  12198. body.videoExplorer.content-fix #playerTabWrapper,
  12199. body.videoExplorer.content-fix .videoExplorerMenu,
  12200. body.videoExplorer.content-fix #playlist,
  12201. {*#playerTabWrapper .playerCommentPanel*}
  12202. body:not(.full_with_browser) .mylistPopupPanel.fixed
  12203. {
  12204. -moz-transform: translateZ(0);
  12205. -webkit-transform: translateZ(0);
  12206. transform: translateZ(0);
  12207. }
  12208. {* Firefoxだと問題がある要素はこちら *}
  12209. body.videoExplorer.content-fix #leftPanel,
  12210. body.videoExplorer.content-fix #content,
  12211. #popupMarquee
  12212. {
  12213. -webkit-transform: translateZ(0);
  12214. }
  12215. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]
  12216. .replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  12217. if (conf.enableGpuLayer) {
  12218. addStyle(__gpuLayer__, 'watchItLaterGpuLayer');
  12219. }
  12220.  
  12221. var __debug_css__ = Util.here(function() {/*
  12222. .videoExplorer #playerContainerWrapper, .videoExplorer #external_nicoplayer,
  12223. .videoExplorerOpening #playerContainerWrapper, .videoExplorerOpening #external_nicoplayer
  12224. {
  12225. {*transition: width 0.4s ease, height 0.4s ease;*}
  12226. }
  12227. #playerAlignmentArea .toggleCommentPanel {
  12228. {*transition: 0.4s ease-in-out;*}
  12229. }
  12230. {*
  12231. body:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer,
  12232. body:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea{
  12233. transition: width 0.4s ease;
  12234. }
  12235. body:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer{
  12236. transition: height 0.4s ease 0.4s;
  12237. }
  12238. *}
  12239.  
  12240. */});
  12241. if (conf.debugMode) addStyle(__debug_css__, 'watchItLater_debug_css');
  12242. } // end initOtherCss
  12243.  
  12244. function initCustomPlayerSize($, conf, w) {
  12245. var tpl = Util.here(function() {/*
  12246. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerAlignmentArea
  12247. { width: {$alignmentAreaWidth}px; }
  12248. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) .w_wide #playerAlignmentArea
  12249. { width: {$alignmentAreaWideWidth}px; }
  12250.  
  12251. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoplayerContainer {
  12252. height: {$playerHeight}px !important;
  12253. }
  12254. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #playerNicoplayer
  12255. { width: {$playerWidth}px !important;}
  12256.  
  12257. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #external_nicoplayer
  12258. { width: {$playerWidth}px !important; height: {$playerHeight}px !important; }
  12259.  
  12260. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMapContainer {
  12261. width: {$playerWidth}px !important;
  12262. }
  12263. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #nicoHeatMap {
  12264. transform: scaleX({$heatMapScale}); -webkit-transform: scaleX({$heatMapScale});
  12265. }
  12266. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #smart_music_kiosk {
  12267. -webkit-transform: scaleX({$songriumScale}); -webkit-transform-origin: 0 0;
  12268. }
  12269.  
  12270. body.size_normal.w_size_custom:not(.videoExplorer):not(.full_with_browser) #inspire_category {
  12271. left: {$songriumCategoryLeft}px !important;
  12272. }
  12273.  
  12274. */});
  12275. var PROFILE_SET = {
  12276. 'WQHD': [2560, 1440],
  12277. '1080p': [1920, 1080],
  12278. 'HD+': [1600, 900],
  12279. 'WXGA+': [1400, 810],
  12280. 'WXGA': [1366, 768],
  12281. '720p': [1280, 720],
  12282. 'WSVGA+': [1152, 648],
  12283. 'WSVGA': [1024, 576],
  12284. 'QHD': [ 960, 540]//, // 元より小さいパターンもサポートする?
  12285. // 'WVGA': [ 854, 480],
  12286. // 'NORMAL': [ 640, 360],
  12287. // 'SMALL': [ 512, 288],
  12288. // 'ECO': [ 352, 200],
  12289. };
  12290. var CONTROL_HEIGHT = 46, INPUT_HEIGHT = 30, PLAYER_TAB_WIDTH = 326 + 10, PLAYER_TAB_WIDTH_WIDE = 420 + 10, TAB_MARGIN = 0;
  12291. var SONGRIUM_WIDTH = 898;
  12292. var HORIZONTAL_MARGIN = 1.05; // 両端に 2.5% x 2 のマージンがある
  12293. var $videoHeader = $('#videoHeader');
  12294.  
  12295. var getTargetSize = function(targetWidth, targetHeight) {
  12296. var plWidth = Math.round(targetWidth * HORIZONTAL_MARGIN);
  12297. var plHeight = targetHeight + CONTROL_HEIGHT + INPUT_HEIGHT;
  12298. var alWidth = plWidth + PLAYER_TAB_WIDTH;
  12299. var alWidthW = plWidth + PLAYER_TAB_WIDTH_WIDE;
  12300. return {
  12301. playerWidth: plWidth,
  12302. playerHeight: plHeight,
  12303. alignmentAreaWidth: alWidth,
  12304. alignmentAreaWideWidth: alWidthW,
  12305. heatMapScale: plWidth / 100,
  12306. songriumScale: plWidth / SONGRIUM_WIDTH,
  12307. songriumCategoryLeft: plWidth + 32
  12308. };
  12309. };
  12310. var suggestProfile = function() {
  12311. var iw = $(window).innerWidth(), ih = $(window).innerHeight();
  12312. var hh = (WatchController.isFixedHeader() ? $("#siteHeader").outerHeight() : 0);
  12313. iw -= (conf.wideCommentPanel ? PLAYER_TAB_WIDTH_WIDE : PLAYER_TAB_WIDTH);
  12314. iw -= TAB_MARGIN;
  12315.  
  12316. ih -= hh;
  12317. for (var v in PROFILE_SET) {
  12318. var w = PROFILE_SET[v][0], h = PROFILE_SET[v][1];
  12319. if (w * HORIZONTAL_MARGIN <= iw && h <= ih) {
  12320. return {w: w, h: h, name: v};
  12321. }
  12322. }
  12323. return null;
  12324. };
  12325. var generateCss = function() {
  12326. var profile = '';
  12327. if (PROFILE_SET[conf.customPlayerSize]) {
  12328. var s = PROFILE_SET[conf.customPlayerSize];
  12329. profile = {w: s[0], h: s[1], name: conf.customPlayerSize};
  12330. } else {
  12331. profile = suggestProfile();
  12332. }
  12333. if (!profile) return {css: '', profile: ''};
  12334. var ts = getTargetSize(profile.w, profile.h);
  12335. var css = tpl;
  12336. for (var v in ts) {
  12337. css = css.split('{$' + v + '}').join(ts[v]);
  12338. }
  12339. return {css: css, profile: profile};
  12340. };
  12341. var customStyleElement = null, lastCssName = '';
  12342. var updateStyle = function() {
  12343. if (WatchController.isFullScreen() || WatchController.isSearchMode()) {
  12344. return;
  12345. }
  12346.  
  12347. var result = generateCss();
  12348. var css = result.css, profile = result.profile, name = profile.name;
  12349.  
  12350. if (lastCssName === name) {
  12351. return;
  12352. }
  12353. lastCssName = name;
  12354. if (customStyleElement) {
  12355. customStyleElement.innerHTML = css;
  12356. } else {
  12357. customStyleElement = addStyle(css, 'customPlayerSize');
  12358. }
  12359. EventDispatcher.dispatch('onPlayerAlignmentAreaResize');
  12360. };
  12361. var toggleCustomSize = function(v) {
  12362. if (typeof v === 'boolean') {
  12363. $('body').toggleClass('w_size_custom', v);
  12364. } else {
  12365. $('body').toggleClass('w_size_custom');
  12366. }
  12367. };
  12368. var clearStyle = function() {
  12369. if (customStyleElement) {
  12370. customStyleElement.innerHTML = '';
  12371. toggleCustomSize(false);
  12372. }
  12373. lastCssName = '';
  12374. };
  12375. if (conf.customPlayerSize !== '') {
  12376. updateStyle();
  12377. toggleCustomSize();
  12378. }
  12379. EventDispatcher.addEventListener('on.config.customPlayerSize', function(v) {
  12380. if (v === '') {
  12381. clearStyle();
  12382. } else {
  12383. updateStyle();
  12384. toggleCustomSize(true);
  12385. }
  12386. });
  12387. EventDispatcher.addEventListener('on.config.wideCommentPanel', function(v) {
  12388. if (conf.customPlayerSize !== '') {
  12389. updateStyle();
  12390. }
  12391. });
  12392. EventDispatcher.addEventListener('onWindowResizeEnd', function() {
  12393. if (conf.customPlayerSize !== '') {
  12394. updateStyle();
  12395. }
  12396. });
  12397.  
  12398. if (conf.debugMode) {
  12399. WatchItLater.debug.customSize = {
  12400. suggestProfile: suggestProfile,
  12401. getTargetSize: getTargetSize,
  12402. generateCss: generateCss,
  12403. updateStyle: updateStyle,
  12404. toggleCustomSize: toggleCustomSize
  12405. };
  12406. }
  12407.  
  12408. } //
  12409.  
  12410. function initStageVideo($, conf, w) {
  12411. var onStageVideoAvailabilityUpdated = function(v) {
  12412. $('#nicoplayerContainerInner').toggleClass('stageVideo', v);
  12413. };
  12414.  
  12415. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  12416. onStageVideoAvailabilityUpdated(WatchController.isStageVideoAvailable());
  12417. if (conf.forceEnableStageVideo) {
  12418. try {$('#external_nicoplayer')[0].setIsForceUsingStageVideo(true); } catch (e) { console.log(e);}
  12419. }
  12420. });
  12421.  
  12422. var pac = watch.PlayerInitializer.playerAreaConnector;
  12423.  
  12424. pac.addEventListener('onStageVideoAvailabilityUpdated', onStageVideoAvailabilityUpdated);
  12425.  
  12426. // console.log('StageVideo', $('#external_nicoplayer')[0].isStageVideoSupported() ? 'supported' : 'not supported');
  12427. // console.log('ColorSpaces', $('#external_nicoplayer')[0].getStageVideoSupportedColorSpaces());
  12428.  
  12429. } //
  12430.  
  12431. /**
  12432. * Chromeなら ALT+C
  12433. * Firefoxなら ALT+SHIFT+C で召喚
  12434. *
  12435. * どこでもコメント入力開始する隠し機能
  12436. *
  12437. * :m[0-9a-p] xxxxx でマイリストする機能もある
  12438. *
  12439. **/
  12440. function initInvisibleCommentInput($, conf, w) {
  12441. var $view = $(Util.here(function() {/*
  12442. <div class="invisibleInput">
  12443. <form action="javascript: void(0);">
  12444. <input type="text" value="" autocomplete="on" name="chat" accesskey="c"
  12445. list="myMylist" placeholder="コメント入力" class="invisibleCommentInput"
  12446. maxlength="75"
  12447. ></form>
  12448. <label><input type="checkbox" class="autoPause" checked="checked">動画を自動で一時停止する</label>
  12449. </div>
  12450. */}));
  12451. var css = Util.here(function() {/*
  12452. .invisibleInput {
  12453. position: fixed; z-index: 10000;
  12454. left: 100px; bottom: -100px; width: 300px;
  12455. padding: 16px;
  12456. border: 1px solid black;
  12457. background: #eee;
  12458.  
  12459. transition: bottom 0.4s ease 0.1s;
  12460. }
  12461. .invisibleInput.active {
  12462. bottom: 50px;
  12463. transition: bottom 0.4s ease;
  12464. box-shadow: 2px 2px 2px #333;
  12465. }
  12466.  
  12467. .invisibleInput .invisibleCommentInput {
  12468. width: 100%; font-size: 140%;
  12469. }
  12470. */});
  12471. var $dataList = $('<datalist id="myMylist"></datalist>');
  12472. var $form = $view.find('form');
  12473. var $input = $view.find('input');
  12474. var $autoPause = $view.find('.autoPause').prop('checked', conf.autoPauseInvisibleInput);
  12475.  
  12476. var prevent = function(e) { e.stopPropagation(); e.preventDefault(); };
  12477. var preventEsc = function(e) { if (e.keyCode === 27) { prevent(e); } };
  12478. var isAutoPause = function() { return !!$autoPause.prop('checked'); };
  12479.  
  12480. var mylistList = [];
  12481. var
  12482. onMylistUpdate = function(status, result) {
  12483. if (status === "ok") {
  12484. Popup.show('マイリストに追加しました');
  12485. } else {
  12486. Popup.alert('マイリスト追加に失敗: ' + result.error.description);
  12487. }
  12488. }, addMylist = function(n, d) {
  12489. var num = parseInt(n, 36);
  12490. var description = d || '';
  12491. if (num === 0) {
  12492. Mylist.addDefListItem(WatchController.getWatchId(), onMylistUpdate, description);
  12493. } else
  12494. if (mylistList[num]) {
  12495. Mylist.addMylistItem (WatchController.getWatchId(), mylistList[num].id, onMylistUpdate, description);
  12496. }
  12497. }, showMylist = function(n) {
  12498. var num = parseInt(n, 36);
  12499. if (num === 0) {
  12500. WatchController.showDeflist();
  12501. } else
  12502. if (mylistList[num]) {
  12503. WatchController.showMylist(mylistList[num].id);
  12504. }
  12505. }, seekVideo = function(v) {
  12506. var vpos = WatchController.vpos(), currentVpos = vpos;
  12507. if (v.match(/^([\-+]\d+)/)) {
  12508. vpos += parseInt(RegExp.$1, 10) * 1000;
  12509. } else
  12510. if (v.match(/^\d+$/)) {
  12511. vpos = parseInt(v, 10) * 1000;
  12512. } else
  12513. if (v.match(/^(\d+):(\d+)$/)) {
  12514. vpos = parseInt(RegExp.$1, 10) * 60 * 1000 + parseInt(RegExp.$2, 10) * 1000;
  12515. }
  12516.  
  12517. vpos = Math.max(vpos, 0);
  12518.  
  12519. if (vpos != currentVpos) {
  12520. console.log('seek video', vpos / 1000);
  12521. WatchController.vpos(vpos);
  12522. }
  12523. };
  12524.  
  12525. var isPlaying = false;
  12526. $input
  12527. .on('focus', function(e) {
  12528. isPlaying = WatchController.isPlaying();
  12529. if (isAutoPause()) {
  12530. WatchController.pause();
  12531. }
  12532. $view.addClass('active');
  12533. }).on('blur', function(e) {
  12534. if (isAutoPause() && isPlaying) {
  12535. WatchController.play();
  12536. }
  12537. $view.removeClass('active');
  12538. }).on('keyup', function(e) {
  12539. if (e.keyCode === 27) { // ESC
  12540. prevent(e);
  12541. $input.blur();
  12542. }
  12543. }).on('keydown', preventEsc).on('keyup', preventEsc);
  12544.  
  12545. $autoPause.on('click', function() {
  12546. conf.setValue('autoPauseInvisibleInput', !!$autoPause.prop('checked'));
  12547. });
  12548.  
  12549. $form
  12550. .on('submit', function(e) {
  12551. //prevent(e);
  12552. var val = $.trim($input.val());
  12553.  
  12554. if (val.match(/^:m([0-9a-p])(.*)/)) {
  12555. addMylist(RegExp.$1, RegExp.$2);
  12556. } else
  12557. if (val.match(/^:o([0-9a-p])/)) {
  12558. showMylist(RegExp.$1);
  12559. } else
  12560. if (val.match(/^:s[  \s](.+)/)) {
  12561. WatchController.nicoSearch(RegExp.$1, 'keyword');
  12562. } else
  12563. if (val.match(/^:t[  \s](.+)/)) {
  12564. WatchController.nicoSearch(RegExp.$1, 'tag');
  12565. } else
  12566. if (val.match(/^:v[  \s]([0-9:+\-]+)$/)) {
  12567. seekVideo(RegExp.$1);
  12568. } else {
  12569. WatchController.postComment(val);
  12570. }
  12571.  
  12572. setTimeout(function() { $input.val(''); } , 100);
  12573. $input.blur();
  12574. });
  12575.  
  12576. Mylist.loadMylistList(function(list) {
  12577. mylistList = list.concat();
  12578. mylistList.unshift({description: '', id: '', name: 'とりあえずマイリスト'});
  12579. var isFx = Util.Browser.isFx();
  12580.  
  12581. var tmp = [];
  12582. for (var i = 0, len = mylistList.length; i < len; i++) {
  12583. var c = i.toString(36);
  12584. // それぞれのブラウザで補完しやすい形式に
  12585. if (isFx) { // Fx
  12586. tmp.push('<option value=":m' + c + '">:m'+c+'\t 「' + mylistList[i].name + '」に追加</option>');
  12587. tmp.push('<option value=":o' + c + '">:o'+c+'\t 「' + mylistList[i].name + '」を開く</option>');
  12588. } else { // Chrome
  12589. tmp.push('<option value=":m' + c + '">「' + mylistList[i].name + '」に追加</option>');
  12590. tmp.push('<option value=":o' + c + '">「' + mylistList[i].name + '」を開く</option>');
  12591. }
  12592. }
  12593. tmp.sort();
  12594. if (isFx) {
  12595. tmp.push('<option value=":s ">:s [キーワード検索]</option>');
  12596. tmp.push('<option value=":t ">:t [タグ検索]</option>');
  12597. tmp.push('<option value=":v ">:v [シーク(秒)]</option>');
  12598. } else {
  12599. tmp.push('<option value=":s ">キーワード検索</option>');
  12600. tmp.push('<option value=":t ">タグ検索</option>');
  12601. tmp.push('<option value=":v ">シーク(秒)</option>');
  12602. }
  12603. $dataList.html(tmp.join('\n'));
  12604. });
  12605.  
  12606.  
  12607. addStyle(css, 'invisibleInput');
  12608. $('body').append($view).append($dataList);
  12609. } //
  12610.  
  12611. function initHeatMap($, conf, w) {
  12612. if (!conf.enableHeatMap) return;
  12613. //if (!w.Worker) return;
  12614. //
  12615. // TODO: Web Workers
  12616. var canvasWidth = 100, canvasHeight = 12;
  12617. var comments = [], duration = 0, canvas = null, context = null;
  12618. var commentReady = false, videoReady = false, updated = false, palette = [];
  12619. var __css__ = Util.here(function(){/*
  12620. #nicoHeatMapContainer {
  12621. position: absolute; z-index: 200;
  12622. bottom: 0px; left: 0;
  12623. width: 672px;
  12624. background: #000; height: 6px;
  12625. overflow: hidden;
  12626. }
  12627. .setting_panel #nicoHeatMapContainer { display: none; opacity: 0; }
  12628. .size_normal #nicoHeatMapContainer {
  12629. width: 898px;
  12630. }
  12631. .oldTypeCommentInput #nicoHeatMapContainer {
  12632. bottom: 29px;
  12633. display: none;
  12634. }
  12635. #nicoHeatMap {
  12636. position: absolute; top: 0; left: 0;
  12637. transform-origin: 0 0 0;-webkit-transform-origin: 0 0 0;
  12638. transform: scaleX(6.72);-webkit-transform: scaleX(6.72);
  12639. }
  12640. {* パズルみたいになってきた *}
  12641. body.size_normal:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12642. body.size_medium:not(.full_with_browser) #content:hover #nicoHeatMapContainer,
  12643. body.videoExplorer #content.w_adjusted:hover #nicoHeatMapContainer,
  12644. body:not(.full_with_browser) #nicoHeatMapContainer.displayAlways {
  12645. display: block;
  12646. }
  12647. #nicoHeatMapContainer.displayAlways {
  12648. cursor: pointer;
  12649. }
  12650. .size_normal #nicoHeatMap {
  12651. transform: scaleX(8.98); -webkit-transform: scaleX(8.98);
  12652. }
  12653. .setting_panel #nicoHeatMapContainer, .full_with_browser #nicoHeatMapContainer, .size_small #content:not(.w_adjusted) #nicoHeatMapContainer {
  12654. display: none;
  12655. }
  12656. body.full_with_browser.w_fullScreenMenu.trueBrowserFull #nicoHeatMapContainer {
  12657. bottom: 0; position: fixed;
  12658. }
  12659. .full_with_browser.w_fullScreenMenu #nicoHeatMapContainer {
  12660. display: block;
  12661. }
  12662. .full_with_browser.w_fullScreenMenu .oldTypeCommentInput #nicoHeatMapContainer {
  12663. bottom: 29px; height: 6px;
  12664. }
  12665. .full_with_browser.w_fullScreenMenu #nicoHeatMapContainer {
  12666. width: 100%;
  12667. }
  12668. */});
  12669. addStyle(__css__, 'NicoHeatMapCss');
  12670.  
  12671. watch.PlayerInitializer.playerAreaConnector.addEventListener('onCommentListInitialized', function() {
  12672. w.setTimeout(function() {
  12673. commentReady = true;
  12674. update();
  12675. }, 1000);
  12676. });
  12677. EventDispatcher.addEventListener('onVideoInitialized', function() {
  12678. videoReady = true;
  12679. update();
  12680. });
  12681. EventDispatcher.addEventListener('onVideoChangeStatusUpdated', function() {
  12682. commentReady = videoReady = updated = false;
  12683. clearCanvas();
  12684. });
  12685.  
  12686. var update = function() {
  12687. if (!commentReady || !videoReady || updated) return;
  12688. updated = true;
  12689. initCanvas();
  12690. getComments();
  12691. getDuration();
  12692. if (comments.length < 1 || duration < 1) {
  12693. return;
  12694. }
  12695. getHeatMap(function(map) {
  12696. var scale = duration >= canvasWidth ? 1 : (canvasWidth / duration);
  12697. var blockWidth = (canvasWidth / map.length) * scale;
  12698. for (i = map.length - 1; i >= 0; i--) {
  12699. context.fillStyle = palette[map[i]] || palette[0];
  12700. context.beginPath();
  12701. context.fillRect(i * scale, 0, blockWidth, canvasHeight);
  12702. }
  12703. });
  12704. };
  12705.  
  12706. var getComments = function() {
  12707. comments = [];
  12708.  
  12709. var list = watch.PlayerInitializer.commentPanelViewController.commentLists;
  12710. for (var i = 0; i < list.length; i++) {
  12711. if (list[i].listName === 'commentlist:main' && list[i].comments.length > 0) {
  12712. comments = list[i].comments;
  12713. break;
  12714. }
  12715. var ct = list[i].comments;
  12716. comments = (comments.length < ct.length) ? ct : comments;
  12717. }
  12718. list = null;
  12719. };
  12720. var getDuration = function() {
  12721. var exp = document.getElementById('external_nicoplayer');//$('#external_nicoplayer')[0];
  12722. duration = exp.ext_getTotalTime(); //
  12723. };
  12724. var initCanvas = function() {
  12725. if (!canvas) {
  12726. var $container = $('<div id="nicoHeatMapContainer" />');
  12727. $container.on('dblclick', function(e) {
  12728. e.preventDefault();
  12729. e.stopPropagation();
  12730. var $this = $(this).toggleClass('displayAlways');
  12731. conf.setValue('heatMapDisplayMode', $this.hasClass('displayAlways') ? 'always' : 'hover');
  12732. });
  12733. canvas = document.createElement('canvas');
  12734. canvas.id = 'nicoHeatMap';
  12735. canvas.width = canvasWidth;
  12736. canvas.height = canvasHeight;
  12737. $container.append(canvas);
  12738. $('#nicoplayerContainerInner').append($container);
  12739. context = canvas.getContext('2d');
  12740. if (conf.heatMapDisplayMode === 'always') {
  12741. $container.addClass('displayAlways');
  12742. }
  12743.  
  12744. initPalette();
  12745. }
  12746. clearCanvas();
  12747. };
  12748. var initPalette = function() {
  12749. for (var c = 0; c < 256; c++) {
  12750. var
  12751. r = Math.floor((c > 127) ? (c / 2 + 128) : 0),
  12752. g = Math.floor((c > 127) ? (255 - (c - 128) * 2) : (c * 2)),
  12753. b = Math.floor((c > 127) ? 0 : (255 - c * 2));
  12754. palette.push('rgb(' + r + ', ' + g + ', ' + b + ')');
  12755. }
  12756. };
  12757. var clearCanvas = function() {
  12758. if (!context) return;
  12759. context.fillStyle = palette[0];
  12760. context.beginPath();
  12761. context.fillRect(0, 0, canvasWidth, canvasHeight);
  12762. };
  12763.  
  12764. var getHeatMap = function(callback) {
  12765. var map = new Array(100), i = map.length; while(i > 0) map[--i] = 0;
  12766. var exp = $('#external_nicoplayer')[0];
  12767. var ratio = duration > map.length ? (map.length / duration) : 1;
  12768.  
  12769. for (i = comments.length - 1; i >= 0; i--) {
  12770. var pos = comments[i].vpos , mpos = Math.min(Math.floor(pos * ratio / 1000), map.length -1);
  12771. map[mpos]++;
  12772. }
  12773.  
  12774. var max = 0;
  12775. for (i = map.length - 4; i >= 0; i--) max = Math.max(map[i], max); // 末尾は固まってる事があるので参考にしない
  12776. if (max > 0) {
  12777. var rate = 255 / max;
  12778. for (i = map.length - 1; i >= 0; i--) {
  12779. map[i] = Math.min(255, Math.floor(map[i] * rate));
  12780. }
  12781. }
  12782. if (typeof callback === 'function') {
  12783. callback(map);
  12784. }
  12785. };
  12786. } // end of initHeatMap
  12787.  
  12788. /**
  12789. * 既存のポップアップの難点
  12790. *
  12791. * ・閉じる機能がなく、邪魔でも消えるまで待つしかない
  12792. * ・消えるまでの時間が毎回違う?
  12793. * ・クリックしたら消えるのかなと思ったらマイページに飛ばされる
  12794. * ・Chrome以外では動画プレイヤーの上に表示できない (半透明の部分が欠ける)
  12795. * ・↑によってプレイヤー上でフェードイン・アウトが出来ないため、まったく見えない状態から突然出現したようになる
  12796. * ・タイマー処理がバグっていて、一個目の表示中に2個目を連続表示すると2個目がすぐ消える
  12797. *
  12798. * … という所があんまりなので、パッチをあてて直す。
  12799. * ・Chrome以外は半透明をやめて画面外からのスライドにする
  12800. * ・CSS3アニメーションを使う(jQueryより軽い)
  12801. * ・クリックでマイページに飛ぶのをやめて、クリックで消えるようにする
  12802. * ・マウスオーバーしてる間は引っ込まない
  12803. * ・消えるまでの時間を4秒に固定
  12804. *
  12805. *
  12806. * このパッチでも直らない問題
  12807. * ・自分が動画投稿やレビューをしたという情報がなぜか自分にも通知される
  12808. *
  12809. */
  12810. function initPopupMarquee() {
  12811. if (!conf.replacePopupMarquee) { return; }
  12812. var
  12813. marquee = watch.PopupMarqueeInitializer.popupMarqueeViewController,
  12814. itemList = marquee.itemList,
  12815. $popup = $('#popupMarquee'),
  12816. $inner = $popup.find('.popupMarqueeContent'),
  12817. closeTimer = null,
  12818. popupDuration = 6000;
  12819.  
  12820. var
  12821. resetCloseTimer = function() {
  12822. if (closeTimer) {
  12823. clearTimeout(closeTimer);
  12824. closeTimer = null;
  12825. }
  12826. },
  12827. setCloseTimer = function() {
  12828. resetCloseTimer();
  12829. closeTimer = setTimeout(function() {
  12830. disappear();
  12831. closeTimer = null;
  12832. }, popupDuration);
  12833. },
  12834. onData = function(data) {
  12835. $inner.html(data);
  12836.  
  12837. $popup.removeClass('hide').removeClass('show');
  12838. setTimeout(function() {
  12839. $popup.removeClass('hide').addClass('show');
  12840. }, 100);
  12841. setCloseTimer();
  12842. },
  12843. disappear = function() {
  12844. $popup.removeClass('show');
  12845. resetCloseTimer();
  12846. setTimeout(function() {
  12847. if (!$popup.hasClass('show')) $popup.addClass('hide');
  12848.  
  12849. setTimeout(function() {
  12850. itemList.next();
  12851. }, Math.random() * 5000 + 5000);
  12852.  
  12853. }, 500);
  12854. },
  12855. __css__ = Util.here(function() {/*
  12856. #popupMarquee {
  12857. -webkit-filter: opacity( 0%); {* chrome以外はflashの上に半透明要素を置けない *}
  12858. background: #000 !important;
  12859. transition: -webkit-filter 0.25s ease-in, top 0.5s ease-in, bottom 0.5s ease-in; display: block;
  12860. }
  12861. #popupMarquee.show {
  12862. -webkit-filter: opacity(100%);
  12863. transition: -webkit-filter 1.00s ease-out, top 0.5s ease-out, bottom 0.5s ease-out; display: block;
  12864. }
  12865.  
  12866. #popupMarquee.hide {
  12867. opacity: 0; z-index: -1;
  12868. }
  12869.  
  12870. #popupMarquee.popupMarqueeTopRight:not(.show), #popupMarquee.popupMarqueeTopLeft:not(.show) { top: -600px; }
  12871. #popupMarquee.popupMarqueeBottomRight:not(.show), #popupMarquee.popupMarqueeBottomLeft:not(.show) { bottom: -600px; }
  12872. */});
  12873.  
  12874. addStyle(__css__, 'popupMarqueeFix');
  12875.  
  12876. itemList.eventTypeListenerMap.popup = []; //itemList.removeEventListener('popup', marquee.onData);
  12877. $popup
  12878. .css({opacity: ''})
  12879. .off('click').off('mouseover').off('mouseleave').off('mousemove')
  12880. .on('mouseover', resetCloseTimer)
  12881. .on('mouseout', setCloseTimer)
  12882. .on('click', disappear);
  12883.  
  12884. marquee.onData = $.proxy(onData, marquee);
  12885. marquee.disappear = $.proxy(disappear, marquee);
  12886. itemList.addEventListener('popup', $.proxy(onData, marquee));
  12887. } //
  12888.  
  12889.  
  12890. function initScroll($, conf, w) {
  12891. // 動画切り換え時にページの一番上までスクロールするようになったのを強引に阻止する
  12892. window.WatchApp.ns.model.state.WatchPageRouter.getInstance()._scroll = function() {};
  12893.  
  12894. var beforePlayerOffsetTop = 0, $playerAlignmentArea = $('#playerAlignmentArea');
  12895. var $window = $(window);
  12896. var beforeReset = function() {
  12897. beforePlayerOffsetTop = $playerAlignmentArea.offset().top;
  12898. };
  12899. var afterReset = function() {
  12900. var diff = $playerAlignmentArea.offset().top - beforePlayerOffsetTop;
  12901. var scrollTop = $window.scrollTop();
  12902. $window.scrollTop(scrollTop + diff);
  12903. };
  12904. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  12905. watchInfoModel.addEventListener('beforeReset', beforeReset);
  12906. watchInfoModel.addEventListener('afterReset', afterReset);
  12907.  
  12908.  
  12909. // 動画選択画面閉じた時にページの一番上までスクロールするようになったのを強引に阻止する
  12910. window.WatchApp.ns.util.WindowUtil.scroll_org = window.WatchApp.ns.util.WindowUtil.scroll;
  12911. var no_thanks = function() {
  12912. window.WatchApp.ns.util.WindowUtil.scroll = function() {};
  12913. };
  12914. var restore = function() {
  12915. window.WatchApp.ns.util.WindowUtil.scroll = window.WatchApp.ns.util.WindowUtil.scroll_org;
  12916. };
  12917.  
  12918. var vv = window.WatchApp.ns.init.BottomContentInitializer.videoExplorerModeViewController;
  12919. vv.onVideoExplorerClose_org = vv.onVideoExplorerClose;
  12920. vv.onVideoExplorerClose = $.proxy(function() {
  12921. no_thanks();
  12922. this.onVideoExplorerClose_org();
  12923. restore();
  12924. window.WatchApp.ns.util.WindowUtil.scrollFit('#playerContainerWrapper');
  12925. }, vv);
  12926.  
  12927. $ = conf = w = null;
  12928. } //
  12929.  
  12930. function initOther() {
  12931. if (conf.headerViewCounter) $('#siteHeaderInner').width($('#siteHeaderInner').width() + 200);
  12932.  
  12933. initAdditionalButtons();
  12934. initSquareThumbnail();
  12935.  
  12936. ConfigPanel.addChangeEventListener(function(name, newValue, oldValue) {
  12937. if (name === 'squareThumbnail') {
  12938. initSquareThumbnail();
  12939. } else
  12940. if (name === 'enableAutoTagContainerHeight') {
  12941. if (newValue) { watch.TagInitializer.tagViewController.tagViewPinStatus.changeStatus(true); }
  12942. } else
  12943. if (name === 'enableMylistDeleteButton') {
  12944. $('.videoExplorerBody').toggleClass('enableMylistDeleteButton', newValue);
  12945. } else
  12946. if (name === 'enableYukkuriPlayButton') {
  12947. newValue ? Yukkuri.show() : Yukkuri.hide();
  12948. } else
  12949. if (name === 'noNicoru') {
  12950. $('body').toggleClass('w_noNicoru', newValue);
  12951. } else
  12952. if (name === 'playerBgStyle') {
  12953. $('#content')
  12954. .toggleClass('w_flat_gray', newValue === 'gray')
  12955. .toggleClass('w_flat_white', newValue === 'white');
  12956. } else
  12957. if (name === 'compactVideoInfo') {
  12958. $('#content, #outline').toggleClass('w_compact', newValue);
  12959. } else
  12960. if (name === 'disableHorizontalScroll') {
  12961. $('body').toggleClass('w_disableHorizontalScroll', newValue);
  12962. } else
  12963. if (name === 'hideCommentPanelSocialButtons') {
  12964. $('#playerTabContainer').toggleClass('w_noSocial', newValue);
  12965. }
  12966. });
  12967.  
  12968. if (conf.enableMylistDeleteButton) $('.videoExplorerBody').addClass('enableMylistDeleteButton');
  12969.  
  12970. if (conf.noNicoru) $('body').addClass('w_noNicoru');
  12971.  
  12972. if (conf.playerBgStyle !== '') $('#content').addClass('w_flat_' + conf.playerBgStyle);
  12973.  
  12974. if (conf.compactVideoInfo) $('#content, #outline').addClass('w_compact');
  12975. onWatchInfoReset(watchInfoModel);
  12976.  
  12977. if (conf.enableYukkuriPlayButton) { Yukkuri.show(); }
  12978.  
  12979. if (conf.disableHorizontalScroll) $('body').addClass('w_disableHorizontalScroll');
  12980.  
  12981. if (conf.hideCommentPanelSocialButtons) $('#playerTabContainer').addClass('w_noSocial');
  12982.  
  12983. $('#videoHeaderMenu .searchText input').attr({'accesskey': '@'}).on('focus', function() {
  12984. WatchController.scrollTop(0, 400);
  12985. });
  12986.  
  12987. watch.PlayerInitializer.commentPanelViewController.commentPanelContentModel.addEventListener('change', function(name) {
  12988. if (name === 'log_comment') {
  12989. $('.logDateSelect .logTime input')[0].setAttribute('type', 'time');
  12990. }
  12991. });
  12992.  
  12993. if (!w.Ads) {
  12994. // hostsに 0.0.0.0 ads.nicovideo.jp してるとスクリプトエラーがうるさいのでダミーを入れる
  12995. w.Ads = {
  12996. Advertisement: function() { return {set: function() {}}; }
  12997. };
  12998. }
  12999.  
  13000. var overrideGenerateURL = function() {
  13001. var wpc = WatchApp.ns.init.WatchPageInitializer.watchPageController;
  13002. wpc.generateWatchURL_org = wpc.generateWatchURL;
  13003. wpc.generateWatchURL = $.proxy(function(s) {
  13004. var ret = this.generateWatchURL_org(s);
  13005. // これのせいで既読リンクの色が変わらないので除去
  13006. ret = ret.replace(/\/(videoExplorer|ichiba)/, '');
  13007. return ret;
  13008. }, wpc);
  13009. };
  13010. overrideGenerateURL();
  13011.  
  13012. // 再現性不明のエラーをとりあえず握りつぶしつつ自動再生を3/2までの仕様に戻す
  13013. var overrrideWindowUtil = function() {
  13014. var wu = WatchApp.ns.util.WindowUtil;
  13015. wu.checkInview_org = wu.checkInview;
  13016. wu.checkInview = function() { return true; };
  13017. //wu.checkInview = $.proxy(function(a) {
  13018. // if (a.length < 0) { return true; }
  13019. // try {
  13020. // this.checkInview_org(a);
  13021. // } catch (e) {
  13022. // console.log('%cerror in WindowUtil.checkInview', 'color: red; ', e, a);
  13023. // console.trace();
  13024. // }
  13025. //}, wu);
  13026. };
  13027. overrrideWindowUtil();
  13028.  
  13029. // ニコる数を取得するためにコメントパネルがめちゃくちゃ重くなってるのを改善
  13030. WatchApp.ns.model.player.NicoPlayerConnector.getCommentNicoruCount = function(name, num) {
  13031. if (conf.noNicoru) {
  13032. return 0;
  13033. }
  13034. return window.PlayerApp.ns.player.Nicoplayer.getInstance().getCommentNicoruCount(name, num);
  13035. };
  13036.  
  13037. var playerConfig = watch.PlayerInitializer.nicoPlayerConnector.playerConfig;
  13038. if (conf.autoPlayIfWindowActive === 'yes') {
  13039. playerConfig.set({autoPlay: false});
  13040. }
  13041. if (conf.autoPlay2ndVideo) {
  13042. playerConfig.set({autoPlay: false});
  13043. EventDispatcher.addEventListener('onFirstVideoInitialized', function() {
  13044. WatchController.pause();
  13045. WatchController.vpos(0);
  13046. setTimeout(function() {
  13047. playerConfig.set({autoPlay: true});
  13048. }, 3000);
  13049. });
  13050. $(window).on('beforeunload.watchItLater.autoPlay2ndVideo', function(e) {
  13051. playerConfig.set({autoPlay: false});
  13052. });
  13053. }
  13054.  
  13055. if (conf.debugMode) {
  13056. watch.PopupMarqueeInitializer.popupMarqueeViewController.itemList.addEventListener('popup', function(body) {
  13057. console.log('%c popup: ' + body, 'background: #0ff');
  13058. });
  13059. console.log(JSON.parse($('#watchAPIDataContainer').text()));
  13060.  
  13061. //WatchApp.ns.util.WindowUtil.shake = function() { console.log('%cshake', 'background: lightgreen;');};
  13062. //NicoPlayerConnector.getCommentNicoruCount_org = NicoPlayerConnector.getCommentNicoruCount;
  13063. }
  13064. }
  13065.  
  13066. // ?ref=等がついてるせいで未読既読のリンクの色が変わらなくなる問題の対策
  13067. // ShinjukuWatchと違いこっちはプレイリスト消えないモードがあるので、マイリスト等からの遷移でも遠慮無く全部消す
  13068. if (location.href.indexOf('?') >= 0) {
  13069. window.history.replaceState('', '', location.href.split('?')[0]);
  13070. }
  13071.  
  13072.  
  13073. function initTest(test) {
  13074. var console = window.console;
  13075. var expect = test.expect;
  13076. WatchApp.mixin(WatchItLater.test.spec, {
  13077. testChannelVideo: function(def) {
  13078. ChannelVideoList.load(function(result) {
  13079. console.log('ChannelVideoList.load', result);
  13080. expect(result.name).toEqual('ニコニコアプリちゃんねるの動画', 'チャンネル名');
  13081. expect(result.list.length >= 30).toBeTrue('2013/08/28時点で33件');
  13082. def.resolve();
  13083. }, {id: '55', ownerName: 'ニコニコアプリちゃんねる'});
  13084. },
  13085. testNewNicoSearch: function(def) {
  13086. var size = 15;
  13087. var search = new NewNicoSearch({});
  13088. search.load({query: 'vocaloid', size: size}, function(err, result) {
  13089. console.log('testNewNicoSearch.load', err, result);
  13090. expect(err).toBeNull('err === null');
  13091. expect(result[0].dqnid) .toBeTruthy('先頭にdqnidが含まれる(なんの略?)');
  13092. expect(typeof result[0].values[0].total).toEqual('number', 'ヒット件数');
  13093. expect(result[0].values[0].service) .toEqual('video', '検索の種類');
  13094.  
  13095. expect(result[1].type).toEqual('stats', 'type === stats'); // データの開始?
  13096.  
  13097. expect(result[2].type ).toEqual ('hits', 'type === hits');
  13098. expect(result[2].values ).toBeTruthy('ヒットした内容');
  13099. expect(result[2].values.length ).toEqual (size, 'sizeで指定した件数が返る');
  13100. expect(result[2].values[0].cmsid).toBeTruthy('ヒットした内容にデータが含まれる');
  13101.  
  13102. expect(result[3].type).toEqual('hits', 'type === stats'); // データの終了?
  13103. def.resolve();
  13104. });
  13105. },
  13106. testNewNicoSearchWrapperQuery: function(def) {
  13107. var wrapper = new NewNicoSearchWrapper({search: {}});
  13108. var params = {
  13109. searchWord: 'VOCALOID',
  13110. searchType: 'tag',
  13111. u: '1m',
  13112. l: 'short',
  13113. sort: 'l',
  13114. order: 'a',
  13115. page: 3
  13116. };
  13117. var query = wrapper._buildSearchQuery(params);
  13118.  
  13119. console.log(params, query);
  13120. expect(query.query).toEqual(params.searchWord, '検索ワードのセット');
  13121. expect(query.from).toEqual(params.page * 32 - 32, 'ページ番号 -> fromの変換');
  13122. expect(query.sort_by).toEqual('length_seconds', 'l -> length_seconds');
  13123. expect(query.order).toEqual('asc', 'a -> asc');
  13124.  
  13125. // TODO:
  13126. expect(JSON.stringify(query.search).indexOf('["tags"]') >= 0).toBeTrue('タグ検索');
  13127. var filters = JSON.stringify(query.filters);
  13128. //console.log(filters);
  13129. expect(query.filters.length >= 2).toBeTrue('filters.lengthが2以上');
  13130. expect(filters.indexOf('"field":"start_time"') >= 0).toBeTrue('filtersにstart_timeが含まれる');
  13131. expect(filters.indexOf('"field":"length_seconds"') >= 0).toBeTrue('filtersにlength_secondsが含まれる');
  13132. def.resolve();
  13133. },
  13134. testNewNicoSearchWrapper: function(def) {
  13135. console.log('testNewNicoSearchWrapper');
  13136. var search = new NewNicoSearch({});
  13137. var wrapper = new NewNicoSearchWrapper({search: search});
  13138. wrapper.load({searchWord: 'ぬこぬこ動画', size: 100}, function(err, result) {
  13139. console.log('testNewNicoSearchWrapper.load', err, result);
  13140. expect(err).toBeNull('err === null');
  13141. expect(typeof result.count).toEqual('number', '件数がnumber');
  13142. expect(result.count > 0).toBeTrue('件数が入っている');
  13143. expect(result.list.length).toBeTruthy('データが入っている');
  13144. expect(result.list.length).toEqual(100, 'sizeで指定した件数が入っている');
  13145. expect(result.list[0].type).toEqual(0, 'type === 0');
  13146. expect(/^\d+:\d+/.test(result.list[0].length)).toBeTrue('動画長がmm:dd形式で入ってる');
  13147. def.resolve();
  13148.  
  13149. });
  13150. },
  13151. testNicoSearchRelatedTag: function(def) {
  13152. var related = new NicoSearchRelatedTag({});
  13153. related.load('voiceroid', function(err, result) {
  13154. console.log('testNicoSearchRelatedTag.load', err, result);
  13155. console.log(expect(err));
  13156. expect(err).toBeNull('err === null');
  13157. expect(result.type).toEqual('tags', 'type === "tags"');
  13158. expect(result.values).toBeTruthy('データが入っている');
  13159. expect(typeof result.values[0]._rowid).toEqual('number', 'データに_rowidが入っている');
  13160. expect(typeof result.values[0].tag) .toEqual('string', 'データにtagが入っている');
  13161. def.resolve();
  13162. });
  13163. },
  13164. testSearchSuggest: function(def) {
  13165. var suggest = new NicoSearchSuggest({});
  13166. suggest.load('MMD', function(err, result) {
  13167. console.log('testSearchSuggest.load', err, result);
  13168. console.log(expect(err));
  13169. expect(err).toBeNull('err === null');
  13170. expect(result.candidates).toBeTruthy('suggestの中身がある');
  13171. expect(result.candidates.length).toBeTruthy('suggestのlengthがある');
  13172. def.resolve();
  13173. });
  13174. },
  13175. testUpdateMylistComment: function(def) {
  13176. // 一個以上マイリストがあって先頭のマイリストになにか登録されている必要がある
  13177. var Mylist = WatchItLater.mylist;
  13178. var randomMessage = 'RND: ' + Math.random();
  13179.  
  13180. var d = new $.Deferred();
  13181. d.promise()
  13182. .then(function() {
  13183. var d = new $.Deferred();
  13184. Mylist.loadMylistList(function(mylistList) {
  13185. expect(mylistList.length > 0).toBeTruthy('マイリスト一覧が1件以上');
  13186. console.log('先頭のマイリスト', mylistList[0].id, mylistList[0].name);
  13187. var groupId = mylistList[0].id;
  13188. if (mylistList.length <= 0) {
  13189. d.reject();
  13190. return;
  13191. }
  13192. d.resolve(groupId);
  13193. });
  13194. return d.promise();
  13195. })
  13196. .then(function(groupId) {
  13197. var d = new $.Deferred();
  13198. Mylist.reloadMylist(groupId, function(mylist) {
  13199. expect(mylist.length > 0).toBeTruthy('マイリストアイテムが一個以上');
  13200. var item = mylist[0];
  13201. var watchId = item.item_data.watch_id;
  13202. console.log('マイリスト先頭のアイテム', watchId, item.item_data.title);
  13203. d.resolve(watchId, groupId);
  13204. });
  13205. return d.promise();
  13206. })
  13207. .then(function(watchId, groupId) {
  13208. var d = new $.Deferred();
  13209. Mylist.updateMylistItem(watchId, groupId, function(result) {
  13210. expect(result).toEqual('ok', 'updateMylistItem() result=ok');
  13211. d.resolve(watchId, groupId);
  13212. }, randomMessage);
  13213. return d.promise();
  13214. })
  13215. .then(Util.Deferred.wait(500))
  13216. .then(function(watchId, groupId) {
  13217. var d = new $.Deferred();
  13218. Mylist.reloadMylist(groupId, function(newlist) {
  13219. console.log('reloadMylist', groupId, newlist);
  13220. expect(newlist[0].description)
  13221. .toEqual(randomMessage, 'マイリストコメントが更新できている => ' + newlist[0].description);
  13222. d.resolve();
  13223. });
  13224. return d.promise();
  13225. }).then(function() {
  13226. def.resolve();
  13227. });
  13228. d.resolve();
  13229. },
  13230. testVideoRanking: function(def) {
  13231. VideoRanking.load(null, {id: -4000})
  13232. .then(function(result) {
  13233. console.log('VideoRanking.load result:', result);
  13234. expect(result.name).toEqual('カテゴリ合算', 'ダミーマイリストの名前が一致');
  13235. expect(result.list.length).toEqual(300, 'カテゴリ合算ランキングは300件');
  13236. expect(result.list[ 0].title.indexOf('第001位')).toEqual(0,'ランキング1位のタイトル');
  13237. expect(result.list[299].title.indexOf('第300位')).toEqual(0,'ランキング300位のタイトル');
  13238. def.resolve();
  13239. },
  13240. function() {
  13241. def.reject();
  13242. });
  13243. },
  13244. testNicorepoVideo: function(def) {
  13245. NicorepoVideo.loadAll(null, null)
  13246. .then(function(result) {
  13247. console.log('NicorepoVideo.loadAll result:', result);
  13248. expect(result.name).toEqual('【ニコレポ】すべての動画', 'ダミーマイリストの名前が一致');
  13249. expect(result.list).toBeTruthy('ニコレポがある');
  13250. def.resolve();
  13251. },
  13252. function() {
  13253. def.reject();
  13254. });
  13255. },
  13256. testVideoInfoLoader: function(def) {
  13257. var loader = new VideoInfoLoader({});
  13258. $.when(
  13259. loader.load('sm9').then(function(result) {
  13260. expect(result.id).toEqual('sm9', '存在する動画ID');
  13261. expect(result.length).toEqual('5:19', 'length');
  13262. return this.done();
  13263. }, function(err) {
  13264. return this.fail();
  13265. }),
  13266.  
  13267. loader.load('sm1').then(function(result) {
  13268. return new $.Deferred().reject().promise();
  13269. }, function(resp) {
  13270. expect(resp.status).toEqual('fail', '存在しない動画ID');
  13271. return new $.Deferred().resolve().promise();
  13272. })
  13273.  
  13274. ).then(function() { def.resolve(); }, function() { def.reject(); });
  13275. },
  13276. testRelatedVideo: function(def) {
  13277. var loader = new RelatedVideo({});
  13278. loader.load('sm9').then(function(result) {
  13279. console.log('RelatedVideo', result);
  13280. expect(result.list).toBeTruthy('関連動画がある');
  13281. expect(result.list[0].title).toBeTruthy('タイトルがある');
  13282. expect(result.list[0].title.length >= 0).toBeTrue('タイトル長がある');
  13283. expect(typeof result.list[0].type === 'number').toBeTrue('type属性がある');
  13284. def.resolve();
  13285. });
  13286. },
  13287. testVideoArray: function(def) {
  13288. window.WatchItLater.loader.videoArrayAPILoader.load(['sm9', 'sm13']).then(function(result) {
  13289. console.log('VideoArrayAPILoader', result);
  13290. expect(result['sm9']).toBeTruthy('動画情報');
  13291. expect(result['sm13'].title).toBeTruthy('タイトルがある');
  13292. expect(result['sm13'].title.length >= 0).toBeTrue('タイトル長がある');
  13293. window.WatchItLater.loader.videoArrayAPILoader.load(['sm13', '1394785382']).then(function(result) {
  13294. console.log('VideoArrayAPILoader', result);
  13295. expect(result['1394785382']).toBeTruthy('スレッドIDでも引ける');
  13296. expect(result['1394785382'].title).toEqual('鬼灯の冷徹 第10話「十王の晩餐」「ダイエットは地獄みたいなもの」', '動画タイトル一致');
  13297. def.resolve();
  13298. });
  13299. });
  13300. }
  13301.  
  13302.  
  13303. }); // end WatchApp.mixin
  13304.  
  13305. } // end initTest
  13306.  
  13307. window.console.time('init WatchItLater');
  13308. // window.console.profile('init WatchItLater');
  13309. LocationHashParser.initialize();
  13310. initNews();
  13311. initShortcutKey();
  13312. initMouse();
  13313. initTouch();
  13314. initEvents();
  13315.  
  13316. initSearchContent($, conf, w);
  13317. initUserVideoContent($, conf, w);
  13318. initMylistContent($, conf, w);
  13319. initUploadedVideoContent($, conf, w);
  13320. initDeflistContent($, conf, w);
  13321. initVideoExplorer($, conf, w);
  13322.  
  13323. initRightPanel($, conf, w);
  13324. initLeftPanel($, conf, w);
  13325. initVideoReview($, conf, w);
  13326.  
  13327. initHidariue();
  13328. initVideoCounter();
  13329. initScreenMode();
  13330. initPlaylist($, conf, w);
  13331.  
  13332. initPageBottom($, conf, w);
  13333. initPageHeader($, conf, w);
  13334. initVideoTagContainer($, conf, w);
  13335.  
  13336. initNicoS($, conf, w);
  13337. initInvisibleCommentInput($, conf, w);
  13338. initOtherCss();
  13339. initCustomPlayerSize($, conf, w);
  13340.  
  13341. initStageVideo($, conf, w);
  13342. initHeatMap($, conf, w);
  13343. initPopupMarquee();
  13344. initMylistPanel($, conf, w);
  13345. initScroll($, conf, w);
  13346. initOther();
  13347. // window.console.profileEnd('init WatchItLater');
  13348. window.console.timeEnd('init WatchItLater');
  13349.  
  13350. onWindowResizeEnd();
  13351.  
  13352. if (conf.debugMode) {
  13353. initTest(WatchItLater.test);
  13354. }
  13355. };
  13356.  
  13357. if (window.PlayerApp) {
  13358. (function() {
  13359. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  13360. if (watchInfoModel.initialized) {
  13361. window.WatchItLater.WatchController =
  13362. window.WatchController =
  13363. _watchController(window);
  13364. ZeroFunc(window);
  13365. } else {
  13366. var onReset = function() {
  13367. watchInfoModel.removeEventListener('reset', onReset);
  13368. window.setTimeout(function() {
  13369. window.WatchItLater.WatchController =
  13370. window.WatchController =
  13371. _watchController(window);
  13372.  
  13373. ZeroFunc(window);
  13374. }, 0);
  13375. };
  13376. watchInfoModel.addEventListener('reset', onReset);
  13377. }
  13378. })();
  13379. } else
  13380. if (location.host === 'www.nicovideo.jp' && location.pathname ==='/stamp') {
  13381. niconicodoRedirect();
  13382. }
  13383.  
  13384.  
  13385. /**
  13386. * 原宿プレイヤーでのあれこれ
  13387. *
  13388. * マイリストパネルだけ追加
  13389. *
  13390. */
  13391. (function() {
  13392. if (!w.Video) return;
  13393. if (!location.href.match(/\/watch\/(sm\d+|nm\d+|so\d+|\d+)/)) return;
  13394. var watchId = undefined, videoId = undefined;
  13395. if (w.Video === null) {
  13396. watchId = RegExp.$1;
  13397. w.Video = {id: watchId};
  13398. } else {
  13399. Video = w.Video;
  13400. watchId = Video.v;
  13401. videoId = Video.id;
  13402. }
  13403. var watchId = RegExp.$1;
  13404. var iframe = Mylist.getPanel('');
  13405. iframe.id = "mylist_add_frame";
  13406. iframe.setAttribute('style', 'position: fixed; right: 0; bottom: 0;');
  13407.  
  13408. document.body.appendChild(iframe);
  13409. iframe.watchId(watchId, videoId);
  13410. })();
  13411.  
  13412.  
  13413. /**
  13414. * キーボードイベント他
  13415. *
  13416. */
  13417. (function() {
  13418. w.document.body.addEventListener('keydown', function(e) {
  13419. if (e.keyCode === 27 || e.keyCode === 88) { // ESC or x
  13420. AnchorHoverPopup.hidePopup();
  13421. Popup.hide();
  13422. }
  13423. }, false);
  13424. w.document.body.addEventListener('click', function(e) {
  13425. var tagName = e.target.tagName, className = e.target.className;
  13426. //console.log(tagName, className);
  13427. if (tagName !== 'BUTTON' && tagName !== 'SELECT' && tagName !== 'OPTION' && className !== 'popupTagItem' && className.indexOf('mylistPopupPanel') < 0) {
  13428. AnchorHoverPopup.hidePopup();
  13429. }
  13430.  
  13431. }, false);
  13432. var touchInitialized = false;
  13433. TouchEventDispatcher.onflick(function(e) {
  13434. if (e.direction === 'right') {
  13435. if (!touchInitialized) {
  13436. document.getElementById('videoTagPopupContainer').className += ' w_touch';
  13437. touchInitialized = true;
  13438. }
  13439. }
  13440. }, false);
  13441. // w.document.body.addEventListener('dblclick', function(e) {var tagName = e.target.tagName, className = e.target.className;console.log(tagName, className);});
  13442.  
  13443. })(w);
  13444.  
  13445. //===================================================
  13446. //===================================================
  13447. //===================================================
  13448.  
  13449. }); // end of monkey();
  13450.  
  13451. /**
  13452. * スマートフォン用APIを利用して動画情報を取得する
  13453. */
  13454. var spapi = (function() {
  13455. if (window.name.indexOf('watchItLaterAPILoader') < 0 ) { return; }
  13456. var resp = document.getElementsByTagName('nicovideo_video_response');
  13457. var session = location.hash.length > 1 ? location.hash.substr(1) : location.search;
  13458. var origin = 'http://' + location.host.replace(/^.*?\./, 'www.');
  13459. var xml = '';
  13460. if (resp.length > 0) {
  13461. xml = resp[0].outerHTML;
  13462. }
  13463.  
  13464. try {
  13465. parent.postMessage(JSON.stringify({
  13466. id: 'WatchItLater',
  13467. type: 'VideoArrayAPILoader',
  13468. body: {
  13469. session: session,
  13470. xml: xml
  13471. }
  13472. }),
  13473. origin);
  13474. } catch (e) {
  13475. console.log('err', e);
  13476. }
  13477. });
  13478.  
  13479. try {
  13480. if (location.host === 'flapi.nicovideo.jp') {
  13481. return;
  13482. } else
  13483. if (location.host === 'i.nicovideo.jp') {
  13484. spapi();
  13485. } else
  13486. if (location.host.indexOf('smile-') >= 0) {
  13487. return;
  13488. } else
  13489. if (location.host.indexOf('localhost.') === 0 || location.host.indexOf('www.') === 0 || !this.GM_getValue || this.GM_getValue.toString().indexOf("not supported")>-1) {
  13490. isNativeGM = false;
  13491. var inject = document.createElement("script");
  13492. inject.id = "monkey";
  13493. inject.setAttribute("type", "text/javascript");
  13494. inject.setAttribute("charset", "UTF-8");
  13495.  
  13496. inject.appendChild(document.createTextNode("(" + monkey + ")(false)"));
  13497. // inject.appendChild(document.createTextNode("try {(" + monkey + ")(false) } catch(e) { console.log(e); }"));
  13498.  
  13499. if (document.body) {
  13500. document.body.appendChild(inject);
  13501. } else {
  13502. document.documentElement.appendChild(inject);
  13503. }
  13504. } else {
  13505. // やや古いFirefoxはここらしい
  13506. monkey(true);
  13507. }
  13508.  
  13509. } catch(e) {
  13510. // 最近のFirefoxはここに飛んでくる
  13511. monkey(true);
  13512. }
  13513. })();
  13514.  
  13515.