智能网页书签管理器

一键复制网页标题和URL,支持在线管理书签,可自定义隐藏浮动按钮,让您的网络冲浪更加高效!

目前為 2024-09-19 提交的版本,檢視 最新版本

// ==UserScript==
// @name         智能网页书签管理器
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  一键复制网页标题和URL,支持在线管理书签,可自定义隐藏浮动按钮,让您的网络冲浪更加高效!
// @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,
    hiddenUrls: []
});
 
// 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: ${shouldShowButton() ? 'block' : 'none'};
`;
 
// Add button to the page
document.body.appendChild(button);
 
// Function to check if button should be shown
function shouldShowButton() {
    if (!settings.showFloatingButton) return false;
    const currentUrl = window.location.href;
    return !settings.hiddenUrls.some(url => currentUrl.includes(url));
}
 
// 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: 2147483647;  // 最高的 z-index 值
            font-family: Arial, sans-serif;
            width: 600px;
            max-height: 80vh;
            overflow-y: auto;
        `;
        settingsPopup.innerHTML = `
            <h2 style="margin-top: 0; margin-bottom: 20px; color: #333; text-align: left;">设置</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;">常规设置</button>
                <button id="addRecordTab" style="flex: 1; padding: 10px; background-color: #ddd; color: black; border: none; cursor: pointer;">添加记录</button>
                <button id="serverConfigTab" style="flex: 1; padding: 10px; background-color: #ddd; color: black; border: none; cursor: pointer;">服务器配置</button>
            </div>
            <div id="settingsContent">
                <div style="margin-bottom: 15px;">
                    <label for="account" style="display: block; margin-bottom: 5px; color: #666; text-align: left;">账户:</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; text-align: left;">访问令牌:</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 style="display: flex; align-items: center; color: #666; text-align: left;">
                        <input type="checkbox" id="debugMode" ${settings.debugMode ? 'checked' : ''} style="margin-right: 5px;">
                        调试模式
                    </label>
                </div>
                <div style="margin-bottom: 15px;">
                    <label style="display: flex; align-items: center; color: #666; text-align: left;">
                        <input type="checkbox" id="showFloatingButton" ${settings.showFloatingButton ? 'checked' : ''} style="margin-right: 5px;">
                        显示浮动按钮
                    </label>
                </div>
                <div style="margin-bottom: 15px;">
                    <label for="hiddenUrls" style="display: block; margin-bottom: 5px; color: #666; text-align: left;">隐藏按钮的 URL (每行一个):</label>
                    <div style="display: flex; align-items: center;">
                        <textarea id="hiddenUrls" style="flex-grow: 1; height: 100px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;">${settings.hiddenUrls.join('\n')}</textarea>
                        <button id="addCurrentUrl" style="margin-left: 10px; padding: 8px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">添加当前URL</button>
                    </div>
                </div>
            </div>
            <div id="addRecordContent" style="display: none;">
                <div style="margin-bottom: 15px;">
                    <label for="bulkInput" style="display: block; margin-bottom: 5px; color: #666; text-align: left;">粘贴标题和 URL (每行一个):</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%;">添加记录</button>
            </div>
            <div id="serverConfigContent" style="display: none;">
                <div style="margin-bottom: 15px;">
                    <label for="serverUrl" style="display: block; margin-bottom: 5px; color: #666; text-align: left;">服务器 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; text-align: left;">注册 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>
            <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;">保存</button>
                <button id="closeSettings" style="background-color: #f44336; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer;">关闭</button>
            </div>
        `;
        document.body.appendChild(settingsPopup);
 
    const settingsTab = document.getElementById('settingsTab');
    const addRecordTab = document.getElementById('addRecordTab');
    const serverConfigTab = document.getElementById('serverConfigTab');
    const settingsContent = document.getElementById('settingsContent');
    const addRecordContent = document.getElementById('addRecordContent');
    const serverConfigContent = document.getElementById('serverConfigContent');
 
    function setActiveTab(tab, content) {
        [settingsTab, addRecordTab, serverConfigTab].forEach(t => {
            t.style.backgroundColor = '#ddd';
            t.style.color = 'black';
        });
        [settingsContent, addRecordContent, serverConfigContent].forEach(c => c.style.display = 'none');
        tab.style.backgroundColor = '#4CAF50';
        tab.style.color = 'white';
        content.style.display = 'block';
    }
 
    settingsTab.addEventListener('click', () => setActiveTab(settingsTab, settingsContent));
    addRecordTab.addEventListener('click', () => setActiveTab(addRecordTab, addRecordContent));
    serverConfigTab.addEventListener('click', () => setActiveTab(serverConfigTab, serverConfigContent));
 
    document.getElementById('addCurrentUrl').addEventListener('click', function() {
        const currentUrl = window.location.href;
        const hiddenUrlsTextarea = document.getElementById('hiddenUrls');
        hiddenUrlsTextarea.value += (hiddenUrlsTextarea.value ? '\n' : '') + currentUrl;
    });
 
    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;
        settings.hiddenUrls = document.getElementById('hiddenUrls').value.split('\n').map(url => url.trim()).filter(url => url);
        GM_setValue('settings', settings);
        button.style.display = shouldShowButton() ? '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('记录添加成功!');
    });
}
 
// Right-click menu for settings
button.addEventListener('contextmenu', function(e) {
    e.preventDefault();
    showSettings();
});
 
// Register menu command for Tampermonkey
GM_registerMenuCommand("设置", showSettings);
 
// Reset button state when the page is about to unload
window.addEventListener('beforeunload', function() {
    button.innerHTML = '📋';
    button.style.backgroundColor = '#4CAF50';
});
})();