Fix Broken Paragraphs

Merge seemingly incomplete paragraphs with the one to its next.

// ==UserScript==
// @name         Fix Broken Paragraphs
// @namespace    http://tampermonkey.net/
// @version      7
// @description  Merge seemingly incomplete paragraphs with the one to its next.
// @match        *://ranobes.top/*
// @match        *://ranobes.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ranobes.top
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    function ellipsize(s) {
        return s.substring(0, 24).trim().replace(/[^\w\s\d]+$/, "") + "...";
    }

    function log(text) {
        console.log(`Fix Broken Paragraphs: ${text}`);
    }

    function regex(re_list, opts) {
        return new RegExp(re_list.map(r => r.source).join(''), opts)
    }

    let section = document.querySelector(".story #arrticle");

    if (!section) {
        return;
    }

    let is_broken = false;
    let nodes = [];
    // `nodes` contain `p` and `#text` nodes.
    // Order of node matters.

    // Every ranobes chapter content section has a paragraph not in a `p` node but as a `#text` node.
    // As of 12-2024.
    section.childNodes.forEach(child => {
        if (child.nodeType === Node.TEXT_NODE || child.nodeName === 'P') {
            nodes.push(child);
        }
    })

    const properPragraphPattern = regex([
        /([?!.](|\s*?['"\)])$)|/, // If ends with `?`, `!`, or `.` — and, with optional qoute
        /([\*\>\-}\]…:;]$)|/, // Consider proper if ends with either
        /(\.{3,}$)/ // If ends with ellipsis
    ]);

    for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        const text = node.textContent.trim();

        if (
            node &&
            // Consider those below this length as heading
            text.length > 32 &&
            !properPragraphPattern.test(text)
        ) {
            const nextNode = nodes[i + 1];

            if (nextNode) {
                log(`\`${ellipsize(node.textContent)}\` seems broken; merging with \`${ellipsize(nextNode.textContent)}\``);

                const nextText = nextNode.textContent.trim();
                node.textContent += ' ' + nextText.charAt(0).toLowerCase() + nextText.slice(1);
                nextNode.remove();
                nodes.splice(i + 1, 1);
                i--;

                is_broken = true;
            }
        }
    }

    if (!is_broken) {
        log("There's nothing to fix...");
    }
})();