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

创建一个可编辑的浮窗记事本,支持拖拽和位置记忆

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         SIMPLE TEXT NOTE FOR COPY 浏览器浮窗记事本
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  创建一个可编辑的浮窗记事本,支持拖拽和位置记忆
// @author       leifeng
// @match        *://*/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 样式定义
    const style = `
        .floating-notepad {
            position: fixed;
            bottom: -310px;
            right: 20px;
            width: 300px;
            background-color: #e9f7e8;
            border-radius: 8px 8px 0 0;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            z-index: 9999;
            transition: all 0.3s ease;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
        }

        .floating-notepad.collapsed {
            bottom: -330px;
        }

        .notepad-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 5px 15px;
            background-color: #4a6cf7;
            color: white;
            border-radius: 8px 8px 0 0;
            user-select: none;
            cursor: move;
            height: 30px;
        }

        .notepad-header.over-limit {
            background-color: #dc3545;
        }

        .notepad-title {
            font-weight: 600;
            font-size: 15px;
        }

        .notepad-control-buttons {
            display: flex;
            align-items: center;
        }

        .notepad-toggle-btn, .notepad-center-btn, .notepad-hide-btn {
            font-size: 18px;
            cursor: pointer;
            padding: 0 10px; /* 增加按钮之间的间距 */
            transition: transform 0.2s ease;
        }

        .notepad-toggle-btn:hover, .notepad-center-btn:hover, .notepad-hide-btn:hover {
            transform: scale(1.2);
        }

        .notepad-footer {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 5px;
            padding: 8px 15px;
            border-top: 1px solid #e0e0e0;
        }

        .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;
        }

        .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: #ffffff;
            border: 1px solid #ced4da;
            border-radius: 4px;
            font-size: 14px;
            line-height: 1.4;
            min-height: 40px;
            transition: all 0.2s ease;
        }

        .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;
            color: #333333;
        }

        .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;
            color: #333333;
            transition: all 0.2s ease;
        }

        .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);
        }
    `;

    // 添加样式
    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;

        // 控制按钮容器
        const controlButtons = document.createElement('div');
        controlButtons.className = 'notepad-control-buttons';

        const centerBtn = document.createElement('div');
        centerBtn.className = 'notepad-center-btn';
        centerBtn.innerHTML = '⮙';
        centerBtn.addEventListener('click', centerNotepad);

        const toggleBtn = document.createElement('div');
        toggleBtn.className = 'notepad-toggle-btn';
        toggleBtn.innerHTML = '⮛';
        toggleBtn.addEventListener('click', toggleNotepad);

        const hideBtn = document.createElement('div');
        hideBtn.className = 'notepad-hide-btn';
        hideBtn.innerHTML = '✕';
        hideBtn.addEventListener('click', hideNotepad);

        controlButtons.appendChild(centerBtn); // 先添加 ⮙ 按钮
        controlButtons.appendChild(toggleBtn); // 再添加 ⮛ 按钮
        controlButtons.appendChild(hideBtn);   // 最后添加 ✕ 按钮

        header.appendChild(title);
        header.appendChild(controlButtons);

        // 内容区域
        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 initialDragPosition = { x: 0, y: 0 };
        let initialElementPosition = { bottom: 0, right: 20 };
        let expandedItemId = null;
        let currentEditingId = null;

        // 加载已保存的笔记
        loadNotes();

        // 窗口拖动功能
        header.addEventListener('mousedown', startDrag);

        // 点击外部关闭展开的编辑框
        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) {
            if (e.target.closest('.notepad-control-buttons')) return;

            e.preventDefault();
            isDragging = true;

            const rect = container.getBoundingClientRect();
            initialDragPosition = { x: e.clientX, y: e.clientY };

            const computedStyle = getComputedStyle(container);
            initialElementPosition = {
                bottom: parseInt(computedStyle.bottom) || 0,
                right: parseInt(computedStyle.right) || 20
            };

            container.style.zIndex = "10000";

            // 拖拽时隐藏编辑框
            if (currentEditingId) {
                saveCurrentEdit();
                collapseAllEditors();
            }
        }

        function handleDrag(e) {
            if (!isDragging) return;

            e.preventDefault();

            const dx = e.clientX - initialDragPosition.x;
            const dy = e.clientY - initialDragPosition.y;

            container.classList.remove('top-position', 'collapsed');

            // 计算新位置
            let newBottom = initialElementPosition.bottom - dy;
            let newRight = initialElementPosition.right + dx;

            // 边界限制
            const minBottom = -container.offsetHeight + header.offsetHeight;
            const maxBottom = window.innerHeight - header.offsetHeight;
            const minRight = 0;
            const maxRight = window.innerWidth - container.offsetWidth;

            newBottom = Math.max(minBottom, Math.min(maxBottom, newBottom));
            newRight = Math.max(minRight, Math.min(maxRight, newRight));

            // 更新位置
            container.style.bottom = `${newBottom}px`;
            container.style.right = `${newRight}px`;
            container.style.top = 'auto';
        }

        function stopDrag() {
            if (!isDragging) return;

            isDragging = false;
            container.style.zIndex = "9999";
        }

        // 笔记管理功能
        function loadNotes() {
            let items = getSavedItems();

            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) => {
                    if (e.target.closest('.notepad-item-edit-area')) return;

                    if (container.classList.contains('collapsed')) {
                        toggleNotepad();
                    }

                    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;
                });

                editArea.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter') {
                        if (e.shiftKey) {
                            e.preventDefault();
                            editArea.blur();
                        }
                    }
                });

                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');

            noteEl.classList.remove('notepad-item-empty');
            viewMode.style.display = 'none';
            editMode.style.display = 'block';
            editMode.querySelector('.notepad-item-edit-area').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) {
            newText = newText.trim();

            const items = getSavedItems();
            const itemIndex = items.findIndex(item => item.id === id);

            if (itemIndex !== -1) {
                items[itemIndex].text = newText.substring(0, MAX_CHAR_COUNT);
                items[itemIndex].timestamp = new Date().toISOString();
                saveItems(items);
                renderNotes(items);
            }

            resetHeader();
        }

        function saveCurrentEdit() {
            if (!currentEditingId) return;

            const editArea = document.querySelector(`.notepad-item[data-id="${currentEditingId}"] .notepad-item-edit-area`);
            if (editArea) {
                saveEditedNote(currentEditingId, editArea.value);
            }
        }

        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('确定要清除所有笔记吗?')) {
                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} 条笔记`;
        }

        function toggleNotepad() {
            container.classList.toggle('collapsed');

            if (container.classList.contains('collapsed')) {
                container.style.bottom = '-330px';
                container.style.top = 'auto';
            } else {
                container.style.bottom = '-310px';
                container.style.top = 'auto';
            }
        }

        function centerNotepad() {
            const windowHeight = window.innerHeight;
            const notepadHeight = container.offsetHeight;
            const centerPosition = (windowHeight - notepadHeight) / 2;

            container.classList.remove('top-position', 'collapsed');
            container.style.top = `${centerPosition}px`;
            container.style.bottom = 'auto';
        }

        function hideNotepad() {
            const windowHeight = window.innerHeight;
            const initialBottom = -310;

            container.classList.remove('top-position', 'collapsed');
            container.style.bottom = `${initialBottom - windowHeight}px`;
            container.style.top = 'auto';
        }

        // 添加鼠标事件监听
        document.addEventListener('mousemove', handleDrag);
        document.addEventListener('mouseup', stopDrag);
        document.addEventListener('mouseleave', stopDrag);
    }

    // 初始化记事本
    createNotepad();
})();