SIMPLE TEXT NOTE FOR COPY 浏览器浮窗记事本

SIMPLE TEXT NOTE FOR COPY 创建一个可编辑的浮窗记事本,默认包含10个记事栏目

目前為 2025-05-23 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
})();