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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions