优化窗格显示

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;
}
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;
+8 -4
View File
@@ -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
+9
View File
@@ -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
+8 -4
View File
@@ -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 = 语音伴读 · 先备课再听
+9
View File
@@ -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 = 语音伴读
+3
View File
@@ -34,6 +34,9 @@ async function onMainWindowLoad(win: _ZoteroTypes.MainWindow): Promise<void> {
win.MozXULElement.insertFTLIfNeeded(
`${addon.data.config.addonRef}-mainWindow.ftl`,
);
win.MozXULElement.insertFTLIfNeeded(
`${addon.data.config.addonRef}-addon.ftl`,
);
registerItemMenus();
}
+5 -1
View File
@@ -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,8 +120,10 @@ 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");
if (!inItemPane) {
const brand = this.el("div", "chatpapers-brand");
brand.append(
createLucideIcon(this.doc, Headphones, {
@@ -129,8 +132,9 @@ export class LecturePaneView {
}),
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")),
);
@@ -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");
+6 -1
View File
@@ -28,6 +28,7 @@ import {
isAbortError,
type AbortHandle,
} from "../../utils/abort";
import { isItemPaneSectionBody } from "./itemPaneSection";
export class ChatView {
private body: HTMLElement;
@@ -60,7 +61,9 @@ 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");
if (!inItemPane) {
const brand = this.el("div", "chatpapers-brand");
brand.append(
createLucideIcon(this.doc, MessageSquareText, {
@@ -69,6 +72,8 @@ export class ChatView {
}),
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");
+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(
body: HTMLElement,
event?: Event,
+9 -2
View File
@@ -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");