From 3b07e2a3c1c6533599fb0dc888edb208c3fd21e4 Mon Sep 17 00:00:00 2001 From: yhy Date: Tue, 1 Sep 2026 08:38:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=AA=97=E6=A0=BC=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addon/content/chatpapers.css | 10 +++++ addon/locale/en-US/addon.ftl | 12 +++-- addon/locale/en-US/mainWindow.ftl | 9 ++++ addon/locale/zh-CN/addon.ftl | 12 +++-- addon/locale/zh-CN/mainWindow.ftl | 9 ++++ src/hooks.ts | 3 ++ src/modules/lecture/ui/lecturePane.ts | 22 +++++---- src/modules/lecture/ui/registerLecturePane.ts | 11 ++++- src/modules/ui/chatView.ts | 23 ++++++---- src/modules/ui/itemPaneSection.ts | 45 +++++++++++++++++++ src/modules/ui/readerPane.ts | 11 ++++- 11 files changed, 137 insertions(+), 30 deletions(-) diff --git a/addon/content/chatpapers.css b/addon/content/chatpapers.css index f2afd34..fcf4ef9 100644 --- a/addon/content/chatpapers.css +++ b/addon/content/chatpapers.css @@ -87,6 +87,16 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root { gap: 4px; } +collapsible-section[custom][data-pane="chatpapers-chat"] > :not(.head) .chatpapers-header, +collapsible-section[custom][data-pane="chatpapers-lecture"] > :not(.head) .chatpapers-header { + gap: 2px; +} + +collapsible-section[custom][data-pane="chatpapers-chat"] > :not(.head) .chatpapers-header .chatpapers-brand, +collapsible-section[custom][data-pane="chatpapers-lecture"] > :not(.head) .chatpapers-header .chatpapers-brand { + display: none; +} + .chatpapers-brand { display: flex; align-items: center; diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl index e015509..7144fa6 100644 --- a/addon/locale/en-US/addon.ftl +++ b/addon/locale/en-US/addon.ftl @@ -2,8 +2,10 @@ startup-begin = ChatPapers is loading startup-finish = ChatPapers is ready prefs-title = ChatPapers -item-section-chat-head = ChatPapers -item-section-chat-sidenav = Chat with paper +item-section-chat-head = + .label = ChatPapers +item-section-chat-sidenav = + .tooltiptext = Chat with paper item-pane-open-window = .tooltiptext = Open in separate window item-pane-adaptive-height = @@ -67,8 +69,10 @@ cite-copied = Bibliography copied cite-exported = Bibliography exported cite-export-cancel = Export cancelled -item-section-lecture-head = Voice lecture -item-section-lecture-sidenav = Voice lecture +item-section-lecture-head = + .label = Voice lecture +item-section-lecture-sidenav = + .tooltiptext = Voice lecture lecture-loading = Loading voice lecture… lecture-mode-label = Voice lecture · Prepare then listen diff --git a/addon/locale/en-US/mainWindow.ftl b/addon/locale/en-US/mainWindow.ftl index eece86b..1383b97 100644 --- a/addon/locale/en-US/mainWindow.ftl +++ b/addon/locale/en-US/mainWindow.ftl @@ -1,3 +1,12 @@ itemmenu-label = ChatPapers itemmenu-chat = ChatPapers: Chat itemmenu-compare = ChatPapers: Compare papers + +item-section-chat-head = + .label = ChatPapers +item-section-chat-sidenav = + .tooltiptext = Chat with paper +item-section-lecture-head = + .label = Voice lecture +item-section-lecture-sidenav = + .tooltiptext = Voice lecture diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl index d849f8a..40483e1 100644 --- a/addon/locale/zh-CN/addon.ftl +++ b/addon/locale/zh-CN/addon.ftl @@ -2,8 +2,10 @@ startup-begin = ChatPapers 加载中 startup-finish = ChatPapers 已就绪 prefs-title = ChatPapers -item-section-chat-head = ChatPapers -item-section-chat-sidenav = 论文对话 +item-section-chat-head = + .label = ChatPapers +item-section-chat-sidenav = + .tooltiptext = 论文对话 item-pane-open-window = .tooltiptext = 在独立窗口中打开 item-pane-adaptive-height = @@ -67,8 +69,10 @@ cite-copied = 参考文献已复制到剪贴板 cite-exported = 参考文献已导出 cite-export-cancel = 已取消导出 -item-section-lecture-head = 语音伴读 -item-section-lecture-sidenav = 语音伴读 +item-section-lecture-head = + .label = 语音伴读 +item-section-lecture-sidenav = + .tooltiptext = 语音伴读 lecture-loading = 正在加载语音伴读… lecture-mode-label = 语音伴读 · 先备课再听 diff --git a/addon/locale/zh-CN/mainWindow.ftl b/addon/locale/zh-CN/mainWindow.ftl index 57891f9..6915e2a 100644 --- a/addon/locale/zh-CN/mainWindow.ftl +++ b/addon/locale/zh-CN/mainWindow.ftl @@ -1,3 +1,12 @@ itemmenu-label = ChatPapers itemmenu-chat = ChatPapers:对话 itemmenu-compare = ChatPapers:多篇对照 + +item-section-chat-head = + .label = ChatPapers +item-section-chat-sidenav = + .tooltiptext = 论文对话 +item-section-lecture-head = + .label = 语音伴读 +item-section-lecture-sidenav = + .tooltiptext = 语音伴读 diff --git a/src/hooks.ts b/src/hooks.ts index 911dee7..ba9262b 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -34,6 +34,9 @@ async function onMainWindowLoad(win: _ZoteroTypes.MainWindow): Promise { win.MozXULElement.insertFTLIfNeeded( `${addon.data.config.addonRef}-mainWindow.ftl`, ); + win.MozXULElement.insertFTLIfNeeded( + `${addon.data.config.addonRef}-addon.ftl`, + ); registerItemMenus(); } diff --git a/src/modules/lecture/ui/lecturePane.ts b/src/modules/lecture/ui/lecturePane.ts index f6e9228..01da8f9 100644 --- a/src/modules/lecture/ui/lecturePane.ts +++ b/src/modules/lecture/ui/lecturePane.ts @@ -58,6 +58,7 @@ import { } from "../application/playSelectionExplain"; import { getString } from "../../../utils/locale"; import { createLucideIcon } from "../../../utils/icons"; +import { isItemPaneSectionBody } from "../../ui/itemPaneSection"; export class LecturePaneView { private doc: Document; @@ -119,18 +120,21 @@ export class LecturePaneView { const parent = getRegularItem(this.item); const title = String(parent.getField("title") || "ChatPapers"); + const inItemPane = isItemPaneSectionBody(this.body); const header = this.el("div", "chatpapers-header"); - const brand = this.el("div", "chatpapers-brand"); - brand.append( - createLucideIcon(this.doc, Headphones, { - size: 18, - className: "chatpapers-brand-icon", - }), - this.el("div", "chatpapers-brand-text", "ChatPapers"), - ); + if (!inItemPane) { + const brand = this.el("div", "chatpapers-brand"); + brand.append( + createLucideIcon(this.doc, Headphones, { + size: 18, + className: "chatpapers-brand-icon", + }), + this.el("div", "chatpapers-brand-text", "ChatPapers"), + ); + header.append(brand); + } header.append( - brand, this.el("div", "chatpapers-title", title), this.el("div", "chatpapers-meta", getString("lecture-mode-label")), ); diff --git a/src/modules/lecture/ui/registerLecturePane.ts b/src/modules/lecture/ui/registerLecturePane.ts index d366cd4..7316ccd 100644 --- a/src/modules/lecture/ui/registerLecturePane.ts +++ b/src/modules/lecture/ui/registerLecturePane.ts @@ -5,7 +5,9 @@ import { ensureChatPapersStyles } from "../../ui/readerPane"; import { applyAdaptiveHeight, applyItemPaneSectionButtonIcons, + applyItemPaneSectionHeadLabel, buildItemPaneSectionButtons, + clearSectionSummary, onItemPaneSectionToggle, prepareItemPaneBody, refreshSectionOpenHeight, @@ -36,6 +38,7 @@ export function registerLecturePane() { if (doc) ensureChatPapersStyles(doc); prepareItemPaneBody(body); applyAdaptiveHeight(body, "lecture"); + applyItemPaneSectionHeadLabel(body, "lecture"); }, onDestroy: ({ body }) => { views.get(body)?.destroy(); @@ -45,17 +48,19 @@ export function registerLecturePane() { setEnabled(Boolean(item && findPdfAttachment(item))); return true; }, - onRender: ({ body }) => { + onRender: ({ body, setSectionSummary }) => { body.replaceChildren(); const doc = body.ownerDocument; if (!doc) return; + applyItemPaneSectionHeadLabel(body, "lecture"); + clearSectionSummary(body, setSectionSummary); applyItemPaneSectionButtonIcons(body); const loading = doc.createElement("div"); loading.className = "chatpapers-loading"; loading.textContent = getString("lecture-loading"); body.append(loading); }, - onAsyncRender: async ({ body, item }) => { + onAsyncRender: async ({ body, item, setSectionSummary }) => { if (!item) return; const doc = body.ownerDocument; if (!doc) return; @@ -72,6 +77,8 @@ export function registerLecturePane() { err.textContent = getString("lecture-mount-error"); body.append(err); } + clearSectionSummary(body, setSectionSummary); + applyItemPaneSectionHeadLabel(body, "lecture"); applyAdaptiveHeight(body, "lecture"); applyItemPaneSectionButtonIcons(body); const section = body.closest("collapsible-section"); diff --git a/src/modules/ui/chatView.ts b/src/modules/ui/chatView.ts index 7c8160b..38d226a 100644 --- a/src/modules/ui/chatView.ts +++ b/src/modules/ui/chatView.ts @@ -28,6 +28,7 @@ import { isAbortError, type AbortHandle, } from "../../utils/abort"; +import { isItemPaneSectionBody } from "./itemPaneSection"; export class ChatView { private body: HTMLElement; @@ -60,15 +61,19 @@ export class ChatView { const preset = cfg.provider ? getProviderPreset(cfg.provider) : undefined; const providerLabel = preset?.label || cfg.provider || "未配置"; + const inItemPane = isItemPaneSectionBody(this.body); const header = this.el("div", "chatpapers-header"); - const brand = this.el("div", "chatpapers-brand"); - brand.append( - createLucideIcon(this.doc, MessageSquareText, { - size: 18, - className: "chatpapers-brand-icon", - }), - this.el("div", "chatpapers-brand-text", "ChatPapers"), - ); + if (!inItemPane) { + const brand = this.el("div", "chatpapers-brand"); + brand.append( + createLucideIcon(this.doc, MessageSquareText, { + size: 18, + className: "chatpapers-brand-icon", + }), + this.el("div", "chatpapers-brand-text", "ChatPapers"), + ); + header.append(brand); + } const titleRow = this.el("div", "chatpapers-title-row"); titleRow.append( this.el( @@ -82,7 +87,7 @@ export class ChatView { createLucideIcon(this.doc, Cpu, { size: 13, className: "chatpapers-meta-icon" }), this.el("span", undefined, `${providerLabel} · ${cfg.model || "无模型"}`), ); - header.append(brand, titleRow, meta); + header.append(titleRow, meta); this.messagesEl = this.el("div", "chatpapers-messages"); this.emptyEl = this.el("div", "chatpapers-empty"); diff --git a/src/modules/ui/itemPaneSection.ts b/src/modules/ui/itemPaneSection.ts index 8295d75..7969f0e 100644 --- a/src/modules/ui/itemPaneSection.ts +++ b/src/modules/ui/itemPaneSection.ts @@ -226,6 +226,51 @@ export function buildItemPaneSectionButtons(kind: ItemPaneKind) { ]; } +export function isItemPaneSectionBody(body: HTMLElement): boolean { + return Boolean(body.closest("collapsible-section")); +} + +export type SectionSummaryUpdater = (summary: string) => void; + +export function clearSectionSummary( + body: HTMLElement, + updater?: SectionSummaryUpdater, +): void { + updater?.(""); + const section = body.closest("collapsible-section"); + if (section) section.setAttribute("summary", ""); +} + +const SECTION_HEAD_MESSAGE: Record = { + chat: "item-section-chat-head", + lecture: "item-section-lecture-head", +}; + +/** Ensure native section head shows plugin name (not just the icon). */ +export function applyItemPaneSectionHeadLabel( + body: HTMLElement, + kind: ItemPaneKind, +): void { + const section = body.closest("collapsible-section") as HTMLElement | null; + if (!section) return; + + const messageId = SECTION_HEAD_MESSAGE[kind]; + const l10nId = getLocaleID(messageId); + section.dataset.l10nId = l10nId; + + const label = getString(messageId, "label"); + if (label && label !== l10nId) { + section.setAttribute("label", label); + return; + } + + try { + body.ownerDocument?.l10n?.setAttributes?.(section, l10nId); + } catch { + // Fluent may not be ready yet; label stays empty until next render hook. + } +} + function resolveCollapsibleSection( body: HTMLElement, event?: Event, diff --git a/src/modules/ui/readerPane.ts b/src/modules/ui/readerPane.ts index faa555d..08a7f72 100644 --- a/src/modules/ui/readerPane.ts +++ b/src/modules/ui/readerPane.ts @@ -4,7 +4,9 @@ import { ChatView } from "./chatView"; import { applyAdaptiveHeight, applyItemPaneSectionButtonIcons, + applyItemPaneSectionHeadLabel, buildItemPaneSectionButtons, + clearSectionSummary, onItemPaneSectionToggle, prepareItemPaneBody, refreshSectionOpenHeight, @@ -34,6 +36,7 @@ export function registerChatPane() { if (doc) ensureStyles(doc); prepareItemPaneBody(body); applyAdaptiveHeight(body, "chat"); + applyItemPaneSectionHeadLabel(body, "chat"); }, onDestroy: ({ body }) => { views.get(body)?.destroy(); @@ -45,17 +48,19 @@ export function registerChatPane() { void tabType; return true; }, - onRender: ({ body }) => { + onRender: ({ body, setSectionSummary }) => { body.replaceChildren(); const doc = body.ownerDocument; if (!doc) return; + applyItemPaneSectionHeadLabel(body, "chat"); + clearSectionSummary(body, setSectionSummary); applyItemPaneSectionButtonIcons(body); const loading = doc.createElement("div"); loading.className = "chatpapers-loading"; loading.textContent = getString("chat-loading"); body.append(loading); }, - onAsyncRender: async ({ body, item }) => { + onAsyncRender: async ({ body, item, setSectionSummary }) => { if (!item) return; const doc = body.ownerDocument; if (!doc) return; @@ -63,6 +68,8 @@ export function registerChatPane() { const view = new ChatView(doc, body, item); views.set(body, view); await view.mount(); + applyItemPaneSectionHeadLabel(body, "chat"); + clearSectionSummary(body, setSectionSummary); applyAdaptiveHeight(body, "chat"); applyItemPaneSectionButtonIcons(body); const section = body.closest("collapsible-section");