// ==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/[email protected]/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();
})();