- // ==UserScript==
- // @name 文本大爆炸
- // @namespace http://tampermonkey.net/
- // @author 突徒土兔
- // @version 3.1
- // @description 仿照锤子的大爆炸,对选中文本进行分词
- // @match *://*/*
- // @license CC-BY-NC-4.0
- // @icon https://s2.loli.net/2024/09/25/6PxlMHA7EZVqwsJ.png
- // @require https://cdn.jsdelivr.net/npm/segmentit@2.0.3/dist/umd/segmentit.js
- // ==/UserScript==
-
- (function () {
- "use strict";
-
- // 触发分词按钮
- let button = null;
- // 弹出窗口
- let popupContainer = null;
- // 分词器
- const segmentit = Segmentit.useDefault(new Segmentit.Segment());
- // 是否处于拖动
- let isDragging = false;
- // 开始拖动的元素
- let startElement = null;
-
- /**
- * 创建样式
- */
- function createStyles() {
- const style = document.createElement("style");
- style.textContent = `
- .word-explosion-button {
- position: absolute;
- background-color: rgba(255,255,255, 0.4);
- color: #000;
- border: none;
- border-radius: 50%;
- cursor: pointer;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- font-size: 16px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.15);
- transition: all 0.3s ease;
- z-index: 9999;
- width: 30px;
- height: 30px;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .word-explosion-button:hover {
- background-color: rgba(255,255,255, 0.75);
- box-shadow: 0 4px 20px rgba(0,0,0,0.25);
- transform: scale(1.1);
- transition: transform 0.3s ease;
- }
- .word-explosion-popup {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background-color: rgba(240, 240, 240, 0.8);
- backdrop-filter: blur(10px);
- padding: 20px;
- border-radius: 10px;
- box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
- z-index: 10000;
- max-width: 80%;
- max-height: 80%;
- overflow: auto;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- align-items: center;
- opacity: 0;
- animation: fadeIn 0.5s ease forwards;
- }
-
- @keyframes fadeIn {
- from {
- opacity: 0;
- transform: translate(-50%, -50%) scale(0.9);
- }
- to {
- opacity: 1;
- transform: translate(-50%, -50%) scale(1);
- }
- }
- .word-explosion-word {
- margin: 2px;
- height: 30px; /* 让所有该类所有对象都有一样的高度 */
- padding: 4px 8px;
- background-color: rgba(255, 255, 255, 0.5);
- border: none;
- border-radius: 10px;
- cursor: pointer;
- transition: all 0.3s ease;
- font-size: 14px;
- display: inline-flex;
- align-items: center;
- }
- .word-explosion-word.selected {
- background-color: #0078D4;
- color: white;
- }
- .word-explosion-copy {
- display: block;
- margin-top: 15px;
- padding: 10px 20px;
- background-color: #0078D4;
- color: white;
- border: none;
- border-radius: 20px;
- cursor: pointer;
- font-size: 14px;
- transition: all 0.3s ease;
- }
- .word-explosion-copy:hover {
- background-color: #106EBE;
- }
- `;
- document.head.appendChild(style);
- }
-
- /**
- * 创建按钮
- * 该函数用于在页面中创建一个按钮,并将其添加到文档的 body 中。
- * 按钮的初始状态是隐藏的。
- */
- function createButton() {
- // 创建一个新的按钮元素
- button = document.createElement("button");
- // 设置按钮的文本内容
- button.textContent = "🔨";
- // 设置按钮的类名
- button.className = "word-explosion-button";
- // 设置按钮的初始显示状态为隐藏
- button.style.display = "none";
- // 将按钮添加到文档的 body 中
- document.body.appendChild(button);
- }
-
- /**
- * 显示按钮并将其定位到选中文本旁边
- * 该函数用于在用户选择文本后,显示按钮并将按钮定位到选中文本的旁边。
- */
- function showButtonAtSelection() {
- // 获取当前的文本选择对象
- const selection = window.getSelection();
- // 检查是否有选中的文本
- if (selection.rangeCount > 0) {
- // 获取选中的第一个范围
- const range = selection.getRangeAt(0);
- // 获取选中范围的边界矩形
- const rect = range.getBoundingClientRect();
- // 设置按钮的顶部位置为选中范围的底部加上滚动条的偏移量
- button.style.top = `${rect.bottom + window.scrollY + 5}px`;
- // 设置按钮的左侧位置为选中范围的左侧加上滚动条的偏移量
- button.style.left = `${rect.left + window.scrollX}px`;
- // 显示按钮
- button.style.display = "block";
- }
- }
-
- /**
- * 隐藏按钮
- * 该函数用于隐藏按钮。
- */
- function hideButton() {
- // 将按钮的显示状态设置为隐藏
- button.style.display = "none";
- }
-
- /**
- * 创建弹出窗口
- * 该函数用于创建一个弹出窗口,并将其添加到文档的 body 中。
- * 弹出窗口的初始状态是隐藏的。
- */
- function createPopup() {
- // 创建一个新的 div 元素作为弹出窗口的容器
- popupContainer = document.createElement("div");
- // 设置弹出窗口的类名为 "word-explosion-popup"
- popupContainer.className = "word-explosion-popup";
- // 设置弹出窗口的初始显示状态为隐藏
- popupContainer.style.display = "none";
- // 将弹出窗口添加到文档的 body 中
- document.body.appendChild(popupContainer);
-
- // 添加事件监听器,用于实现拖动选择功能
- popupContainer.addEventListener("mousedown", onMouseDown);
- document.addEventListener("mousemove", onMouseMove);
- document.addEventListener("mouseup", onMouseUp);
-
- // 添加事件监听器,用于隐藏弹出窗口
- document.addEventListener("click", (event) => {
- // 如果点击事件的目标不在弹出窗口内且不在按钮内,则隐藏弹出窗口
- if (
- !popupContainer.contains(event.target) &&
- !button.contains(event.target)
- ) {
- hidePopup();
- }
- });
- }
-
- /**
- * 显示弹出窗口
- * 该函数用于显示弹出窗口,并将分词结果显示在弹出窗口中。
- * @param {Array} words - 分词结果数组
- */
- function showPopup(words) {
- // 清空弹出窗口的内容
- popupContainer.innerHTML = "";
- // 遍历分词结果数组
- words.forEach((word) => {
- // 创建一个新的按钮元素
- const wordButton = document.createElement("button");
- // 设置按钮的文本内容为分词结果
- wordButton.textContent = word;
- // 设置按钮的类名为 "word-explosion-word"
- wordButton.className = "word-explosion-word";
- // 为按钮添加点击事件监听器,用于切换 "selected" 类
- wordButton.addEventListener("click", () =>
- wordButton.classList.toggle("selected")
- );
- // 将按钮添加到弹出窗口中
- popupContainer.appendChild(wordButton);
- });
-
- // 创建一个新的按钮元素,用于复制选中的文本
- const copyButton = document.createElement("button");
- // 设置按钮的文本内容为 "复制选中文本"
- copyButton.textContent = "复制选中文本";
- // 设置按钮的类名为 "word-explosion-copy"
- copyButton.className = "word-explosion-copy";
- // 设置按钮的宽度为 100%
- copyButton.style.width = "100%";
- // 为按钮添加点击事件监听器,用于复制选中的文本
- copyButton.addEventListener("click", copySelectedWords);
- // 将按钮添加到弹出窗口中
- popupContainer.appendChild(copyButton);
-
- // 显示弹出窗口
- popupContainer.style.display = "flex";
- }
-
- /**
- * 隐藏弹出窗口
- * 该函数用于隐藏弹出窗口。
- */
- function hidePopup() {
- // 将弹出窗口的显示状态设置为隐藏
- popupContainer.style.display = "none";
- }
-
- /**
- * 分词函数
- * 该函数用于对输入的文本进行分词,并返回分词结果数组。
- * @param {string} text - 需要分词的文本
- * @returns {Array} - 分词结果数组
- */
- function wordExplosion(text) {
- // 使用 segmentit 库对文本进行分词,并提取分词结果
- let result = segmentit.doSegment(text).map((item) => item.w);
- // 初始化一个空数组来存储带有空格的分词结果
- let newResult = [];
- // 分词过程中丢失了空格
- // 遍历原始文本,插入空格
- let textIndex = 0;
- for (let i = 0; i < result.length; i++) {
- newResult.push(result[i]);
- textIndex += result[i].length;
- while (textIndex < text.length && text[textIndex] === " ") {
- newResult.push(" ");
- textIndex++;
- }
- }
- // 在控制台输出分词结果
- console.log(`分词结果:\n${newResult}`);
- // 返回分词结果数组,如果结果为空则返回空数组
- return newResult || [];
- }
-
- /**
- * 复制选中的单词
- * 该函数用于将选中的单词复制到剪贴板,并弹出提示。
- */
- function copySelectedWords() {
- // 获取所有选中的单词按钮
- const selectedWords = Array.from(
- popupContainer.querySelectorAll(".word-explosion-word.selected")
- )
- // 提取每个按钮的文本内容
- .map((button) => button.textContent)
- // 将所有选中的单词连接成一个字符串
- .join("");
- // 将选中的单词复制到剪贴板
- navigator.clipboard
- .writeText(selectedWords)
- .then(() => {
- // 复制成功后弹出提示
- alert(`已复制:\n${selectedWords}`);
- })
- .catch((err) => {
- // 复制失败时在控制台输出错误信息
- console.error("复制失败: ", err);
- });
- }
-
- /**
- * 监听选择事件
- * 该函数用于监听用户的选择事件,并在用户选择文本后显示按钮。
- */
- document.addEventListener("selectionchange", function () {
- // 获取当前的文本选择对象
- const selection = window.getSelection();
- // 检查选中的文本是否不为空
- if (selection.toString().trim() !== "") {
- // 显示按钮并将其定位到选中文本旁边
- showButtonAtSelection();
- } else {
- // 隐藏按钮
- hideButton();
- }
- });
-
- /**
- * 监听按钮点击事件
- * 该函数用于在用户点击按钮后,对选中的文本进行分词,并显示弹出窗口。
- */
- function onButtonClick() {
- // 获取当前的文本选择对象
- const selection = window.getSelection();
- // 获取选中的文本
- const text = selection.toString();
- // 对选中的文本进行分词
- const words = wordExplosion(text);
- // 显示弹出窗口并将分词结果显示在弹出窗口中
- showPopup(words);
- // 隐藏按钮
- hideButton();
- }
-
- let longPressTimer = null;
- const longPressThreshold = 200; // 长按阈值,单位为毫秒
-
- /**
- * 处理鼠标按下事件
- * 该函数用于处理鼠标按下事件,实现长按选择功能。
- * @param {MouseEvent} event - 鼠标按下事件对象
- */
- function onMouseDown(event) {
- // 检查鼠标按下的目标是否为单词按钮
- if (event.target.classList.contains("word-explosion-word")) {
- // 设置一个定时器,用于检测长按操作
- longPressTimer = setTimeout(() => {
- // 如果长按时间超过阈值,则开始拖动选择
- isDragging = true;
- // 记录开始拖动的元素
- startElement = event.target;
- // 为开始拖动的元素添加 "selected" 类
- startElement.classList.add("selected");
- }, longPressThreshold);
- }
- }
-
- /**
- * 处理鼠标移动事件
- * 该函数用于处理鼠标移动事件,实现拖动选择功能。
- * @param {MouseEvent} event - 鼠标移动事件对象
- */
- function onMouseMove(event) {
- // 检查是否正在进行拖动选择
- if (isDragging && startElement) {
- // 获取当前鼠标位置下的元素
- const currentElement = document.elementFromPoint(
- event.clientX,
- event.clientY
- );
- // 检查当前元素是否为单词按钮且不是开始拖动的元素
- if (
- currentElement &&
- currentElement.classList.contains("word-explosion-word") &&
- currentElement !== startElement
- ) {
- // 为当前元素添加 "selected" 类
- currentElement.classList.add("selected");
- }
- }
- }
-
- /**
- * 处理鼠标松开事件
- * 该函数用于处理鼠标松开事件,结束拖动选择功能。
- * @param {MouseEvent} event - 鼠标松开事件对象
- */
- function onMouseUp(event) {
- // 清除长按定时器
- clearTimeout(longPressTimer);
- // 结束拖动选择
- isDragging = false;
- // 清空开始拖动的元素
- startElement = null;
- }
-
- /**
- * 初始化脚本
- * 该函数用于初始化脚本,创建样式、按钮和弹出窗口,并添加事件监听器。
- */
- function init() {
- // 创建样式
- createStyles();
- // 创建按钮
- createButton();
- // 创建弹出窗口
- createPopup();
- // 为按钮添加点击事件监听器
- button.addEventListener("click", onButtonClick);
- }
-
- // 初始化脚本
- init();
- })();