Skip to content

拖拉拽删除功能 #3

@koi-pig

Description

@koi-pig

// ==UserScript==
// @name File History Manager (Purple Style - Optimized)
// @namespace http://tampermonkey.net/
// @Version 1.1
// @description Manage file-history in localStorage with visual interface (Purple Style, Enhanced Drag & Drop)
// @author You
// @match https://www.dlink666.com/*
// @grant none
// ==/UserScript==

(function() {
'use strict';

// 创建管理面板
const panel = document.createElement('div');
panel.style.cssText = `
    position: fixed;
    bottom: 20px;
    right: 20px;
    width: 500px;
    max-height: 70vh;
    background-color: #f0e6ff;
    border: 2px dashed #d0bfff;
    border-radius: 10px;
    box-shadow: 0 4px 20px rgba(128, 0, 255, 0.2);
    padding: 15px;
    z-index: 9999;
    overflow: auto;
    display: none;
    font-family: Arial, sans-serif;
`;

// 面板标题
const title = document.createElement('h3');
title.textContent = 'File History Manager';
title.style.cssText = `
    margin-top: 0;
    color: #8000ff;
    font-size: 18px;
    margin-bottom: 15px;
`;
panel.appendChild(title);

// 按钮容器
const btnContainer = document.createElement('div');
btnContainer.style.cssText = `
    display: flex;
    gap: 10px;
    margin-bottom: 15px;
`;

// 刷新按钮
const refreshBtn = createButton('🔄 Refresh', '#e0c3fc', refreshList);
btnContainer.appendChild(refreshBtn);

// 添加新项按钮
const addBtn = createButton('➕ Add New', '#e0c3fc', () => showEditDialog(null));
btnContainer.appendChild(addBtn);

// 关闭按钮
const closeBtn = createButton('✖ Close', '#ffb3d9', () => panel.style.display = 'none');
closeBtn.style.marginLeft = 'auto';
btnContainer.appendChild(closeBtn);

panel.appendChild(btnContainer);

// 文件列表容器
const listContainer = document.createElement('div');
listContainer.style.cssText = `
    margin-top: 10px;
    min-height: 100px;
`;
listContainer.id = 'file-list-container';
panel.appendChild(listContainer);

// 添加面板到页面
document.body.appendChild(panel);

// 创建触发按钮
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '📁 File History';
toggleBtn.style.cssText = `
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 9998;
    background: linear-gradient(135deg, #e0c3fc 0%, #d0bfff 100%);
    color: #8000ff;
    border: none;
    border-radius: 8px;
    padding: 12px 24px;
    cursor: pointer;
    font-weight: bold;
    box-shadow: 0 2px 10px rgba(128, 0, 255, 0.3);
    transition: all 0.3s ease;
`;
toggleBtn.onmouseover = () => {
    toggleBtn.style.transform = 'translateY(-2px)';
    toggleBtn.style.boxShadow = '0 4px 15px rgba(128, 0, 255, 0.4)';
};
toggleBtn.onmouseout = () => {
    toggleBtn.style.transform = 'translateY(0)';
    toggleBtn.style.boxShadow = '0 2px 10px rgba(128, 0, 255, 0.3)';
};
toggleBtn.onclick = () => {
    const isVisible = panel.style.display === 'block';
    panel.style.display = isVisible ? 'none' : 'block';
    if (!isVisible) refreshList();
};
document.body.appendChild(toggleBtn);

// 创建编辑对话框
const editDialog = createEditDialog();
document.body.appendChild(editDialog);

// 辅助函数:创建按钮
function createButton(text, bgColor, onClick) {
    const btn = document.createElement('button');
    btn.textContent = text;
    btn.style.cssText = `
        background-color: ${bgColor};
        color: #8000ff;
        border: none;
        border-radius: 5px;
        padding: 8px 15px;
        cursor: pointer;
        font-size: 14px;
        transition: all 0.2s ease;
    `;
    btn.onmouseover = () => btn.style.transform = 'scale(1.05)';
    btn.onmouseout = () => btn.style.transform = 'scale(1)';
    btn.onclick = onClick;
    return btn;
}

// 创建编辑对话框
function createEditDialog() {
    const dialog = document.createElement('div');
    dialog.style.cssText = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 400px;
        background-color: #f0e6ff;
        border: 2px solid #d0bfff;
        border-radius: 10px;
        padding: 20px;
        z-index: 10000;
        display: none;
        box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
    `;
    dialog.innerHTML = `
        <h3 style="margin-top: 0; color: #8000ff;">Edit File Info</h3>
        <div style="margin-bottom: 10px;">
            <label style="display: block; margin-bottom: 5px; color: #8000ff;">Name:</label>
            <input type="text" id="edit-name" style="width: 100%; padding: 8px; border: 1px solid #d0bfff; border-radius: 5px; box-sizing: border-box;">
        </div>
        <div style="margin-bottom: 10px;">
            <label style="display: block; margin-bottom: 5px; color: #8000ff;">URL:</label>
            <input type="text" id="edit-url" style="width: 100%; padding: 8px; border: 1px solid #d0bfff; border-radius: 5px; box-sizing: border-box;">
        </div>
        <div style="margin-bottom: 10px;">
            <label style="display: block; margin-bottom: 5px; color: #8000ff;">Size (bytes):</label>
            <input type="number" id="edit-size" style="width: 100%; padding: 8px; border: 1px solid #d0bfff; border-radius: 5px; box-sizing: border-box;">
        </div>
        <div style="display: flex; gap: 10px; justify-content: flex-end;">
            <button id="edit-save" style="background-color: #e0c3fc; color: #8000ff; border: none; border-radius: 5px; padding: 8px 20px; cursor: pointer;">Save</button>
            <button id="edit-cancel" style="background-color: #ffdddd; color: #ff0000; border: none; border-radius: 5px; padding: 8px 20px; cursor: pointer;">Cancel</button>
        </div>
    `;
    return dialog;
}

// 显示编辑对话框
function showEditDialog(index) {
    const dialog = editDialog;
    const nameInput = dialog.querySelector('#edit-name');
    const urlInput = dialog.querySelector('#edit-url');
    const sizeInput = dialog.querySelector('#edit-size');
    const saveBtn = dialog.querySelector('#edit-save');
    const cancelBtn = dialog.querySelector('#edit-cancel');

    const fileHistory = getFileHistory();

    if (index !== null && fileHistory[index]) {
        nameInput.value = fileHistory[index].name || '';
        urlInput.value = fileHistory[index].url || '';
        sizeInput.value = fileHistory[index].size || 0;
    } else {
        nameInput.value = '';
        urlInput.value = '';
        sizeInput.value = 0;
    }

    dialog.style.display = 'block';

    saveBtn.onclick = () => {
        const newItem = {
            name: nameInput.value,
            url: urlInput.value,
            size: parseInt(sizeInput.value) || 0
        };

        if (index !== null) {
            fileHistory[index] = newItem;
        } else {
            fileHistory.push(newItem);
        }

        saveFileHistory(fileHistory);
        dialog.style.display = 'none';
        refreshList();
    };

    cancelBtn.onclick = () => {
        dialog.style.display = 'none';
    };
}

// 刷新列表(改进的拖拽实现)
function refreshList() {
    listContainer.innerHTML = '';
    const fileHistory = getFileHistory();

    if (!fileHistory || fileHistory.length === 0) {
        listContainer.innerHTML = '<div style="text-align: center; color: #8000ff; padding: 20px;">No files in history.</div>';
        return;
    }

    let draggedElement = null;
    let draggedIndex = null;

    // 反转数组以显示最新的在上面
    const reversedHistory = [...fileHistory].reverse();

    reversedHistory.forEach((file, displayIndex) => {
        // 计算原始索引
        const index = fileHistory.length - 1 - displayIndex;
        const fileItem = document.createElement('div');
        fileItem.className = 'file-item';
        fileItem.draggable = true;
        fileItem.setAttribute('data-index', index);
        fileItem.style.cssText = `
            padding: 12px;
            margin-bottom: 8px;
            border: 1px solid #d0bfff;
            border-radius: 8px;
            background-color: white;
            display: flex;
            align-items: center;
            cursor: move;
            transition: all 0.2s ease;
            user-select: none;
        `;

        // 拖拽手柄
        const dragHandle = document.createElement('div');
        dragHandle.innerHTML = '⋮⋮';
        dragHandle.style.cssText = `
            font-size: 20px;
            color: #d0bfff;
            margin-right: 10px;
            cursor: grab;
        `;
        fileItem.appendChild(dragHandle);

        // 文件信息
        const info = document.createElement('div');
        info.style.flex = '1';
        info.innerHTML = `
            <div style="font-weight: bold; color: #8000ff; margin-bottom: 4px;">${escapeHtml(file.name)}</div>
            <div style="font-size: 12px; color: #666;">Size: ${formatFileSize(file.size)}</div>
            <div style="font-size: 12px; color: #666;">URL: <a href="${escapeHtml(file.url)}" target="_blank" style="color: #8000ff;">Link</a></div>
        `;
        fileItem.appendChild(info);

        // 操作按钮
        const actions = document.createElement('div');
        actions.style.cssText = 'display: flex; gap: 5px;';

        const editBtn = createButton('✏️', '#e0c3fc', () => showEditDialog(index));
        editBtn.style.padding = '5px 10px';
        actions.appendChild(editBtn);

        const deleteBtn = createButton('🗑️', '#ffdddd', () => {
            if (confirm('Are you sure you want to delete this item?')) {
                fileHistory.splice(index, 1);
                saveFileHistory(fileHistory);
                refreshList();
            }
        });
        deleteBtn.style.padding = '5px 10px';
        deleteBtn.style.color = '#ff0000';
        actions.appendChild(deleteBtn);

        fileItem.appendChild(actions);
        listContainer.appendChild(fileItem);

        // 拖拽事件(改进版)
        fileItem.addEventListener('dragstart', (e) => {
            draggedElement = fileItem;
            draggedIndex = index;
            fileItem.style.opacity = '0.4';
            dragHandle.style.cursor = 'grabbing';
            e.dataTransfer.effectAllowed = 'move';
            e.dataTransfer.setData('text/html', fileItem.innerHTML);
        });

        fileItem.addEventListener('dragend', () => {
            fileItem.style.opacity = '1';
            dragHandle.style.cursor = 'grab';
            // 移除所有拖拽样式
            document.querySelectorAll('.file-item').forEach(item => {
                item.style.borderTop = '';
                item.style.borderBottom = '';
            });
        });

        fileItem.addEventListener('dragover', (e) => {
            e.preventDefault();
            if (draggedElement === fileItem) return;

            const rect = fileItem.getBoundingClientRect();
            const midpoint = rect.top + rect.height / 2;

            // 清除所有边框
            document.querySelectorAll('.file-item').forEach(item => {
                item.style.borderTop = '';
                item.style.borderBottom = '';
            });

            // 根据鼠标位置显示插入位置
            if (e.clientY < midpoint) {
                fileItem.style.borderTop = '3px solid #8000ff';
            } else {
                fileItem.style.borderBottom = '3px solid #8000ff';
            }
        });

        fileItem.addEventListener('dragleave', () => {
            fileItem.style.borderTop = '';
            fileItem.style.borderBottom = '';
        });

        fileItem.addEventListener('drop', (e) => {
            e.preventDefault();
            if (draggedElement === fileItem) return;

            const rect = fileItem.getBoundingClientRect();
            const midpoint = rect.top + rect.height / 2;
            const targetIndex = parseInt(fileItem.getAttribute('data-index'));

            // 确定插入位置
            let insertIndex = e.clientY < midpoint ? targetIndex : targetIndex + 1;

            // 调整索引(如果从上往下拖)
            if (draggedIndex < insertIndex) {
                insertIndex--;
            }

            // 因为显示是反转的,所以移动时也要考虑反转
            moveItem(draggedIndex, insertIndex);
            refreshList();
        });

        // 鼠标悬停效果
        fileItem.addEventListener('mouseenter', () => {
            if (!draggedElement) {
                fileItem.style.backgroundColor = '#f9f3ff';
                fileItem.style.transform = 'translateX(5px)';
            }
        });

        fileItem.addEventListener('mouseleave', () => {
            if (!draggedElement) {
                fileItem.style.backgroundColor = 'white';
                fileItem.style.transform = 'translateX(0)';
            }
        });
    });
}

// 移动项目位置
function moveItem(fromIndex, toIndex) {
    if (fromIndex === toIndex) return;

    const fileHistory = getFileHistory();
    const [item] = fileHistory.splice(fromIndex, 1);
    fileHistory.splice(toIndex, 0, item);
    saveFileHistory(fileHistory);
}

// 获取文件历史
function getFileHistory() {
    const data = localStorage.getItem('file-history');
    try {
        return data ? JSON.parse(data) : [];
    } catch (e) {
        console.error('Failed to parse file-history:', e);
        return [];
    }
}

// 保存文件历史
function saveFileHistory(history) {
    localStorage.setItem('file-history', JSON.stringify(history));
}

// 格式化文件大小
function formatFileSize(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

// HTML转义
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// 初始刷新
refreshList();

})();

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions