ニコニコ静画のUIをいじる
// ==UserScript== // @name MOD_Seiga // @namespace https://github.com/segabito/ // @description ニコニコ静画のUIをいじる // @include *://seiga.nicovideo.jp/seiga/* // @include *://seiga.nicovideo.jp/tag/* // @include *://seiga.nicovideo.jp/illust/* // @include *://lohas.nicoseiga.jp/o/* // @version 0.4.2 // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js // ==/UserScript== // 本家にprototype.jsが入って衝突するようになった window.jQuery.noConflict(); (function() { var monkey = (function(){ 'use strict'; var $ = window.jQuery; var A4_WIDTH = 210, A4_HEIGHT = 297; window.MOD_Seiga = { initialize: function() { this.initializeUserConfig(); var path = location.pathname; if (path.indexOf('/seiga/') === 0) { this.initializeSeigaView(); } else if (path.indexOf('/illust/') === 0 && path.indexOf('ranking') < 0) { this.initializeIllustTop(); } else if (path.indexOf('/tag/') === 0) { this.initializeTagSearch(); } else if (path.indexOf('/o/') === 0) { this.initializeFullView(); } }, initializeSeigaView: function() { if (document.querySelector('#content')) { this.initializeBaseLayout(); this.initializeScrollTop(); this.initializeDescription(); this.initializeCommentLink(); this.initializeThumbnail(); this.initializePageTopButton(); this.initializeKnockout(); this.initializeFullScreenRequest(); this.initializeSaveScreenshotRequest(); this.initializeOther(); this.initializeSettingPanel(); document.body.classList.add('MOD_Seiga_View'); this.initializeCss(); } else { // 春画っぽい。説明文の自動リンクだけやる this.initializeDescription(); } }, initializeIllustTop: function() { this.initializeThumbnail(); this.initializeSettingPanel(); this.initializePageTopButton(); document.body.classList.add('MOD_Seiga_Top'); this.initializeCss(); }, initializeTagSearch: function() { this.initializeThumbnail(); this.initializeSettingPanel(); this.initializePageTopButton(); setTimeout(function() { var search = document.querySelector('#bar_search'); var tagwatchQuery = document.querySelector('#ko_tagwatch_min').getAttribute('data-query'); var val = search.value; if ( val === 'イラストを検索' || val === '春画を検索') { search.value = tagwatchQuery; search.classList.add('edited'); search.style.color = '#000'; } }, 1000); document.body.classList.add('MOD_Seiga_TagSearch'); this.initializeCss(); }, initializeFullView: function() { document.body.classList.add('MOD_Seiga_FullView'); this.initializeFullscreenImage(); this.initializeSaveScreenshot(); this.initializeCss(); }, addStyle: function(styles, id) { var elm = document.createElement('style'); elm.type = 'text/css'; if (id) { elm.id = id; } var text = styles.toString(); text = document.createTextNode(text); elm.appendChild(text); var head = document.getElementsByTagName('head'); head = head[0]; head.appendChild(elm); return elm; }, fullScreen: { now: function() { if (document.fullScreenElement || document.mozFullScreen || document.webkitIsFullScreen) { return true; } return false; }, request: function(target) { var elm = typeof target === 'string' ? document.getElementById(target) : target; if (!elm) { return; } if (elm.requestFullScreen) { elm.requestFullScreen(); } else if (elm.webkitRequestFullScreen) { elm.webkitRequestFullScreen(); } else if (elm.mozRequestFullScreen) { elm.mozRequestFullScreen(); } }, cancel: function() { if (document.cancelFullScreen) { document.cancelFullScreen(); } else if (document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } } }, initializeCss: function() { var __common_css__ = (` .MOD_SeigaSettingMenu a { font-weight: bolder; color: darkblue !important; } #MOD_SeigaSettingPanel { position: fixed; bottom: 2000px; right: 8px; z-index: -1; width: 500px; background: #f0f0f0; border: 1px solid black; padding: 8px; transition: bottom 0.4s ease-out; text-align: left; } #MOD_SeigaSettingPanel.open { display: block; bottom: 8px; box-shadow: 0 0 8px black; z-index: 10000; } #MOD_SeigaSettingPanel .close { position: absolute; cursor: pointer; right: 8px; top: 8px; } #MOD_SeigaSettingPanel .panelInner { background: #fff; border: 1px inset; padding: 8px; min-height: 300px; overflow-y: scroll; max-height: 500px; } #MOD_SeigaSettingPanel .panelInner .item { border-bottom: 1px dotted #888; margin-bottom: 8px; padding-bottom: 8px; } #MOD_SeigaSettingPanel .panelInner .item:hover { background: #eef; } #MOD_SeigaSettingPanel .windowTitle { font-size: 150%; } #MOD_SeigaSettingPanel .itemTitle { } #MOD_SeigaSettingPanel label { margin-right: 12px; } #MOD_SeigaSettingPanel small { color: #666; } #MOD_SeigaSettingPanel .expert { margin: 32px 0 16px; font-size: 150%; background: #ccc; } .comment_info .mod_link, .description .otherSite { text-decoration: underline; font-weight: bolder; } /* 画面が狭いときに操作不能になる部分などを直す */ @media screen and (max-width: 1023px) { .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .comment_list { position: fixed; right: 25px; top: 105px; bottom: 105px; overflow-y: auto; } .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .comment_list .text { margin: 0 16px 0 0; } .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .res .inner { position: fixed; bottom: 0; right: 5px; } .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_header .control{ position: fixed; top: 35px; right: 25px; /* 横幅が狭いと閉じるを押せない問題の対応 */ } } @media screen and (max-width: 1183px) { .mod_hidePageTopButton #pagetop { display: none !important; } } @media print { body { background: #000 !important; /* 背景を黒にしたい場合は「背景画像を印刷」にチェック */ margin: 0; padding: 0; overflow: hidden; width: 210mm; /* height: calc(297mm - 19mm); */ /* 19mmは印刷マージン */ } body.landscape { width: 297mm; /* height: calc(210mm - 19mm); */ } .toggleFullScreen, .control { display: none !important; opacity: 0 !important; } .MOD_Seiga_FullView .illust_view_big img { top: 0 !important; left: 0 !important; transform: inherit !important; -webkit-transform: inherit !important; } .MOD_Seiga_FullView:not(.landscape) .illust_view_big img { width: auto; height: auto; } .MOD_Seiga_FullView:not(.landscape).fitV .illust_view_big img { /*width: auto; height: calc(297mm - 19mm); */ } .MOD_Seiga_FullView.landscape .illust_view_big img { /*width: 297mm; height: auto; */ } .MOD_Seiga_FullView.landscape.fitV .illust_view_big img { /*width: auto; height: calc(210mm - 19mm); */ margin-top: 0; } } .saveScreenshotFrame { position: fixed; top: -200%; left: -200%; width: 32px; left: 32px; } `).trim(); var __css__ = (` /* マイページや投稿へのリンクがあっても、すぐ上にniconico共通のヘッダーがあるのでいらないと思う。ということで省スペース優先で消す。*/ #header { background: #fff; } #header .sg_global_bar { display: none; } #header_cnt { width: 1004px; } /* サムネのホバー調整 */ .list_item_cutout.middle { height: 154px; text-align: center; } .list_item_cutout.middle a { height: 100%; overflow: visible; } .list_item_cutout.middle a .illust_info, .list_item_cutout.middle a .illust_info:hover { bottom: 0; } /* サムネのカットなくすやつ。 */ .list_item_cutout.mod_no_trim .thum img { display: none; } .list_item_cutout.mod_no_trim .thum { display: block; width: 100%; height: calc(100% - 40px); background-repeat: no-repeat; background-position: center center; background-size: contain; -moz-background-size: contain; -webkit-background-size: contain; -o-background-size: contain; -ms-background-size: contain; } .list_item.mod_no_trim .thum img { display: none; } .list_item.mod_no_trim .thum { display: block; width: 100%; height: 0; background-repeat: no-repeat; background-position: center center; background-size: contain; -moz-background-size: contain; -webkit-background-size: contain; -o-background-size: contain; -ms-background-size: contain; } .MOD_Seiga_View .list_item.mod_no_trim .thum { } .list_item.mod_no_trim .thum:hover, .list_item_cutout.mod_no_trim .thum:hover { background-size: cover; } .MOD_Seiga_Top .list_item_cutout.mod_no_trim .thum { height: calc(100% - 50px); } .MOD_Seiga_Top .list_item_cutout.mod_no_trim a { height: 100%; width: 100%; } .MOD_Seiga_Top .list_item_cutout.mod_no_trim.large a .illust_info { bottom: 0px !important; background-color: rgba(60, 60, 60, 1); padding: 10px 40px; } .MOD_Seiga_Top .list_item_cutout.mod_no_trim.large { width: 190px; height: 190px; } .MOD_Seiga_Top .rank_box .item_list.mod_no_trim .more_link a { width: 190px; } /* タグ検索時、カウンタなどが常時見えるように修正 */ .MOD_Seiga_TagSearch .list_item.large a .illust_count { opacity: 1; } .MOD_Seiga_TagSearch .list_item.large a { height: 321px; } .MOD_Seiga_TagSearch .list_item.large { height: 351px; } /* タイトルと説明文・投稿者アイコンだけコンクリートの地面に置いてあるように感じたので絨毯を敷いた */ .MOD_Seiga .im_head_bar .inner { background-color: #FFFFFF; border-color: #E8E8E8; border-radius: 5px; border-style: solid; border-width: 0 0 1px; box-shadow: 0 0 15px rgba(0, 0, 0, 0.05); display: block; margin-top: 20px; /*margin-bottom: 20px;*/ margin-left: auto; margin-right: auto; padding: 15px; position: relative; width: 974px; } /* タグの位置調整 */ .illust_main .mod_tag-top.illust_sub_info { padding-bottom: 25px; padding-top: 0; } .illust_sub_info.mod_tag-description-bottom { margin-top: 15px; } .im_head_bar .illust_tag h2 { float: left; font-size: 116.7%; line-height: 120%; margin: 4px 10px 0 -2px; overflow: hidden; } .im_head_bar .illust_sub_info input#tags { margin-bottom: 15px; margin-top: 5px; padding: 4px 10px; width: 280px; } .im_head_bar .illust_sub_info ul li.btn { bottom: 15px; position: absolute; right: 15px; } /* タグ右上 */ .description.mod_tag-description-right { float: left; } .illust_sub_info.mod_tag-description-right { width: 300px; float: right; margin: 0; } .mod_tag-description-right .tag { background: none repeat scroll 0 0 rgba(0, 0, 0, 0); border: medium none; margin: 0; } .mod_tag-description-right .tag a { padding: 0 5px; } .mod_tag-description-right .tag li { } .mod_tag-description-right .tag li a { padding: 0 5px 0 0; border: 0; } .im_head_bar .illust_sub_info.mod_tag-description-right ul li.btn.active { } /* 右下だと被ることがあるので仕方なく */ .im_head_bar .illust_sub_info.mod_tag-description-right ul li.btn { display: inline-block; position: relative; bottom: auto; right: auto; } #ichiba_box { width: 1004px; margin: 0 auto 20px; border: 1px solid #ddd; border-radius: 8px; } #content.illust { padding: 0; } #related_info .ad_tag { display: none;} /* 右カラム広告のせいで無駄に横スクロールが発生しているのを修正 */ .related_info .sub_info_side { overflow-x: hidden; } .illust_main .illust_side .clip.mod_top { padding: 10px 10px 0; } .MOD_Seiga_FullView #content.illust_big .illust_view_big { margin: 0 auto; } .MOD_Seiga_FullView .control { position: absolute; right: 0; top: 0; z-index: 1000; opacity: 0; transition: opacity 0.5s ease; } .MOD_Seiga_FullView:hover .control { opacity: 1; } .MOD_Seiga_FullView .illust_view_big img { /*transform: scale(1); -webkit-transform: scale(1); transition: transform 0.3s ease, -webkit-transform 0.3s ease;*/ } .MOD_Seiga_FullView:not(.mod_noScale) .illust_view_big img { position: absolute; top: 0; left: 0; transform-origin: 0 0 0; -webkit-transform-origin: 0 0 0; } .MOD_Seiga_FullView.mod_contain { overflow: hidden; } .MOD_Seiga_FullView.mod_cover { } .MOD_Seiga_FullView.mod_contain .illust_view_big img, .MOD_Seiga_FullView.mod_cover .illust_view_big img { /*display: none;*/ } .MOD_Seiga_FullView .illust_view_big { background-repeat: no-repeat; background-position: center center; } .MOD_Seiga_FullView.mod_contain .illust_view_big { background-size: contain; } .MOD_Seiga_FullView.mod_cover .illust_view_big { background-size: cover; } .MOD_Seiga .toggleFullScreen, .MOD_Seiga .saveScreenshot { display: block; width: 200px; margin: 8px auto; transition: 0.4s opacity, 0.4s box-shadow; } .MOD_Seiga_FullView .toggleFullScreen { position: fixed; top: 0; left: 0; z-index: 1000; margin: 0; padding: 4px; cursor: pointer; border: none; background: transparent; color: #0CA5D2; font-weight: bolder; } .MOD_Seiga .toggleFullScreen:hover, .MOD_Seiga .saveScreenshot:hover { box-shadow: 2px 2px 2px #333; } .MOD_Seiga_FullView .toggleFullScreen button, .MOD_Seiga_FullView .saveScreenshot button { opacity: 0; margin: 0; padding: 0; cursor: pointer; transition: 0.4s opacity, 0.4s box-shadow; border: 1px solid #000; background: #fff; color: #0CA5D2; font-weight: bolder; } .MOD_Seiga_FullView .toggleFullScreen.show button, .MOD_Seiga_FullView .toggleFullScreen button:hover { opacity: 1; box-shadow: 2px 2px 2px #333; } .MOD_Seiga_FullView:fullscreen .toggleFullScreen { display: none; } #ko_watchlist_info.mod_hide { display: none !important; } .saveScreenShotFrame, .fullScreenRequestFrame { position: fixed; width: 100px; height: 100px; left: -999px; top: -999px; } .MOD_Seiga_FullView .closeButton { opacity: 0; transition: 0.4s opacity ease; } .MOD_Seiga_FullView .closeButton:hover { opacity: 1; } body.fullScreenFrame { background: #000; } body.MOD_Seiga_FullView img { cursor: none; } body.MOD_Seiga_FullView.mouseMoving img{ cursor: default; } `).trim(); this.addStyle(__common_css__); if (this.config.get('applyCss')) { this.addStyle(__css__); } }, initializeUserConfig: function() { var prefix = 'MOD_Seiga_'; var conf = { applyCss: true, topUserInfo: true, tagPosition: 'description-bottom', noTrim: true, hidePageTopButton: true, clipPosition: 'bottom', hideBottomUserInfo: false }; this.config = { get: function(key) { try { if (window.localStorage.hasOwnProperty(prefix + key)) { return JSON.parse(window.localStorage.getItem(prefix + key)); } return conf[key]; } catch (e) { return conf[key]; } }, set: function(key, value) { window.localStorage.setItem(prefix + key, JSON.stringify(value)); } }; }, initializeBaseLayout: function() { var description = document.querySelector('#content .description, #content .discription'); description.classList.add('description'); document.querySelector('.controll').classList.add('control'); $('#related_info').after($('#ichiba_box')); if (this.config.get('hideBottomUserInfo') === true) { document.querySelector('#ko_watchlist_info').classList.add('mod_hide'); } }, initializeScrollTop: function() { var reset = this.resetScrollTop = function() { var nofix = document.body.classList.contains('nofix'); var commonHeaderHeight = nofix ? 0 : 36; //$bar.outerHeight(); document.documentElement.scrollTop = (Math.max( document.documentElement.scrollTop, 128 /*$('#content .im_head_bar').offset().top*/ - commonHeaderHeight )); }; setTimeout(reset, 100); reset(); }, initializeDescription: function() { var description = document.querySelector('#content .description, #content .discription, .illust_user_exp'); if (!description) { return; } var html = description.innerHTML; // 説明文中のURLの自動リンク var linkmatch = /<a.*?<\/a>/, links = [], n; html = html.split('<br />').join(' <br /> '); while ((n = linkmatch.exec(html)) !== null) { links.push(n); html = html.replace(n, ' <!----> '); } html = html.replace(/(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi, '<a href="$1" target="_blank" class="otherSite">$1</a>'); for (var i = 0, len = links.length; i < len; i++) { html = html.replace(' <!----> ', links[i]); } html = html.split(' <br /> ').join('<br />'); description.innerHTML = html; }, initializeCommentLink: function() { var videoReg = /((sm|nm|so)\d+)/g; var seigaReg = /(im\d+)/g; var bookReg = /((bk|mg)\d+)/g; var urlReg = /(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi; var autoLink = function(text) { text = text .replace(videoReg, '<a href="//www.nicovideo.jp/watch/$1" class="video mod_link">$1</a>') .replace(seigaReg, '<a href="/seiga/$1" class="illust mod_link">$1</a>') .replace(bookReg, '<a href="/watch/$1" class="book mod_link">$1</a>') .replace(urlReg, '<a href="$1" target="_blank" class="otherSite mod_link">$1</a>'); return text; }; var commentLink = function(selector) { Array.from(document.querySelectorAll(selector)).forEach((elm) => { var html = elm.innerHTML, linked = autoLink(html); if (html !== linked) { elm.innerHTML = linked; } elm.classList.add('mod_linked'); }); }; setTimeout(function() { commentLink('#comment_list .comment_info .text:not(.mod_linked)'); var updateTimer = null; document.body.addEventListener('DOMNodeInserted', function(e) { if (e.target.className && e.target.className.indexOf('comment_list_item') >= 0) { if (updateTimer) { updateTimer = clearTimeout(updateTimer); } updateTimer = setTimeout(function() { updateTimer = clearTimeout(updateTimer); commentLink('#comment_list .comment_info .text:not(.mod_linked)'); }, 100); } }); }, 100); }, initializeThumbnail: function() { if (this.config.get('noTrim') !== true) { return; } var treg = /^(https?:\/\/lohas.nicoseiga.jp\/+thumb\/+.\d+)([a-z\?]*)/; var noTrim = function() { Array.from(document.querySelectorAll('.list_item_cutout, #main .list_item:not(.mod_no_trim)')).forEach((elm) => { var $thum = $(elm).find('.thum'); var $img = $thum.find('img'); var src = $img.attr('src') || ''; if ($thum.length * $img.length < 1 || !treg.test(src)) return; // TODO: 静画のサムネの種類を調べる var url = RegExp.$1 + 'q?';//RegExp.$2 === 't' ? src : RegExp.$1 + 'q?'; $thum.css({'background-image': 'url("' + url + '")'}); elm.classList.add('mod_no_trim'); }); }; noTrim(); document.body.addEventListener('AutoPagerize_DOMNodeInserted', function() { setTimeout(function() { noTrim(); }, 500); }); }, initializePageTopButton: function() { document.body.classList.toggle('mod_hidePageTopButton', this.config.get('hidePageTopButton')); }, initializeKnockout: function() { }, initializeFullScreenRequest: function() { var $body = $('body'); var $iframe = $('<iframe allowfullscreen="on" class="fullScreenRequestFrame" name="fullScreenRequestFrame"></iframe>'); $body.append($iframe); var $fullScreenButton = $([ '<button class="toggleFullScreen btn normal" title="フルスクリーン表示に切り換えます(Fキー)">', '<span>フルスクリーン</span>', '</button>', ''].join('')); $('.illust_main .illust_wrapper .inner .thum_large:first').append($fullScreenButton); var toggleFullScreen = $.proxy(function() { if (this.fullScreen.now()) { this.fullScreen.cancel(); } else { $iframe[0].contentWindow.location.replace($('#illust_link').attr('href')); this.fullScreen.request($iframe[0]); } }, this); $fullScreenButton.on('click', function(e) { e.preventDefault(); e.stopPropagation(); toggleFullScreen(); }); var onMessage = function(event) { if (event.origin.indexOf('nicoseiga.jp') < 0) return; try { var data = JSON.parse(event.data); if (data.id !== 'MOD_Seiga') { return; } if (data.command === 'toggleFullScreen') { toggleFullScreen(); } } catch (e) { console.log('Exception', e); console.trace(); } }; window.addEventListener('message', onMessage); $body.on('keydown.watchItLater', function(e) { if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } if (e.keyCode === 70) { // F toggleFullScreen(); } }); }, initializeOther: function() { document.body.classList.add('MOD_Seiga'); }, initializeSettingPanel: function() { var $menu = $('<li class="MOD_SeigaSettingMenu"><a href="javascript:;" title="MOD_Seigaの設定変更">MOD_Seiga設定</a></li>'); var $panel = $('<div id="MOD_SeigaSettingPanel" />');//.addClass('open'); var $button = $('<button class="toggleSetting playerBottomButton">設定</botton>'); $button.on('click', function(e) { e.stopPropagation(); e.preventDefault(); $panel.toggleClass('open'); }); var config = this.config; $menu.find('a').on('click', function() { $panel.toggleClass('open'); }); var __tpl__ = (` <div class="panelHeader"> <h1 class="windowTitle">MOD_Seigaの設定</h1> <p>設定はリロード後に反映されます</p> <button class="close" title="閉じる">×</button> </div> <div class="panelInner"> <!--<div class="item" data-setting-name="topUserInfo" data-menu-type="radio"> <h3 class="itemTitle">投稿者情報を右上に移動 </h3> <label><input type="radio" value="true" > する</label> <label><input type="radio" value="false"> しない</label> </div>--> <div class="item" data-setting-name="noTrim" data-menu-type="radio"> <h3 class="itemTitle">サムネイルの左右カットをやめる </h3> <label><input type="radio" value="true" >やめる</label> <label><input type="radio" value="false">やめない</label> </div> <div class="item" data-setting-name="hidePageTopButton" data-menu-type="radio"> <h3 class="itemTitle">ウィンドウ幅が狭いときはページトップに戻るボタンを隠す</h3> <label><input type="radio" value="true" >隠す</label> <label><input type="radio" value="false">隠さない</label> </div> <div class="item" data-setting-name="hideBottomUserInfo" data-menu-type="radio"> <h3 class="itemTitle">ページ下側の投稿者情報を隠す</h3> <small>右上だけでいい場合など</small><br> <label><input type="radio" value="true" >隠す</label> <label><input type="radio" value="false">隠さない</label> </div> <div class="item" data-setting-name="clipPosition" data-menu-type="radio"> <h3 class="itemTitle">クリップ登録メニューの位置(旧verのみ)</h3> <label><input type="radio" value=""top"" >上</label> <label><input type="radio" value=""bottom"">下</label> </div> <!-- <div class="item" data-setting-name="tagPosition" data-menu-type="radio"> <h3 class="itemTitle">タグの位置(旧verのみ) </h3> <label><input type="radio" value=""description-bottom"">説明文の下</label> <label><input type="radio" value=""description-right"">説明文の右</label> <label><input type="radio" value=""top"">画像の上</label> <label><input type="radio" value=""default"">画像の下(標準)</label> </div> --> <div class="expert"> <h2>上級者向け設定</h2> </div> <div class="item" data-setting-name="applyCss" data-menu-type="radio"> <h3 class="itemTitle">MOD_Seiga標準のCSSを使用する</h3> <small>他のuserstyleを使用する場合は「しない」を選択してください</small><br> <label><input type="radio" value="true" > する</label> <label><input type="radio" value="false"> しない</label> </div> </div> `).trim(); $panel.html(__tpl__); $panel.find('.item').on('click', function(e) { var $this = $(this); var settingName = $this.attr('data-setting-name'); var value = JSON.parse($this.find('input:checked').val()); console.log('seting-name', settingName, 'value', value); config.set(settingName, value); }).each(function(e) { var $this = $(this); var settingName = $this.attr('data-setting-name'); var value = config.get(settingName); $this.addClass(settingName); $this.find('input').attr('name', settingName).val([JSON.stringify(value)]); }); $panel.find('.close').click(function() { $panel.removeClass('open'); }); $('#siteHeaderRightMenuFix').after($menu); $('body').append($panel); }, initializeFullscreenImage: function() { var $body = $('body'), $container = $('.illust_view_big'), $img = $container.find('img'), scale = 1; var $fullScreenButton = $([ '<div class="toggleFullScreen show">', '<button title="フルスクリーン表示に切り換えます">', '<span>フルスクリーン</span>', '</button>', '</div>', ''].join('')); var $window = $(window); $('.controll').addClass('control'); var clearCss = function() { $body.removeClass('mod_contain mod_cover mod_noScale'); $container.css({width: '', height: ''}); $img.css({'transform': '', '-webkit-transform': '', top: '', left: ''}); }; // ウィンドウの内枠フィット (画面におさまる範囲で最大化) var contain = function() { clearCss(); $body.addClass('mod_contain'); scale = Math.min( $window.innerWidth() / $img.outerWidth(), $window.innerHeight() / $img.outerHeight() ); scale = Math.min(scale, 10); $img.css({ 'transform': 'scale(' + scale + ')', '-webkit-transform': 'scale(' + scale + ')', 'left': ($window.innerWidth() - $img.outerWidth() * scale) / 2 + 'px', 'top': ($window.innerHeight() - $img.outerHeight() * scale) / 2 + 'px' }); $container.width($window.innerWidth()); $container.height($window.innerHeight()); // $container.css('background-image', 'url("' + $img.attr('src') + '")'); }; // ウィンドウの外枠フィット var cover = function() { clearCss(); $body.addClass('mod_cover').css('overflow', 'scroll'); scale = Math.max( $window.innerWidth() / $img.outerWidth(), $window.innerHeight() / $img.outerHeight() ); scale = Math.min(scale, 10); $img.css({ 'transform': 'scale(' + scale + ')', '-webkit-transform': 'scale(' + scale + ')', }); // ウィンドウサイズの計算にスクロールバーの幅を含めるための措置 おもにwindows用 $body.css('overflow', ''); }; // 原寸大表示 var noScale = function() { clearCss(); $body.addClass('mod_noScale'); scale = 1; $container.css('background-image', ''); }; // クリックごとに表示を切り替える処理 var onClick = function(e) { if (e.button > 0) { return; } // TODO: クリックした位置が中心になるようにスクロール if ($body.hasClass('mod_noScale')) { contain(); } else if ($body.hasClass('mod_contain')) { cover(); } else { noScale(); } }; // ウィンドウがリサイズされた時などの再計算用 var update = function() { if ($body.hasClass('mod_contain')) { contain(); } else if ($body.hasClass('mod_cover')) { cover(); } }; // モニターいっぱい表示を切り換える var toggleFullScreen = $.proxy(function() { if (window.name === 'fullScreenRequestFrame') { parent.postMessage(JSON.stringify({ id: 'MOD_Seiga', command: 'toggleFullScreen' }), 'http://seiga.nicovideo.jp'); return; } if (this.fullScreen.now()) { this.fullScreen.cancel(document.documentElement); } else { this.fullScreen.request(document.documentElement); } }, this); window.setTimeout($.proxy(function() { $fullScreenButton.removeClass('show'); }, this), 2000); // /seiga/imXXXXXXからiframeで呼ばれてる時の処理 if (window.name === 'fullScreenRequestFrame') { $('img[src$="btn_close.png"]') .addClass('closeButton') .off() .on('click', function() { toggleFullScreen(); }); $('body').addClass('fullScreenFrame'); } $fullScreenButton.on('click', function(e) { e.preventDefault(); e.stopPropagation(); toggleFullScreen(); }); $body.append($fullScreenButton); // マウスを動かさない時はカーソルを消す var mousemoveStopTimer = null; var showMouseNsec = function() { $body.addClass('mouseMoving'); if (mousemoveStopTimer) { window.clearTimeout(mousemoveStopTimer); mousemoveStopTimer = null; } mousemoveStopTimer = window.setTimeout(function() { $body.removeClass('mouseMoving'); mousemoveStopTimer = null; }, 1000); }; $body .on('mousemove.MOD_Seiga', showMouseNsec) .on('mousedown.MOD_Seiga', showMouseNsec); contain(); $img.on('click', onClick); $window.on('resize', update); var onImageLoad = $.proxy(function() { this.initializePrintCss($img.clone()); contain(); $img.off('load.MOD_Seiga'); }, this); if ($img[0] && $img[0].complete) { onImageLoad(); } else { $img.on('load.MOD_Seiga', onImageLoad); } // おもにwindows等、縦ホイールしかない環境で横スクロールしやすくする // Firefoxでうまくいかない var hasWheelDeltaX = false; $(document).on('mousewheel', function(e) { var delta = 0; if ($body.hasClass('mod_contain')) { return; } if (document.body.scrollHeight > window.innerHeight) { return; } if (hasWheelDeltaX) { return; } if (!e.originalEvent) { return; } var oe = e.originalEvent; if (typeof oe.wheelDelta === 'number') { if (typeof oe.wheelDeltaX === 'number' && oe.wheelDeltaX !== 0) { hasWheelDeltaX = true; return; // ホイールで横スクロールできる環境だとかえって邪魔なのでなにもしない } delta = e.originalEvent.wheelDelta / Math.abs(e.originalEvent.wheelDelta); } if (delta === 0) return; e.preventDefault(); e.stopPropagation(); var scrollLeft = $body.scrollLeft() - (delta * $window.innerWidth() / 10); $body.scrollLeft(Math.max(scrollLeft, 0)); }); }, initializeSaveScreenshot: function() { $('body').on('keydown', (e) => { if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } if (e.keyCode === 83) { // S window.MOD_Seiga.saveScreenshot(); } }); }, initializeSaveScreenshotRequest: function() { const $body = $('body'); const $iframe = $('<iframe allowfullscreen="on" class="saveScreenshotFrame" name="saveScreenshotFrame"></iframe>'); $iframe[0].srcdoc = ''; $body.append($iframe); const $saveButton = $([ '<button class="saveScreenshot btn normal" title="画像を保存(Sキー)">', '<span>画像を保存</span>', '</button>', ''].join('')); $('.illust_main .illust_wrapper .inner .thum_large:first').append($saveButton); $saveButton.on('click', (e) => { e.preventDefault(); e.stopPropagation(); $iframe[0].contentWindow.location.replace($('#illust_link').attr('href')); //window.open( // $('#illust_link').attr('href'), // 'saveScreenshotFrame', // 'width=400, height=300, personalbar=0, toolbar=0, scrollbars=1, sizable=1'); }); $(window).on('beforeunload', () => { $iframe.remove(); $iframe[0].srcdoc = ''; }); $('body').on('keydown', (e) => { if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } if (e.keyCode === 83) { // S $saveButton.click(); } }); }, initializePrintCss: function($img) { this.initializePrintCss = function() {}; var self = this; $img.css({ width: 'auto', height: 'auto', transform: '', left: '100%', top: '100%', opacity: 0, position: 'fixed' }); var $body = $('body'); $body.append($img); window.setTimeout(function() { var width = $img.outerWidth(), height = $img.outerHeight(); $img.remove(); // TODO: 用紙サイズ変更 var paperMarginV = 0.1; // 19; // 紙送りマージン? var imageRatio = height / Math.max(width, 1); var paperRatio = (A4_HEIGHT - paperMarginV) / A4_WIDTH; var landscapeRatio = (A4_WIDTH - paperMarginV) / A4_HEIGHT; var isLandscape = Math.abs(paperRatio - imageRatio) > Math.abs(landscapeRatio - imageRatio); paperRatio = isLandscape ? landscapeRatio : paperRatio; var isFitV = paperRatio < imageRatio; var paperWidth = isLandscape ? A4_HEIGHT : A4_WIDTH; var paperHeight = (isLandscape ? A4_WIDTH : A4_HEIGHT) - paperMarginV; var marginLeft = 0, marginTop = 0, imageWidth = paperWidth, imageHeight = paperHeight; if (isFitV) { // タテ合わせ imageWidth = paperHeight / imageRatio; marginLeft = (paperWidth - imageWidth) / 2; } else { // ヨコ合わせ imageHeight = paperWidth * imageRatio; marginTop = (paperHeight - imageHeight) / 2; } var pageSize = isLandscape ? 'A4 lanscape' : 'A4'; var css = [ '@page { margin: 0; size: ', pageSize, '; }\n\n ', '@media print {\n', '\t.MOD_Seiga_FullView .illust_view_big img {\n ', '\t\tmargin-left: ', marginLeft, 'mm; ', '\t\tmargin-top: ', marginTop, 'mm; ', '\t\twidth:', imageWidth, 'mm !important; ', '\t\theight:', imageHeight, 'mm !important;', '\t\n}\n', '}', ].join(''); //console.log('paper?', paperWidth, paperHeight, imageWidth, imageHeight); //console.log('ratio?', width, height, isLandscape, paperRatio, imageRatio); console.log('print css\n', css); self._printCss = self.addStyle(css, 'MOD_Seiga_print'); $body .toggleClass('landscape', isLandscape) .toggleClass('fitV', isFitV); }, 100); } }; const toSafeName = function(text) { text = text.trim() .replace(/</g, '<') .replace(/>/g, '>') .replace(/\?/g, '?') .replace(/:/g, ':') .replace(/\|/g, '|') .replace(/\//g, '/') .replace(/\\/g, '¥') .replace(/"/g, '”') .replace(/\./g, '.') ; return text; }; window.MOD_Seiga.saveScreenshot = function() { const div = document.querySelector('.illust_view_big'); const img = div.querySelector('.illust_view_big img'); window.console.info('img', img, div, document.querySelectorAll('img')); const illustId = (div.getAttribute('data-watch_url') || '').split('/')[4]; window.console.info('illustId', illustId); const title = toSafeName(document.title); const fileName = `${illustId}-${title}`; window.console.info('fileName', fileName); const link = document.createElement('a'); link.download = fileName; link.href = img.src; link.textContent = 'save image'; document.body.appendChild(link); link.click(); link.remove(); window.setTimeout(() => { if (window.name === 'saveScreenshotFrame') { location.replace('/favicon.ico'); } }, 0); }; window.MOD_Seiga.initialize(); if (window.name === 'saveScreenshotFrame') { window.console.info('saveScreenshot', window.name); window.MOD_Seiga.saveScreenshot(); } }); var script = document.createElement("script"); script.id = "MOD_SeigaLoader"; script.setAttribute("type", "text/javascript"); script.setAttribute("charset", "UTF-8"); script.appendChild(document.createTextNode("(" + monkey + ")()")); document.body.appendChild(script); })();