// ==UserScript==
// @name 超级书签管理器软件
// @namespace http://tampermonkey.net/
// @version 1.24
// @description 超级书签管理器是一个高效的书签管理工具,提供了添加、编辑、删除、排序、导出和导入书签的功能。界面简洁美观,支持拖拽排序和一次打开多个书签,提升用户的书签管理体验。
// @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @author wll
// @match *://*/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// @grant GM_log
// @license AGPL-3.0-or-later
// ==/UserScript==
/* 脚本特点及好处:
* 1. **集中管理**:提供一个居中显示的书签管理界面,可以一次性管理所有书签。
* 2. **快速添加**:通过菜单选项快速添加当前页面为书签,支持自定义书签名称。
* 3. **拖放排序**:支持拖放功能,可以方便地对书签进行排序调整。
* 4. **多列展示**:书签以最多四列的方式展示,布局合理,方便查看。
* 5. **书签编辑和删除**:提供直观的编辑和删除按钮,方便快速修改或移除书签。
* 6. **数据存储**:利用油猴自带的数据存储功能,确保书签数据持久保存。
* 7. **导入导出**:支持书签的导入和导出功能,方便在不同设备之间迁移书签。
* 8. **多选打开**:可以一次选择多个书签,并一键打开所有选中的书签。
* 9. **通知提示**:操作成功后会弹出通知提示,无需点击确认,操作体验流畅。
* 10. **精致界面**:界面美观简洁,字体和元素大小适中,小巧精致,提供良好的用户体验。
体验其便捷和高效的书签管理功能。
*/
(function() {
'use strict';
const defaultBookmarks = [
{ title: '百度', url: 'https://www.baidu.com' },
{ title: '搜狗', url: 'https://www.sogou.com' },
{ title: 'Bing', url: 'https://www.bing.com' },
{ title: '谷歌', url: 'https://www.google.com' }
];
let isManagerOpen = false;
// GM_log("脚本初始化中...");
GM_registerMenuCommand("管理书签", async () => {
// GM_log("管理书签菜单被点击");
if (!isManagerOpen) {
await openBookmarkManager();
}
});
GM_registerMenuCommand("添加当前页到书签", async () => {
// GM_log("添加当前页到书签菜单被点击");
await addCurrentPageBookmark();
});
async function initializeBookmarks() {
// GM_log("初始化书签数据...");
let bookmarks = await GM_getValue('bookmarks', null);
if (!bookmarks) {
bookmarks = defaultBookmarks;
await GM_setValue('bookmarks', bookmarks);
// GM_log("书签已初始化为默认数据");
}
return bookmarks;
}
async function getBookmarks() {
const bookmarks = await GM_getValue('bookmarks', defaultBookmarks);
// GM_log("书签数据已获取", bookmarks);
return bookmarks;
}
async function setBookmarks(bookmarks) {
// GM_log("设置书签数据", bookmarks);
await GM_setValue('bookmarks', bookmarks);
}
async function getSelectedBookmarks() {
const selectedBookmarks = await GM_getValue('selectedBookmarks', []);
// GM_log("获取选择的书签", selectedBookmarks);
return selectedBookmarks;
}
async function setSelectedBookmarks(selectedBookmarks) {
// GM_log("设置选择的书签", selectedBookmarks);
await GM_setValue('selectedBookmarks', selectedBookmarks);
}
function showNotification(message) {
// GM_log("显示通知:", message);
const notification = document.createElement('div');
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '10px';
notification.style.right = '10px';
notification.style.padding = '10px';
notification.style.backgroundColor = '#007bfa';
notification.style.color = 'white';
notification.style.borderRadius = '5px';
notification.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
notification.style.zIndex = '9999';
document.body.appendChild(notification);
setTimeout(() => {
document.body.removeChild(notification);
}, 1500); // 提示时间为 1.5 秒
}
async function addCurrentPageBookmark() {
// GM_log("添加当前页面到书签");
const bookmarks = await getBookmarks();
const currentUrl = window.location.href;
const currentTitle = document.title;
if (!bookmarks.some(bookmark => bookmark.url === currentUrl)) {
const newTitle = prompt('输入书签名称', currentTitle);
if (newTitle !== null && newTitle.trim() !== '') {
const newBookmark = { title: newTitle.trim(), url: currentUrl };
bookmarks.push(newBookmark);
await setBookmarks(bookmarks);
showNotification('书签已添加');
await renderBookmarks();
}
} else {
showNotification('该页面已经在书签中');
}
}
async function exportBookmarks() {
// GM_log("导出书签");
const bookmarks = await getBookmarks();
const date = new Date();
const dateStr = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}-${date.getMinutes().toString().padStart(2, '0')}`;
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(bookmarks, null, 2));
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", `超级书签管理器_${dateStr}.json`);
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
showNotification('书签已导出');
}
async function importBookmarks() {
// GM_log("导入书签");
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'application/json';
fileInput.onchange = async (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = async (e) => {
try {
const importedBookmarks = JSON.parse(e.target.result);
const existingBookmarks = await getBookmarks();
const newBookmarks = importedBookmarks.filter(bookmark =>
!existingBookmarks.some(existing => existing.url === bookmark.url)
);
if (newBookmarks.length > 0) {
const allBookmarks = existingBookmarks.concat(newBookmarks);
await setBookmarks(allBookmarks);
await renderBookmarks();
showNotification(`书签已导入,共导入${newBookmarks.length}条新书签`);
} else {
showNotification('没有新书签被导入');
}
} catch (error) {
showNotification('导入失败: 无效的 JSON 文件');
}
};
reader.readAsText(file);
}
};
fileInput.click();
}
// 渲染书签列表
async function renderBookmarks() {
// GM_log("渲染书签列表");
const bookmarkList = document.querySelector('.bookmark-list');
if (!bookmarkList) {
// GM_log("未找到书签列表元素");
return;
}
const bookmarks = await getBookmarks();
const selectedBookmarks = await getSelectedBookmarks();
// GM_log("即将渲染的书签数据:", bookmarks);
bookmarkList.innerHTML = ''; // Clear the list before re-rendering
bookmarks.forEach((bookmark, index) => {
const card = document.createElement('div');
card.style.position = 'relative';
card.style.border = '1px solid #ccc';
card.style.padding = '10px';
card.style.borderRadius = '5px';
card.style.backgroundColor = '#fff';
card.draggable = true;
card.ondragstart = (e) => {
e.dataTransfer.setData('text/plain', index);
};
card.ondragover = (e) => {
e.preventDefault();
};
card.ondrop = async (e) => {
e.preventDefault();
const draggedIndex = e.dataTransfer.getData('text/plain');
const draggedBookmark = bookmarks.splice(draggedIndex, 1)[0];
bookmarks.splice(index, 0, draggedBookmark);
await setBookmarks(bookmarks);
await renderBookmarks();
showNotification('书签已排序');
};
const editButton = document.createElement('span');
editButton.innerHTML = '✎';
editButton.title = '编辑';
editButton.style.position = 'absolute';
editButton.style.top = '5px';
editButton.style.left = '5px';
editButton.style.fontSize = '14px';
editButton.style.cursor = 'pointer';
editButton.onclick = async (e) => {
e.stopPropagation();
const newTitle = prompt('输入新的书签名称', bookmark.title);
if (newTitle !== null && newTitle.trim() !== '') {
bookmark.title = newTitle.trim();
await setBookmarks(bookmarks);
await renderBookmarks();
showNotification('书签已修改');
}
};
card.appendChild(editButton);
const removeButton = document.createElement('span');
removeButton.innerHTML = '🗑';
removeButton.title = '删除';
removeButton.style.position = 'absolute';
removeButton.style.bottom = '5px';
removeButton.style.left = '5px';
removeButton.style.fontSize = '14px';
removeButton.style.cursor = 'pointer';
removeButton.onclick = async (e) => {
e.stopPropagation();
bookmarks.splice(index, 1);
await setBookmarks(bookmarks);
await renderBookmarks();
showNotification('书签已删除');
};
card.appendChild(removeButton);
const title = document.createElement('div');
title.textContent = bookmark.title;
title.style.marginBottom = '30px';
title.style.cursor = 'pointer';
title.style.color = '#007bfa';
title.style.paddingRight = '20px';
title.onmouseover = () => { title.style.textDecoration = 'underline'; };
title.onmouseout = () => { title.style.textDecoration = 'none'; };
title.onclick = (e) => {
if (e.target !== editButton && e.target !== removeButton) {
window.open(bookmark.url, '_blank');
}
};
card.appendChild(title);
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'bookmark-checkbox';
checkbox.style.position = 'absolute';
checkbox.style.top = '5px';
checkbox.style.right = '5px';
checkbox.setAttribute('data-url', bookmark.url);
checkbox.checked = selectedBookmarks.includes(bookmark.url);
checkbox.onclick = async (e) => {
e.stopPropagation();
if (checkbox.checked) {
selectedBookmarks.push(bookmark.url);
} else {
const index = selectedBookmarks.indexOf(bookmark.url);
if (index !== -1) {
selectedBookmarks.splice(index, 1);
}
}
await setSelectedBookmarks(selectedBookmarks);
};
card.appendChild(checkbox);
card.onclick = (e) => {
if (e.target !== checkbox && e.target !== editButton && e.target !== removeButton && e.target !== title) {
window.open(bookmark.url, '_blank');
}
};
bookmarkList.appendChild(card);
});
// GM_log("书签列表渲染完成");
}
async function openBookmarkManager() {
// GM_log("打开书签管理器");
isManagerOpen = true;
await initializeBookmarks();
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
overlay.style.zIndex = '9999';
overlay.onclick = () => {
document.body.removeChild(overlay);
document.body.removeChild(bookmarkUI);
isManagerOpen = false;
// GM_log("关闭书签管理器");
};
const bookmarkUI = document.createElement('div');
bookmarkUI.className = 'bookmark-manager';
bookmarkUI.style.position = 'fixed';
bookmarkUI.style.top = '50%';
bookmarkUI.style.left = '50%';
bookmarkUI.style.transform = 'translate(-50%, -50%)';
bookmarkUI.style.width = '600px';
bookmarkUI.style.backgroundColor = '#f9f9f9';
bookmarkUI.style.border = '1px solid #ccc';
bookmarkUI.style.padding = '20px';
bookmarkUI.style.zIndex = '10000';
bookmarkUI.style.maxHeight = '80%';
bookmarkUI.style.overflowY = 'auto';
bookmarkUI.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
bookmarkUI.style.borderRadius = '10px';
bookmarkUI.style.fontFamily = 'Arial, sans-serif';
bookmarkUI.onclick = (e) => {
e.stopPropagation();
};
const title = document.createElement('h2');
title.textContent = '书签管理';
title.style.textAlign = 'center';
title.style.marginTop = '0';
bookmarkUI.appendChild(title);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'space-between';
buttonContainer.style.marginBottom = '10px';
bookmarkUI.appendChild(buttonContainer);
const addButton = document.createElement('button');
addButton.textContent = '添加当前页到书签';
addButton.style.backgroundColor = '#007bfa';
addButton.style.color = 'white';
addButton.style.border = 'none';
addButton.style.padding = '10px';
addButton.style.borderRadius = '5px';
addButton.style.cursor = 'pointer';
addButton.onmouseover = () => { addButton.style.backgroundColor = '#005bb5'; };
addButton.onmouseout = () => { addButton.style.backgroundColor = '#007bfa'; };
addButton.onclick = async () => {
await addCurrentPageBookmark();
await renderBookmarks();
};
buttonContainer.appendChild(addButton);
const exportButton = document.createElement('button');
exportButton.textContent = '导出书签';
exportButton.style.backgroundColor = '#007bfa';
exportButton.style.color = 'white';
exportButton.style.border = 'none';
exportButton.style.padding = '10px';
exportButton.style.borderRadius = '5px';
exportButton.style.cursor = 'pointer';
exportButton.onmouseover = () => { exportButton.style.backgroundColor = '#005bb5'; };
exportButton.onmouseout = () => { exportButton.style.backgroundColor = '#007bfa'; };
exportButton.onclick = async () => {
await exportBookmarks();
};
buttonContainer.appendChild(exportButton);
const importButton = document.createElement('button');
importButton.textContent = '导入书签';
importButton.style.backgroundColor = '#007bfa';
importButton.style.color = 'white';
importButton.style.border = 'none';
importButton.style.padding = '10px';
importButton.style.borderRadius = '5px';
importButton.style.cursor = 'pointer';
importButton.onmouseover = () => { importButton.style.backgroundColor = '#005bb5'; };
importButton.onmouseout = () => { importButton.style.backgroundColor = '#007bfa'; };
importButton.onclick = async () => {
await importBookmarks();
await renderBookmarks();
};
buttonContainer.appendChild(importButton);
const openMultipleButton = document.createElement('button');
openMultipleButton.textContent = '打开选择的书签';
openMultipleButton.style.backgroundColor = '#007bfa';
openMultipleButton.style.color = 'white';
openMultipleButton.style.border = 'none';
openMultipleButton.style.padding = '10px';
openMultipleButton.style.borderRadius = '5px';
openMultipleButton.style.cursor = 'pointer';
openMultipleButton.onmouseover = () => { openMultipleButton.style.backgroundColor = '#005bb5'; };
openMultipleButton.onmouseout = () => { openMultipleButton.style.backgroundColor = '#007bfa'; };
openMultipleButton.onclick = () => {
const selectedCheckboxes = document.querySelectorAll('.bookmark-checkbox:checked');
if (selectedCheckboxes.length === 0) {
showNotification('请选择至少一个书签');
return;
}
selectedCheckboxes.forEach(checkbox => {
const url = checkbox.getAttribute('data-url');
window.open(url, '_blank');
});
};
buttonContainer.appendChild(openMultipleButton);
const bookmarkList = document.createElement('div');
bookmarkList.className = 'bookmark-list';
bookmarkList.style.display = 'grid';
bookmarkList.style.gridTemplateColumns = 'repeat(auto-fit, minmax(120px, 1fr))';
bookmarkList.style.gap = '10px';
bookmarkList.style.minHeight = '200px';
bookmarkUI.appendChild(bookmarkList);
document.body.appendChild(overlay);
document.body.appendChild(bookmarkUI);
await renderBookmarks(); // 确保渲染书签列表在元素创建后执行
}
initializeBookmarks();
})();