优化窗格显示

This commit is contained in:
yhy
2026-09-01 08:38:36 +08:00
parent af2ee38443
commit 3b07e2a3c1
11 changed files with 137 additions and 30 deletions
+10
View File
@@ -87,6 +87,16 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
gap: 4px; 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 { .chatpapers-brand {
display: flex; display: flex;
align-items: center; align-items: center;
+8 -4
View File
@@ -2,8 +2,10 @@ startup-begin = ChatPapers is loading
startup-finish = ChatPapers is ready startup-finish = ChatPapers is ready
prefs-title = ChatPapers prefs-title = ChatPapers
item-section-chat-head = ChatPapers item-section-chat-head =
item-section-chat-sidenav = Chat with paper .label = ChatPapers
item-section-chat-sidenav =
.tooltiptext = Chat with paper
item-pane-open-window = item-pane-open-window =
.tooltiptext = Open in separate window .tooltiptext = Open in separate window
item-pane-adaptive-height = item-pane-adaptive-height =
@@ -67,8 +69,10 @@ cite-copied = Bibliography copied
cite-exported = Bibliography exported cite-exported = Bibliography exported
cite-export-cancel = Export cancelled cite-export-cancel = Export cancelled
item-section-lecture-head = Voice lecture item-section-lecture-head =
item-section-lecture-sidenav = Voice lecture .label = Voice lecture
item-section-lecture-sidenav =
.tooltiptext = Voice lecture
lecture-loading = Loading voice lecture… lecture-loading = Loading voice lecture…
lecture-mode-label = Voice lecture · Prepare then listen lecture-mode-label = Voice lecture · Prepare then listen
+9
View File
@@ -1,3 +1,12 @@
itemmenu-label = ChatPapers itemmenu-label = ChatPapers
itemmenu-chat = ChatPapers: Chat itemmenu-chat = ChatPapers: Chat
itemmenu-compare = ChatPapers: Compare papers 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
+8 -4
View File
@@ -2,8 +2,10 @@ startup-begin = ChatPapers 加载中
startup-finish = ChatPapers 已就绪 startup-finish = ChatPapers 已就绪
prefs-title = ChatPapers prefs-title = ChatPapers
item-section-chat-head = ChatPapers item-section-chat-head =
item-section-chat-sidenav = 论文对话 .label = ChatPapers
item-section-chat-sidenav =
.tooltiptext = 论文对话
item-pane-open-window = item-pane-open-window =
.tooltiptext = 在独立窗口中打开 .tooltiptext = 在独立窗口中打开
item-pane-adaptive-height = item-pane-adaptive-height =
@@ -67,8 +69,10 @@ cite-copied = 参考文献已复制到剪贴板
cite-exported = 参考文献已导出 cite-exported = 参考文献已导出
cite-export-cancel = 已取消导出 cite-export-cancel = 已取消导出
item-section-lecture-head = 语音伴读 item-section-lecture-head =
item-section-lecture-sidenav = 语音伴读 .label = 语音伴读
item-section-lecture-sidenav =
.tooltiptext = 语音伴读
lecture-loading = 正在加载语音伴读… lecture-loading = 正在加载语音伴读…
lecture-mode-label = 语音伴读 · 先备课再听 lecture-mode-label = 语音伴读 · 先备课再听
+9
View File
@@ -1,3 +1,12 @@
itemmenu-label = ChatPapers itemmenu-label = ChatPapers
itemmenu-chat = ChatPapers:对话 itemmenu-chat = ChatPapers:对话
itemmenu-compare = 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 = 语音伴读
+3
View File
@@ -34,6 +34,9 @@ async function onMainWindowLoad(win: _ZoteroTypes.MainWindow): Promise<void> {
win.MozXULElement.insertFTLIfNeeded( win.MozXULElement.insertFTLIfNeeded(
`${addon.data.config.addonRef}-mainWindow.ftl`, `${addon.data.config.addonRef}-mainWindow.ftl`,
); );
win.MozXULElement.insertFTLIfNeeded(
`${addon.data.config.addonRef}-addon.ftl`,
);
registerItemMenus(); registerItemMenus();
} }
+5 -1
View File
@@ -58,6 +58,7 @@ import {
} from "../application/playSelectionExplain"; } from "../application/playSelectionExplain";
import { getString } from "../../../utils/locale"; import { getString } from "../../../utils/locale";
import { createLucideIcon } from "../../../utils/icons"; import { createLucideIcon } from "../../../utils/icons";
import { isItemPaneSectionBody } from "../../ui/itemPaneSection";
export class LecturePaneView { export class LecturePaneView {
private doc: Document; private doc: Document;
@@ -119,8 +120,10 @@ export class LecturePaneView {
const parent = getRegularItem(this.item); const parent = getRegularItem(this.item);
const title = String(parent.getField("title") || "ChatPapers"); const title = String(parent.getField("title") || "ChatPapers");
const inItemPane = isItemPaneSectionBody(this.body);
const header = this.el("div", "chatpapers-header"); const header = this.el("div", "chatpapers-header");
if (!inItemPane) {
const brand = this.el("div", "chatpapers-brand"); const brand = this.el("div", "chatpapers-brand");
brand.append( brand.append(
createLucideIcon(this.doc, Headphones, { createLucideIcon(this.doc, Headphones, {
@@ -129,8 +132,9 @@ export class LecturePaneView {
}), }),
this.el("div", "chatpapers-brand-text", "ChatPapers"), this.el("div", "chatpapers-brand-text", "ChatPapers"),
); );
header.append(brand);
}
header.append( header.append(
brand,
this.el("div", "chatpapers-title", title), this.el("div", "chatpapers-title", title),
this.el("div", "chatpapers-meta", getString("lecture-mode-label")), this.el("div", "chatpapers-meta", getString("lecture-mode-label")),
); );
@@ -5,7 +5,9 @@ import { ensureChatPapersStyles } from "../../ui/readerPane";
import { import {
applyAdaptiveHeight, applyAdaptiveHeight,
applyItemPaneSectionButtonIcons, applyItemPaneSectionButtonIcons,
applyItemPaneSectionHeadLabel,
buildItemPaneSectionButtons, buildItemPaneSectionButtons,
clearSectionSummary,
onItemPaneSectionToggle, onItemPaneSectionToggle,
prepareItemPaneBody, prepareItemPaneBody,
refreshSectionOpenHeight, refreshSectionOpenHeight,
@@ -36,6 +38,7 @@ export function registerLecturePane() {
if (doc) ensureChatPapersStyles(doc); if (doc) ensureChatPapersStyles(doc);
prepareItemPaneBody(body); prepareItemPaneBody(body);
applyAdaptiveHeight(body, "lecture"); applyAdaptiveHeight(body, "lecture");
applyItemPaneSectionHeadLabel(body, "lecture");
}, },
onDestroy: ({ body }) => { onDestroy: ({ body }) => {
views.get(body)?.destroy(); views.get(body)?.destroy();
@@ -45,17 +48,19 @@ export function registerLecturePane() {
setEnabled(Boolean(item && findPdfAttachment(item))); setEnabled(Boolean(item && findPdfAttachment(item)));
return true; return true;
}, },
onRender: ({ body }) => { onRender: ({ body, setSectionSummary }) => {
body.replaceChildren(); body.replaceChildren();
const doc = body.ownerDocument; const doc = body.ownerDocument;
if (!doc) return; if (!doc) return;
applyItemPaneSectionHeadLabel(body, "lecture");
clearSectionSummary(body, setSectionSummary);
applyItemPaneSectionButtonIcons(body); applyItemPaneSectionButtonIcons(body);
const loading = doc.createElement("div"); const loading = doc.createElement("div");
loading.className = "chatpapers-loading"; loading.className = "chatpapers-loading";
loading.textContent = getString("lecture-loading"); loading.textContent = getString("lecture-loading");
body.append(loading); body.append(loading);
}, },
onAsyncRender: async ({ body, item }) => { onAsyncRender: async ({ body, item, setSectionSummary }) => {
if (!item) return; if (!item) return;
const doc = body.ownerDocument; const doc = body.ownerDocument;
if (!doc) return; if (!doc) return;
@@ -72,6 +77,8 @@ export function registerLecturePane() {
err.textContent = getString("lecture-mount-error"); err.textContent = getString("lecture-mount-error");
body.append(err); body.append(err);
} }
clearSectionSummary(body, setSectionSummary);
applyItemPaneSectionHeadLabel(body, "lecture");
applyAdaptiveHeight(body, "lecture"); applyAdaptiveHeight(body, "lecture");
applyItemPaneSectionButtonIcons(body); applyItemPaneSectionButtonIcons(body);
const section = body.closest("collapsible-section"); const section = body.closest("collapsible-section");
+6 -1
View File
@@ -28,6 +28,7 @@ import {
isAbortError, isAbortError,
type AbortHandle, type AbortHandle,
} from "../../utils/abort"; } from "../../utils/abort";
import { isItemPaneSectionBody } from "./itemPaneSection";
export class ChatView { export class ChatView {
private body: HTMLElement; private body: HTMLElement;
@@ -60,7 +61,9 @@ export class ChatView {
const preset = cfg.provider ? getProviderPreset(cfg.provider) : undefined; const preset = cfg.provider ? getProviderPreset(cfg.provider) : undefined;
const providerLabel = preset?.label || cfg.provider || "未配置"; const providerLabel = preset?.label || cfg.provider || "未配置";
const inItemPane = isItemPaneSectionBody(this.body);
const header = this.el("div", "chatpapers-header"); const header = this.el("div", "chatpapers-header");
if (!inItemPane) {
const brand = this.el("div", "chatpapers-brand"); const brand = this.el("div", "chatpapers-brand");
brand.append( brand.append(
createLucideIcon(this.doc, MessageSquareText, { createLucideIcon(this.doc, MessageSquareText, {
@@ -69,6 +72,8 @@ export class ChatView {
}), }),
this.el("div", "chatpapers-brand-text", "ChatPapers"), this.el("div", "chatpapers-brand-text", "ChatPapers"),
); );
header.append(brand);
}
const titleRow = this.el("div", "chatpapers-title-row"); const titleRow = this.el("div", "chatpapers-title-row");
titleRow.append( titleRow.append(
this.el( this.el(
@@ -82,7 +87,7 @@ export class ChatView {
createLucideIcon(this.doc, Cpu, { size: 13, className: "chatpapers-meta-icon" }), createLucideIcon(this.doc, Cpu, { size: 13, className: "chatpapers-meta-icon" }),
this.el("span", undefined, `${providerLabel} · ${cfg.model || "无模型"}`), this.el("span", undefined, `${providerLabel} · ${cfg.model || "无模型"}`),
); );
header.append(brand, titleRow, meta); header.append(titleRow, meta);
this.messagesEl = this.el("div", "chatpapers-messages"); this.messagesEl = this.el("div", "chatpapers-messages");
this.emptyEl = this.el("div", "chatpapers-empty"); this.emptyEl = this.el("div", "chatpapers-empty");
+45
View File
@@ -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<ItemPaneKind, FluentMessageId> = {
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( function resolveCollapsibleSection(
body: HTMLElement, body: HTMLElement,
event?: Event, event?: Event,
+9 -2
View File
@@ -4,7 +4,9 @@ import { ChatView } from "./chatView";
import { import {
applyAdaptiveHeight, applyAdaptiveHeight,
applyItemPaneSectionButtonIcons, applyItemPaneSectionButtonIcons,
applyItemPaneSectionHeadLabel,
buildItemPaneSectionButtons, buildItemPaneSectionButtons,
clearSectionSummary,
onItemPaneSectionToggle, onItemPaneSectionToggle,
prepareItemPaneBody, prepareItemPaneBody,
refreshSectionOpenHeight, refreshSectionOpenHeight,
@@ -34,6 +36,7 @@ export function registerChatPane() {
if (doc) ensureStyles(doc); if (doc) ensureStyles(doc);
prepareItemPaneBody(body); prepareItemPaneBody(body);
applyAdaptiveHeight(body, "chat"); applyAdaptiveHeight(body, "chat");
applyItemPaneSectionHeadLabel(body, "chat");
}, },
onDestroy: ({ body }) => { onDestroy: ({ body }) => {
views.get(body)?.destroy(); views.get(body)?.destroy();
@@ -45,17 +48,19 @@ export function registerChatPane() {
void tabType; void tabType;
return true; return true;
}, },
onRender: ({ body }) => { onRender: ({ body, setSectionSummary }) => {
body.replaceChildren(); body.replaceChildren();
const doc = body.ownerDocument; const doc = body.ownerDocument;
if (!doc) return; if (!doc) return;
applyItemPaneSectionHeadLabel(body, "chat");
clearSectionSummary(body, setSectionSummary);
applyItemPaneSectionButtonIcons(body); applyItemPaneSectionButtonIcons(body);
const loading = doc.createElement("div"); const loading = doc.createElement("div");
loading.className = "chatpapers-loading"; loading.className = "chatpapers-loading";
loading.textContent = getString("chat-loading"); loading.textContent = getString("chat-loading");
body.append(loading); body.append(loading);
}, },
onAsyncRender: async ({ body, item }) => { onAsyncRender: async ({ body, item, setSectionSummary }) => {
if (!item) return; if (!item) return;
const doc = body.ownerDocument; const doc = body.ownerDocument;
if (!doc) return; if (!doc) return;
@@ -63,6 +68,8 @@ export function registerChatPane() {
const view = new ChatView(doc, body, item); const view = new ChatView(doc, body, item);
views.set(body, view); views.set(body, view);
await view.mount(); await view.mount();
applyItemPaneSectionHeadLabel(body, "chat");
clearSectionSummary(body, setSectionSummary);
applyAdaptiveHeight(body, "chat"); applyAdaptiveHeight(body, "chat");
applyItemPaneSectionButtonIcons(body); applyItemPaneSectionButtonIcons(body);
const section = body.closest("collapsible-section"); const section = body.closest("collapsible-section");