MOD_Seiga

ニコニコ静画のUIをいじる

目前为 2017-10-04 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name MOD_Seiga
  3. // @namespace https://github.com/segabito/
  4. // @description ニコニコ静画のUIをいじる
  5. // @include *://seiga.nicovideo.jp/seiga/*
  6. // @include *://seiga.nicovideo.jp/tag/*
  7. // @include *://seiga.nicovideo.jp/illust/*
  8. // @include *://lohas.nicoseiga.jp/o/*
  9. // @version 0.4.2
  10. // @grant none
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
  12. // ==/UserScript==
  13.  
  14. // 本家にprototype.jsが入って衝突するようになった
  15. window.jQuery.noConflict();
  16.  
  17. (function() {
  18. var monkey = (function(){
  19. 'use strict';
  20. var $ = window.jQuery;
  21.  
  22. var A4_WIDTH = 210, A4_HEIGHT = 297;
  23.  
  24.  
  25. window.MOD_Seiga = {
  26. initialize: function() {
  27. this.initializeUserConfig();
  28. var path = location.pathname;
  29. if (path.indexOf('/seiga/') === 0) {
  30. this.initializeSeigaView();
  31. } else
  32. if (path.indexOf('/illust/') === 0 && path.indexOf('ranking') < 0) {
  33. this.initializeIllustTop();
  34. } else
  35. if (path.indexOf('/tag/') === 0) {
  36. this.initializeTagSearch();
  37. } else
  38. if (path.indexOf('/o/') === 0) {
  39. this.initializeFullView();
  40. }
  41.  
  42. },
  43. initializeSeigaView: function() {
  44. if (document.querySelector('#content')) {
  45. this.initializeBaseLayout();
  46.  
  47. this.initializeScrollTop();
  48. this.initializeDescription();
  49. this.initializeCommentLink();
  50. this.initializeThumbnail();
  51. this.initializePageTopButton();
  52. this.initializeKnockout();
  53. this.initializeFullScreenRequest();
  54. this.initializeSaveScreenshotRequest();
  55.  
  56. this.initializeOther();
  57. this.initializeSettingPanel();
  58.  
  59. document.body.classList.add('MOD_Seiga_View');
  60. this.initializeCss();
  61. } else {
  62. // 春画っぽい。説明文の自動リンクだけやる
  63. this.initializeDescription();
  64. }
  65.  
  66. },
  67. initializeIllustTop: function() {
  68. this.initializeThumbnail();
  69. this.initializeSettingPanel();
  70.  
  71. this.initializePageTopButton();
  72. document.body.classList.add('MOD_Seiga_Top');
  73. this.initializeCss();
  74. },
  75. initializeTagSearch: function() {
  76. this.initializeThumbnail();
  77. this.initializeSettingPanel();
  78.  
  79. this.initializePageTopButton();
  80. setTimeout(function() {
  81. var search = document.querySelector('#bar_search');
  82. var tagwatchQuery =
  83. document.querySelector('#ko_tagwatch_min').getAttribute('data-query');
  84. var val = search.value;
  85. if (
  86. val === 'イラストを検索' ||
  87. val === '春画を検索') {
  88. search.value = tagwatchQuery;
  89. search.classList.add('edited');
  90. search.style.color = '#000';
  91. }
  92. }, 1000);
  93. document.body.classList.add('MOD_Seiga_TagSearch');
  94. this.initializeCss();
  95. },
  96. initializeFullView: function() {
  97. document.body.classList.add('MOD_Seiga_FullView');
  98. this.initializeFullscreenImage();
  99. this.initializeSaveScreenshot();
  100. this.initializeCss();
  101. },
  102. addStyle: function(styles, id) {
  103. var elm = document.createElement('style');
  104. elm.type = 'text/css';
  105. if (id) { elm.id = id; }
  106.  
  107. var text = styles.toString();
  108. text = document.createTextNode(text);
  109. elm.appendChild(text);
  110. var head = document.getElementsByTagName('head');
  111. head = head[0];
  112. head.appendChild(elm);
  113. return elm;
  114. },
  115. fullScreen: {
  116. now: function() {
  117. if (document.fullScreenElement || document.mozFullScreen || document.webkitIsFullScreen) {
  118. return true;
  119. }
  120. return false;
  121. },
  122. request: function(target) {
  123. var elm = typeof target === 'string' ? document.getElementById(target) : target;
  124. if (!elm) { return; }
  125. if (elm.requestFullScreen) {
  126. elm.requestFullScreen();
  127. } else if (elm.webkitRequestFullScreen) {
  128. elm.webkitRequestFullScreen();
  129. } else if (elm.mozRequestFullScreen) {
  130. elm.mozRequestFullScreen();
  131. }
  132. },
  133. cancel: function() {
  134. if (document.cancelFullScreen) {
  135. document.cancelFullScreen();
  136. } else if (document.webkitCancelFullScreen) {
  137. document.webkitCancelFullScreen();
  138. } else if (document.mozCancelFullScreen) {
  139. document.mozCancelFullScreen();
  140. }
  141. }
  142. },
  143. initializeCss: function() {
  144. var __common_css__ = (`
  145. .MOD_SeigaSettingMenu a {
  146. font-weight: bolder; color: darkblue !important;
  147. }
  148. #MOD_SeigaSettingPanel {
  149. position: fixed;
  150. bottom: 2000px; right: 8px;
  151. z-index: -1;
  152. width: 500px;
  153. background: #f0f0f0; border: 1px solid black;
  154. padding: 8px;
  155. transition: bottom 0.4s ease-out;
  156. text-align: left;
  157. }
  158. #MOD_SeigaSettingPanel.open {
  159. display: block;
  160. bottom: 8px;
  161. box-shadow: 0 0 8px black;
  162. z-index: 10000;
  163. }
  164. #MOD_SeigaSettingPanel .close {
  165. position: absolute;
  166. cursor: pointer;
  167. right: 8px; top: 8px;
  168. }
  169. #MOD_SeigaSettingPanel .panelInner {
  170. background: #fff;
  171. border: 1px inset;
  172. padding: 8px;
  173. min-height: 300px;
  174. overflow-y: scroll;
  175. max-height: 500px;
  176. }
  177. #MOD_SeigaSettingPanel .panelInner .item {
  178. border-bottom: 1px dotted #888;
  179. margin-bottom: 8px;
  180. padding-bottom: 8px;
  181. }
  182. #MOD_SeigaSettingPanel .panelInner .item:hover {
  183. background: #eef;
  184. }
  185. #MOD_SeigaSettingPanel .windowTitle {
  186. font-size: 150%;
  187. }
  188. #MOD_SeigaSettingPanel .itemTitle {
  189. }
  190. #MOD_SeigaSettingPanel label {
  191. margin-right: 12px;
  192. }
  193. #MOD_SeigaSettingPanel small {
  194. color: #666;
  195. }
  196. #MOD_SeigaSettingPanel .expert {
  197. margin: 32px 0 16px;
  198. font-size: 150%;
  199. background: #ccc;
  200. }
  201.  
  202. .comment_info .mod_link, .description .otherSite {
  203. text-decoration: underline;
  204. font-weight: bolder;
  205. }
  206.  
  207. /* 画面が狭いときに操作不能になる部分などを直す */
  208. @media screen and (max-width: 1023px) {
  209. .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .comment_list {
  210. position: fixed;
  211. right: 25px;
  212. top: 105px; bottom: 105px; overflow-y: auto;
  213. }
  214. .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .comment_list .text {
  215. margin: 0 16px 0 0;
  216. }
  217. .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .res .inner {
  218. position: fixed;
  219. bottom: 0; right: 5px;
  220. }
  221. .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_header .control{
  222. position: fixed; top: 35px; right: 25px; /* 横幅が狭いと閉じるを押せない問題の対応 */
  223. }
  224. }
  225. @media screen and (max-width: 1183px) {
  226. .mod_hidePageTopButton #pagetop { display: none !important; }
  227. }
  228.  
  229. @media print {
  230.  
  231. body {
  232. background: #000 !important; /* 背景を黒にしたい場合は「背景画像を印刷」にチェック */
  233. margin: 0;
  234. padding: 0;
  235. overflow: hidden;
  236. width: 210mm;
  237. /* height: calc(297mm - 19mm); */ /* 19mmは印刷マージン */
  238. }
  239. body.landscape {
  240. width: 297mm;
  241. /* height: calc(210mm - 19mm); */
  242. }
  243. .toggleFullScreen, .control {
  244. display: none !important;
  245. opacity: 0 !important;
  246. }
  247.  
  248. .MOD_Seiga_FullView .illust_view_big img {
  249. top: 0 !important;
  250. left: 0 !important;
  251. transform: inherit !important;
  252. -webkit-transform: inherit !important;
  253. }
  254.  
  255. .MOD_Seiga_FullView:not(.landscape) .illust_view_big img {
  256. width: auto;
  257. height: auto;
  258. }
  259. .MOD_Seiga_FullView:not(.landscape).fitV .illust_view_big img {
  260. /*width: auto;
  261. height: calc(297mm - 19mm); */
  262. }
  263. .MOD_Seiga_FullView.landscape .illust_view_big img {
  264. /*width: 297mm;
  265. height: auto; */
  266. }
  267. .MOD_Seiga_FullView.landscape.fitV .illust_view_big img {
  268. /*width: auto;
  269. height: calc(210mm - 19mm); */
  270. margin-top: 0;
  271. }
  272. }
  273.  
  274. .saveScreenshotFrame {
  275. position: fixed;
  276. top: -200%;
  277. left: -200%;
  278. width: 32px;
  279. left: 32px;
  280. }
  281.  
  282. `).trim();
  283.  
  284. var __css__ = (`
  285.  
  286.  
  287. /* マイページや投稿へのリンクがあっても、すぐ上にniconico共通のヘッダーがあるのでいらないと思う。ということで省スペース優先で消す。*/
  288. #header { background: #fff; }
  289. #header .sg_global_bar {
  290. display: none;
  291. }
  292. #header_cnt { width: 1004px; }
  293.  
  294. /* サムネのホバー調整 */
  295. .list_item_cutout.middle {
  296. height: 154px;
  297. text-align: center;
  298. }
  299. .list_item_cutout.middle a {
  300. height: 100%;
  301. overflow: visible;
  302. }
  303. .list_item_cutout.middle a .illust_info, .list_item_cutout.middle a .illust_info:hover {
  304. bottom: 0;
  305. }
  306.  
  307. /* サムネのカットなくすやつ。 */
  308. .list_item_cutout.mod_no_trim .thum img {
  309. display: none;
  310. }
  311.  
  312.  
  313. .list_item_cutout.mod_no_trim .thum {
  314. display: block;
  315. width: 100%;
  316. height: calc(100% - 40px);
  317. background-repeat: no-repeat;
  318. background-position: center center;
  319. background-size: contain;
  320. -moz-background-size: contain;
  321. -webkit-background-size: contain;
  322. -o-background-size: contain;
  323. -ms-background-size: contain;
  324. }
  325.  
  326. .list_item.mod_no_trim .thum img {
  327. display: none;
  328. }
  329.  
  330. .list_item.mod_no_trim .thum {
  331. display: block;
  332. width: 100%;
  333. height: 0;
  334. background-repeat: no-repeat;
  335. background-position: center center;
  336. background-size: contain;
  337. -moz-background-size: contain;
  338. -webkit-background-size: contain;
  339. -o-background-size: contain;
  340. -ms-background-size: contain;
  341. }
  342. .MOD_Seiga_View .list_item.mod_no_trim .thum {
  343. }
  344. .list_item.mod_no_trim .thum:hover, .list_item_cutout.mod_no_trim .thum:hover {
  345. background-size: cover;
  346. }
  347.  
  348. .MOD_Seiga_Top .list_item_cutout.mod_no_trim .thum {
  349. height: calc(100% - 50px);
  350. }
  351. .MOD_Seiga_Top .list_item_cutout.mod_no_trim a {
  352. height: 100%;
  353. width: 100%;
  354. }
  355. .MOD_Seiga_Top .list_item_cutout.mod_no_trim.large a .illust_info {
  356. bottom: 0px !important;
  357. background-color: rgba(60, 60, 60, 1);
  358. padding: 10px 40px;
  359. }
  360. .MOD_Seiga_Top .list_item_cutout.mod_no_trim.large {
  361. width: 190px;
  362. height: 190px;
  363. }
  364. .MOD_Seiga_Top .rank_box .item_list.mod_no_trim .more_link a {
  365. width: 190px;
  366. }
  367.  
  368. /* タグ検索時、カウンタなどが常時見えるように修正 */
  369. .MOD_Seiga_TagSearch .list_item.large a .illust_count {
  370. opacity: 1;
  371. }
  372. .MOD_Seiga_TagSearch .list_item.large a {
  373. height: 321px;
  374. }
  375. .MOD_Seiga_TagSearch .list_item.large {
  376. height: 351px;
  377. }
  378.  
  379. /* タイトルと説明文・投稿者アイコンだけコンクリートの地面に置いてあるように感じたので絨毯を敷いた */
  380. .MOD_Seiga .im_head_bar .inner {
  381. background-color: #FFFFFF;
  382. border-color: #E8E8E8;
  383. border-radius: 5px;
  384. border-style: solid;
  385. border-width: 0 0 1px;
  386. box-shadow: 0 0 15px rgba(0, 0, 0, 0.05);
  387. display: block;
  388. margin-top: 20px;
  389. /*margin-bottom: 20px;*/
  390. margin-left: auto;
  391. margin-right: auto;
  392. padding: 15px;
  393. position: relative;
  394. width: 974px;
  395. }
  396.  
  397.  
  398. /* タグの位置調整 */
  399. .illust_main .mod_tag-top.illust_sub_info {
  400. padding-bottom: 25px;
  401. padding-top: 0;
  402. }
  403.  
  404.  
  405. .illust_sub_info.mod_tag-description-bottom {
  406. margin-top: 15px;
  407. }
  408. .im_head_bar .illust_tag h2 {
  409. float: left;
  410. font-size: 116.7%;
  411. line-height: 120%;
  412. margin: 4px 10px 0 -2px;
  413. overflow: hidden;
  414. }
  415. .im_head_bar .illust_sub_info input#tags {
  416. margin-bottom: 15px;
  417. margin-top: 5px;
  418. padding: 4px 10px;
  419. width: 280px;
  420. }
  421. .im_head_bar .illust_sub_info ul li.btn {
  422. bottom: 15px;
  423. position: absolute;
  424. right: 15px;
  425. }
  426.  
  427. /* タグ右上 */
  428. .description.mod_tag-description-right {
  429. float: left;
  430. }
  431. .illust_sub_info.mod_tag-description-right {
  432. width: 300px;
  433. float: right;
  434. margin: 0;
  435. }
  436. .mod_tag-description-right .tag {
  437. background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
  438. border: medium none;
  439. margin: 0;
  440. }
  441. .mod_tag-description-right .tag a {
  442. padding: 0 5px;
  443. }
  444. .mod_tag-description-right .tag li {
  445. }
  446. .mod_tag-description-right .tag li a {
  447. padding: 0 5px 0 0;
  448. border: 0;
  449. }
  450. .im_head_bar .illust_sub_info.mod_tag-description-right ul li.btn.active {
  451. }
  452. /* 右下だと被ることがあるので仕方なく */
  453. .im_head_bar .illust_sub_info.mod_tag-description-right ul li.btn {
  454. display: inline-block;
  455. position: relative;
  456. bottom: auto; right: auto;
  457. }
  458.  
  459. #ichiba_box {
  460. width: 1004px;
  461. margin: 0 auto 20px;
  462. border: 1px solid #ddd;
  463. border-radius: 8px;
  464. }
  465.  
  466. #content.illust { padding: 0; }
  467.  
  468. #related_info .ad_tag { display: none;}
  469.  
  470. /* 右カラム広告のせいで無駄に横スクロールが発生しているのを修正 */
  471. .related_info .sub_info_side {
  472. overflow-x: hidden;
  473. }
  474.  
  475.  
  476. .illust_main .illust_side .clip.mod_top {
  477. padding: 10px 10px 0;
  478. }
  479.  
  480.  
  481. .MOD_Seiga_FullView #content.illust_big .illust_view_big {
  482. margin: 0 auto;
  483. }
  484.  
  485. .MOD_Seiga_FullView .control {
  486. position: absolute;
  487. right: 0;
  488. top: 0;
  489. z-index: 1000;
  490. opacity: 0;
  491. transition: opacity 0.5s ease;
  492. }
  493.  
  494. .MOD_Seiga_FullView:hover .control {
  495. opacity: 1;
  496. }
  497.  
  498. .MOD_Seiga_FullView .illust_view_big img {
  499. /*transform: scale(1); -webkit-transform: scale(1);
  500. transition: transform 0.3s ease, -webkit-transform 0.3s ease;*/
  501. }
  502.  
  503. .MOD_Seiga_FullView:not(.mod_noScale) .illust_view_big img {
  504. position: absolute;
  505. top: 0;
  506. left: 0;
  507. transform-origin: 0 0 0;
  508. -webkit-transform-origin: 0 0 0;
  509. }
  510.  
  511. .MOD_Seiga_FullView.mod_contain {
  512. overflow: hidden;
  513. }
  514. .MOD_Seiga_FullView.mod_cover {
  515. }
  516. .MOD_Seiga_FullView.mod_contain .illust_view_big img,
  517. .MOD_Seiga_FullView.mod_cover .illust_view_big img {
  518. /*display: none;*/
  519. }
  520.  
  521. .MOD_Seiga_FullView .illust_view_big {
  522. background-repeat: no-repeat;
  523. background-position: center center;
  524. }
  525. .MOD_Seiga_FullView.mod_contain .illust_view_big {
  526. background-size: contain;
  527. }
  528. .MOD_Seiga_FullView.mod_cover .illust_view_big {
  529. background-size: cover;
  530. }
  531.  
  532. .MOD_Seiga .toggleFullScreen,
  533. .MOD_Seiga .saveScreenshot {
  534. display: block;
  535. width: 200px;
  536. margin: 8px auto;
  537. transition: 0.4s opacity, 0.4s box-shadow;
  538. }
  539. .MOD_Seiga_FullView .toggleFullScreen {
  540. position: fixed;
  541. top: 0;
  542. left: 0;
  543. z-index: 1000;
  544. margin: 0;
  545. padding: 4px;
  546. cursor: pointer;
  547. border: none;
  548. background: transparent;
  549. color: #0CA5D2;
  550. font-weight: bolder;
  551. }
  552. .MOD_Seiga .toggleFullScreen:hover,
  553. .MOD_Seiga .saveScreenshot:hover {
  554. box-shadow: 2px 2px 2px #333;
  555. }
  556.  
  557. .MOD_Seiga_FullView .toggleFullScreen button,
  558. .MOD_Seiga_FullView .saveScreenshot button {
  559. opacity: 0;
  560. margin: 0;
  561. padding: 0;
  562. cursor: pointer;
  563. transition: 0.4s opacity, 0.4s box-shadow;
  564. border: 1px solid #000;
  565. background: #fff;
  566. color: #0CA5D2;
  567. font-weight: bolder;
  568. }
  569. .MOD_Seiga_FullView .toggleFullScreen.show button,
  570. .MOD_Seiga_FullView .toggleFullScreen button:hover {
  571. opacity: 1;
  572. box-shadow: 2px 2px 2px #333;
  573. }
  574. .MOD_Seiga_FullView:fullscreen .toggleFullScreen {
  575. display: none;
  576. }
  577.  
  578. #ko_watchlist_info.mod_hide { display: none !important; }
  579.  
  580. .saveScreenShotFrame,
  581. .fullScreenRequestFrame {
  582. position: fixed;
  583. width: 100px;
  584. height: 100px;
  585. left: -999px;
  586. top: -999px;
  587. }
  588.  
  589. .MOD_Seiga_FullView .closeButton {
  590. opacity: 0;
  591. transition: 0.4s opacity ease;
  592. }
  593.  
  594. .MOD_Seiga_FullView .closeButton:hover {
  595. opacity: 1;
  596. }
  597.  
  598. body.fullScreenFrame {
  599. background: #000;
  600. }
  601.  
  602. body.MOD_Seiga_FullView img {
  603. cursor: none;
  604. }
  605.  
  606. body.MOD_Seiga_FullView.mouseMoving img{
  607. cursor: default;
  608. }
  609.  
  610.  
  611. `).trim();
  612.  
  613.  
  614. this.addStyle(__common_css__);
  615.  
  616. if (this.config.get('applyCss')) {
  617. this.addStyle(__css__);
  618. }
  619. },
  620. initializeUserConfig: function() {
  621. var prefix = 'MOD_Seiga_';
  622. var conf = {
  623. applyCss: true,
  624. topUserInfo: true,
  625. tagPosition: 'description-bottom',
  626. noTrim: true,
  627. hidePageTopButton: true,
  628. clipPosition: 'bottom',
  629. hideBottomUserInfo: false
  630. };
  631.  
  632. this.config = {
  633. get: function(key) {
  634. try {
  635. if (window.localStorage.hasOwnProperty(prefix + key)) {
  636. return JSON.parse(window.localStorage.getItem(prefix + key));
  637. }
  638. return conf[key];
  639. } catch (e) {
  640. return conf[key];
  641. }
  642. },
  643. set: function(key, value) {
  644. window.localStorage.setItem(prefix + key, JSON.stringify(value));
  645. }
  646. };
  647. },
  648. initializeBaseLayout: function() {
  649. var description = document.querySelector('#content .description, #content .discription');
  650. description.classList.add('description');
  651. document.querySelector('.controll').classList.add('control');
  652.  
  653. $('#related_info').after($('#ichiba_box'));
  654.  
  655. if (this.config.get('hideBottomUserInfo') === true) {
  656. document.querySelector('#ko_watchlist_info').classList.add('mod_hide');
  657. }
  658.  
  659. },
  660. initializeScrollTop: function() {
  661. var reset = this.resetScrollTop = function() {
  662. var nofix = document.body.classList.contains('nofix');
  663. var commonHeaderHeight = nofix ? 0 : 36; //$bar.outerHeight();
  664. document.documentElement.scrollTop = (Math.max(
  665. document.documentElement.scrollTop,
  666. 128 /*$('#content .im_head_bar').offset().top*/ - commonHeaderHeight
  667. ));
  668. };
  669. setTimeout(reset, 100);
  670. reset();
  671. },
  672. initializeDescription: function() {
  673. var description = document.querySelector('#content .description, #content .discription, .illust_user_exp');
  674. if (!description) { return; }
  675. var html = description.innerHTML;
  676.  
  677. // 説明文中のURLの自動リンク
  678. var linkmatch = /<a.*?<\/a>/, links = [], n;
  679. html = html.split('<br />').join(' <br /> ');
  680. while ((n = linkmatch.exec(html)) !== null) {
  681. links.push(n);
  682. html = html.replace(n, ' <!----> ');
  683. }
  684. html = html.replace(/(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi, '<a href="$1" target="_blank" class="otherSite">$1</a>');
  685. for (var i = 0, len = links.length; i < len; i++) {
  686. html = html.replace(' <!----> ', links[i]);
  687. }
  688. html = html.split(' <br /> ').join('<br />');
  689.  
  690. description.innerHTML = html;
  691. },
  692. initializeCommentLink: function() {
  693. var videoReg = /((sm|nm|so)\d+)/g;
  694. var seigaReg = /(im\d+)/g;
  695. var bookReg = /((bk|mg)\d+)/g;
  696. var urlReg = /(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi;
  697.  
  698. var autoLink = function(text) {
  699. text = text
  700. .replace(videoReg, '<a href="//www.nicovideo.jp/watch/$1" class="video mod_link">$1</a>')
  701. .replace(seigaReg, '<a href="/seiga/$1" class="illust mod_link">$1</a>')
  702. .replace(bookReg, '<a href="/watch/$1" class="book mod_link">$1</a>')
  703. .replace(urlReg, '<a href="$1" target="_blank" class="otherSite mod_link">$1</a>');
  704. return text;
  705. };
  706. var commentLink = function(selector) {
  707. Array.from(document.querySelectorAll(selector)).forEach((elm) => {
  708. var html = elm.innerHTML, linked = autoLink(html);
  709. if (html !== linked) {
  710. elm.innerHTML = linked;
  711. }
  712. elm.classList.add('mod_linked');
  713. });
  714. };
  715.  
  716. setTimeout(function() {
  717. commentLink('#comment_list .comment_info .text:not(.mod_linked)');
  718. var updateTimer = null;
  719. document.body.addEventListener('DOMNodeInserted', function(e) {
  720. if (e.target.className && e.target.className.indexOf('comment_list_item') >= 0) {
  721. if (updateTimer) {
  722. updateTimer = clearTimeout(updateTimer);
  723. }
  724. updateTimer = setTimeout(function() {
  725. updateTimer = clearTimeout(updateTimer);
  726. commentLink('#comment_list .comment_info .text:not(.mod_linked)');
  727. }, 100);
  728. }
  729. });
  730.  
  731. }, 100);
  732. },
  733. initializeThumbnail: function() {
  734. if (this.config.get('noTrim') !== true) { return; }
  735.  
  736. var treg = /^(https?:\/\/lohas.nicoseiga.jp\/+thumb\/+.\d+)([a-z\?]*)/;
  737. var noTrim = function() {
  738. Array.from(document.querySelectorAll('.list_item_cutout, #main .list_item:not(.mod_no_trim)')).forEach((elm) => {
  739. var $thum = $(elm).find('.thum');
  740. var $img = $thum.find('img');
  741. var src = $img.attr('src') || '';
  742. if ($thum.length * $img.length < 1 || !treg.test(src)) return;
  743. // TODO: 静画のサムネの種類を調べる
  744. var url = RegExp.$1 + 'q?';//RegExp.$2 === 't' ? src : RegExp.$1 + 'q?';
  745. $thum.css({'background-image': 'url("' + url + '")'});
  746. elm.classList.add('mod_no_trim');
  747. });
  748. };
  749. noTrim();
  750. document.body.addEventListener('AutoPagerize_DOMNodeInserted', function() {
  751. setTimeout(function() {
  752. noTrim();
  753. }, 500);
  754. });
  755. },
  756. initializePageTopButton: function() {
  757. document.body.classList.toggle('mod_hidePageTopButton',
  758. this.config.get('hidePageTopButton'));
  759. },
  760. initializeKnockout: function() {
  761. },
  762. initializeFullScreenRequest: function() {
  763. var $body = $('body');
  764. var $iframe = $('<iframe allowfullscreen="on" class="fullScreenRequestFrame" name="fullScreenRequestFrame"></iframe>');
  765.  
  766. $body.append($iframe);
  767.  
  768. var $fullScreenButton = $([
  769. '<button class="toggleFullScreen btn normal" title="フルスクリーン表示に切り換えます(Fキー)">',
  770. '<span>フルスクリーン</span>',
  771. '</button>',
  772. ''].join(''));
  773. $('.illust_main .illust_wrapper .inner .thum_large:first').append($fullScreenButton);
  774. var toggleFullScreen = $.proxy(function() {
  775. if (this.fullScreen.now()) {
  776. this.fullScreen.cancel();
  777. } else {
  778. $iframe[0].contentWindow.location.replace($('#illust_link').attr('href'));
  779. this.fullScreen.request($iframe[0]);
  780. }
  781. }, this);
  782. $fullScreenButton.on('click', function(e) {
  783. e.preventDefault();
  784. e.stopPropagation();
  785. toggleFullScreen();
  786. });
  787.  
  788. var onMessage = function(event) {
  789. if (event.origin.indexOf('nicoseiga.jp') < 0) return;
  790. try {
  791. var data = JSON.parse(event.data);
  792. if (data.id !== 'MOD_Seiga') { return; }
  793. if (data.command === 'toggleFullScreen') {
  794. toggleFullScreen();
  795. }
  796. } catch (e) {
  797. console.log('Exception', e);
  798. console.trace();
  799. }
  800. };
  801.  
  802. window.addEventListener('message', onMessage);
  803. $body.on('keydown.watchItLater', function(e) {
  804. if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
  805. return;
  806. }
  807. if (e.keyCode === 70) { // F
  808. toggleFullScreen();
  809. }
  810. });
  811.  
  812. },
  813. initializeOther: function() {
  814. document.body.classList.add('MOD_Seiga');
  815. },
  816. initializeSettingPanel: function() {
  817. var $menu = $('<li class="MOD_SeigaSettingMenu"><a href="javascript:;" title="MOD_Seigaの設定変更">MOD_Seiga設定</a></li>');
  818. var $panel = $('<div id="MOD_SeigaSettingPanel" />');//.addClass('open');
  819. var $button = $('<button class="toggleSetting playerBottomButton">設定</botton>');
  820.  
  821. $button.on('click', function(e) {
  822. e.stopPropagation(); e.preventDefault();
  823. $panel.toggleClass('open');
  824. });
  825.  
  826. var config = this.config;
  827. $menu.find('a').on('click', function() { $panel.toggleClass('open'); });
  828.  
  829. var __tpl__ = (`
  830. <div class="panelHeader">
  831. <h1 class="windowTitle">MOD_Seigaの設定</h1>
  832. <p>設定はリロード後に反映されます</p>
  833. <button class="close" title="閉じる">×</button>
  834. </div>
  835. <div class="panelInner">
  836. <!--<div class="item" data-setting-name="topUserInfo" data-menu-type="radio">
  837. <h3 class="itemTitle">投稿者情報を右上に移動 </h3>
  838. <label><input type="radio" value="true" > する</label>
  839. <label><input type="radio" value="false"> しない</label>
  840. </div>-->
  841.  
  842. <div class="item" data-setting-name="noTrim" data-menu-type="radio">
  843. <h3 class="itemTitle">サムネイルの左右カットをやめる </h3>
  844. <label><input type="radio" value="true" >やめる</label>
  845. <label><input type="radio" value="false">やめない</label>
  846. </div>
  847.  
  848. <div class="item" data-setting-name="hidePageTopButton" data-menu-type="radio">
  849. <h3 class="itemTitle">ウィンドウ幅が狭いときはページトップに戻るボタンを隠す</h3>
  850. <label><input type="radio" value="true" >隠す</label>
  851. <label><input type="radio" value="false">隠さない</label>
  852. </div>
  853.  
  854. <div class="item" data-setting-name="hideBottomUserInfo" data-menu-type="radio">
  855. <h3 class="itemTitle">ページ下側の投稿者情報を隠す</h3>
  856. <small>右上だけでいい場合など</small><br>
  857. <label><input type="radio" value="true" >隠す</label>
  858. <label><input type="radio" value="false">隠さない</label>
  859. </div>
  860.  
  861. <div class="item" data-setting-name="clipPosition" data-menu-type="radio">
  862. <h3 class="itemTitle">クリップ登録メニューの位置(旧verのみ)</h3>
  863. <label><input type="radio" value="&quot;top&quot;" >上</label>
  864. <label><input type="radio" value="&quot;bottom&quot;">下</label>
  865. </div>
  866. <!--
  867. <div class="item" data-setting-name="tagPosition" data-menu-type="radio">
  868. <h3 class="itemTitle">タグの位置(旧verのみ) </h3>
  869. <label><input type="radio" value="&quot;description-bottom&quot;">説明文の下</label>
  870. <label><input type="radio" value="&quot;description-right&quot;">説明文の右</label>
  871. <label><input type="radio" value="&quot;top&quot;">画像の上</label>
  872. <label><input type="radio" value="&quot;default&quot;">画像の下(標準)</label>
  873. </div>
  874. -->
  875.  
  876. <div class="expert">
  877. <h2>上級者向け設定</h2>
  878. </div>
  879. <div class="item" data-setting-name="applyCss" data-menu-type="radio">
  880. <h3 class="itemTitle">MOD_Seiga標準のCSSを使用する</h3>
  881. <small>他のuserstyleを使用する場合は「しない」を選択してください</small><br>
  882. <label><input type="radio" value="true" > する</label>
  883. <label><input type="radio" value="false"> しない</label>
  884. </div>
  885. </div>
  886. `).trim();
  887. $panel.html(__tpl__);
  888. $panel.find('.item').on('click', function(e) {
  889. var $this = $(this);
  890. var settingName = $this.attr('data-setting-name');
  891. var value = JSON.parse($this.find('input:checked').val());
  892. console.log('seting-name', settingName, 'value', value);
  893. config.set(settingName, value);
  894. }).each(function(e) {
  895. var $this = $(this);
  896. var settingName = $this.attr('data-setting-name');
  897. var value = config.get(settingName);
  898. $this.addClass(settingName);
  899. $this.find('input').attr('name', settingName).val([JSON.stringify(value)]);
  900. });
  901. $panel.find('.close').click(function() {
  902. $panel.removeClass('open');
  903. });
  904.  
  905.  
  906. $('#siteHeaderRightMenuFix').after($menu);
  907. $('body').append($panel);
  908. },
  909. initializeFullscreenImage: function() {
  910. var $body = $('body'), $container = $('.illust_view_big'), $img = $container.find('img'), scale = 1;
  911. var $fullScreenButton = $([
  912. '<div class="toggleFullScreen show">',
  913. '<button title="フルスクリーン表示に切り換えます">',
  914. '<span>フルスクリーン</span>',
  915. '</button>',
  916. '</div>',
  917. ''].join(''));
  918. var $window = $(window);
  919. $('.controll').addClass('control');
  920.  
  921. var clearCss = function() {
  922. $body.removeClass('mod_contain mod_cover mod_noScale');
  923. $container.css({width: '', height: ''});
  924. $img.css({'transform': '', '-webkit-transform': '', top: '', left: ''});
  925. };
  926.  
  927. // ウィンドウの内枠フィット (画面におさまる範囲で最大化)
  928. var contain = function() {
  929. clearCss();
  930. $body.addClass('mod_contain');
  931. scale = Math.min(
  932. $window.innerWidth() / $img.outerWidth(),
  933. $window.innerHeight() / $img.outerHeight()
  934. );
  935. scale = Math.min(scale, 10);
  936. $img.css({
  937. 'transform': 'scale(' + scale + ')',
  938. '-webkit-transform': 'scale(' + scale + ')',
  939. 'left': ($window.innerWidth() - $img.outerWidth() * scale) / 2 + 'px',
  940. 'top': ($window.innerHeight() - $img.outerHeight() * scale) / 2 + 'px'
  941. });
  942. $container.width($window.innerWidth());
  943. $container.height($window.innerHeight());
  944. // $container.css('background-image', 'url("' + $img.attr('src') + '")');
  945. };
  946.  
  947. // ウィンドウの外枠フィット
  948. var cover = function() {
  949. clearCss();
  950. $body.addClass('mod_cover').css('overflow', 'scroll');
  951. scale = Math.max(
  952. $window.innerWidth() / $img.outerWidth(),
  953. $window.innerHeight() / $img.outerHeight()
  954. );
  955. scale = Math.min(scale, 10);
  956. $img.css({
  957. 'transform': 'scale(' + scale + ')',
  958. '-webkit-transform': 'scale(' + scale + ')',
  959. });
  960. // ウィンドウサイズの計算にスクロールバーの幅を含めるための措置 おもにwindows用
  961. $body.css('overflow', '');
  962. };
  963.  
  964. // 原寸大表示
  965. var noScale = function() {
  966. clearCss();
  967. $body.addClass('mod_noScale');
  968. scale = 1;
  969. $container.css('background-image', '');
  970. };
  971.  
  972. // クリックごとに表示を切り替える処理
  973. var onClick = function(e) {
  974. if (e.button > 0) { return; }
  975. // TODO: クリックした位置が中心になるようにスクロール
  976. if ($body.hasClass('mod_noScale')) {
  977. contain();
  978. } else
  979. if ($body.hasClass('mod_contain')) {
  980. cover();
  981. } else {
  982. noScale();
  983. }
  984. };
  985. // ウィンドウがリサイズされた時などの再計算用
  986. var update = function() {
  987. if ($body.hasClass('mod_contain')) {
  988. contain();
  989. } else
  990. if ($body.hasClass('mod_cover')) {
  991. cover();
  992. }
  993. };
  994.  
  995. // モニターいっぱい表示を切り換える
  996. var toggleFullScreen = $.proxy(function() {
  997. if (window.name === 'fullScreenRequestFrame') {
  998. parent.postMessage(JSON.stringify({
  999. id: 'MOD_Seiga',
  1000. command: 'toggleFullScreen'
  1001. }),
  1002. 'http://seiga.nicovideo.jp');
  1003. return;
  1004. }
  1005.  
  1006. if (this.fullScreen.now()) {
  1007. this.fullScreen.cancel(document.documentElement);
  1008. } else {
  1009. this.fullScreen.request(document.documentElement);
  1010. }
  1011. }, this);
  1012.  
  1013. window.setTimeout($.proxy(function() {
  1014. $fullScreenButton.removeClass('show');
  1015. }, this), 2000);
  1016.  
  1017. // /seiga/imXXXXXXからiframeで呼ばれてる時の処理
  1018. if (window.name === 'fullScreenRequestFrame') {
  1019. $('img[src$="btn_close.png"]')
  1020. .addClass('closeButton')
  1021. .off()
  1022. .on('click', function() {
  1023. toggleFullScreen();
  1024. });
  1025. $('body').addClass('fullScreenFrame');
  1026. }
  1027.  
  1028. $fullScreenButton.on('click', function(e) {
  1029. e.preventDefault();
  1030. e.stopPropagation();
  1031. toggleFullScreen();
  1032. });
  1033. $body.append($fullScreenButton);
  1034.  
  1035.  
  1036. // マウスを動かさない時はカーソルを消す
  1037. var mousemoveStopTimer = null;
  1038. var showMouseNsec = function() {
  1039. $body.addClass('mouseMoving');
  1040. if (mousemoveStopTimer) {
  1041. window.clearTimeout(mousemoveStopTimer);
  1042. mousemoveStopTimer = null;
  1043. }
  1044. mousemoveStopTimer = window.setTimeout(function() {
  1045. $body.removeClass('mouseMoving');
  1046. mousemoveStopTimer = null;
  1047. }, 1000);
  1048. };
  1049. $body
  1050. .on('mousemove.MOD_Seiga', showMouseNsec)
  1051. .on('mousedown.MOD_Seiga', showMouseNsec);
  1052.  
  1053.  
  1054. contain();
  1055. $img.on('click', onClick);
  1056. $window.on('resize', update);
  1057. var onImageLoad = $.proxy(function() {
  1058. this.initializePrintCss($img.clone());
  1059. contain();
  1060. $img.off('load.MOD_Seiga');
  1061. }, this);
  1062.  
  1063. if ($img[0] && $img[0].complete) {
  1064. onImageLoad();
  1065. } else {
  1066. $img.on('load.MOD_Seiga', onImageLoad);
  1067. }
  1068.  
  1069.  
  1070. // おもにwindows等、縦ホイールしかない環境で横スクロールしやすくする
  1071. // Firefoxでうまくいかない
  1072. var hasWheelDeltaX = false;
  1073. $(document).on('mousewheel', function(e) {
  1074. var delta = 0;
  1075.  
  1076. if ($body.hasClass('mod_contain')) { return; }
  1077.  
  1078. if (document.body.scrollHeight > window.innerHeight) { return; }
  1079.  
  1080. if (hasWheelDeltaX) { return; }
  1081.  
  1082. if (!e.originalEvent) { return; }
  1083. var oe = e.originalEvent;
  1084.  
  1085. if (typeof oe.wheelDelta === 'number') {
  1086. if (typeof oe.wheelDeltaX === 'number' && oe.wheelDeltaX !== 0) {
  1087. hasWheelDeltaX = true;
  1088. return; // ホイールで横スクロールできる環境だとかえって邪魔なのでなにもしない
  1089. }
  1090.  
  1091. delta = e.originalEvent.wheelDelta / Math.abs(e.originalEvent.wheelDelta);
  1092. }
  1093. if (delta === 0) return;
  1094.  
  1095. e.preventDefault();
  1096. e.stopPropagation();
  1097.  
  1098. var scrollLeft = $body.scrollLeft() - (delta * $window.innerWidth() / 10);
  1099. $body.scrollLeft(Math.max(scrollLeft, 0));
  1100. });
  1101. },
  1102. initializeSaveScreenshot: function() {
  1103. $('body').on('keydown', (e) => {
  1104. if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
  1105. return;
  1106. }
  1107. if (e.keyCode === 83) { // S
  1108. window.MOD_Seiga.saveScreenshot();
  1109. }
  1110. });
  1111. },
  1112. initializeSaveScreenshotRequest: function() {
  1113. const $body = $('body');
  1114. const $iframe = $('<iframe allowfullscreen="on" class="saveScreenshotFrame" name="saveScreenshotFrame"></iframe>');
  1115. $iframe[0].srcdoc = '';
  1116. $body.append($iframe);
  1117.  
  1118. const $saveButton = $([
  1119. '<button class="saveScreenshot btn normal" title="画像を保存(Sキー)">',
  1120. '<span>画像を保存</span>',
  1121. '</button>',
  1122. ''].join(''));
  1123. $('.illust_main .illust_wrapper .inner .thum_large:first').append($saveButton);
  1124.  
  1125. $saveButton.on('click', (e) => {
  1126. e.preventDefault();
  1127. e.stopPropagation();
  1128. $iframe[0].contentWindow.location.replace($('#illust_link').attr('href'));
  1129. //window.open(
  1130. // $('#illust_link').attr('href'),
  1131. // 'saveScreenshotFrame',
  1132. // 'width=400, height=300, personalbar=0, toolbar=0, scrollbars=1, sizable=1');
  1133. });
  1134. $(window).on('beforeunload', () => {
  1135. $iframe.remove();
  1136. $iframe[0].srcdoc = '';
  1137. });
  1138.  
  1139. $('body').on('keydown', (e) => {
  1140. if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
  1141. return;
  1142. }
  1143. if (e.keyCode === 83) { // S
  1144. $saveButton.click();
  1145. }
  1146. });
  1147. },
  1148. initializePrintCss: function($img) {
  1149. this.initializePrintCss = function() {};
  1150. var self = this;
  1151.  
  1152. $img.css({
  1153. width: 'auto', height: 'auto',
  1154. transform: '', left: '100%', top: '100%', opacity: 0, position: 'fixed'
  1155. });
  1156.  
  1157. var $body = $('body');
  1158. $body.append($img);
  1159.  
  1160. window.setTimeout(function() {
  1161. var width = $img.outerWidth(), height = $img.outerHeight();
  1162. $img.remove();
  1163.  
  1164. // TODO: 用紙サイズ変更
  1165. var paperMarginV = 0.1; // 19; // 紙送りマージン?
  1166. var imageRatio = height / Math.max(width, 1);
  1167. var paperRatio = (A4_HEIGHT - paperMarginV) / A4_WIDTH;
  1168. var landscapeRatio = (A4_WIDTH - paperMarginV) / A4_HEIGHT;
  1169. var isLandscape =
  1170. Math.abs(paperRatio - imageRatio) > Math.abs(landscapeRatio - imageRatio);
  1171.  
  1172. paperRatio = isLandscape ? landscapeRatio : paperRatio;
  1173.  
  1174. var isFitV = paperRatio < imageRatio;
  1175. var paperWidth = isLandscape ? A4_HEIGHT : A4_WIDTH;
  1176. var paperHeight = (isLandscape ? A4_WIDTH : A4_HEIGHT) - paperMarginV;
  1177. var marginLeft = 0, marginTop = 0, imageWidth = paperWidth, imageHeight = paperHeight;
  1178.  
  1179. if (isFitV) { // タテ合わせ
  1180. imageWidth = paperHeight / imageRatio;
  1181. marginLeft = (paperWidth - imageWidth) / 2;
  1182. } else { // ヨコ合わせ
  1183. imageHeight = paperWidth * imageRatio;
  1184. marginTop = (paperHeight - imageHeight) / 2;
  1185. }
  1186. var pageSize = isLandscape ? 'A4 lanscape' : 'A4';
  1187. var css = [
  1188. '@page { margin: 0; size: ', pageSize, '; }\n\n ',
  1189. '@media print {\n',
  1190. '\t.MOD_Seiga_FullView .illust_view_big img {\n ',
  1191. '\t\tmargin-left: ', marginLeft, 'mm; ',
  1192. '\t\tmargin-top: ', marginTop, 'mm; ',
  1193. '\t\twidth:', imageWidth, 'mm !important; ',
  1194. '\t\theight:', imageHeight, 'mm !important;',
  1195. '\t\n}\n',
  1196. '}',
  1197. ].join('');
  1198.  
  1199. //console.log('paper?', paperWidth, paperHeight, imageWidth, imageHeight);
  1200. //console.log('ratio?', width, height, isLandscape, paperRatio, imageRatio);
  1201. console.log('print css\n', css);
  1202.  
  1203. self._printCss = self.addStyle(css, 'MOD_Seiga_print');
  1204. $body
  1205. .toggleClass('landscape', isLandscape)
  1206. .toggleClass('fitV', isFitV);
  1207. }, 100);
  1208. }
  1209. };
  1210.  
  1211. const toSafeName = function(text) {
  1212. text = text.trim()
  1213. .replace(/</g, '<')
  1214. .replace(/>/g, '>')
  1215. .replace(/\?/g, '?')
  1216. .replace(/:/g, ':')
  1217. .replace(/\|/g, '|')
  1218. .replace(/\//g, '/')
  1219. .replace(/\\/g, '¥')
  1220. .replace(/"/g, '”')
  1221. .replace(/\./g, '.')
  1222. ;
  1223. return text;
  1224. };
  1225.  
  1226. window.MOD_Seiga.saveScreenshot = function() {
  1227. const div = document.querySelector('.illust_view_big');
  1228. const img = div.querySelector('.illust_view_big img');
  1229. window.console.info('img', img, div, document.querySelectorAll('img'));
  1230. const illustId =
  1231. (div.getAttribute('data-watch_url') || '').split('/')[4];
  1232. window.console.info('illustId', illustId);
  1233. const title = toSafeName(document.title);
  1234. const fileName = `${illustId}-${title}`;
  1235. window.console.info('fileName', fileName);
  1236.  
  1237. const link = document.createElement('a');
  1238. link.download = fileName;
  1239. link.href = img.src;
  1240. link.textContent = 'save image';
  1241. document.body.appendChild(link);
  1242. link.click();
  1243. link.remove();
  1244. window.setTimeout(() => {
  1245. if (window.name === 'saveScreenshotFrame') {
  1246. location.replace('/favicon.ico');
  1247. }
  1248. }, 0);
  1249. };
  1250.  
  1251. window.MOD_Seiga.initialize();
  1252. if (window.name === 'saveScreenshotFrame') {
  1253. window.console.info('saveScreenshot', window.name);
  1254. window.MOD_Seiga.saveScreenshot();
  1255. }
  1256. });
  1257.  
  1258. var script = document.createElement("script");
  1259. script.id = "MOD_SeigaLoader";
  1260. script.setAttribute("type", "text/javascript");
  1261. script.setAttribute("charset", "UTF-8");
  1262. script.appendChild(document.createTextNode("(" + monkey + ")()"));
  1263. document.body.appendChild(script);
  1264.  
  1265. })();