-
Notifications
You must be signed in to change notification settings - Fork 24
Open
Description
// ==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();
})();
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels