なろう・カクヨム閲覧設定

なろうとカクヨムの書式を整形して読みやすくします。

// ==UserScript==
// @name         なろう・カクヨム閲覧設定
// @namespace    http://tampermonkey.net/
// @version      2.00
// @description  なろうとカクヨムの書式を整形して読みやすくします。
// @author       You
// @match        https://kakuyomu.jp/works/*/episodes/*
// @match        https://ncode.syosetu.com/*
// @icon         none
// @grant        none
// @license      MIT License
// ==/UserScript==

const OPTION_1 = true; // 空行を圧縮する
const OPTION_2 = true; // 地の文・会話文間に空行を挿入
const OPTION_3 = true; // 行頭空白を挿入、または削除する
const OPTION_4 = true; // 三点リーダを修正する
const OPTION_5 = true; // 前書き・後書きを折りたたむ(なろう)

(function() {
    'use strict';
    let targets = document.querySelectorAll("div.widget-episodeBody p, div#novel_honbun p, div#honbun p");
    for(let i = 0; i < targets.length; i++){
        if(OPTION_1 && targets[i].innerHTML.match(/(<br.*>|^[  ]+$)/)){
            // 空行削除
            targets[i].remove();
            continue;
        }
        // 校正
        // セリフ内改行を削除
        if(targets[i].innerHTML.match(/^[「\((『【《]/) && !targets[i].innerHTML.match(/[」\))』》】]/)){
            let nex_elm = targets[i].nextElementSibling;
            if(nex_elm != null) {
                nex_elm.innerHTML = targets[i].innerHTML + nex_elm.innerHTML.replace(/^[  ]/, '');
                targets[i].remove();
                continue;
            }
        }
        // 各種書式整形
        targets[i].innerHTML = targets[i].innerHTML
            .replace(/\(/g, '(')// 半角括弧を全角に置換
            .replace(/\)/g, ')')// 半角括弧を全角に置換
            .replace(/^[  ]+|[  ]+$/, '')// 行頭/行末空白を削除
            .replace(/([\?\!?!⁉︎♪])(?![」)』  \?\!?!⁉︎♪]|$)/g, '$1 ')// 感嘆符と疑問符の組版処理
            .replace(/([\??])+/g, '$1')// 連続した符号の削除
            .replace(/([\!!])+/g, '$1')// 連続した符号の削除
            .replace(/([\!!][\??]|[\??][\!!])+/g, '$1')// 連続した符号の削除
            .replace(/[A-Za-z0-9]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 0xFEE0); })// 全角数字を半角に変換
            .replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,' )// 4桁以上の数字にカンマ付け
        ;
        // 字下げ(行頭空白)を挿入
        if(OPTION_3){
            targets[i].innerHTML = targets[i].innerHTML
                .replace(/^(?![  「\((『【《◆◇●○■□▽▼△▲★☆・×*--‐―ー])/, ' ')// 字下げ
        }
        // 三点リーダもどきを修正する
        if(OPTION_4){
            targets[i].innerHTML = targets[i].innerHTML
                .replace(/…+/g, '……')// 長すぎる三点リーダを圧縮
                .replace(/(?<!<rt>[・。.]*?)[・。.]{2,}(?![・。.]*?<\/rt>)/g, '……');// 三点リーダもどきを修正(傍点は除外)
        }
    }

    targets = document.querySelectorAll("div.widget-episodeBody p, div#novel_honbun p, div#honbun p");
    for(let i = 0; i < targets.length; i++){
        let pre_elm = targets[i].previousElementSibling;
        if(pre_elm == null) continue;

        // 会話文、地の文、記号のみの行(セパレータ行)それぞれの間に空行を挿入
        const regexp = /^[「(『【《].*[」)』》】]$/;
        if(OPTION_2 &&( targets[i].innerHTML.match(regexp) && !pre_elm.innerHTML.match(regexp) ||
           !targets[i].innerHTML.match(regexp) && pre_elm.innerHTML.match(regexp) ||
           targets[i].innerHTML.match(/^[◆◇●○■□▽▼△▲★☆・×*--‐―ー]+/) ||
           pre_elm.innerHTML.match(/^[◆◇●○■□▽▼△▲★☆・×*--‐―ー]+/)
          )){
            let new_blank = document.createElement("p");
            new_blank.innerHTML = "<br>";
            targets[i].before(new_blank);
        }
    }

    // 前書き後書き 折りたたみ
    if(OPTION_5){
        targets = document.querySelectorAll("div#novel_p, div#novel_a");
        for(let i = 0; i < targets.length; i++){
            let text = targets[i].innerHTML;
            const clickText = ()=>{
                targets[i].innerHTML = text;
                targets[i].removeEventListener('click', clickText);
            };
            targets[i].addEventListener('click', clickText);
            targets[i].innerHTML = (targets[i].id == "novel_p"? "▼ 前書きを表示する":"▼ 後書きを表示する");
        }
    }


})();