// ==UserScript==
// @name SIMPLE TEXT NOTE FOR COPY 浏览器浮窗记事本
// @namespace http://tampermonkey.net/
// @version 1.0
// @description SIMPLE TEXT NOTE FOR COPY 创建一个可编辑的浮窗记事本,默认包含10个记事栏目
// @author leifeng
// @match *://*/*
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function ()
{
'use strict';
// 样式定义
const style = `
.floating-notepad {
position: fixed;
bottom: -200px; /* 默认位置下移,只显示顶部 */
right: 20px;
width: 300px;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 9999;
transition: bottom 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.notepad-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background-color: #4a6cf7; /* 默认蓝色 */
color: white;
border-radius: 8px 8px 0 0;
cursor: move;
user-select: none;
transition: background-color 0.3s ease;
}
.notepad-header.over-limit {
background-color: #dc3545; /* 超过限制时显示红色 */
}
.notepad-title {
font-weight: 600;
font-size: 15px;
transition: all 0.3s ease;
}
.notepad-content {
padding: 15px;
max-height: 350px;
display: flex;
flex-direction: column;
}
.notepad-list {
flex: 1;
margin-bottom: 10px;
max-height: 250px;
overflow-y: auto;
padding-right: 5px;
}
.notepad-item {
padding: 8px 10px;
margin-bottom: 8px;
background-color: #f8f9fa;
border-radius: 4px;
font-size: 14px;
line-height: 1.4;
position: relative;
transition: all 0.2s ease;
min-height: 40px;
}
.notepad-item:hover {
background-color: #f1f3f5;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.notepad-item-empty {
border: 1px dashed #ced4da;
background-color: transparent;
display: flex;
align-items: center;
color: #6c757d;
font-size: 13px;
}
.notepad-item-empty:hover {
border-color: #4a6cf7;
color: #4a6cf7;
}
.notepad-item-text {
padding: 0;
}
.notepad-item-char-count {
font-size: 11px;
color: #6c757d;
margin-top: 3px;
text-align: right;
}
.notepad-item-edit-area {
width: 100%;
padding: 5px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
resize: none;
margin-bottom: 3px;
box-sizing: border-box;
min-height: 40px;
background-color: white;
}
.notepad-item-edit-area.expanded {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% - 40px);
max-width: 500px;
height: calc(100% - 100px);
max-height: 400px;
z-index: 10000;
font-size: 16px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.notepad-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
}
.notepad-count {
font-size: 12px;
color: #6c757d;
}
.notepad-clear-all {
font-size: 12px;
color: #dc3545;
cursor: pointer;
text-decoration: underline;
transition: color 0.2s ease;
}
.notepad-clear-all:hover {
color: #c82333;
}
`;
// 添加样式
GM_addStyle(style);
// 存储键名
const STORAGE_KEY = 'floating_notepad_items';
const MAX_CHAR_COUNT = 500; // 最大字符数限制
const DEFAULT_TITLE = '浮窗记事本'; // 默认标题
// 创建记事本元素
function createNotepad()
{
// 主容器
const container = document.createElement('div');
container.className = 'floating-notepad';
// 头部
const header = document.createElement('div');
header.className = 'notepad-header';
const title = document.createElement('div');
title.className = 'notepad-title';
title.textContent = DEFAULT_TITLE;
header.appendChild(title);
// 内容区域
const content = document.createElement('div');
content.className = 'notepad-content';
// 列表区域
const listContainer = document.createElement('div');
listContainer.className = 'notepad-list';
// 底部区域
const footer = document.createElement('div');
footer.className = 'notepad-footer';
const countIndicator = document.createElement('div');
countIndicator.className = 'notepad-count';
updateCountIndicator();
const clearAllBtn = document.createElement('div');
clearAllBtn.className = 'notepad-clear-all';
clearAllBtn.textContent = '清除所有';
clearAllBtn.addEventListener('click', clearAllNotes);
footer.appendChild(countIndicator);
footer.appendChild(clearAllBtn);
content.appendChild(listContainer);
content.appendChild(footer);
container.appendChild(header);
container.appendChild(content);
document.body.appendChild(container);
// 状态管理
let isDragging = false;
let offsetY = 0;
let expandedItemId = null;
let currentEditingId = null; // 当前正在编辑的项目 ID
const DRAG_SPEED_FACTOR = 1.5; // 拖拽速度因子,增加后拖拽更快
// 加载已保存的笔记
loadNotes();
// 拖动功能
header.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', stopDrag);
// 点击外部关闭展开的编辑框
document.addEventListener('click', (e) =>
{
if (expandedItemId && !e.target.closest('.notepad-item-edit-area.expanded'))
{
collapseAllEditors();
}
});
// ESC 键关闭展开的编辑框
document.addEventListener('keydown', (e) =>
{
if (e.key === 'Escape' && expandedItemId)
{
collapseAllEditors();
}
});
// 函数定义
function startDrag(e)
{
e.preventDefault();
isDragging = true;
// 计算鼠标在头部的垂直偏移
const rect = header.getBoundingClientRect();
offsetY = e.clientY - rect.top;
// 提升层级
container.style.zIndex = "10000";
// 添加样式
header.style.cursor = 'grabbing';
}
function handleDrag(e)
{
if (!isDragging) return;
// 计算新的 top 值,保持鼠标与顶部的距离一致
const newTop = e.clientY - offsetY;
// 转换为 bottom 值,并应用速度因子
const newBottom = window.innerHeight - newTop - container.offsetHeight;
// 限制移动范围,确保不会完全移出屏幕
const minBottom = -container.offsetHeight + header.offsetHeight;
const maxBottom = window.innerHeight - header.offsetHeight;
// 应用拖拽速度因子
container.style.bottom = Math.max(minBottom, Math.min(maxBottom, newBottom * DRAG_SPEED_FACTOR)) + "px";
}
function stopDrag()
{
if (!isDragging) return;
isDragging = false;
container.style.zIndex = "9999";
header.style.cursor = 'move';
}
function loadNotes()
{
let items = getSavedItems();
// 如果没有保存的笔记,创建 10 个空的笔记
if (items.length === 0)
{
items = Array.from({ length: 10 }, (_, i) => ({
id: Date.now() + i,
text: '',
timestamp: new Date().toISOString()
}));
saveItems(items);
}
renderNotes(items);
}
function renderNotes(items)
{
listContainer.innerHTML = '';
items.forEach(item =>
{
const noteEl = document.createElement('div');
noteEl.className = item.text ? 'notepad-item' : 'notepad-item notepad-item-empty';
noteEl.dataset.id = item.id;
// 创建查看模式元素
const viewMode = document.createElement('div');
viewMode.className = 'notepad-item-view-mode';
const textEl = document.createElement('div');
textEl.className = 'notepad-item-text';
textEl.textContent = item.text || '点击编辑...';
const charCountEl = document.createElement('div');
charCountEl.className = 'notepad-item-char-count';
charCountEl.textContent = item.text ? `${item.text.length}/${MAX_CHAR_COUNT}` : `0/${MAX_CHAR_COUNT}`;
viewMode.appendChild(textEl);
viewMode.appendChild(charCountEl);
// 创建编辑模式元素
const editMode = document.createElement('div');
editMode.className = 'notepad-item-edit-mode';
editMode.style.display = 'none';
const editArea = document.createElement('textarea');
editArea.className = 'notepad-item-edit-area';
editArea.value = item.text;
editArea.maxLength = MAX_CHAR_COUNT; // 设置最大长度
const editCharCountEl = document.createElement('div');
editCharCountEl.className = 'notepad-item-char-count';
editCharCountEl.textContent = `${item.text.length}/${MAX_CHAR_COUNT}`;
editMode.appendChild(editArea);
editMode.appendChild(editCharCountEl);
// 添加到笔记元素
noteEl.appendChild(viewMode);
noteEl.appendChild(editMode);
// 点击进入编辑模式
noteEl.addEventListener('click', (e) =>
{
currentEditingId = item.id;
switchToEditMode(noteEl, item);
// 延迟扩展,确保编辑模式已激活
setTimeout(() =>
{
expandEditor(editArea, item.id);
checkCharCount(editArea); // 初始检查
}, 10);
});
// 编辑区域输入事件
editArea.addEventListener('input', () =>
{
const length = editArea.value.length;
editCharCountEl.textContent = `${length}/${MAX_CHAR_COUNT}`;
checkCharCount(editArea);
});
// 粘贴事件,处理超限情况
editArea.addEventListener('paste', () =>
{
setTimeout(() =>
{
const length = editArea.value.length;
editCharCountEl.textContent = `${length}/${MAX_CHAR_COUNT}`;
checkCharCount(editArea);
}, 10); // 等待粘贴完成
});
// 失去焦点事件
editArea.addEventListener('blur', () =>
{
saveEditedNote(item.id, editArea.value);
collapseAllEditors();
resetHeader(); // 重置标题栏
currentEditingId = null;
});
// 按 Enter 换行,Shift+Enter 保存
editArea.addEventListener('keydown', (e) =>
{
if (e.key === 'Enter')
{
if (e.shiftKey)
{
// Shift+Enter:保存
e.preventDefault();
editArea.blur();
} else
{
// Enter:正常换行
// 默认行为,不需要阻止
}
}
});
listContainer.appendChild(noteEl);
});
updateCountIndicator(items.length);
}
function switchToEditMode(noteEl, item)
{
// 关闭其他所有编辑模式
document.querySelectorAll('.notepad-item-edit-mode').forEach(el =>
{
if (el.parentElement.dataset.id !== item.id)
{
el.style.display = 'none';
el.parentElement.querySelector('.notepad-item-view-mode').style.display = 'block';
if (!el.parentElement.querySelector('.notepad-item-text').textContent.trim())
{
el.parentElement.classList.add('notepad-item-empty');
}
}
});
// 切换当前笔记到编辑模式
const viewMode = noteEl.querySelector('.notepad-item-view-mode');
const editMode = noteEl.querySelector('.notepad-item-edit-mode');
const editArea = editMode.querySelector('.notepad-item-edit-area');
noteEl.classList.remove('notepad-item-empty');
viewMode.style.display = 'none';
editMode.style.display = 'block';
editArea.focus();
}
function expandEditor(editArea, itemId)
{
// 先折叠所有已展开的编辑器
collapseAllEditors();
// 展开当前编辑器
editArea.classList.add('expanded');
expandedItemId = itemId;
// 存储光标位置
const cursorPos = editArea.selectionStart;
// 重新设置光标位置
setTimeout(() =>
{
editArea.focus();
editArea.setSelectionRange(cursorPos, cursorPos);
}, 10);
}
function collapseAllEditors()
{
document.querySelectorAll('.notepad-item-edit-area.expanded').forEach(el =>
{
el.classList.remove('expanded');
});
expandedItemId = null;
}
function saveEditedNote(id, newText)
{
// 限制文本长度不超过最大值
if (newText.length > MAX_CHAR_COUNT)
{
newText = newText.substring(0, MAX_CHAR_COUNT);
}
newText = newText.trim();
const items = getSavedItems();
const itemIndex = items.findIndex(item => item.id === id);
if (itemIndex !== -1)
{
items[itemIndex].text = newText;
items[itemIndex].timestamp = new Date().toISOString();
saveItems(items);
renderNotes(items);
}
resetHeader(); // 保存后重置标题栏
}
function checkCharCount(editArea)
{
const length = editArea.value.length;
// 当字符数达到或超过限制时显示红色标题
if (length >= MAX_CHAR_COUNT)
{
title.textContent = '字符数达限制';
header.classList.add('over-limit');
} else
{
title.textContent = DEFAULT_TITLE;
header.classList.remove('over-limit');
}
}
function resetHeader()
{
title.textContent = DEFAULT_TITLE;
header.classList.remove('over-limit');
}
function clearAllNotes()
{
if (confirm('确定要清除所有笔记吗?'))
{
// 创建 10 个空笔记而不是完全清空
const emptyItems = Array.from({ length: 10 }, (_, i) => ({
id: Date.now() + i,
text: '',
timestamp: new Date().toISOString()
}));
saveItems(emptyItems);
renderNotes(emptyItems);
}
}
function getSavedItems()
{
try
{
const saved = sessionStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
} catch (e)
{
console.error('Failed to load notes:', e);
return [];
}
}
function saveItems(items)
{
try
{
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(items));
} catch (e)
{
console.error('Failed to save notes:', e);
}
}
function updateCountIndicator(count = 0)
{
countIndicator.textContent = `共 ${count} 条笔记`;
}
}
// 初始化记事本
createNotepad();
})();