Read the section highlight

enlish

当前为 2025-09-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         Read the section highlight
// @namespace    http://tampermonkey.net/
// @version      2.4
// @license      MIT
// @description  enlish
// @author       an vu an
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const menu = document.createElement("div");
    menu.style.position = "fixed";
    menu.style.bottom = "20px";
    menu.style.right = "20px";
    menu.style.zIndex = "9999";
    menu.style.background = "#00000000";
    menu.style.border = "1px solid #ccc";
    menu.style.borderRadius = "8px";
    menu.style.padding = "10px";
    menu.style.fontSize = "14px";
    menu.style.fontFamily = "sans-serif";
    menu.style.boxShadow = "0 4px 12px rgba(0,0,0,0.3)";
    document.body.appendChild(menu);

    menu.innerHTML = `
      <div style="margin-bottom:6px; font-weight:bold;">🔊 Read</div>
      <button id="speakBtn">▶️ Read 1 time</button>
      <button id="speakLoopBtn">🔁 Read continuously</button>
      <button id="stopBtn">⏹ Stop</button>
    `;

    const speakBtn = menu.querySelector("#speakBtn");
    const speakLoopBtn = menu.querySelector("#speakLoopBtn");
    const stopBtn = menu.querySelector("#stopBtn");

    let selectedVoice = null;
    let loopMode = false;

    function loadVoices() {
        const voices = speechSynthesis.getVoices();
        const vi = voices.find(v => v.lang.startsWith("vi"));
        if (vi) {
            selectedVoice = vi;
        } else {
            selectedVoice = voices[0]; // fallback
        }
    }
    window.speechSynthesis.onvoiceschanged = loadVoices;
    loadVoices();

    function speakText(text) {
        const synth = window.speechSynthesis;
        synth.cancel();

        text = text.replace(/[^\p{L}\p{N}\s.,!?]/gu, "");

        if (!text) {
            alert("❗ No valid content to read");
            return;
        }

        const utter = new SpeechSynthesisUtterance(text);
        if (selectedVoice) {
            utter.voice = selectedVoice;
            utter.lang = selectedVoice.lang;
        } else {
            utter.lang = "vi-VN";
        }

        utter.onend = () => {
            if (loopMode) speakText(text);
        };

        synth.speak(utter);
    }

    speakBtn.addEventListener("click", () => {
        loopMode = false;
        const text = window.getSelection().toString().trim();
        if (!text) {
            alert("❗ Hãy bôi xanh văn bản để đọc");
            return;
        }
        speakText(text);
    });

    speakLoopBtn.addEventListener("click", () => {
        loopMode = true;
        const text = window.getSelection().toString().trim();
        if (!text) {
            alert("❗ Highlight text to read");
            return;
        }
        speakText(text);
    });

    stopBtn.addEventListener("click", () => {
        loopMode = false;
        window.speechSynthesis.cancel();
    });
})();