Readwise 阅读优化

优化Readwise阅读体验:保持原始格式和符号位置,实现智能分句,处理特殊光标符号,并通过<br>标签转换实现段落自动缩进

目前為 2025-01-07 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Readwise 阅读优化
// @namespace    readwise.reader
// @version      2.2.0
// @description  优化Readwise阅读体验:保持原始格式和符号位置,实现智能分句,处理特殊光标符号,并通过<br>标签转换实现段落自动缩进
// @match        https://read.readwise.io/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加自定义字体与段落样式
    const styleSheet = document.createElement('style');
    styleSheet.textContent = `
        @font-face {
            font-family: "仓耳华新体";
            src: url("https://ziti-beta.vercel.app/fonts/仓耳华新体.ttf") format("truetype");
            font-weight: normal;
            font-style: normal;
        }

        .document-content, .document-content * {
            font-family: "仓耳华新体", sans-serif !important;
        }

        .document-content p {
            text-indent: 2em !important;
            margin-bottom: 1em !important;
            line-height: 1.8 !important;
            display: block !important;
        }

        /* 处理链接样式 */
        .document-content p em {
            display: block !important;
            margin: 0.5em 0 !important;
            text-indent: 0 !important;
        }

        /* 处理以br开头的段落 */
        .document-content p[data-formatted="true"] br:first-child {
            display: none !important;
        }

        .document-content li p {
            text-indent: 0 !important;
        }
    `;
    document.head.appendChild(styleSheet);

    function splitParagraphsOnBr(p) {
        const html = p.innerHTML;
        if (html.includes('<br')) {
            // 按 <br> 分割文本,并过滤空白
            const parts = html.split(/<br\s*\/?>/i).map(s => s.trim()).filter(s => s);
            if (parts.length > 1) {
                const fragment = document.createDocumentFragment();
                let buffer = '';

                for (let i = 0; i < parts.length; i++) {
                    const part = parts[i];
                    const hasOpenQuote = (buffer + part).match(/[「『《]/g)?.length || 0;
                    const hasCloseQuote = (buffer + part).match(/[」』》]/g)?.length || 0;

                    if (hasOpenQuote > hasCloseQuote && i < parts.length - 1) {
                        buffer += part + ' ';
                        continue;
                    }

                    const newP = document.createElement('p');
                    newP.innerHTML = buffer + part;
                    newP.dataset.formatted = 'true';
                    fragment.appendChild(newP);
                    buffer = '';
                }

                if (buffer) {
                    const newP = document.createElement('p');
                    newP.innerHTML = buffer.trim();
                    newP.dataset.formatted = 'true';
                    fragment.appendChild(newP);
                }

                p.parentNode.replaceChild(fragment, p);
            }
        }
    }

    function formatAllParagraphs() {
        const paras = document.querySelectorAll('.document-content p:not([data-formatted])');
        let formattedCount = 0;

        paras.forEach(para => {
            let html = para.innerHTML.trim();
            if (!html) {
                para.dataset.formatted = 'true';
                return;
            }

            // 处理链接段落
            if (html.includes('[') && html.includes(']') && html.includes('http')) {
                const links = html.match(/\[.*?\].*?(?=\[|$)/g);
                if (links) {
                    const fragment = document.createDocumentFragment();
                    links.forEach(link => {
                        const newP = document.createElement('p');
                        newP.innerHTML = link.trim();
                        newP.dataset.formatted = 'true';
                        fragment.appendChild(newP);
                    });
                    para.parentNode.replaceChild(fragment, para);
                    return;
                }
            }

            // 先处理<br>标签
            if (html.includes('<br')) {
                const parts = html.split(/<br\s*\/?>/i).map(s => s.trim()).filter(s => s);
                if (parts.length > 1) {
                    let combinedParts = [];
                    let buffer = '';

                    for (let i = 0; i < parts.length; i++) {
                        const part = parts[i];
                        const combinedText = buffer + part;
                        const hasOpenQuote = (combinedText.match(/[「『《]/g) || []).length;
                        const hasCloseQuote = (combinedText.match(/[」』》]/g) || []).length;

                        if (hasOpenQuote > hasCloseQuote && i < parts.length - 1) {
                            buffer += part + ' ';
                        } else {
                            combinedParts.push(buffer + part);
                            buffer = '';
                        }
                    }

                    if (buffer) {
                        combinedParts.push(buffer.trim());
                    }

                    // 现在对每个合并后的部分进行句子分割
                    const fragment = document.createDocumentFragment();
                    combinedParts.forEach(part => {
                        // 在句子末尾插入<split>,但要避免在引号内分割
                        let segments = [];
                        let currentSegment = '';
                        let inQuote = false;

                        for (let i = 0; i < part.length; i++) {
                            const char = part[i];
                            currentSegment += char;

                            if (char === '「') inQuote = true;
                            else if (char === '」') {
                                inQuote = false;
                                // 如果引号后面紧跟句号等,不要立即分割
                                continue;
                            }

                            if (!inQuote && /[。!?!?]/.test(char) && i < part.length - 1 && !/["']/.test(part[i + 1])) {
                                segments.push(currentSegment.trim());
                                currentSegment = '';
                            }
                        }

                        if (currentSegment) {
                            segments.push(currentSegment.trim());
                        }

                        segments = segments.filter(s => s);

                        segments.forEach(segment => {
                            const newP = document.createElement('p');
                            newP.innerHTML = segment;
                            newP.dataset.formatted = 'true';
                            fragment.appendChild(newP);
                        });
                    });

                    para.parentNode.replaceChild(fragment, para);
                    formattedCount++;
                    return;
                }
            }

            // 如果没有<br>标签,使用常规的句子分割
            // 在句子末尾插入<split>
            html = html.replace(/([。!?!?])([^"'」』》])/g, '$1<split>$2');
            let segments = html.split(/<split>/).map(s => s.trim()).filter(s => s);

            if (segments.length <= 1) {
                para.dataset.formatted = 'true';
                return;
            }

            const fragment = document.createDocumentFragment();
            segments.forEach(segment => {
                const newP = document.createElement('p');
                newP.innerHTML = segment;
                newP.dataset.formatted = 'true';
                fragment.appendChild(newP);
            });

            para.parentNode.replaceChild(fragment, para);
            formattedCount++;
        });

        if (formattedCount > 0) {
            console.log(`分句成功:${formattedCount} 个段落已重新分段`);
        }

        return formattedCount;
    }

    const observer = new MutationObserver(() => {
        formatAllParagraphs();
    });
    observer.observe(document.body, { childList: true, subtree: true });

    document.addEventListener('DOMContentLoaded', () => {
        formatAllParagraphs();
        setTimeout(formatAllParagraphs, 500);
        setTimeout(formatAllParagraphs, 1000);
        setTimeout(formatAllParagraphs, 2000);
    });

    let attempts = 0;
    const maxAttempts = 20;
    const intervalId = setInterval(() => {
        const count = formatAllParagraphs();
        attempts++;
        if (count > 0 || attempts >= maxAttempts) {
            clearInterval(intervalId);
        }
    }, 500);

    window.addEventListener('load', () => {
        formatAllParagraphs();
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址