Greasy Fork is available in English.

Narrow_DynamicHeader

小説家になろうのヘッダーを便利にする

// ==UserScript==
// @name        Narrow_DynamicHeader
// @namespace   phodra
// @description 小説家になろうのヘッダーを便利にする
// @version     1.2
// @include     http://ncode.syosetu.com/*
// @include     http://novelcom.syosetu.com/*
// @include     http://novel18.syosetu.com/*
// @include     http://novelcom18.syosetu.com/*
// ==/UserScript==


(function (){
	// ユーザーID
	// 自分のIDを調べて書き換えてください。
	// 通常版のユーザーIDは、ホーム画面の右上に以下のように表示されています。
	//  (ユーザー名)[ID:000000]でログイン中
	// 18禁サイト用のユーザーIDは、Xホーム右側の
	// 「評価をつけた作品一覧」のURLから得ることが出来ます。
	const MY_USER_ID = 0;
	const MY_XUSER_ID = 0;



	// ※そのまま移動させると元の場所が埋め立てられるので、
	// ※クローンを作成したのちにオリジナルは visibility = hidden で隠す。
	// ヘッダー(オリジナル)
	var $header_org = $("#novel_header");
	// ヘッダー(クローン)
	var $header = $header_org.clone(true);
	$header.css( 'position', 'relative');
	
	// オリジナルを隠す
	$header_org.css( 'visibility', 'hidden');
	// header Height
	var headerH = $header.outerHeight();
	
	// ヘッダーの親になるボックス
	var $box = $("<div id='box'>");
	$box.css( 'position', 'fixed');
	$box.append($header);

	// マウス検知エリア
	var $detect =  $("<div id='detect_area' />");
	$detect.css(
		{
			'position': 'fixed',
			'height': headerH,
			'top': 0
		}
	);
	// ヘッダーボックスに乗せる、外す
	$detect.hover(
		function()
		{
			_boxon = true;
			if( scTop>0 ) HeaderShow(true);
			else _show = 2;
		},
		function()
		{
			_boxon = false;
			if( scTop>0 ) HeaderHide( !_lockB && !_lockM );
			else _show = 0;
		}
	);
	$detect.append($box);
	$("body").append($detect);



	// 章タイトルとサブタイトルのラベル
	if( $(".margin_r20").size() ){
		// コンテンツinfoのクローン
		var $info_org = $(".contents1");
		var $info =  $info_org.clone(true);
		$info_org.css( 'visibility', 'hidden');
		$info.css(
			{
				'left': 0,
				'right': 0,
				'margin': 'auto',
				'opacity': '0.8'
			}
		);
	
		var $label = $("<div />");
		var $cp_title = $info.children(".chapter_title");
		$cp_title.hide();
		if( $cp_title.size() )
		{
			var $cp_title2 = $("<span class='label_chapter' />");
			$cp_title2.append($(".chapter_title").text());
			
			$label.append($cp_title2);
			$label.append(" - ");
		}
		// サブタイトル用のエレメント
		var $label_sub = $("<span class='label_subtitle' />");
		$label_sub.append($(".novel_subtitle").text());
		$label.append($label_sub);
		$info.append($label);
		$box.append($info);
	}
	var boxheight = $box.height();


	// 移動ボタン群
	const NDH_BTN = 'ndh_button';
	var move_a = "<a class='"+ NDH_BTN +"' />";
	var move_div = "<div class='"+ NDH_BTN +"' />";

	// 前ページ
	var bn_p = $("a:contains('<<')");
	if( bn_p.size() )
	{
		var $preEp = $(move_a);
		$preEp.text("<");
		$preEp.attr(
			{
				'alt': 'Prev Episode',
				'href': bn_p.attr('href')
			}
		);
		$preEp.css('left', '15px');
		$header.append($preEp);
	}

	// 次ページ
	var bn_n = $("a:contains('>>')");
	if( bn_n.size() )
	{
		var $nxtEp = $(move_a);
		$nxtEp.text(">");
		$nxtEp.attr(
			{
				'alt': 'Next Episode',
				'href': bn_n.attr('href')
			}
		);
		$nxtEp.css( 'left', '55px');
		$header.append($nxtEp);
	}
	
	$("#pageBottom").remove();
	$("#pageTop").remove();

	// 最上部へ移動
	var $ptop = $(move_div);
	$ptop.attr( 'alt', 'Scroll Top');
	$ptop.css( 'right', '55px');
	$ptop.text("↑");
	$ptop.click( function(e)
		{
			$("html,body").animate(
				{	'scroll-top': 0
				}, 500
			);
		}
	);
	$header.append($ptop);
	
	// 最下部へ移動
	var $pbtm = $(move_div);
	$pbtm.text("↓");
	$pbtm.attr( 'alt', 'Scroll Bottom');
	$pbtm.css( 'right', '15px');
	$pbtm.click( function(e)
		{
			$("html,body").animate(
				{	'scroll-top':
					$(document).height() -$(window).height()
				}, 500
			);
		}
	);
	$header.append($pbtm);
	
	// AutoPagerizeのページ移動
	// ※インストールしていなくても作製して非表示にしておき、
	//  AutoPagerizeの初期化イベントで表示させる。
	//  AP初期化イベント中ではなくこの時点で作成するのは、
	//  高さの計算を簡略化するため。
	//  前のページ
	var $ap_prev = $(move_div)
	$ap_prev.text("△");
	$ap_prev.attr( 'alt', 'Scroll Prev Page');
	$ap_prev.css(
		{
			'display': 'none',
			'right': '140px',
		}
	);
	$ap_prev.click( function(e)
		{
			$("html,body").animate(
				{	'scroll-top':
					scTop==ap.seam[ap.page]?
					ap.seam[ap.page-1]: ap.seam[ap.page]
				}, 500
			);
		}
	);
	$header.append($ap_prev);

	// 次のページ
	var $ap_next = $(move_div);
	$ap_next.text("▽");
	$ap_next.attr( 'alt', 'Scroll Next Page');
	$ap_next.css(
		{
			'display': 'none',
			'right': '100px',
		}
	);
	$ap_next.click( function(e)
		{
			$("html,body").animate(
				{	'scroll-top':
					ap.page+1<ap.seam.length?
					ap.seam[ap.page+1]:
					$(document).height() -$(window).height()
				}, 500
			);
		}
	);
	$header.append($ap_next);

	// 移動ボタンのスタイル
	var NDH_BTN_Style = 
		"." + NDH_BTN +"{ \
			box-sizing: border-box; \
			border: solid 1px transparent; \
		}" +
		"." + NDH_BTN + ":hover{ \
			border: outset 1px black; \
		}" +
		"." + NDH_BTN + ":active{ \
			border: inset 1px black; \
			background-color: #fafafa; \
		}";
	var mb_style = $("<style type='text/css' />");
	mb_style.append(NDH_BTN_Style);
	$("head").append(mb_style);
	
	// 追加したボタンのスタイルをまとめて設定
	$( "." +NDH_BTN ).css(
		{
			'cursor': 'pointer',
			'position': 'absolute',
			'top': 0,
			'bottom': 0,
			'margin': '4px',
			'padding': '0px 10px',
			'line-height': function()
			{
				return $(this).height()+'px';
			}
		}
	);
	



	// フラグ
	var _boxon = false;
	var _lockB = 0, _lockM = 0;
	var _show = 0;
	
	// 表示設定
	var $navi_box = $("#novelnavi_right");
	if( $navi_box.size() )
	{
		$header_org.find("#novelnavi_right").remove();
		$header.find("#novelnavi_right").hide();
		var $navi = $("<div id='navi_button' class='" + NDH_BTN + "' />");
		$navi.css(
			{
				'position': 'absolute',
				'display': 'block',
				'margin': 0,
				'top': 16,
				'right': 3,
				'height': 28,
				'width': 10
			}
		);
		$navi.click( function()
			{
				if( _lockM )
				{
					$("#menu_off").click();
					_lockM = false;
				}else
				{
					$("#menu_on").click();
					_lockM = true;
				}
			}
		);
		$header.append($navi);

		var $navi_menu = $(".novelview_navi");
		$navi_menu.css(
			{
				'top': headerH,
				'right': 0
			}
		);
		$header.append($navi_menu);

		$("input[name='fix_menu_bar']").prop(
			{
				'disabled': true,
				'checked': false,
			}
		);
		
		$("#menu_off_2").click(
			function(e){ $navi.click(); }
		);
	}



	// AutoPagerize 互換
	var ap;
	// AP用変数とボタンを初期化
	var AP_Init = function()
	{
		ap =
		{
			'seam': [0],
			'page': 0,
		};
		$ap_next.show();
		$ap_prev.show();
	};
	// ページを継ぎ足した時、継ぎ目の位置を記録する
	var AP_SeamLine = function()
	{
		if( ap != null)
		{
			var $ap_sep = $(".autopagerize_page_separator");
			var $ap_sep_last = $ap_sep.eq(-1);
			ap.seam[$ap_sep.index($ap_sep_last)+1] =
				parseInt($ap_sep_last.offset().top)-boxheight;
		}
	};
	
	if( window.AutoPagerize )
	{
		console.log( 'window.AutoPagerize' );
		// 初期化
		AP_Init();
		// 継ぎ足した時
		AutoPagerize.addFilter(AP_SeamLine);
	}else
	{
		$(document).on(
			{
				'GM_AutoPagerizeLoaded': function(){
					AP_Init();
				},
				'GM_AutoPagerizeNextPageLoaded': function(){
					AP_SeamLine();
				}
			}
		);
		
	}

	var scTop = $(window).scrollTop();
	// ヘッダーを表示させる
	var HeaderShow = function(bool)
	{
		if( _show<1 && bool )
		{
			_show = 1;
			// 消えている最中でもすぐにまた表示させる
			$box.stop();
			// ヘッダーを表示させるアニメ
			$box.animate(
				{	'top': 0},
				{	'duration': 'fast',
					'easing'  : 'swing',
					'complete': function()
					{ _show = 2; }
				}
			);
		}
	}
	// ヘッダーを隠す
	var HeaderHide = function(bool)
	{
		if( _show>0 && bool )
		{
			_show = -1;
			$box.stop();
			// ヘッダーを非表示にするアニメ
			$box.animate(
				{	'top': -boxheight},
				{
					'duration': 'normal',
					'easing'  : 'linear',
					'complete': function()
					{ _show = 0; },
					'progress' : function(e)
					{
						if( parseInt($(this).css('top')) <= -scTop )
						{
							$box.stop(false,true);
							$box.css( 'top', -scTop);
						}
					}
				}
			);
		}
	}
	// スクロール位置によってヘッダー位置を調整
	// ※上端でチラ見えしてるときは絶対座標っぽくずらす
	//  そうでなければ、画面のすぐ上で待機させる
	var HeaderPosSet = function()
	{
		if( scTop <= boxheight ||
			parseInt($box.css('top')) != -boxheight )
		{
			$box.stop();
			$box.css( 'top', scTop<=boxheight? -scTop: -boxheight );
		}
	}
	HeaderPosSet();
	
	var novel_title = $(".margin_r20:first").text();
	$(window).on(
		{
			'ready resize': function()
			{
				$detect.width($(window).width());
				$box.width($(window).width());
			},
			'scroll': function()
			{
				scTop = $(window).scrollTop();
				// ヘッダーを追従させる
				if( _show==0 && !_lockB && !_lockM ) HeaderPosSet();

				// AutoPagerize
				if( ap != null )
				{
					for( var i=ap.seam.length-1; i>=0; i-- )
					{
						if( scTop >= ap.seam[i]-1 )
						{
							if( ap.page != i )
							{
								ap.page = i;
								if( $("#novel_honbun").size() )
								{
									$label_sub.text(
										$(".novel_subtitle").eq(i).text());
									document.title =
										novel_title + " - " + $label_sub.text();
								}
							}
							break;
						}
					}
				}
				
				if( $(".novel_hyouka,#novel_footer,#footer").offset().top
					< scTop + $(window).height() )
				{
					if( !_lockB )
					{
						HeaderShow(true);
						_lockB = true;
					}
				}else
				{
					if( _lockB ) _lockB = false;
					HeaderHide( !_lockM && !_boxon );
				}
			}
		}
	);



	var href;
	// ヘッダーに「目次」を追加
	var $index_li = $("<li />");
	var $index_node = $("<a />");
	var index_href = $("#contents_main>a:first").attr('href');
	if( index_href==null ){
		// 携帯用のアドレスから生成
		var handheld = $("link[media='handheld']").attr('href');
		index_href = handheld.match(/\/n\d+?\w+?\//);
	}
	$index_node.text("目次");
	$index_node.attr( 'href', index_href);
	$index_li.append($index_node);
	$header.find("li:contains('感想')").before($index_li);

	var userid;
	// ノベルフッターから作者コードを抜く
	var user_href = $(".undernavi a:contains('マイページ')").attr('href');
	if( user_href==null ){
		// よくわからんけど作者コードっぽいので引っこ抜く
		var atom = $("link[title='Atom']").attr('href');
		if( atom ){
			userid = atom.match(/(\d+|x\d+[^\.]+?)/)[0] + "/";
			user_href = "http://mypage.syosetu.com/" + userid;
		}else{
			userid=null;
			user_href=null;
		}
	}else{
		userid = user_href.match(/(\d+\/|x\d+.+)/)[0];
	}
	
	// ヘッダーに「作者マイページ」を追加
	var $user_li = $("<li />");
	var $user_node = $("<a />");
	$user_node.text("作者");
	if( userid ){
		$user_node.attr( 'href', user_href);
	}else{
		$user_node.css( 'cssText', 'color: rgba(200,200,200,0.3) !important;');
	}
	$user_li.append($user_node);
	$header.find("li:contains('感想')").before($user_li);

	// ヘッダーに「メール」を追加
	var $mail_li = $("<li />");
	var $mail_node = $("<a />");
	$mail_node.text("メール");
	if( userid && userid[0]!="x" ){
		$mail_node.attr( 'href', 'http://syosetu.com/message/sendinput/to/' + userid);
	}else{
		$mail_node.css( 'cssText', 'color: rgba(200,200,200,0.3) !important;');
	}
	$mail_li.append($mail_node);
	$header.find("li:contains('感想')").before($mail_li);

	// N2コードを取得
	var dlurl, n2code;
	dlurl = $(".undernavi li:contains('ダウンロード')>a");
	if( dlurl.size() ){
		n2code = dlurl.attr('href').match(/\d+\//);
	}
	var bm_config = userid && userid[0]=="x"?
	"http://syosetu.com/favnovelmain18/updateinput/xidfavncode/" + MY_XUSER_ID:
	"http://syosetu.com/favnovelmain/updateinput/useridfavncode/" + MY_USER_ID;
	bm_config += "_" + n2code;
	
	var dl_prm = {
		'hankaku': '0',
		'code': 'utf-8',
		'kaigyo': 'crlf'
	};
	// テキストダウンロードボタンを追加
	var num = location.href.match( /\d+(?=\/$)/ );
	var $down_li = $("<li />");
	var $down_node = $("<a />");
	$down_node.text("DL");
	if( n2code && num ){
		var txtdl_url = userid[0]=="x"?
		"http://novel18.syosetu.com/txtdownload/dlstart/ncode/":
		"http://ncode.syosetu.com/txtdownload/dlstart/ncode/";
		$down_node.attr( 'href',
			txtdl_url + n2code +
			"?hankaku=" + dl_prm.hankaku +
			"&code=" + dl_prm.code +
			"&kaigyo=" + dl_prm.kaigyo +
			"&no=" + location.href.match( /\d+(?=\/$)/ )
		);
	}else{
		$down_node.css( 'cssText', 'color: rgba(200,200,200,0.3) !important;');
		$down_node.css( 'pointerEvents', 'none');
	}
	$down_li.append($down_node);
	$header.find("li:contains('レビュー')").after($down_li);
	
	// 「縦書で読む」を消す
	$header.find("li a.menu").parent().hide();
	
	// 「ブックマークに追加」/「ブックマークを解除」を改変
	var $fav = $("li.booklist,li.booklist_now");
	var $fav_a = $fav.children("a");
	$fav_a.css(
		'cssText',
		"color: #F4FA58 !important;"
	);
	$fav_a.css( 'font-size', '150%');
	if( $("li.booklist").size() ){
		$fav_a.text("☆");
		$fav_a.attr( 'alt', 'ブックマークに追加');
	}else{
		$fav_a.text("★");
		$fav_a.attr(
			{
				'href': bm_config,
				'alt': 'ブックマーク設定'
			}
		);
	}
	$fav.attr( 'class', null);
	
	// しおりを挿む/しおり中ボタンの改変
	var $bmark_img = $("<img>");
	var $bmark = $header.find("li.bookmark,li.bookmark_now");
	$bmark.attr(
		{
			'id': 'bookmark_icongap',
			'class': null
		}
	);
	var $bmark_a = $bmark.children("a");
	if( $bmark_a.size() ){
		$bmark_a.text( "挿栞");
		$bmark_a.attr( 'alt', 'しおりを挿む');
		$bmark_img.attr( 'src', '/novelview/img/bookmarker_now.png');
	}else{
		$bmark.text("");
		$bmark_a = $("<a />");
		$bmark_a.attr(
			{
				'href': bm_config,
				'alt': 'ブックマーク設定'
			}
		);
		$bmark_a.text("設定");
		$bmark.append($bmark_a);
		$bmark_img.attr( 'src', '/novelview/img/bookmarker.png');
	}

	var col = $header_org.find("li>a:first").css('border-left-color');
	if( $fav.size() ){
		$bmark_img.css(
			{
				'pointer-events': 'none',
				'position': 'absolute',
				'top': 0,
				'bottom': 0,
				'margin': 'auto',
				'padding': '0px 8px 0px 12px',
			}
		);
		$bmark_a.before($bmark_img);

		$bmark_a.css(
			'cssText',
			// padding-left は画像サイズから手計算。
			// ※自動計算はloadイベントを使用しなければならないので、
			//  表示の反映に若干ラグが出る(&コードがややこしくなる)。
			"padding-left: 33px !important;" +
			"border-left: none !important;"
		);

		// <a>要素のテキストを縦中央合わせ
		// ※heightが確定していなければ、適切なline-heightを求められない
		var Li_Fix = function($li_a){
			$li_a.outerHeight(headerH-1);
			$li_a.css(
				{
					'line-height': $li_a.height()+'px',
					'border-right': '1px solid ' + col,
				}
			);
			$li_a.parent().css('padding',0);
		};
		Li_Fix($bmark_a);
		Li_Fix($fav_a);
	}else{
		$down_node.css( 'border-right', '1px solid ' + col );
	}





	// 感想ページ
	if( location.href.indexOf("impression")>0){
		// コメントフォームをコメント一覧の上に持っていく
		var hyouka = "#hyoukalan";
		var $hyouka = $(hyouka);
		if( $hyouka.size() )
		{
			var $target = $("h1:eq(0)");
			$target.before($hyouka);
			$target.before($("<hr>"));
			
			// 横幅いっぱいにする
			$("textarea," + hyouka).css(
				{
					'box-sizing': 'border-box',
					'width': '100%'
				}
			);
			
			// "▽感想を書く"を消去
			$(".input").hide();
		}
	}
})();



// novel_headerのpositionを記録しないようにする
window.changeMenuBar = function(fixMenuBar){}