Greasy Fork

Greasy Fork is available in English.

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

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

当前为 2025-05-23 提交的版本,查看 最新版本

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