Compare commits

...
3 changed files with 757 additions and 211 deletions
+11 -2
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Yakit Chrome Endpoint",
"version": "0.0.6",
"version": "0.0.7",
"description": "A Endpoint for Yakit MITM or more",
"options_ui": {
"page": "proxy/options.html",
@@ -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": [
+533 -209
View File
@@ -201,13 +201,15 @@ console.log("Content script starting...");
createPanel() {
if (!document.body) {
console.log("Body not available during panel creation");
console.log("Body not ready, waiting...");
return;
}
// 创建容器
const container = document.createElement("div");
container.id = "yakit-proxy-panel";
// 添加高z-index确保在页面最上层
container.style.zIndex = "2147483647";
// 创建 shadow DOM
const shadow = container.attachShadow({mode: "open"});
@@ -215,36 +217,59 @@ console.log("Content script starting...");
// 添加样式
const style = document.createElement("style");
style.textContent = `
/* 重置所有样式以避免宿主页面的CSS影响 */
.yak-proxy-root * {
all: initial;
box-sizing: border-box !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
line-height: normal !important;
color: initial !important;
font-size: 14px !important;
text-align: left !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
outline: none !important;
text-decoration: none !important;
width: auto !important;
height: auto !important;
min-width: auto !important;
min-height: auto !important;
max-width: none !important;
max-height: none !important;
background: none !important;
}
.floating-panel {
position: fixed;
top: 30%;
right: 0;
transform: translateY(-30%);
background: white;
z-index: 2147483647;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
width: 50px;
height: 40px;
overflow: hidden;
position: fixed !important;
top: 30% !important;
right: 0 !important;
transform: translateY(-30%) !important;
background: white !important;
z-index: 2147483647 !important;
width: 50px !important;
height: 40px !important;
overflow: hidden !important;
transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1),
height 0.2s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.2s ease;
background-color 0.2s ease !important;
box-sizing: border-box !important;
}
/* 吸附状态 */
.floating-panel:not(.expanded):not(.dragging) {
border-radius: 50px 0 0 50px;
box-shadow: -4px 0 20px rgba(0,0,0,0.15);
border: 1px solid #eee;
border-right: none;
border-radius: 50px 0 0 50px !important;
box-shadow: -4px 0 20px rgba(0,0,0,0.15) !important;
border: 1px solid #eee !important;
border-right: none !important;
}
/* 拖动状态 */
.floating-panel.dragging {
cursor: grabbing !important;
user-select: none;
opacity: 0.95;
transition: none;
user-select: none !important;
opacity: 0.95 !important;
transition: none !important;
}
.floating-panel.dragging * {
@@ -252,216 +277,432 @@ console.log("Content script starting...");
}
.floating-panel:active {
cursor: grabbing;
cursor: grabbing !important;
}
.floating-panel .panel-header {
cursor: grab;
cursor: grab !important;
}
.floating-panel .panel-header:active {
cursor: grabbing;
cursor: grabbing !important;
}
/* 吸附状态悬浮时 */
.floating-panel:not(.expanded):hover {
width: 120px;
background: #fff7e6;
border-color: #ffd591;
width: 120px !important;
background: #fff7e6 !important;
border-color: #ffd591 !important;
}
/* 展开状态 - 立即应用圆角变化 */
.floating-panel.expanded {
width: 180px;
height: auto;
max-height: 400px;
border-radius: 8px 0 0 8px;
box-shadow: -2px 0 10px rgba(0,0,0,0.1);
border: 1px solid #eee;
border-right: none;
width: 180px !important;
height: auto !important;
max-height: 400px !important;
border-radius: 8px 0 0 8px !important;
box-shadow: -2px 0 10px rgba(0,0,0,0.1) !important;
border: 1px solid #eee !important;
border-right: none !important;
/* 展开时立即应用新的圆角 */
transition:
width 0.2s cubic-bezier(0.4, 0, 0.2, 1),
height 0.2s cubic-bezier(0.4, 0, 0.2, 1),
border-radius 0s,
background-color 0.2s ease;
background-color 0.2s ease !important;
}
/* 链接面板展开状态 */
.floating-panel.expanded[data-active-tab="links"] {
width: 280px !important;
max-height: 600px !important;
}
.panel-header {
height: 40px;
min-height: 40px;
display: flex;
align-items: center;
padding: 0 8px;
cursor: pointer;
user-select: none;
height: 40px !important;
min-height: 40px !important;
display: flex !important;
align-items: center !important;
padding: 0 8px !important;
cursor: pointer !important;
user-select: none !important;
}
/* 吸附状态的头部 */
.floating-panel:not(.expanded) .panel-header {
background: transparent;
background: transparent !important;
}
/* 展开状态的头部 */
.floating-panel.expanded .panel-header {
background: #f8f9fa;
border-bottom: 1px solid #eee;
background: #f8f9fa !important;
border-bottom: 1px solid #eee !important;
}
.header-content {
display: flex;
align-items: center;
flex: 1;
overflow: hidden;
display: flex !important;
align-items: center !important;
flex: 1 !important;
overflow: hidden !important;
}
/* 优化图标大小过渡 */
.yak-icon {
width: 36px;
height: 36px;
min-width: 36px;
object-fit: contain;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
width: 36px !important;
height: 36px !important;
min-width: 36px !important;
object-fit: contain !important;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)) !important;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
.floating-panel.expanded .yak-icon {
width: 24px;
height: 24px;
min-width: 24px;
width: 24px !important;
height: 24px !important;
min-width: 24px !important;
}
.active-proxy-info {
display: flex;
align-items: center;
margin-left: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #ff6b00;
font-size: 13px;
font-weight: 500;
display: flex !important;
align-items: center !important;
margin-left: 8px !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
color: #ff6b00 !important;
font-size: 13px !important;
font-weight: 500 !important;
}
.active-proxy-info span:first-child {
margin-right: 6px;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1));
margin-right: 6px !important;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1)) !important;
}
.active-proxy-info span:nth-child(2) {
color: #333 !important;
}
.panel-content {
display: none;
background: white;
overflow-y: auto;
max-height: 360px;
opacity: 0;
transition: opacity 0.2s ease;
display: none !important;
background: white !important;
overflow-y: auto !important;
max-height: 360px !important;
opacity: 0 !important;
transition: opacity 0.2s ease !important;
}
.floating-panel.expanded .panel-content {
display: block;
opacity: 1;
display: block !important;
opacity: 1 !important;
}
.panel-content::-webkit-scrollbar {
width: 4px;
width: 4px !important;
}
.panel-content::-webkit-scrollbar-track {
background: #f5f5f5;
background: #f5f5f5 !important;
}
.panel-content::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 4px;
background: #ddd !important;
border-radius: 4px !important;
}
.panel-content::-webkit-scrollbar-thumb:hover {
background: #ccc;
background: #ccc !important;
}
.proxy-item {
display: flex;
align-items: center;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
position: relative;
display: flex !important;
align-items: center !important;
padding: 8px 12px !important;
cursor: pointer !important;
transition: all 0.2s !important;
white-space: nowrap !important;
position: relative !important;
}
.proxy-item:hover {
background: #fff7e6;
background: #fff7e6 !important;
}
.proxy-item.active {
background: #fff7e6;
color: #ff6b00;
background: #fff7e6 !important;
color: #ff6b00 !important;
}
.proxy-item.active span {
color: #ff6b00 !important;
}
.proxy-item span:first-child {
margin-right: 8px;
font-size: 16px;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1));
margin-right: 8px !important;
font-size: 16px !important;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1)) !important;
}
.proxy-item span {
color: #333 !important;
}
.proxy-status {
position: absolute;
right: 12px;
width: 6px;
height: 6px;
border-radius: 50%;
background: #52c41a;
box-shadow: 0 0 4px rgba(82,196,26,0.3);
position: absolute !important;
right: 12px !important;
width: 6px !important;
height: 6px !important;
border-radius: 50% !important;
background: #52c41a !important;
box-shadow: 0 0 4px rgba(82,196,26,0.3) !important;
}
.proxy-item.active .proxy-status {
background: #ff6b00;
box-shadow: 0 0 4px rgba(255,107,0,0.3);
background: #ff6b00 !important;
box-shadow: 0 0 4px rgba(255,107,0,0.3) !important;
}
.divider {
height: 1px;
background: #f0f0f0;
margin: 4px 0;
height: 1px !important;
background: #f0f0f0 !important;
margin: 4px 0 !important;
}
.action-button {
display: flex;
align-items: center;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
color: #666;
display: flex !important;
align-items: center !important;
padding: 8px 12px !important;
cursor: pointer !important;
transition: all 0.2s !important;
white-space: nowrap !important;
color: #666 !important;
}
.action-button:hover {
background: #fff7e6;
color: #ff6b00;
background: #fff7e6 !important;
color: #ff6b00 !important;
}
.action-button:hover span {
color: #ff6b00 !important;
}
.action-button span:first-child {
margin-right: 8px;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1));
margin-right: 8px !important;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1)) !important;
}
.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";
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);
@@ -485,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();
}
}
});
@@ -647,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) => {
@@ -686,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";
@@ -761,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);
}
}
}
});
});
// 添加代理按钮
@@ -802,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;
@@ -819,8 +1142,9 @@ console.log("Content script starting...");
const boundedTop = Math.max(0, Math.min(newTop, maxTop));
// 保持水平位置不变,只改变垂直位置
panel.style.top = `${boundedTop}px`;
panel.style.transform = "translateY(0)"; // 移除默认的 translateY(-50%)
// 使用setProperty方法添加!important标记确保样式生效
panel.style.setProperty('top', `${boundedTop}px`, 'important');
panel.style.setProperty('transform', 'translateY(0)', 'important'); // 移除默认的 translateY(-50%)
};
const onMouseDown = (e) => {
@@ -840,11 +1164,11 @@ console.log("Content script starting...");
startTop = rect.top;
// 开始拖动时固定当前位置
panel.style.top = `${startTop}px`;
panel.style.transform = "translateY(0)";
panel.style.setProperty('top', `${startTop}px`, 'important');
panel.style.setProperty('transform', 'translateY(0)', 'important');
// 添加拖动时的视觉反馈
panel.style.transition = "none";
panel.style.setProperty('transition', 'none', 'important');
panel.classList.add("dragging");
// 防止文本选择
@@ -870,7 +1194,7 @@ console.log("Content script starting...");
}
panel.classList.remove("dragging");
panel.style.transition = "";
panel.style.setProperty('transition', '', 'important');
// 保存位置
const top = panel.getBoundingClientRect().top;
@@ -888,8 +1212,8 @@ console.log("Content script starting...");
const savedPosition = localStorage.getItem("yakitProxyPanelPosition");
if (savedPosition) {
const top = (parseFloat(savedPosition) / 100) * window.innerHeight;
panel.style.top = `${top}px`;
panel.style.transform = "translateY(0)";
panel.style.setProperty('top', `${top}px`, 'important');
panel.style.setProperty('transform', 'translateY(0)', 'important');
}
},
+213
View File
@@ -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;