Copy URL and Title with Management

Adds a draggable floating button to copy the current page's title and URL, with enhanced UI and error handling, with online management for all saved urls

目前为 2024-09-19 提交的版本。查看 最新版本

// ==UserScript==
// @name         Copy URL and Title with Management
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Adds a draggable floating button to copy the current page's title and URL, with enhanced UI and error handling, with online management for all saved urls
// @match        *://*/*
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @license      GPL-3.0
// ==/UserScript==

(function() {
    'use strict';

    // Helper function to generate UUID
    function generateUUID() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    // Initialize settings
    let settings = GM_getValue('settings', {
        account: generateUUID().slice(0, 10),
        accessToken: generateUUID(),
        serverUrl: 'http://localhost:5000/api/save',
        registerUrl: 'https://c.putaojie.top',
        debugMode: false,
        showFloatingButton: true
    });

    // Create floating button
    const button = document.createElement('div');
    button.innerHTML = '📋';
    button.style.cssText = `
        position: fixed;
        bottom: 20px;
        right: 20px;
        width: 50px;
        height: 50px;
        background-color: #4CAF50;
        color: white;
        border-radius: 50%;
        text-align: center;
        line-height: 50px;
        font-size: 24px;
        cursor: move;
        z-index: 9999;
        box-shadow: 0 2px 5px rgba(0,0,0,0.3);
        transition: background-color 0.3s;
        display: ${settings.showFloatingButton ? 'block' : 'none'};
    `;

    // Add button to the page
    document.body.appendChild(button);

    // Dragging functionality (unchanged)
    let isDragging = false;
    let startX, startY, startLeft, startTop;

    button.addEventListener('mousedown', function(e) {
        isDragging = true;
        startX = e.clientX;
        startY = e.clientY;
        startLeft = parseInt(window.getComputedStyle(button).left);
        startTop = parseInt(window.getComputedStyle(button).top);
        e.preventDefault();
    });

    document.addEventListener('mousemove', function(e) {
        if (!isDragging) return;
        let newLeft = startLeft + e.clientX - startX;
        let newTop = startTop + e.clientY - startY;
        
        newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - button.offsetWidth));
        newTop = Math.max(0, Math.min(newTop, window.innerHeight - button.offsetHeight));
        
        button.style.left = newLeft + 'px';
        button.style.top = newTop + 'px';
        button.style.bottom = 'auto';
        button.style.right = 'auto';
    });

    document.addEventListener('mouseup', function() {
        isDragging = false;
    });

    // Button click event
    button.addEventListener('click', function(e) {
        if (isDragging) return;

        const pageTitle = document.title;
        const pageUrl = window.location.href;
        const copyText = `${pageTitle}\n${pageUrl}`;

        GM_setClipboard(copyText, 'text');

        // Send data to server
        sendDataToServer(pageTitle, pageUrl);

        if (settings.debugMode) {
            alert(`Copied to clipboard and sending to server:\nTitle: ${pageTitle}\nURL: ${pageUrl}`);
        }
    });

    function sendDataToServer(title, url) {
        GM_xmlhttpRequest({
            method: "POST",
            url: settings.serverUrl,
            data: JSON.stringify({
                account: settings.account,
                accessToken: settings.accessToken,
                title: title,
                url: url
            }),
            headers: {
                "Content-Type": "application/json"
            },
            onload: function(response) {
                if (response.status === 200) {
                    button.innerHTML = '✅';
                    button.style.backgroundColor = '#45a049';
                    console.log("Data sent to server successfully:", response.responseText);
                } else {
                    button.innerHTML = '❌';
                    button.style.backgroundColor = '#f44336';
                    console.error("Failed to send data to server:", response.statusText);
                }
            },
            onerror: function(response) {
                button.innerHTML = '❌';
                button.style.backgroundColor = '#f44336';
                console.error("Failed to send data to server:", response.statusText);
            }
        });
    }

    // Settings popup
    function showSettings() {
        const settingsPopup = document.createElement('div');
        settingsPopup.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.2);
            z-index: 10001;
            font-family: Arial, sans-serif;
            width: 400px;
        `;
        settingsPopup.innerHTML = `
            <h2 style="margin-top: 0; margin-bottom: 20px; color: #333; text-align: center;">Settings</h2>
            <div style="display: flex; margin-bottom: 20px;">
                <button id="settingsTab" style="flex: 1; padding: 10px; background-color: #4CAF50; color: white; border: none; cursor: pointer;">Settings</button>
                <button id="addRecordTab" style="flex: 1; padding: 10px; background-color: #ddd; color: black; border: none; cursor: pointer;">Add Record</button>
            </div>
            <div id="settingsContent">
                <div style="margin-bottom: 15px;">
                    <label for="account" style="display: block; margin-bottom: 5px; color: #666;">Account:</label>
                    <input type="text" id="account" value="${settings.account}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                </div>
                <div style="margin-bottom: 15px;">
                    <label for="accessToken" style="display: block; margin-bottom: 5px; color: #666;">Access Token:</label>
                    <input type="text" id="accessToken" value="${settings.accessToken}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                </div>
                <div style="margin-bottom: 15px;">
                    <label for="serverUrl" style="display: block; margin-bottom: 5px; color: #666;">Server URL:</label>
                    <textarea id="serverUrl" style="width: 100%; height: 60px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;">${settings.serverUrl}</textarea>
                </div>
                <div style="margin-bottom: 15px;">
                    <label for="registerUrl" style="display: block; margin-bottom: 5px; color: #666;">Register URL:</label>
                    <textarea id="registerUrl" style="width: 100%; height: 60px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;">${settings.registerUrl}</textarea>
                </div>
                <div style="margin-bottom: 15px;">
                    <label style="display: flex; align-items: center; color: #666;">
                        <input type="checkbox" id="debugMode" ${settings.debugMode ? 'checked' : ''} style="margin-right: 5px;">
                        Debug Mode
                    </label>
                </div>
                <div style="margin-bottom: 15px;">
                    <label style="display: flex; align-items: center; color: #666;">
                        <input type="checkbox" id="showFloatingButton" ${settings.showFloatingButton ? 'checked' : ''} style="margin-right: 5px;">
                        Show Floating Button
                    </label>
                </div>
            </div>
            <div id="addRecordContent" style="display: none;">
                <div style="margin-bottom: 15px;">
                    <label for="bulkInput" style="display: block; margin-bottom: 5px; color: #666;">Paste Titles and URLs:</label>
                    <textarea id="bulkInput" style="width: 100%; height: 150px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;"></textarea>
                </div>
                <button id="addBulkRecords" style="background-color: #4CAF50; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; width: 100%;">Add Records</button>
            </div>
            <div style="text-align: right; margin-top: 20px;">
                <button id="saveSettings" style="background-color: #4CAF50; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Save</button>
                <button id="closeSettings" style="background-color: #f44336; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer;">Close</button>
            </div>
        `;
        document.body.appendChild(settingsPopup);

        const settingsTab = document.getElementById('settingsTab');
        const addRecordTab = document.getElementById('addRecordTab');
        const settingsContent = document.getElementById('settingsContent');
        const addRecordContent = document.getElementById('addRecordContent');

        settingsTab.addEventListener('click', function() {
            settingsContent.style.display = 'block';
            addRecordContent.style.display = 'none';
            settingsTab.style.backgroundColor = '#4CAF50';
            settingsTab.style.color = 'white';
            addRecordTab.style.backgroundColor = '#ddd';
            addRecordTab.style.color = 'black';
        });

        addRecordTab.addEventListener('click', function() {
            settingsContent.style.display = 'none';
            addRecordContent.style.display = 'block';
            addRecordTab.style.backgroundColor = '#4CAF50';
            addRecordTab.style.color = 'white';
            settingsTab.style.backgroundColor = '#ddd';
            settingsTab.style.color = 'black';
        });

        document.getElementById('saveSettings').addEventListener('click', function() {
            settings.account = document.getElementById('account').value;
            settings.accessToken = document.getElementById('accessToken').value;
            settings.serverUrl = document.getElementById('serverUrl').value;
            settings.registerUrl = document.getElementById('registerUrl').value;
            settings.debugMode = document.getElementById('debugMode').checked;
            settings.showFloatingButton = document.getElementById('showFloatingButton').checked;
            GM_setValue('settings', settings);
            button.style.display = settings.showFloatingButton ? 'block' : 'none';
            settingsPopup.remove();
        });

        document.getElementById('closeSettings').addEventListener('click', function() {
            settingsPopup.remove();
        });

        document.getElementById('addBulkRecords').addEventListener('click', function() {
            const bulkInput = document.getElementById('bulkInput').value;
            const lines = bulkInput.split('\n');
            for (let i = 0; i < lines.length; i += 2) {
                const title = lines[i].trim();
                const url = lines[i + 1] ? lines[i + 1].trim() : '';
                if (title && url) {
                    sendDataToServer(title, url);
                }
            }
            document.getElementById('bulkInput').value = '';
            alert('Records added successfully!');
        });
    }

    // Right-click menu for settings
    button.addEventListener('contextmenu', function(e) {
        e.preventDefault();
        showSettings();
    });

    // Register menu command for Tampermonkey
    GM_registerMenuCommand("Settings", showSettings);

    // Reset button state when the page is about to unload
    window.addEventListener('beforeunload', function() {
        button.innerHTML = '📋';
        button.style.backgroundColor = '#4CAF50';
    });
})();