加入favicon
This commit is contained in:
@@ -808,6 +808,14 @@ h2 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.config-favicon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 3px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.config-domain {
|
||||
font-weight: 600;
|
||||
color: #e94560;
|
||||
|
||||
@@ -139,6 +139,7 @@ export function initBookmarkImport(ctx: PopupCtx) {
|
||||
submitSelector: null,
|
||||
enabled: true,
|
||||
delayMs: null,
|
||||
favicon: null,
|
||||
});
|
||||
added++;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 通过 ctx.openEditModal(cfg | null) 打开:cfg 为 null 表示新增。
|
||||
*/
|
||||
import { t } from "../i18n.js";
|
||||
import { resolveAlias } from "../ui-utils.js";
|
||||
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
|
||||
import type { DomainConfig } from "../types.js";
|
||||
import { $ } from "./dom";
|
||||
import type { PopupCtx } from "./context";
|
||||
@@ -92,6 +92,7 @@ export function initEditConfigModal(ctx: PopupCtx) {
|
||||
submitSelector: editSubmitSelectorInput.value.trim() || null,
|
||||
enabled: editEnabledCheckbox.checked,
|
||||
delayMs: editDelayMsInput.value ? Number(editDelayMsInput.value) : null,
|
||||
favicon: (await fetchFaviconByDomain(domain)) || null,
|
||||
};
|
||||
|
||||
const configs = await EnvStore.loadConfigs();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 添加按钮提交、"填入当前域名"按钮。
|
||||
*/
|
||||
import { t } from "../i18n.js";
|
||||
import { resolveAlias } from "../ui-utils.js";
|
||||
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
|
||||
import type { DomainConfig } from "../types.js";
|
||||
import { $ } from "./dom";
|
||||
import type { PopupCtx } from "./context";
|
||||
@@ -120,6 +120,7 @@ export function initConfigForm(ctx: PopupCtx) {
|
||||
submitSelector: submitSelectorInput.value.trim() || null,
|
||||
enabled: enabledCheckbox.checked,
|
||||
delayMs: delayMsInput.value ? Number(delayMsInput.value) : null,
|
||||
favicon: (await fetchFaviconByDomain(domain)) || null,
|
||||
};
|
||||
|
||||
const configs = await EnvStore.loadConfigs();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 空状态与"添加区/列表区"位置互换。
|
||||
*/
|
||||
import { t } from "../i18n.js";
|
||||
import { closeAllMenus, openConfigUrl } from "../ui-utils.js";
|
||||
import { closeAllMenus, openConfigUrl, faviconFallbackUrl } from "../ui-utils.js";
|
||||
import { $ } from "./dom";
|
||||
import type { PopupCtx } from "./context";
|
||||
|
||||
@@ -137,6 +137,19 @@ export function initConfigList(ctx: PopupCtx) {
|
||||
<div class="config-domain">${displayName} ${enabledTag} ${authTag}</div>
|
||||
`;
|
||||
|
||||
// favicon
|
||||
const faviconSrc = cfg.favicon || faviconFallbackUrl(cfg.domain);
|
||||
if (faviconSrc) {
|
||||
const favicon = document.createElement("img");
|
||||
favicon.className = "config-favicon";
|
||||
favicon.src = faviconSrc;
|
||||
favicon.alt = "";
|
||||
favicon.addEventListener("error", () => {
|
||||
favicon.style.display = "none";
|
||||
});
|
||||
item.appendChild(favicon);
|
||||
}
|
||||
|
||||
if (needsAuth) {
|
||||
const tag = info.querySelector(".tag-unauthorized");
|
||||
if (tag) {
|
||||
|
||||
@@ -148,6 +148,7 @@ export function initTransfer(ctx: PopupCtx) {
|
||||
submitSelector: cfg.submitSelector || null,
|
||||
enabled: cfg.enabled !== false,
|
||||
delayMs: cfg.delayMs || null,
|
||||
favicon: cfg.favicon || null,
|
||||
});
|
||||
|
||||
// 分支 A:导入 v3 环境结构 —— 同名环境合并配置(按 domain 去重),新环境创建
|
||||
|
||||
Vendored
+1
@@ -11,6 +11,7 @@ export interface DomainConfig {
|
||||
submitSelector: string | null;
|
||||
enabled: boolean;
|
||||
delayMs: number | null;
|
||||
favicon: string | null;
|
||||
}
|
||||
|
||||
/** 环境 */
|
||||
|
||||
@@ -38,3 +38,30 @@ export async function resolveAlias(domain: string): Promise<string | null> {
|
||||
export function closeAllMenus() {
|
||||
document.querySelectorAll(".config-menu.open").forEach((m) => m.classList.remove("open"));
|
||||
}
|
||||
/**
|
||||
* 从已打开的标签页里找匹配域名的页面,取其 favIconUrl。
|
||||
* 找不到时返回 null,渲染时用 faviconFallbackUrl 兜底。
|
||||
*/
|
||||
export async function fetchFaviconByDomain(domain: string): Promise<string | null> {
|
||||
try {
|
||||
const base = /^https?:\/\//i.test(domain) ? domain : "*://" + domain;
|
||||
const patterns = [base];
|
||||
if (!base.endsWith("*")) patterns.push(base + "/*");
|
||||
const tabs = await chrome.tabs.query({ url: patterns });
|
||||
for (const tab of tabs) {
|
||||
if (tab.url && tab.favIconUrl && isDomainMatch(tab.url, domain)) return tab.favIconUrl;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 从 domain 构造 /favicon.ico 兜底 URL */
|
||||
export function faviconFallbackUrl(domain: string): string | null {
|
||||
try {
|
||||
const withProto = /^https?:\/\//i.test(domain) ? domain : "https://" + domain;
|
||||
const u = new URL(withProto);
|
||||
return u.origin + "/favicon.ico";
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user