mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-22 03:10:43 +08:00
Refactor proxy panel to include a new links tab with dynamic content loading and improved styling for better user interaction
This commit is contained in:
+10
-1
@@ -26,7 +26,16 @@
|
||||
{
|
||||
"matches": ["<all_urls>","http://mitm/"],
|
||||
"run_at": "document_start",
|
||||
"js": ["proxy/content.js"]
|
||||
"js": [
|
||||
"proxy/links_finder.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matches": ["<all_urls>","http://mitm/"],
|
||||
"run_at": "document_end",
|
||||
"js": [
|
||||
"proxy/content.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
|
||||
+364
-85
@@ -201,7 +201,7 @@ console.log("Content script starting...");
|
||||
|
||||
createPanel() {
|
||||
if (!document.body) {
|
||||
console.log("Body not available during panel creation");
|
||||
console.log("Body not ready, waiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -312,6 +312,12 @@ console.log("Content script starting...");
|
||||
background-color 0.2s ease !important;
|
||||
}
|
||||
|
||||
/* 链接面板展开状态 */
|
||||
.floating-panel.expanded[data-active-tab="links"] {
|
||||
width: 280px !important;
|
||||
max-height: 600px !important;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
height: 40px !important;
|
||||
min-height: 40px !important;
|
||||
@@ -488,24 +494,215 @@ console.log("Content script starting...");
|
||||
.action-button span {
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
/* 添加垂直标签样式 */
|
||||
.tabs-container {
|
||||
display: flex !important;
|
||||
height: 100% !important;
|
||||
min-height: 200px !important;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
width: 40px !important;
|
||||
background: #f8f9fa !important;
|
||||
border-right: 1px solid #eee !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
padding-top: 8px !important;
|
||||
position: sticky !important;
|
||||
top: 0 !important;
|
||||
align-self: flex-start !important;
|
||||
height: 100% !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
margin-bottom: 4px !important;
|
||||
border-radius: 4px !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.2s !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
background: #fff7e6 !important;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
background: #fff7e6 !important;
|
||||
color: #ff6b00 !important;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
flex: 1 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
min-width: 0 !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.tab-panel {
|
||||
display: none !important;
|
||||
height: 100% !important;
|
||||
overflow: hidden !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.tab-panel.active {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* Links Panel Styles */
|
||||
.links-stats {
|
||||
position: sticky !important;
|
||||
top: 0 !important;
|
||||
background: white !important;
|
||||
z-index: 1 !important;
|
||||
padding: 12px !important;
|
||||
border-bottom: 1px solid #eee !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.links-filters {
|
||||
position: sticky !important;
|
||||
top: 43px !important;
|
||||
background: white !important;
|
||||
z-index: 1 !important;
|
||||
padding: 12px !important;
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
gap: 8px !important;
|
||||
border-bottom: 1px solid #eee !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 4px 8px !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #eee !important;
|
||||
background: white !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.2s !important;
|
||||
font-size: 12px !important;
|
||||
color: #666 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 4px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background: #fff7e6 !important;
|
||||
border-color: #ffd591 !important;
|
||||
color: #ff6b00 !important;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: #fff7e6 !important;
|
||||
border-color: #ff6b00 !important;
|
||||
color: #ff6b00 !important;
|
||||
}
|
||||
|
||||
.filter-btn span {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.links-list {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
padding: 12px !important;
|
||||
}
|
||||
|
||||
.link-item {
|
||||
display: flex !important;
|
||||
align-items: flex-start !important;
|
||||
padding: 8px !important;
|
||||
border-bottom: 1px solid #eee !important;
|
||||
gap: 8px !important;
|
||||
transition: all 0.2s ease !important;
|
||||
}
|
||||
|
||||
.link-item[data-hidden="true"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
font-size: 16px !important;
|
||||
min-width: 24px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.link-content {
|
||||
flex: 1 !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.link-url {
|
||||
font-size: 12px !important;
|
||||
color: #1890ff !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
}
|
||||
|
||||
.link-text, .link-alt {
|
||||
font-size: 12px !important;
|
||||
color: #666 !important;
|
||||
margin-top: 4px !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
padding: 4px !important;
|
||||
border-radius: 4px !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.2s !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #f5f5f5 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
// 创建面板内容
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "floating-panel yak-proxy-root";
|
||||
|
||||
// 初始面板内容
|
||||
panel.innerHTML = `
|
||||
<div class="panel-header">
|
||||
<div class="header-content">
|
||||
<img src="${YAK_ICON_URL}" class="yak-icon" alt="Yak" />
|
||||
<div class="active-proxy-info">
|
||||
<!-- 当前代理信息将动态更新 -->
|
||||
<div class="panel-header">
|
||||
<div class="header-content">
|
||||
<img src="${YAK_ICON_URL}" class="yak-icon" alt="Yak" />
|
||||
<div class="active-proxy-info">
|
||||
<span>🟢</span>
|
||||
<span>直接连接</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<!-- 内容将由 _buildPanelHtml 方法动态生成 -->
|
||||
</div>
|
||||
`;
|
||||
<div class="panel-content">
|
||||
<!-- 内容将由 updatePanel 方法动态更新 -->
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 将样式和面板添加到 shadow DOM
|
||||
shadow.appendChild(style);
|
||||
@@ -529,6 +726,10 @@ console.log("Content script starting...");
|
||||
header.addEventListener("click", (e) => {
|
||||
if (!this.isDragging) {
|
||||
floatingPanel.classList.toggle("expanded");
|
||||
// 如果展开且内容为空,则更新面板
|
||||
if (floatingPanel.classList.contains("expanded")) {
|
||||
this.updatePanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -691,37 +892,44 @@ console.log("Content script starting...");
|
||||
return this._updateQueue;
|
||||
},
|
||||
|
||||
// 将 HTML 构建逻辑抽离成单独的方法
|
||||
// 修改面板内容的构建方法
|
||||
_buildPanelHtml(currentProxy, configs) {
|
||||
let html = `
|
||||
<div class="proxy-item ${
|
||||
currentProxy.currentMode === "direct" ? "active" : ""
|
||||
}"
|
||||
data-id="direct"
|
||||
title="直接连接">
|
||||
<span>🟢</span>
|
||||
<span>直接连接</span>
|
||||
${
|
||||
currentProxy.currentMode === "direct"
|
||||
? '<div class="proxy-status"></div>'
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="proxy-item ${
|
||||
currentProxy.currentMode === "system" ? "active" : ""
|
||||
}"
|
||||
data-id="system"
|
||||
title="系统代理">
|
||||
<span>⚙️</span>
|
||||
<span>系统代理</span>
|
||||
${
|
||||
currentProxy.currentMode === "system"
|
||||
? '<div class="proxy-status"></div>'
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
`;
|
||||
<div class="tabs-container">
|
||||
<div class="tab-list">
|
||||
<button class="tab-button active" data-tab="proxy" title="代理设置">🌐</button>
|
||||
<button class="tab-button" data-tab="links" title="页面链接">🔗</button>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-panel active" data-panel="proxy">
|
||||
<div class="proxy-item ${
|
||||
currentProxy.currentMode === "direct" ? "active" : ""
|
||||
}"
|
||||
data-id="direct"
|
||||
title="直接连接">
|
||||
<span>🟢</span>
|
||||
<span>直接连接</span>
|
||||
${
|
||||
currentProxy.currentMode === "direct"
|
||||
? '<div class="proxy-status"></div>'
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="proxy-item ${
|
||||
currentProxy.currentMode === "system" ? "active" : ""
|
||||
}"
|
||||
data-id="system"
|
||||
title="系统代理">
|
||||
<span>⚙️</span>
|
||||
<span>系统代理</span>
|
||||
${
|
||||
currentProxy.currentMode === "system"
|
||||
? '<div class="proxy-status"></div>'
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
`;
|
||||
|
||||
// 添加自定义代理配置
|
||||
configs.forEach((config) => {
|
||||
@@ -730,64 +938,55 @@ console.log("Content script starting...");
|
||||
currentProxy.currentMode === "fixed_servers" && config.enabled;
|
||||
const proxyIcon = config.proxyType === "pac_script" ? "📜" : "🌐";
|
||||
|
||||
// 构建 title 提示信息
|
||||
let tooltipText;
|
||||
if (config.proxyType === "pac_script") {
|
||||
tooltipText = "PAC Script";
|
||||
} else {
|
||||
const scheme = config.scheme
|
||||
? `${config.scheme.toUpperCase()} `
|
||||
: "";
|
||||
tooltipText = `${scheme}${config.host}:${config.port}`;
|
||||
}
|
||||
const tooltipText = config.proxyType === "pac_script"
|
||||
? "PAC Script"
|
||||
: `${config.scheme ? `${config.scheme.toUpperCase()} ` : ""}${config.host}:${config.port}`;
|
||||
|
||||
html += `
|
||||
<div class="proxy-item ${isActive ? "active" : ""}"
|
||||
data-id="${config.id}"
|
||||
title="${tooltipText}">
|
||||
<span>${proxyIcon}</span>
|
||||
<span>${config.name || "未命名代理"}</span>
|
||||
${isActive ? '<div class="proxy-status"></div>' : ""}
|
||||
</div>
|
||||
`;
|
||||
<div class="proxy-item ${isActive ? "active" : ""}"
|
||||
data-id="${config.id}"
|
||||
title="${tooltipText}">
|
||||
<span>${proxyIcon}</span>
|
||||
<span>${config.name || "未命名代理"}</span>
|
||||
${isActive ? '<div class="proxy-status"></div>' : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
html += `
|
||||
<div class="divider"></div>
|
||||
<div class="action-button add-proxy">
|
||||
<span>➕</span>
|
||||
<span>添加代理</span>
|
||||
<div class="divider"></div>
|
||||
<div class="action-button add-proxy">
|
||||
<span>➕</span>
|
||||
<span>添加代理</span>
|
||||
</div>
|
||||
<div class="action-button settings">
|
||||
<span>⚙️</span>
|
||||
<span>设置</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-panel" data-panel="links">
|
||||
<!-- Links panel content will be dynamically populated -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-button settings">
|
||||
<span>⚙️</span>
|
||||
<span>设置</span>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
_bindEventListeners(panel, configs, currentProxy) {
|
||||
// 代理项点击事件
|
||||
// 现有的事件监听器
|
||||
panel.querySelectorAll(".proxy-item").forEach((item) => {
|
||||
const id = item.dataset.id;
|
||||
|
||||
// 使用事件委托来提高性能
|
||||
const clickHandler = async (e) => {
|
||||
item.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
if (this._updating) return;
|
||||
|
||||
if (this._updating) {
|
||||
console.log("Panel is updating, ignoring click");
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加点击反馈
|
||||
const originalOpacity = item.style.opacity;
|
||||
item.style.opacity = "0.7";
|
||||
|
||||
try {
|
||||
// 立即更新 UI 状态,不等待响应
|
||||
panel.querySelectorAll(".proxy-item").forEach((i) => {
|
||||
i.classList.remove("active");
|
||||
i.querySelector("span").style.color = "#666";
|
||||
@@ -805,21 +1004,62 @@ console.log("Content script starting...");
|
||||
});
|
||||
} else {
|
||||
const config = configs.find((c) => c.id === id);
|
||||
if (config) {
|
||||
await switchProxy(config);
|
||||
}
|
||||
if (config) await switchProxy(config);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error handling proxy item click:", error);
|
||||
// 发生错误时恢复原状
|
||||
await this.updatePanel();
|
||||
} finally {
|
||||
item.style.opacity = originalOpacity;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// 使用 { once: true } 确保事件监听器不会重复
|
||||
item.addEventListener("click", clickHandler, {once: true});
|
||||
// Tab 切换事件
|
||||
panel.querySelectorAll(".tab-button").forEach((button) => {
|
||||
console.log("Setting up tab button click handler for:", button.dataset.tab);
|
||||
button.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
const tabId = button.dataset.tab;
|
||||
console.log("Tab button clicked:", tabId);
|
||||
|
||||
// 更新按钮状态
|
||||
panel.querySelectorAll(".tab-button").forEach((btn) => {
|
||||
btn.classList.remove("active");
|
||||
});
|
||||
button.classList.add("active");
|
||||
|
||||
// 更新面板显示
|
||||
panel.querySelectorAll(".tab-panel").forEach((panel) => {
|
||||
panel.classList.remove("active");
|
||||
});
|
||||
const targetPanel = panel.querySelector(`[data-panel="${tabId}"]`);
|
||||
console.log("Target panel found:", !!targetPanel);
|
||||
targetPanel.classList.add("active");
|
||||
|
||||
// 更新面板尺寸
|
||||
const floatingPanel = this.panel.shadowRoot.querySelector(".floating-panel");
|
||||
floatingPanel.setAttribute("data-active-tab", tabId);
|
||||
|
||||
// 如果是链接面板,检查内容并初始化
|
||||
if (tabId === "links") {
|
||||
console.log("Links tab selected, checking panel content...");
|
||||
// 检查面板是否有实际的链接内容
|
||||
const hasContent = targetPanel.querySelector('.links-stats, .links-filters, .links-list');
|
||||
if (!hasContent) {
|
||||
console.log("Links panel needs initialization...");
|
||||
await this._initLinksPanel(targetPanel);
|
||||
} else {
|
||||
console.log("Links panel content found:", hasContent);
|
||||
// 如果内容存在但为空,重新初始化
|
||||
const linksList = targetPanel.querySelector('.links-list');
|
||||
if (linksList && !linksList.children.length) {
|
||||
console.log("Links list is empty, reinitializing...");
|
||||
await this._initLinksPanel(targetPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 添加代理按钮
|
||||
@@ -846,6 +1086,45 @@ console.log("Content script starting...");
|
||||
});
|
||||
},
|
||||
|
||||
// 初始化链接面板
|
||||
async _initLinksPanel(panel) {
|
||||
try {
|
||||
console.log("Creating LinksFinder instance...");
|
||||
if (!window.LinksFinder) {
|
||||
console.error("LinksFinder is not defined! Window properties:", Object.keys(window));
|
||||
throw new Error("LinksFinder class not found");
|
||||
}
|
||||
|
||||
// 清理旧的事件监听器
|
||||
const oldButtons = panel.querySelectorAll('.filter-btn, .copy-btn');
|
||||
oldButtons.forEach(btn => {
|
||||
btn.replaceWith(btn.cloneNode(true));
|
||||
});
|
||||
|
||||
// 创建链接查找器实例
|
||||
const linksFinder = new window.LinksFinder();
|
||||
|
||||
console.log("Building links panel...");
|
||||
// 构建并插入面板内容
|
||||
panel.innerHTML = linksFinder.buildLinksPanel();
|
||||
|
||||
console.log("Binding events...");
|
||||
// 绑定事件
|
||||
linksFinder.bindEvents(panel);
|
||||
|
||||
console.log("Links panel initialization completed");
|
||||
} catch (error) {
|
||||
console.error("Error initializing links panel:", error);
|
||||
panel.innerHTML = `
|
||||
<div style="padding: 16px; text-align: center; color: #ff4d4f;">
|
||||
<span>😢</span>
|
||||
<p>加载链接失败: ${error.message}</p>
|
||||
<p style="font-size: 12px; color: #999;">详细错误: ${error.stack}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
|
||||
_initDragFeature(header, panel) {
|
||||
let isDragging = false;
|
||||
let startY = 0;
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// Links Finder Module
|
||||
class LinksFinder {
|
||||
constructor() {
|
||||
this.links = [];
|
||||
this.lastUpdate = null;
|
||||
}
|
||||
|
||||
// 获取页面所有链接
|
||||
getAllLinks() {
|
||||
const links = [];
|
||||
const seen = new Set();
|
||||
|
||||
// 获取所有 a 标签
|
||||
document.querySelectorAll('a').forEach(a => {
|
||||
const href = a.href;
|
||||
if (href && !seen.has(href) && href.startsWith('http')) {
|
||||
seen.add(href);
|
||||
links.push({
|
||||
type: 'anchor',
|
||||
url: href,
|
||||
text: a.textContent.trim() || href,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有图片链接
|
||||
document.querySelectorAll('img').forEach(img => {
|
||||
const src = img.src;
|
||||
if (src && !seen.has(src) && src.startsWith('http')) {
|
||||
seen.add(src);
|
||||
links.push({
|
||||
type: 'image',
|
||||
url: src,
|
||||
alt: img.alt || 'Image',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有脚本链接
|
||||
document.querySelectorAll('script').forEach(script => {
|
||||
const src = script.src;
|
||||
if (src && !seen.has(src) && src.startsWith('http')) {
|
||||
seen.add(src);
|
||||
links.push({
|
||||
type: 'script',
|
||||
url: src,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有样式表链接
|
||||
document.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
|
||||
const href = link.href;
|
||||
if (href && !seen.has(href) && href.startsWith('http')) {
|
||||
seen.add(href);
|
||||
links.push({
|
||||
type: 'stylesheet',
|
||||
url: href,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.links = links;
|
||||
this.lastUpdate = new Date();
|
||||
return links;
|
||||
}
|
||||
|
||||
// 按类型过滤链接
|
||||
filterLinksByType(type) {
|
||||
return this.links.filter(link => link.type === type);
|
||||
}
|
||||
|
||||
// 获取链接统计信息
|
||||
getLinkStats() {
|
||||
const stats = {
|
||||
total: this.links.length,
|
||||
byType: {}
|
||||
};
|
||||
|
||||
this.links.forEach(link => {
|
||||
if (!stats.byType[link.type]) {
|
||||
stats.byType[link.type] = 0;
|
||||
}
|
||||
stats.byType[link.type]++;
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
// 构建链接面板的 HTML
|
||||
buildLinksPanel() {
|
||||
const links = this.getAllLinks();
|
||||
const stats = this.getLinkStats();
|
||||
|
||||
let html = `
|
||||
<div class="links-stats">
|
||||
<div class="stats-item">
|
||||
<span>🔗</span>
|
||||
<span>总链接: ${stats.total}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="links-filters">
|
||||
<button class="filter-btn active" data-type="all">
|
||||
<span>🔍</span>
|
||||
<span>全部 (${stats.total})</span>
|
||||
</button>
|
||||
${Object.entries(stats.byType).map(([type, count]) => `
|
||||
<button class="filter-btn" data-type="${type}">
|
||||
<span>${this._getTypeIcon(type)}</span>
|
||||
<span>${this._getTypeName(type)} (${count})</span>
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div class="links-list">
|
||||
${links.map(link => this._buildLinkItem(link)).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// 获取链接类型图标
|
||||
_getTypeIcon(type) {
|
||||
const icons = {
|
||||
anchor: '🔗',
|
||||
image: '🖼️',
|
||||
script: '📜',
|
||||
stylesheet: '🎨'
|
||||
};
|
||||
return icons[type] || '🔗';
|
||||
}
|
||||
|
||||
// 获取链接类型名称
|
||||
_getTypeName(type) {
|
||||
const names = {
|
||||
anchor: '链接',
|
||||
image: '图片',
|
||||
script: '脚本',
|
||||
stylesheet: '样式'
|
||||
};
|
||||
return names[type] || type;
|
||||
}
|
||||
|
||||
// 构建单个链接项的 HTML
|
||||
_buildLinkItem(link) {
|
||||
return `
|
||||
<div class="link-item" data-type="${link.type}">
|
||||
<div class="link-icon">${this._getTypeIcon(link.type)}</div>
|
||||
<div class="link-content">
|
||||
<div class="link-url" title="${link.url}">${link.url}</div>
|
||||
${link.text ? `<div class="link-text" title="${link.text}">${link.text}</div>` : ''}
|
||||
${link.alt ? `<div class="link-alt" title="${link.alt}">${link.alt}</div>` : ''}
|
||||
</div>
|
||||
<button class="copy-btn" data-url="${link.url}" title="复制链接">📋</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 绑定事件处理
|
||||
bindEvents(container) {
|
||||
// 过滤按钮点击事件
|
||||
container.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const type = btn.dataset.type;
|
||||
console.log('Filter clicked:', type);
|
||||
|
||||
// 更新按钮状态
|
||||
container.querySelectorAll('.filter-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
|
||||
// 过滤链接显示
|
||||
container.querySelectorAll('.link-item').forEach(item => {
|
||||
if (type === 'all' || item.dataset.type === type) {
|
||||
item.removeAttribute('data-hidden');
|
||||
} else {
|
||||
item.setAttribute('data-hidden', 'true');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 复制按钮点击事件
|
||||
container.querySelectorAll('.copy-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
const url = btn.dataset.url;
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = '✓';
|
||||
btn.style.setProperty('color', '#52c41a', 'important');
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
btn.style.removeProperty('color');
|
||||
}, 1000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err);
|
||||
btn.textContent = '❌';
|
||||
setTimeout(() => {
|
||||
btn.textContent = '📋';
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log("LinksFinder module loaded");
|
||||
// 导出模块
|
||||
window.LinksFinder = LinksFinder;
|
||||
Reference in New Issue
Block a user