From 0793949516077d8946a7c89180c007ecc01c4859 Mon Sep 17 00:00:00 2001 From: huangjianwu Date: Thu, 7 May 2026 12:11:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(extension):=20popup=20=E6=94=B9=E7=B4=A7?= =?UTF-8?q?=E5=87=91=E8=A7=86=E5=9B=BE=EF=BC=8Cmarkdown=20=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E6=8C=AA=E5=88=B0=E4=BE=A7=E8=BE=B9=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前 popup 直接在 400px 宽里渲染 markdown,看起来很挤。改成: - popup:标题 + 封面缩略图 + 进度条 + 「在侧边栏查看」按钮,不再渲染 markdown - 提交「生成笔记」后自动调 chrome.sidePanel.open 把侧边栏拉起来 - 最近任务列表显示标题(拿到时)而非 URL - 新增 logic/api.resolveImageUrl:相对 /static 路径拼后端域名;hdslb / byteimg / kpcdn / ytimg 等带 referer 校验的封面走后端 /api/image_proxy 转发,避免 CORS / 防盗链问题 - 侧边栏顶部同样展示封面 + 标题 + 跳原片链接,方便用户辨识当前任务 Co-Authored-By: Claude Opus 4.7 (1M context) --- BillNote_extension/src/logic/api.ts | 13 ++++ BillNote_extension/src/popup/Popup.vue | 78 ++++++++++++++++--- .../src/sidepanel/Sidepanel.vue | 27 ++++++- 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/BillNote_extension/src/logic/api.ts b/BillNote_extension/src/logic/api.ts index 750a6e5..61d96e8 100644 --- a/BillNote_extension/src/logic/api.ts +++ b/BillNote_extension/src/logic/api.ts @@ -213,3 +213,16 @@ export function absolutizeMarkdownImages(md: string): string { const base = backendUrl() return md.replace(/!\[([^\]]*)\]\((\/static\/[^)]+)\)/g, (_, alt, path) => `![${alt}](${base}${path})`) } + +// 单个图片 URL 的处理:相对路径 → 拼后端域名;B 站等带防盗链的封面 → 走后端 image_proxy +export function resolveImageUrl(url: string | undefined | null): string { + if (!url) + return '' + const base = backendUrl() + if (url.startsWith('/')) + return `${base}${url}` + // B 站封面、抖音封面等会做 referer 校验;走后端代理 + if (/(hdslb|byteimg|kpcdn|akamaized|ytimg)\.com/i.test(url)) + return `${base}/api/image_proxy?url=${encodeURIComponent(url)}` + return url +} diff --git a/BillNote_extension/src/popup/Popup.vue b/BillNote_extension/src/popup/Popup.vue index d9bc674..bcb0ba4 100644 --- a/BillNote_extension/src/popup/Popup.vue +++ b/BillNote_extension/src/popup/Popup.vue @@ -2,11 +2,12 @@ import { computed, onMounted, onUnmounted, ref } from 'vue' import { detectPlatform } from '~/logic/platform' import { settings, settingsReady, tasks, tasksReady, upsertTask } from '~/logic/storage' -import { generateNote, getTaskStatus } from '~/logic/api' +import { generateNote, getTaskStatus, resolveImageUrl } from '~/logic/api' import type { TaskRecord } from '~/logic/types' const tabUrl = ref('') const tabTitle = ref('') +const tabId = ref(undefined) const platform = computed(() => detectPlatform(tabUrl.value)) const supported = computed(() => platform.value !== null) @@ -22,6 +23,7 @@ async function loadActiveTab() { const [tab] = await browser.tabs.query({ active: true, currentWindow: true }) tabUrl.value = tab?.url ?? '' tabTitle.value = tab?.title ?? '' + tabId.value = tab?.id } catch (e) { console.warn('无法读取当前 tab:', e) @@ -87,6 +89,8 @@ async function start() { updatedAt: Date.now(), }) poll(task_id) + // 提交后顺手把侧边栏拉起来,免得用户来回切窗口 + openSidePanel() } catch (e) { errorMsg.value = (e as Error).message @@ -100,6 +104,22 @@ function openOptions() { browser.runtime.openOptionsPage() } +async function openSidePanel() { + // 只能在用户操作触发的同步上下文里调,且需要明确的 tabId + try { + const target = tabId.value ?? (await browser.tabs.query({ active: true, currentWindow: true }))[0]?.id + if (target == null) + return + // @ts-expect-error sidePanel 类型在 polyfill 中不全 + if (typeof chrome !== 'undefined' && chrome.sidePanel?.open) + // @ts-expect-error see above + await chrome.sidePanel.open({ tabId: target }) + } + catch (err) { + console.warn('打开侧边栏失败:', err) + } +} + function selectTask(id: string) { activeTaskId.value = id const t = tasks.value?.find(x => x.taskId === id) @@ -107,10 +127,19 @@ function selectTask(id: string) { poll(id) } +const activeCover = computed(() => activeTask.value?.result?.audio_meta?.cover_url as string | undefined) +const activeTitle = computed(() => (activeTask.value?.result?.audio_meta?.title as string | undefined) || tabTitle.value) + +function fmtTime(ts?: number) { + if (!ts) + return '' + const d = new Date(ts) + return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` +} + onMounted(async () => { await Promise.all([settingsReady, tasksReady]) await loadActiveTab() - // 如果有进行中的任务,恢复轮询 const running = tasks.value?.find(t => t.status !== 'SUCCESS' && t.status !== 'FAILED') if (running) { activeTaskId.value = running.taskId @@ -180,12 +209,40 @@ onUnmounted(() => {
+
+ cover +
+
+ {{ activeTitle || '(未取到标题)' }} +
+
+ {{ fmtTime(activeTask.updatedAt) }} +
+
+
+ - + + +
@@ -198,8 +255,10 @@ onUnmounted(() => { :class="{ 'bg-blue-50': t.taskId === activeTaskId }" @click="selectTask(t.taskId)" > - {{ t.result?.audio_meta?.title || t.videoUrl }} - {{ t.status }} + + {{ (t.result?.audio_meta as { title?: string } | undefined)?.title || t.videoUrl }} + + {{ t.status }}
@@ -209,4 +268,5 @@ onUnmounted(() => { diff --git a/BillNote_extension/src/sidepanel/Sidepanel.vue b/BillNote_extension/src/sidepanel/Sidepanel.vue index c039755..9e71265 100644 --- a/BillNote_extension/src/sidepanel/Sidepanel.vue +++ b/BillNote_extension/src/sidepanel/Sidepanel.vue @@ -1,6 +1,6 @@