// ==UserScript==
// @name 聚合搜索引擎切换(自用)
// @namespace http://tampermonkey.net/
// @version v1.15
// @description 在页面底部显示一个聚合搜索引擎切换导航,支持自定义引擎和拖拽排序
// @author 晚风知我意
// @match *://*/*keyword=*
// @match *://*/*query=*
// @match *://*/*word=*
// @match *://*/*text=*
// @match *://*/*key=*
// @match *://*/*web=*
// @match *://*/*wd=*
// @match *://*/*kw=*
// @match *://*/*q=*
// @match *://*/*p=*
// @grant unsafeWindow
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-body
// @license MIT
// ==/UserScript==
const punkDeafultMark = "Google-Bing-Baidu-MetaSo-YandexSearch-Bilibili-ApkPure-Quark-Zhihu";
const defaultSearchEngines = [
{ name: "谷歌", searchUrl: "https://www.google.com/search?q={keyword}", searchkeyName: ["q"], matchUrl: /google\.com.*?search.*?q=/g, mark: "Google", svgCode: `
` },
{ name: "必应", searchUrl: "https://www.bing.com/search?q={keyword}", searchkeyName: ["q"], matchUrl: /bing\.com.*?search\?q=?/g, mark: "Bing", svgCode: `
` },
{ name: "百度", searchUrl: "https://www.baidu.com/s?wd={keyword}", searchkeyName: ["wd", "word"], matchUrl: /baidu\.com.*?w(or)?d=?/g, mark: "Baidu", svgCode: `
` },
{ name: "密塔", searchUrl: "https://metaso.cn/?s=itab1&q={keyword}", searchkeyName: ["q"], matchUrl: /metaso\.cn.*?q=/g, mark: "MetaSo", svgCode: `
` },
{ name: "Yandex", searchUrl: "https://yandex.com/search/?text={keyword}", searchkeyName: ["text"], matchUrl: /yandex\.com.*?text=/g, mark: "YandexSearch", svgCode: `
` },
{ name: "ApkPure", searchUrl: "https://apkpure.com/search?q={keyword}", searchkeyName: ["q"], matchUrl: /apkpure\.com.*?q=?/g, mark: "ApkPure", svgCode: `
` },
{ name: "哔哩哔哩", searchUrl: "https://m.bilibili.com/search?keyword={keyword}", searchkeyName: ["keyword"], matchUrl: /bilibili\.com.*?keyword=/g, mark: "Bilibili", svgCode: `
` },
{ name: "夸克", searchUrl: "https://quark.sm.cn/s?q={keyword}", searchkeyName: ["q"], matchUrl: /quark\.sm\.cn.*?q=/g, mark: "Quark", svgCode: `
` },
{ name: "知乎", searchUrl: "https://www.zhihu.com/search?type=content&q={keyword}", searchkeyName: ["q"], matchUrl: /zhihu\.com.*?q=/g, mark: "Zhihu", svgCode: `
` },
{
name: "GitHub",
searchUrl: "https://github.com/search?q={keyword}",
searchkeyName: ["q"],
matchUrl: /github\.com.*?search\?q=/,
mark: "GitHub",
svgCode: `
`
},
{
name: "YouTube",
searchUrl: "https://www.youtube.com/results?search_query={keyword}",
searchkeyName: ["search_query"],
matchUrl: "youtube\\.com.*?results\\?search_query=",
mark: "YouTube",
svgCode: `
`
},
{
name: "淘宝",
searchUrl: "https://s.taobao.com/search?q={keyword}",
searchkeyName: ["q"],
matchUrl: "taobao\\.com.*?search\\?q=",
mark: "TaoBao",
svgCode: `
`
},
{
name: "PubMed",
searchUrl: "https://pubmed.ncbi.nlm.nih.gov/?term={keyword}",
searchkeyName: ["term"],
matchUrl: "pubmed\\.ncbi\\.nlm\\.nih\\.gov.*?term={keyword}",
mark: "PubMed",
svgCode: `
`
},
{
name: "DuckDuckGo",
searchUrl: "https://duckduckgo.com/?q={keyword}",
searchkeyName: ["q"],
matchUrl: "duckduckgo\\.com.*?q={keyword}",
mark: "DuckDuckGo",
svgCode: `
`
},
{ name: "搜狗", searchUrl: "https://www.sogou.com/web?query={keyword}", searchkeyName: ["query"], matchUrl: /sogou\.com.*?query=/g, mark: "Sogou", svgCode: `
` },
{ name: "360搜索", searchUrl: "https://www.so.com/s?q={keyword}", searchkeyName: ["q"], matchUrl: /so\.com.*?q=/g, mark: "360Search", svgCode: `` },
{ name: "Startpage", searchUrl: "https://www.startpage.com/sp/search?query={keyword}", searchkeyName: ["query"], matchUrl: /startpage\.com.*?query=/g, mark: "Startpage", svgCode: `` },
{ name: "WolframAlpha", searchUrl: "https://www.wolframalpha.com/input?i={keyword}", searchkeyName: ["i"], matchUrl: /wolframalpha\.com.*?i=/g, mark: "WolframAlpha", svgCode: `` },
{ name: "谷歌学术", searchUrl: "https://scholar.google.com/scholar?q={keyword}", searchkeyName: ["q"], matchUrl: /scholar\.google\..*?q=/g, mark: "GoogleScholar", svgCode: `` },
{ name: "百度学术", searchUrl: "https://xueshu.baidu.com/s?wd={keyword}", searchkeyName: ["wd"], matchUrl: /xueshu\.baidu\.com.*?wd=/g, mark: "BaiduScholar", svgCode: `` },
{ name: "CNKI", searchUrl: "https://search.cnki.net/search.aspx?q={keyword}", searchkeyName: ["q"], matchUrl: /cnki\.net.*?q=/g, mark: "CNKI", svgCode: `` },
{ name: "StackOverflow", searchUrl: "https://stackoverflow.com/search?q={keyword}", searchkeyName: ["q"], matchUrl: /stackoverflow\.com.*?search\?q=/g, mark: "StackOverflow", svgCode: `` },
{ name: "MDN", searchUrl: "https://developer.mozilla.org/zh-CN/search?q={keyword}", searchkeyName: ["q"], matchUrl: /developer\.mozilla\.org.*?q=/g, mark: "MDN", svgCode: `` },
{ name: "Coursera", searchUrl: "https://www.coursera.org/search?query={keyword}", searchkeyName: ["query"], matchUrl: /coursera\.org.*?query=/g, mark: "Coursera", svgCode: `` },
{ name: "京东", searchUrl: "https://search.jd.com/Search?keyword={keyword}", searchkeyName: ["keyword"], matchUrl: /jd\.com.*?keyword=/g, mark: "JD", svgCode: `` },
{ name: "亚马逊", searchUrl: "https://www.amazon.com/s?k={keyword}", searchkeyName: ["k"], matchUrl: /amazon\..*?k=/g, mark: "Amazon", svgCode: `` },
{ name: "AliExpress", searchUrl: "https://www.aliexpress.com/wholesale?SearchText={keyword}", searchkeyName: ["SearchText"], matchUrl: /aliexpress\.com.*?SearchText=/g, mark: "AliExpress", svgCode: `` },
{ name: "微博", searchUrl: "https://s.weibo.com/weibo?q={keyword}", searchkeyName: ["q"], matchUrl: /weibo\.com.*?q=/g, mark: "Weibo", svgCode: `` },
{ name: "抖音", searchUrl: "https://www.douyin.com/search/{keyword}", searchkeyName: ["keyword"], matchUrl: /douyin\.com.*?search/g, mark: "Douyin", svgCode: `` },
{ name: "小红书", searchUrl: "https://www.xiaohongshu.com/search_result?keyword={keyword}", searchkeyName: ["keyword"], matchUrl: /xiaohongshu\.com.*?keyword=/g, mark: "Xiaohongshu", svgCode: `` },
{ name: "豆瓣", searchUrl: "https://www.douban.com/search?q={keyword}", searchkeyName: ["q"], matchUrl: /douban\.com.*?q=/g, mark: "Douban", svgCode: `` },
{ name: "IMDb", searchUrl: "https://www.imdb.com/find?q={keyword}", searchkeyName: ["q"], matchUrl: /imdb\.com.*?q=/g, mark: "IMDb", svgCode: `` },
{ name: "RottenTomatoes", searchUrl: "https://www.rottentomatoes.com/search?search={keyword}", searchkeyName: ["search"], matchUrl: /rottentomatoes\.com.*?search=/g, mark: "RottenTomatoes", svgCode: `` },
{ name: "Steam", searchUrl: "https://store.steampowered.com/search/?term={keyword}", searchkeyName: ["term"], matchUrl: /steampowered\.com.*?term=/g, mark: "Steam", svgCode: `` },
{ name: "Spotify", searchUrl: "https://open.spotify.com/search/{keyword}", searchkeyName: ["q"], matchUrl: /open\.spotify\.com.*?search/g, mark: "Spotify", svgCode: `` },
{ name: "网易云音乐", searchUrl: "https://music.163.com/#/search/m/?s={keyword}", searchkeyName: ["s"], matchUrl: /music\.163\.com.*?s=/g, mark: "NeteaseMusic", svgCode: `` },
{ name: "Pinterest", searchUrl: "https://www.pinterest.com/search/pins/?q={keyword}", searchkeyName: ["q"], matchUrl: /pinterest\..*?q=/g, mark: "Pinterest", svgCode: `` },
{ name: "Flickr", searchUrl: "https://www.flickr.com/search/?text={keyword}", searchkeyName: ["text"], matchUrl: /flickr\.com.*?text=/g, mark: "Flickr", svgCode: `` },
{ name: "维基百科", searchUrl: "https://zh.wikipedia.org/w/index.php?search={keyword}", searchkeyName: ["search"], matchUrl: /wikipedia\.org.*?search=/g, mark: "Wikipedia", svgCode: `` },
{ name: "ArchWiki", searchUrl: "https://wiki.archlinux.org/index.php?search={keyword}", searchkeyName: ["search"], matchUrl: /archlinux\.org.*?search=/g, mark: "ArchWiki", svgCode: `` },
{ name: "微信读书", searchUrl: "https://weread.qq.com/web/search/books?keyword={keyword}", searchkeyName: ["keyword"], matchUrl: /weread\.qq\.com.*?keyword=/g, mark: "WeRead", svgCode: `` },
{ name: "天眼查", searchUrl: "https://www.tianyancha.com/search?key={keyword}", searchkeyName: ["key"], matchUrl: /tianyancha\.com.*?key=/g, mark: "Tianyancha", svgCode: `` },
{
name: "Ecosia",
searchUrl: "https://www.ecosia.org/search?q={keyword}",
searchkeyName: ["q"],
matchUrl: "ecosia\\.org.*?search\\?q=",
mark: "Ecosia",
svgCode: `
`
},
];
let userSearchEngines = GM_getValue("userSearchEngines", []);
let searchUrlMap = [...defaultSearchEngines, ...userSearchEngines];
let lastScrollTop = 0;
let punkJetBoxVisible = true;
let currentInput = "";
let scriptLoaded = false;
let containerAdded = false;
let longPressTimer = null;
let currentDraggedButton = null;
let hasUnsavedChanges = false;
// 检查容器是否已存在
function engineContainerExists() {
return document.querySelector('.engine-container') !== null;
}
function isValidScope() {
return searchUrlMap.some(item => window.location.href.match(item.matchUrl) != null);
}
function getKeywords() {
try {
let keywords = "";
for (let urlItem of searchUrlMap) {
if (window.location.href.match(urlItem.matchUrl) != null) {
for (let keyItem of urlItem.searchkeyName) {
if (window.location.href.indexOf(keyItem) >= 0) {
let url = new URL(window.location.href);
keywords = url.searchParams.get(keyItem);
if (keywords) {
localStorage.setItem("last_successful_keywords", keywords);
sessionStorage.setItem("last_successful_keywords", keywords);
}
return keywords;
}
}
}
}
return sessionStorage.getItem("last_successful_keywords") || localStorage.getItem("last_successful_keywords") || "";
} catch (error) {
console.error("获取关键词时出错:", error.message, "当前URL:", window.location.href);
return "";
}
}
function monitorInputFields() {
const inputFields = document.querySelectorAll('input[type="text"], textarea');
if (inputFields.length > 0) {
inputFields.forEach(input => {
input.addEventListener('input', (event) => {
currentInput = event.target.value;
sessionStorage.setItem("currentInput", currentInput);
});
});
}
const observer = new MutationObserver(() => {
const newInputFields = document.querySelectorAll('input[type="text"], textarea');
newInputFields.forEach(input => {
if (!input.dataset.monitored) {
input.dataset.monitored = true;
input.addEventListener('input', (event) => {
currentInput = event.target.value;
sessionStorage.setItem("currentInput", currentInput);
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function updateSearchBoxPosition() {
const punkJetBox = document.getElementById("punkjet-search-box");
if (!punkJetBox) return;
punkJetBox.style.bottom = "0px";
punkJetBox.style.left = "2%";
punkJetBox.style.width = "96%";
}
// 从当前页面获取搜索引擎信息
function extractSearchEngineFromPage() {
const searchForms = document.querySelectorAll('form[action*="search"], form[action*="query"], form[action*="find"]');
const searchInputs = document.querySelectorAll('input[type="search"], input[name*="search"], input[name*="query"], input[name*="q"]');
let searchInfo = {
name: "",
searchUrl: "",
searchkeyName: [],
matchUrl: "",
mark: "",
found: false
};
if (searchForms.length > 0) {
const form = searchForms[0];
const action = form.getAttribute('action') || '';
const method = (form.getAttribute('method') || 'get').toLowerCase();
if (action) {
let baseUrl = action;
if (!action.startsWith('http')) {
baseUrl = new URL(action, window.location.origin).href;
}
const inputs = form.querySelectorAll('input[name]');
let keyParam = '';
for (let input of inputs) {
const name = input.getAttribute('name');
if (name && (name.includes('q') || name.includes('search') || name.includes('query') || name.includes('keyword'))) {
keyParam = name;
break;
}
}
if (!keyParam && searchInputs.length > 0) {
keyParam = searchInputs[0].getAttribute('name') || 'q';
}
if (keyParam) {
searchInfo.searchUrl = method === 'post' ?
`${baseUrl}?${keyParam}={keyword}` :
`${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${keyParam}={keyword}`;
searchInfo.searchkeyName = [keyParam];
searchInfo.found = true;
const domain = new URL(baseUrl).hostname.replace('www.', '');
searchInfo.name = domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1);
searchInfo.mark = domain.replace(/\./g, '_');
searchInfo.matchUrl = `.*${domain}.*`;
}
}
}
if (!searchInfo.found && searchInputs.length > 0) {
const input = searchInputs[0];
const name = input.getAttribute('name') || 'q';
const domain = window.location.hostname.replace('www.', '');
searchInfo.searchUrl = `${window.location.origin}/search?${name}={keyword}`;
searchInfo.searchkeyName = [name];
searchInfo.name = domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1);
searchInfo.mark = domain.replace(/\./g, '_');
searchInfo.matchUrl = `.*${domain}.*`;
searchInfo.found = true;
}
return searchInfo;
}
function createManagementPanel() {
const panel = document.createElement("div");
panel.id = "engine-management-panel";
panel.style.position = "fixed";
panel.style.top = "50%";
panel.style.left = "50%";
panel.style.transform = "translate(-50%, -50%)";
panel.style.width = "90%";
panel.style.maxWidth = "800px";
panel.style.height = "90vh";
panel.style.maxHeight = "90vh";
panel.style.backgroundColor = "#ffffff";
panel.style.borderRadius = "15px";
panel.style.boxShadow = "0 10px 30px rgba(0,0,0,0.3)";
panel.style.padding = "0";
panel.style.zIndex = "10000";
panel.style.display = "none";
panel.style.overflow = "hidden";
panel.style.fontFamily = "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif";
panel.style.display = "flex";
panel.style.flexDirection = "column";
panel.style.boxSizing = "border-box";
// 顶栏 - 固定高度 15vh
const header = document.createElement("div");
header.style.height = "15vh";
header.style.minHeight = "80px";
header.style.maxHeight = "120px";
header.style.backgroundColor = "#2c3e50";
header.style.color = "white";
header.style.padding = "20px";
header.style.borderRadius = "15px 15px 0 0";
header.style.position = "relative";
header.style.boxSizing = "border-box";
header.style.flexShrink = "0";
const title = document.createElement("h2");
title.textContent = "📊 搜索引擎管理中心";
title.style.margin = "0";
title.style.fontSize = "1.5em";
title.style.fontWeight = "300";
header.appendChild(title);
const subtitle = document.createElement("p");
subtitle.textContent = "管理您的搜索快捷方式";
subtitle.style.margin = "5px 0 0 0";
subtitle.style.opacity = "0.8";
subtitle.style.fontSize = "0.9em";
header.appendChild(subtitle);
const unsavedIndicator = document.createElement("div");
unsavedIndicator.id = "unsaved-indicator";
unsavedIndicator.textContent = "● 有未保存的更改";
unsavedIndicator.style.position = "absolute";
unsavedIndicator.style.top = "15px";
unsavedIndicator.style.right = "20px";
unsavedIndicator.style.color = "#e74c3c";
unsavedIndicator.style.fontSize = "0.8em";
unsavedIndicator.style.display = "none";
header.appendChild(unsavedIndicator);
panel.appendChild(header);
// 主内容区域 - 固定高度 65vh
const content = document.createElement("div");
content.style.height = "65vh";
content.style.minHeight = "300px";
content.style.position = "relative";
content.style.overflow = "hidden";
content.style.padding = "0";
content.style.boxSizing = "border-box";
content.style.display = "flex";
content.style.flexDirection = "column";
content.style.flexShrink = "0";
// 快速操作栏
const quickActions = document.createElement("div");
quickActions.style.padding = "20px";
quickActions.style.display = "flex";
quickActions.style.gap = "10px";
quickActions.style.flexWrap = "wrap";
quickActions.style.justifyContent = "space-between";
quickActions.style.backgroundColor = "#ffffff";
quickActions.style.borderBottom = "1px solid #ecf0f1";
quickActions.style.boxSizing = "border-box";
quickActions.style.flexShrink = "0";
const leftActionGroup = document.createElement("div");
leftActionGroup.style.display = "flex";
leftActionGroup.style.gap = "10px";
leftActionGroup.style.flexWrap = "wrap";
const extractBtn = createActionButton("🌐 自动添加", "#3498db", "自动识别当前页面的搜索引擎");
const addBtn = createActionButton("➕ 手动添加", "#27ae60", "手动添加新的搜索引擎");
leftActionGroup.appendChild(extractBtn);
leftActionGroup.appendChild(addBtn);
const rightActionGroup = document.createElement("div");
rightActionGroup.style.display = "flex";
rightActionGroup.style.gap = "10px";
rightActionGroup.style.flexWrap = "wrap";
const saveBtn = document.createElement("button");
saveBtn.id = "panel-save-btn";
saveBtn.innerHTML = "💾 保存设置";
saveBtn.title = "保存当前设置";
saveBtn.style.cssText = `
padding: 10px 20px;
background: #95a5a6;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 5px;
transition: all 0.3s ease;
opacity: 0.7;
pointer-events: none;
min-width: 120px;
justify-content: center;
`;
const resetBtn = createActionButton("🔄 恢复默认", "#e74c3c", "恢复默认搜索引擎设置");
rightActionGroup.appendChild(saveBtn);
rightActionGroup.appendChild(resetBtn);
quickActions.appendChild(leftActionGroup);
quickActions.appendChild(rightActionGroup);
content.appendChild(quickActions);
// 列表区域 - 可滚动
const listSection = document.createElement("div");
listSection.style.flex = "1";
listSection.style.overflow = "hidden";
listSection.style.padding = "0 20px";
listSection.style.boxSizing = "border-box";
listSection.style.display = "flex";
listSection.style.flexDirection = "column";
listSection.style.overflow = "auto"
const listTitle = document.createElement("h3");
listTitle.textContent = "📋 已配置的搜索引擎";
listTitle.style.color = "#2c3e50";
listTitle.style.margin = "15px 0";
listTitle.style.fontWeight = "500";
listTitle.style.flexShrink = "0";
listSection.appendChild(listTitle);
const engineList = document.createElement("div");
engineList.id = "engine-management-list";
engineList.style.flex = "1";
engineList.style.overflowY = "auto";
engineList.style.overflowX = "hidden";
engineList.style.display = "grid";
engineList.style.gap = "10px";
engineList.style.gridTemplateColumns = "repeat(auto-fill, minmax(300px, 1fr))";
engineList.style.paddingBottom = "10px";
engineList.style.boxSizing = "border-box";
listSection.appendChild(engineList);
content.appendChild(listSection);
// 添加引擎表单区域
const formSection = document.createElement("div");
formSection.id = "add-engine-form";
formSection.style.display = "none";
formSection.style.backgroundColor = "#f8f9fa";
formSection.style.padding = "20px";
formSection.style.borderRadius = "10px";
formSection.style.margin = "10px 0";
formSection.style.boxSizing = "border-box";
formSection.style.flexShrink = "0";
const formTitle = document.createElement("h3");
formTitle.textContent = "✨ 添加新搜索引擎";
formTitle.style.color = "#2c3e50";
formTitle.style.marginBottom = "15px";
formSection.appendChild(formTitle);
const form = document.createElement("div");
form.style.display = "grid";
form.style.gap = "15px";
form.style.gridTemplateColumns = "1fr 1fr";
const fields = [
{ label: "引擎名称", placeholder: "例如: Google", type: "text", id: "engine-name", required: true },
{ label: "唯一标识", placeholder: "例如: google", type: "text", id: "engine-mark", required: true },
{ label: "搜索URL", placeholder: "使用 {keyword} 作为占位符", type: "text", id: "engine-url", required: true, fullWidth: true },
{ label: "关键词参数", placeholder: "例如: q,query,search", type: "text", id: "engine-keys", required: true, fullWidth: true }
];
fields.forEach(field => {
const container = document.createElement("div");
if (field.fullWidth) {
container.style.gridColumn = "1 / -1";
}
const label = document.createElement("label");
label.textContent = field.label;
label.style.display = "block";
label.style.marginBottom = "5px";
label.style.fontWeight = "500";
label.style.color = "#34495e";
container.appendChild(label);
const input = document.createElement("input");
input.type = field.type;
input.placeholder = field.placeholder;
input.id = field.id;
input.required = field.required;
input.style.width = "100%";
input.style.padding = "10px";
input.style.border = "1px solid #ddd";
input.style.borderRadius = "5px";
input.style.fontSize = "14px";
container.appendChild(input);
form.appendChild(container);
});
const iconContainer = document.createElement("div");
iconContainer.style.gridColumn = "1 / -1";
const iconTitle = document.createElement("h4");
iconTitle.textContent = "🎨 图标设置";
iconTitle.style.marginBottom = "10px";
iconTitle.style.color = "#34495e";
iconContainer.appendChild(iconTitle);
const iconGrid = document.createElement("div");
iconGrid.style.display = "grid";
iconGrid.style.gridTemplateColumns = "1fr 2fr 1fr";
iconGrid.style.gap = "10px";
iconGrid.style.alignItems = "end";
const typeGroup = document.createElement("div");
const typeLabel = document.createElement("label");
typeLabel.textContent = "图标类型";
typeLabel.style.display = "block";
typeLabel.style.marginBottom = "5px";
typeLabel.style.fontWeight = "500";
typeGroup.appendChild(typeLabel);
const iconTypeSelect = document.createElement("select");
iconTypeSelect.id = "icon-type";
iconTypeSelect.style.width = "100%";
iconTypeSelect.style.padding = "10px";
iconTypeSelect.style.border = "1px solid #ddd";
iconTypeSelect.style.borderRadius = "5px";
["svg", "image", "text", "emoji"].forEach(type => {
const option = document.createElement("option");
option.value = type;
option.textContent = type.charAt(0).toUpperCase() + type.slice(1);
iconTypeSelect.appendChild(option);
});
typeGroup.appendChild(iconTypeSelect);
iconGrid.appendChild(typeGroup);
const inputGroup = document.createElement("div");
const inputLabel = document.createElement("label");
inputLabel.textContent = "图标内容";
inputLabel.style.display = "block";
inputLabel.style.marginBottom = "5px";
inputLabel.style.fontWeight = "500";
inputGroup.appendChild(inputLabel);
const iconInput = document.createElement("input");
iconInput.type = "text";
iconInput.id = "icon-input";
iconInput.placeholder = "SVG代码、图片URL、文字或表情符号";
iconInput.style.width = "100%";
iconInput.style.padding = "10px";
iconInput.style.border = "1px solid #ddd";
iconInput.style.borderRadius = "5px";
inputGroup.appendChild(iconInput);
iconGrid.appendChild(inputGroup);
const previewGroup = document.createElement("div");
const previewButton = document.createElement("button");
previewButton.textContent = "预览图标";
previewButton.style.width = "100%";
previewButton.style.padding = "10px";
previewButton.style.backgroundColor = "#3498db";
previewButton.style.color = "white";
previewButton.style.border = "none";
previewButton.style.borderRadius = "5px";
previewButton.style.cursor = "pointer";
previewButton.id = "preview-icon";
previewGroup.appendChild(previewButton);
iconGrid.appendChild(previewGroup);
iconContainer.appendChild(iconGrid);
const previewContainer = document.createElement("div");
previewContainer.style.gridColumn = "1 / -1";
previewContainer.style.marginTop = "15px";
previewContainer.style.textAlign = "center";
const previewLabel = document.createElement("label");
previewLabel.textContent = "图标预览 (推荐比例 8:5)";
previewLabel.style.display = "block";
previewLabel.style.marginBottom = "10px";
previewLabel.style.fontWeight = "500";
previewContainer.appendChild(previewLabel);
const iconPreview = document.createElement("div");
iconPreview.id = "icon-preview";
iconPreview.style.width = "88px";
iconPreview.style.height = "55px";
iconPreview.style.border = "2px dashed #bdc3c7";
iconPreview.style.borderRadius = "8px";
iconPreview.style.margin = "0 auto";
iconPreview.style.display = "flex";
iconPreview.style.justifyContent = "center";
iconPreview.style.alignItems = "center";
iconPreview.style.overflow = "hidden";
iconPreview.style.background = "#ecf0f1";
previewContainer.appendChild(iconPreview);
iconContainer.appendChild(previewContainer);
form.appendChild(iconContainer);
formSection.appendChild(form);
const formActions = document.createElement("div");
formActions.style.gridColumn = "1 / -1";
formActions.style.display = "flex";
formActions.style.gap = "10px";
formActions.style.marginTop = "20px";
const saveFormBtn = createActionButton("💾 保存引擎", "#27ae60", "");
const cancelFormBtn = createActionButton("❌ 取消", "#95a5a6", "");
formActions.appendChild(saveFormBtn);
formActions.appendChild(cancelFormBtn);
formSection.appendChild(formActions);
// 将表单添加到列表区域
listSection.appendChild(formSection);
panel.appendChild(content);
// 底栏 - 固定高度 20vh
const footer = document.createElement("div");
footer.style.height = "20vh";
footer.style.minHeight = "60px";
footer.style.maxHeight = "90px";
footer.style.backgroundColor = "#ecf0f1";
footer.style.padding = "15px 20px";
footer.style.borderTop = "1px solid #bdc3c7";
footer.style.display = "flex";
footer.style.justifyContent = "space-between";
footer.style.alignItems = "center";
footer.style.boxSizing = "border-box";
footer.style.flexShrink = "0";
footer.style.borderRadius = "0 0 15px 15px";
const selectedCount = document.createElement("span");
selectedCount.id = "selected-count";
selectedCount.textContent = "已选择 0 个引擎";
selectedCount.style.color = "#7f8c8d";
selectedCount.style.fontSize = "0.9em";
footer.appendChild(selectedCount);
const footerActions = document.createElement("div");
footerActions.style.display = "flex";
footerActions.style.gap = "10px";
const closeBtn = createActionButton("❌ 关闭", "#95a5a6", "");
footerActions.appendChild(closeBtn);
footer.appendChild(footerActions);
panel.appendChild(footer);
document.body.appendChild(panel);
// 事件监听器
extractBtn.addEventListener("click", extractFromCurrentPage);
addBtn.addEventListener("click", () => showAddForm(true));
resetBtn.addEventListener("click", resetToDefault);
previewButton.addEventListener("click", previewIcon);
saveFormBtn.addEventListener("click", saveNewEngine);
cancelFormBtn.addEventListener("click", () => showAddForm(false));
saveBtn.addEventListener("click", saveEngineSettings);
closeBtn.addEventListener("click", closeManagementPanel);
panel.addEventListener("click", (e) => {
if (e.target === panel) {
closeManagementPanel();
}
});
return panel;
}
function createActionButton(text, color, title) {
const button = document.createElement("button");
button.textContent = text;
button.title = title;
button.style.padding = "10px 15px";
button.style.backgroundColor = color;
button.style.color = "white";
button.style.border = "none";
button.style.borderRadius = "8px";
button.style.cursor = "pointer";
button.style.fontSize = "14px";
button.style.minWidth = "120px";
button.style.transition = "all 0.3s ease";
button.addEventListener("mouseenter", () => {
button.style.transform = "translateY(-2px)";
button.style.boxShadow = "0 4px 8px rgba(0,0,0,0.2)";
});
button.addEventListener("mouseleave", () => {
button.style.transform = "translateY(0)";
button.style.boxShadow = "none";
});
return button;
}
function closeManagementPanel() {
if (hasUnsavedChanges) {
if (confirm("⚠️ 您有未保存的更改,确定要关闭吗?")) {
document.getElementById("engine-management-panel").style.display = "none";
hasUnsavedChanges = false;
}
} else {
document.getElementById("engine-management-panel").style.display = "none";
}
}
function markUnsavedChanges() {
hasUnsavedChanges = true;
const indicator = document.getElementById("unsaved-indicator");
const saveBtn = document.getElementById("panel-save-btn");
if (indicator) {
indicator.style.display = "block";
}
if (saveBtn) {
saveBtn.style.opacity = "1";
saveBtn.style.pointerEvents = "auto";
saveBtn.style.background = "#e67e22";
saveBtn.innerHTML = "💾 保存更改";
saveBtn.addEventListener("mouseenter", function() {
this.style.transform = "translateY(-2px)";
this.style.boxShadow = "0 4px 8px rgba(0,0,0,0.2)";
});
saveBtn.addEventListener("mouseleave", function() {
this.style.transform = "translateY(0)";
this.style.boxShadow = "none";
});
}
}
function clearUnsavedChanges() {
hasUnsavedChanges = false;
const indicator = document.getElementById("unsaved-indicator");
const saveBtn = document.getElementById("panel-save-btn");
if (indicator) {
indicator.style.display = "none";
}
if (saveBtn) {
saveBtn.style.opacity = "0.7";
saveBtn.style.pointerEvents = "none";
saveBtn.style.background = "#95a5a6";
saveBtn.innerHTML = "💾 保存设置";
saveBtn.onmouseenter = null;
saveBtn.onmouseleave = null;
setTimeout(() => {
if (!hasUnsavedChanges) {
saveBtn.innerHTML = "✅ 已保存";
saveBtn.style.background = "#27ae60";
setTimeout(() => {
if (!hasUnsavedChanges) {
saveBtn.innerHTML = "💾 保存设置";
saveBtn.style.background = "#95a5a6";
}
}, 2000);
}
}, 100);
}
}
// 从当前页面提取搜索引擎
function extractFromCurrentPage() {
const searchInfo = extractSearchEngineFromPage();
if (!searchInfo.found) {
alert("❌ 无法自动识别当前页面的搜索引擎。请手动添加。");
return;
}
showAddForm(true);
document.getElementById("engine-name").value = searchInfo.name;
document.getElementById("engine-mark").value = searchInfo.mark;
document.getElementById("engine-url").value = searchInfo.searchUrl;
document.getElementById("engine-keys").value = searchInfo.searchkeyName.join(",");
const favicon = document.querySelector('link[rel*="icon"]');
if (favicon) {
const iconUrl = favicon.href;
if (!iconUrl.startsWith('data:')) {
document.getElementById("icon-type").value = "image";
document.getElementById("icon-input").value = iconUrl;
previewIcon();
}
}
alert(`✅ 已自动识别 ${searchInfo.name} 搜索引擎!请检查并保存。`);
}
// 显示/隐藏添加表单
function showAddForm(show) {
const formSection = document.getElementById("add-engine-form");
const engineList = document.getElementById("engine-management-list");
const listTitle = formSection.previousElementSibling;
if (show) {
formSection.style.display = "block";
engineList.style.display = "none";
listTitle.style.display = "none";
document.getElementById("engine-name").value = "";
document.getElementById("engine-mark").value = "";
document.getElementById("engine-url").value = "";
document.getElementById("engine-keys").value = "";
document.getElementById("icon-input").value = "";
document.getElementById("icon-preview").innerHTML = "";
} else {
formSection.style.display = "none";
engineList.style.display = "grid";
listTitle.style.display = "block";
}
}
// 预览图标
function previewIcon() {
const type = document.getElementById("icon-type").value;
const value = document.getElementById("icon-input").value.trim();
const preview = document.getElementById("icon-preview");
preview.innerHTML = "";
preview.style.backgroundImage = "none";
preview.style.backgroundColor = "#ecf0f1";
if (!value) return;
try {
if (type === "svg") {
const parser = new DOMParser();
const svgDoc = parser.parseFromString(value, "image/svg+xml");
if (svgDoc.querySelector("parsererror")) {
throw new Error("无效的SVG代码");
}
preview.innerHTML = value;
} else if (type === "image") {
preview.style.backgroundImage = `url(${value})`;
preview.style.backgroundSize = "contain";
preview.style.backgroundRepeat = "no-repeat";
preview.style.backgroundPosition = "center";
} else if (type === "text") {
preview.textContent = value.length > 4 ? value.substring(0, 4) : value;
preview.style.fontSize = value.length > 4 ? "14px" : "18px";
preview.style.color = "#2c3e50";
preview.style.fontWeight = "bold";
} else if (type === "emoji") {
preview.textContent = value;
preview.style.fontSize = "24px";
}
} catch (e) {
alert("❌ 图标预览失败: " + e.message);
}
}
// 保存新引擎
function saveNewEngine() {
const name = document.getElementById("engine-name").value.trim();
const mark = document.getElementById("engine-mark").value.trim();
const url = document.getElementById("engine-url").value.trim();
const keys = document.getElementById("engine-keys").value.split(',').map(k => k.trim());
const iconType = document.getElementById("icon-type").value;
const iconValue = document.getElementById("icon-input").value.trim();
if (!name || !mark || !url || keys.length === 0) {
alert("❌ 请填写所有必填字段");
return;
}
if (searchUrlMap.some(engine => engine.mark === mark)) {
alert("❌ 标识已存在,请使用其他标识");
return;
}
const newEngine = {
name,
searchUrl: url,
searchkeyName: keys,
matchUrl: new RegExp(`.*${new URL(url).hostname}.*`),
mark,
svgCode: "",
custom: true
};
if (iconValue) {
if (iconType === "svg") {
newEngine.svgCode = iconValue;
} else if (iconType === "image") {
newEngine.svgCode = ``;
} else if (iconType === "text") {
newEngine.svgCode = ``;
} else if (iconType === "emoji") {
newEngine.svgCode = ``;
}
}
userSearchEngines.push(newEngine);
GM_setValue("userSearchEngines", userSearchEngines);
searchUrlMap = [...defaultSearchEngines, ...userSearchEngines];
const currentSetup = GM_getValue("punk_setup_search", punkDeafultMark);
GM_setValue("punk_setup_search", currentSetup + "-" + mark);
// 标记有更改需要保存
markUnsavedChanges();
alert("✅ 搜索引擎添加成功!");
showAddForm(false);
refreshEngineList();
}
// 恢复默认设置
function resetToDefault() {
if (confirm("⚠️ 确定要恢复默认设置吗?这将删除所有自定义搜索引擎。")) {
userSearchEngines = [];
GM_setValue("userSearchEngines", []);
GM_setValue("punk_setup_search", punkDeafultMark);
searchUrlMap = [...defaultSearchEngines];
// 标记有更改需要保存
markUnsavedChanges();
alert("✅ 已恢复默认设置");
refreshEngineList();
}
}
// 刷新引擎列表显示
function refreshEngineList() {
const engineList = document.getElementById("engine-management-list");
const activeMarks = (GM_getValue("punk_setup_search", punkDeafultMark)).split("-");
engineList.innerHTML = "";
searchUrlMap.forEach((engine, index) => {
const engineCard = document.createElement("div");
engineCard.className = "engine-card";
engineCard.style.cssText = `
display: flex;
align-items: center;
padding: 15px;
background: white;
border: 2px solid ${activeMarks.includes(engine.mark) ? '#27ae60' : '#ecf0f1'};
border-radius: 10px;
transition: all 0.3s ease;
cursor: grab;
min-height: 60px;
box-sizing: border-box;
`;
engineCard.addEventListener("mouseenter", () => {
engineCard.style.boxShadow = "0 4px 12px rgba(0,0,0,0.1)";
engineCard.style.transform = "translateY(-2px)";
});
engineCard.addEventListener("mouseleave", () => {
engineCard.style.boxShadow = "none";
engineCard.style.transform = "translateY(0)";
});
// 选择复选框
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.dataset.mark = engine.mark;
checkbox.checked = activeMarks.includes(engine.mark);
checkbox.style.marginRight = "15px";
checkbox.style.transform = "scale(1.2)";
// 复选框更改时标记有未保存更改
checkbox.addEventListener("change", () => {
updateSelectedCount();
markUnsavedChanges();
});
// 引擎图标预览
const iconPreview = document.createElement("div");
iconPreview.style.cssText = `
width: 40px;
height: 25px;
background-image: url('data:image/svg+xml;utf8,${encodeURIComponent(engine.svgCode)}');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
margin-right: 15px;
border: 1px solid #eee;
border-radius: 5px;
flex-shrink: 0;
`;
// 引擎信息
const infoContainer = document.createElement("div");
infoContainer.style.flexGrow = "1";
infoContainer.style.minWidth = "0";
const name = document.createElement("div");
name.textContent = engine.name;
name.style.fontWeight = "bold";
name.style.color = "#2c3e50";
name.style.marginBottom = "5px";
name.style.whiteSpace = "nowrap";
name.style.overflow = "hidden";
name.style.textOverflow = "ellipsis";
const url = document.createElement("div");
url.textContent = engine.searchUrl;
url.style.fontSize = "0.8em";
url.style.color = "#7f8c8d";
url.style.whiteSpace = "nowrap";
url.style.overflow = "hidden";
url.style.textOverflow = "ellipsis";
infoContainer.appendChild(name);
infoContainer.appendChild(url);
// 操作按钮
const actions = document.createElement("div");
actions.style.display = "flex";
actions.style.gap = "5px";
actions.style.flexShrink = "0";
if (engine.custom) {
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "🗑️";
deleteBtn.title = "删除";
deleteBtn.style.cssText = `
padding: 5px 10px;
border: none;
background: #e74c3c;
color: white;
border-radius: 5px;
cursor: pointer;
flex-shrink: 0;
`;
actions.appendChild(deleteBtn);
deleteBtn.addEventListener("click", (e) => {
e.stopPropagation();
if (confirm(`确定要删除 ${engine.name} 吗?`)) {
userSearchEngines = userSearchEngines.filter(e => e.mark !== engine.mark);
GM_setValue("userSearchEngines", userSearchEngines);
const currentSetup = GM_getValue("punk_setup_search", punkDeafultMark);
const newSetup = currentSetup.split("-").filter(m => m !== engine.mark).join("-");
GM_setValue("punk_setup_search", newSetup);
searchUrlMap = [...defaultSearchEngines, ...userSearchEngines];
// 标记有更改需要保存
markUnsavedChanges();
refreshEngineList();
}
});
}
engineCard.appendChild(checkbox);
engineCard.appendChild(iconPreview);
engineCard.appendChild(infoContainer);
engineCard.appendChild(actions);
engineList.appendChild(engineCard);
});
updateSelectedCount();
}
// 更新选中计数
function updateSelectedCount() {
const checkboxes = document.querySelectorAll('#engine-management-list input[type="checkbox"]:checked');
const countElement = document.getElementById("selected-count");
countElement.textContent = `已选择 ${checkboxes.length} 个引擎`;
}
// 保存引擎设置
function saveEngineSettings() {
const checkboxes = document.querySelectorAll('#engine-management-list input[type="checkbox"]');
const activeMarks = [];
checkboxes.forEach(checkbox => {
if (checkbox.checked) {
activeMarks.push(checkbox.dataset.mark);
}
});
if (activeMarks.length === 0) {
alert("⚠️ 请至少选择一个搜索引擎");
return;
}
GM_setValue("punk_setup_search", activeMarks.join("-"));
// 清除未保存更改标记
clearUnsavedChanges();
// 延迟关闭面板,让用户看到成功提示
setTimeout(() => {
document.getElementById("engine-management-panel").style.display = "none";
reloadScript();
}, 1000);
}
// 显示管理面板
function showManagementPanel() {
const panel = document.getElementById("engine-management-panel") || createManagementPanel();
// 重置未保存更改状态
hasUnsavedChanges = false;
clearUnsavedChanges();
refreshEngineList();
panel.style.display = "block";
}
// 拖拽排序功能
function enableDragAndSort() {
const container = document.querySelector('.engine-display');
if (!container) return;
const buttons = container.querySelectorAll('.engine-button');
buttons.forEach(button => {
button.draggable = true;
button.addEventListener('dragstart', (e) => {
currentDraggedButton = button;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', button.getAttribute('url'));
button.classList.add('dragging');
});
button.addEventListener('dragend', () => {
button.classList.remove('dragging');
currentDraggedButton = null;
saveButtonOrder();
});
button.addEventListener('dragover', (e) => {
e.preventDefault();
return false;
});
button.addEventListener('dragenter', (e) => {
e.preventDefault();
button.classList.add('drag-over');
});
button.addEventListener('dragleave', () => {
button.classList.remove('drag-over');
});
button.addEventListener('drop', (e) => {
e.preventDefault();
button.classList.remove('drag-over');
if (currentDraggedButton !== button) {
const container = button.parentNode;
const buttonsArray = Array.from(container.querySelectorAll('.engine-button'));
const draggedIndex = buttonsArray.indexOf(currentDraggedButton);
const targetIndex = buttonsArray.indexOf(button);
if (draggedIndex < targetIndex) {
container.insertBefore(currentDraggedButton, button.nextSibling);
} else {
container.insertBefore(currentDraggedButton, button);
}
// 拖拽排序后标记有未保存更改
markUnsavedChanges();
}
return false;
});
});
}
// 保存按钮顺序
function saveButtonOrder() {
const container = document.querySelector('.engine-display');
if (!container) return;
const buttons = container.querySelectorAll('.engine-button');
const newOrder = Array.from(buttons).map(btn => {
const url = btn.getAttribute('url');
const engine = searchUrlMap.find(e => e.searchUrl === url);
return engine ? engine.mark : null;
}).filter(mark => mark !== null).join('-');
GM_setValue('punk_setup_search', newOrder);
}
function addSearchBox() {
try {
if (engineContainerExists()) {
console.log("引擎容器已存在,跳过添加");
return;
}
const punkJetBox = document.createElement("div");
punkJetBox.id = "punkjet-search-box";
punkJetBox.className = "engine-container";
punkJetBox.style.display = 'flex';
punkJetBox.style.zIndex = '9999';
punkJetBox.style.position = 'fixed';
updateSearchBoxPosition();
const ulList = document.createElement('div');
ulList.className = "engine-display";
// 添加设置按钮
const settingsButton = document.createElement('button');
settingsButton.className = "engine-settings-button";
settingsButton.innerHTML = "ν";
settingsButton.title = "管理搜索引擎";
settingsButton.style.width = "32px";
settingsButton.style.height = "32px";
settingsButton.style.border = "1px solid #f0f0f0";
settingsButton.style.borderRadius = "7px";
settingsButton.style.background = "white";
settingsButton.style.cursor = "pointer";
settingsButton.style.margin = "3px";
settingsButton.style.flexShrink = "0";
settingsButton.style.display = "flex";
settingsButton.style.justifyContent = "center";
settingsButton.style.alignItems = "center";
settingsButton.style.fontSize = "18px";
settingsButton.addEventListener('click', showManagementPanel);
punkJetBox.appendChild(settingsButton);
let fragment = document.createDocumentFragment();
let showList = GM_getValue("punk_setup_search", punkDeafultMark);
showList = showList.split('-');
for (let showListIndex = 0; showListIndex < showList.length; showListIndex++) {
for (let index = 0; index < searchUrlMap.length; index++) {
let item = searchUrlMap[index];
if (item.mark === showList[showListIndex]) {
let button = document.createElement('button');
button.className = "engine-button";
button.style.backgroundImage = `url('data:image/svg+xml;utf8,${encodeURIComponent(item.svgCode)}')`;
button.setAttribute("url", item.searchUrl);
button.setAttribute("title", item.name);
button.innerHTML = '';
fragment.appendChild(button);
button.addEventListener('mouseover', () => {
button.style.backgroundColor = 'rgba(241, 241, 241, 1)';
button.style.transform = 'translateY(-2px)';
button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
});
button.addEventListener('mouseout', () => {
button.style.backgroundColor = 'rgba(240, 240, 244, 1)';
button.style.transform = 'translateY(0)';
button.style.boxShadow = '1px 1px 1px rgba(0, 0, 0, 0.1), 0px 0px 0px rgba(255, 255, 255, 0.5), 6px 6px 10px rgba(0, 0, 0, 0.1) inset, -6px -6px 10px rgba(255, 255, 255, 0) inset';
});
button.addEventListener('click', (event) => {
event.preventDefault();
const url = button.getAttribute("url");
let keywords = "";
const activeInput = document.activeElement;
if (activeInput && (activeInput.tagName === 'INPUT' || activeInput.tagName === 'TEXTAREA')) {
keywords = activeInput.value.trim();
}
if (!keywords) {
const baiduInput = document.querySelector('input#kw');
if (baiduInput) {
keywords = baiduInput.value.trim();
}
}
if (!keywords) {
keywords = getKeywords().trim();
}
if (url && keywords) {
const finalUrl = url.replace('{keyword}', encodeURIComponent(keywords));
window.open(finalUrl, '_blank');
} else {
alert('未找到搜索关键词。');
}
});
// 长按事件(打开上下文菜单)
button.addEventListener('mousedown', (e) => {
longPressTimer = setTimeout(() => {
showContextMenu(e, item);
}, 800);
});
button.addEventListener('mouseup', () => {
clearTimeout(longPressTimer);
});
button.addEventListener('mouseleave', () => {
clearTimeout(longPressTimer);
});
break;
}
}
}
ulList.appendChild(fragment);
punkJetBox.appendChild(ulList);
document.body.appendChild(punkJetBox);
containerAdded = true;
console.log("引擎容器添加成功");
window.addEventListener('resize', updateSearchBoxPosition);
setTimeout(enableDragAndSort, 500);
} catch (error) {
console.error("添加搜索框时出错:", error.message);
}
}
// 显示上下文菜单
function showContextMenu(event, engine) {
const menu = document.createElement("div");
menu.id = "engine-context-menu";
menu.style.cssText = `
position: fixed;
left: ${event.pageX}px;
top: ${event.pageY}px;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10001;
min-width: 120px;
`;
const hideOption = document.createElement("div");
hideOption.className = "context-menu-item";
hideOption.textContent = "👁️ 隐藏引擎";
hideOption.style.cssText = `
padding: 10px 15px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
transition: background 0.3s;
`;
hideOption.addEventListener("mouseenter", () => {
hideOption.style.background = "#f8f9fa";
});
hideOption.addEventListener("mouseleave", () => {
hideOption.style.background = "white";
});
hideOption.addEventListener("click", () => {
const currentSetup = GM_getValue("punk_setup_search", punkDeafultMark);
const newSetup = currentSetup.split("-").filter(m => m !== engine.mark).join("-");
GM_setValue("punk_setup_search", newSetup);
reloadScript();
menu.remove();
});
menu.appendChild(hideOption);
if (engine.custom) {
const deleteOption = document.createElement("div");
deleteOption.className = "context-menu-item";
deleteOption.textContent = "🗑️ 删除引擎";
deleteOption.style.cssText = `
padding: 10px 15px;
cursor: pointer;
color: #e74c3c;
transition: background 0.3s;
`;
deleteOption.addEventListener("mouseenter", () => {
deleteOption.style.background = "#f8f9fa";
});
deleteOption.addEventListener("mouseleave", () => {
deleteOption.style.background = "white";
});
deleteOption.addEventListener("click", () => {
if (confirm(`确定要删除 ${engine.name} 吗?`)) {
userSearchEngines = userSearchEngines.filter(e => e.mark !== engine.mark);
GM_setValue("userSearchEngines", userSearchEngines);
const currentSetup = GM_getValue("punk_setup_search", punkDeafultMark);
const newSetup = currentSetup.split("-").filter(m => m !== engine.mark).join("-");
GM_setValue("punk_setup_search", newSetup);
searchUrlMap = [...defaultSearchEngines, ...userSearchEngines];
reloadScript();
}
menu.remove();
});
menu.appendChild(deleteOption);
}
document.body.appendChild(menu);
const closeMenu = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener("click", closeMenu);
}
};
setTimeout(() => {
document.addEventListener("click", closeMenu);
}, 100);
}
function injectStyle() {
try {
if (document.querySelector('style#engine-container-style')) {
return;
}
const cssNode = document.createElement("style");
cssNode.id = "engine-container-style";
cssNode.textContent = `
.engine-container {
display: flex;
position: fixed;
bottom: 0px;
left: 2%;
width: 96%;
height: 36px;
overflow: hidden;
justify-content: center;
align-items: center;
z-index: 1000;
background-color: rgba(255, 255, 255, 0);
margin-top: 1px;
}
.engine-display {
display: flex;
overflow-x: auto;
white-space: nowrap;
height: 100%;
gap: 0px;
flex-grow: 1;
scrollbar-width: thin;
scrollbar-color: #bdc3c7 transparent;
}
.engine-display::-webkit-scrollbar {
height: 4px;
}
.engine-display::-webkit-scrollbar-track {
background: transparent;
}
.engine-display::-webkit-scrollbar-thumb {
background: #bdc3c7;
border-radius: 2px;
}
.engine-button {
width: 55.5px;
height: 32px;
padding: 0;
border: 1px solid #f0f0f0;
border-radius: 8px;
background-color: rgba(255, 255, 255, 1);
color: transparent;
font-size: 14px;
cursor: pointer;
margin: 2px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
backdrop-filter: blur(5px);
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1), 0px 0px 0px rgba(255, 255, 255, 0.5), 6px 6px 10px rgba(0, 0, 0, 0.1) inset, -6px -6px 10px rgba(255, 255, 255, 0) inset;
transition: all 0.3s ease;
flex-shrink: 0;
}
.engine-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.engine-button:focus {
outline: none;
border-color: #ffb61e;
}
.engine-button.selected {
border: 1px solid #ffb61e;
color: transparent;
}
.engine-button.dragging {
opacity: 0.5;
transform: rotate(5deg);
}
.engine-button.drag-over {
border: 2px dashed #2196F3;
background-color: #f0f8ff;
}
.context-menu-item:hover {
background-color: #f8f9fa;
}
.engine-card {
transition: all 0.3s ease;
}
.engine-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
#engine-management-panel {
animation: slideIn 0.3s ease;
}
#panel-save-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
@keyframes slideIn {
from {
opacity: 0;
transform: translate(-50%, -48%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
`;
document.head.appendChild(cssNode);
} catch (error) {
console.error("注入样式时出错:", error.message);
}
}
function reloadScript() {
const punkJetBox = document.getElementById("punkjet-search-box");
if (punkJetBox) {
punkJetBox.remove();
containerAdded = false;
console.log("移除现有引擎容器");
}
scriptLoaded = false;
init();
}
setInterval(() => {
if (isValidScope() && !containerAdded) {
init();
}
else if (!isValidScope() && containerAdded) {
reloadScript();
}
}, 1000);
function init() {
try {
if (containerAdded || scriptLoaded) {
console.log("脚本已初始化或容器已存在,跳过");
return;
}
if (getKeywords() != null) {
if (!scriptLoaded) {
if (!GM_getValue("punk_setup_search")) {
GM_setValue("punk_setup_search", punkDeafultMark);
}
currentInput = sessionStorage.getItem("currentInput") || "";
monitorInputFields();
addSearchBox();
injectStyle();
scriptLoaded = true;
console.log("脚本初始化完成");
}
}
} catch (error) {
console.error("初始化时出错:", error.message);
}
}
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === 'visible') {
if (!containerAdded) {
init();
}
}
});
document.addEventListener("pageshow", (event) => {
if (event.persisted) {
if (!containerAdded) {
init();
}
}
});
document.addEventListener("DOMContentLoaded", () => {
if (isValidScope()) {
init();
}
});