修改文字堆砌的问题

This commit is contained in:
yhy
2026-08-30 08:28:01 +08:00
parent ce79275d41
commit 3702527613
7 changed files with 284 additions and 103 deletions
+91 -40
View File
@@ -849,9 +849,43 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, currentColor 32%, transparent) transparent;
}
.chatpapers-lecture-beat-list.chatpapers-lecture-scroll-y {
scrollbar-gutter: stable;
}
/* 段落列表:单行省略;详情区:换行(二者作用域分离) */
.chatpapers-lecture-beat-list .chatpapers-lecture-beat-line {
display: flex;
align-items: center;
gap: 8px;
flex: 1 1 auto;
min-width: 0;
max-width: 100%;
overflow: hidden;
}
.chatpapers-lecture-beat-list .chatpapers-lecture-beat-label {
flex: 0 0 auto;
font-size: 11px;
font-weight: 600;
line-height: 1.3;
opacity: 0.72;
white-space: nowrap;
}
.chatpapers-lecture-beat-list .chatpapers-lecture-beat-preview {
flex: 1 1 0;
width: 0;
min-width: 0;
font-size: 12px;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chatpapers-lecture-beat-list.chatpapers-lecture-scroll-y::-webkit-scrollbar,
.chatpapers-lecture-para-detail.chatpapers-lecture-scroll-y::-webkit-scrollbar {
width: 5px;
@@ -1062,19 +1096,32 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
font-size: 11px;
}
.chatpapers-lecture-para-original.is-active-sentence {
.chatpapers-lecture-beat-list .chatpapers-lecture-sentence-item {
max-height: 42px;
}
.chatpapers-lecture-beat-list .chatpapers-lecture-sentence-item .chatpapers-lecture-beat-main {
max-height: 40px;
}
.chatpapers-lecture-para-detail .chatpapers-lecture-para-original.is-active-sentence {
display: block;
margin-top: 2px;
margin-bottom: 8px;
border-left: 3px solid #1f7a8c;
padding-left: 8px;
padding: 6px 8px;
background: color-mix(in srgb, #1f7a8c 8%, transparent);
}
.chatpapers-lecture-sentence-list {
.chatpapers-lecture-para-detail .chatpapers-lecture-sentence-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
.chatpapers-lecture-sentence-chip {
.chatpapers-lecture-para-detail .chatpapers-lecture-sentence-chip {
display: block;
appearance: none;
width: 100%;
box-sizing: border-box;
@@ -1088,15 +1135,24 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
font-size: 11px;
line-height: 1.35;
color: inherit;
white-space: normal;
word-break: break-word;
overflow-wrap: anywhere;
overflow-wrap: break-word;
}
.chatpapers-lecture-sentence-chip.is-active {
.chatpapers-lecture-para-detail .chatpapers-lecture-sentence-chip.is-active {
background: color-mix(in srgb, #1f7a8c 12%, transparent);
border-color: color-mix(in srgb, #1f7a8c 40%, transparent);
}
.chatpapers-lecture-selection-explain .chatpapers-lecture-para-original.is-active-sentence {
display: block;
margin-bottom: 6px;
border-left: 3px solid #1f7a8c;
padding: 6px 8px;
background: color-mix(in srgb, #1f7a8c 8%, transparent);
}
.chatpapers-lecture-pdf-sync-hint {
margin-top: 4px;
font-size: 10px;
@@ -1105,9 +1161,7 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
}
.chatpapers-lecture-para-detail {
display: flex;
flex-direction: column;
gap: 6px;
display: block;
padding: 10px;
border-radius: 8px;
background: color-mix(in srgb, #1f7a8c 5%, transparent);
@@ -1139,15 +1193,25 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
overflow-x: hidden;
max-width: 100%;
font-size: 12px;
line-height: 1.55;
}
.chatpapers-lecture-beat-detail .chatpapers-lecture-beat-script {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
max-width: 100%;
font-size: 12px;
line-height: 1.55;
font-weight: 500;
}
.chatpapers-lecture-para-detail .chatpapers-lecture-para-original-label,
.chatpapers-lecture-para-detail .chatpapers-lecture-para-translation-label,
.chatpapers-lecture-para-detail .chatpapers-lecture-para-context-label {
display: block;
white-space: normal;
}
@@ -1160,8 +1224,15 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
}
.chatpapers-lecture-para-play-btn {
align-self: flex-start;
margin-bottom: 2px;
display: inline-flex;
margin-bottom: 6px;
}
.chatpapers-lecture-beat-detail-meta {
font-size: 11px;
font-weight: 600;
opacity: 0.78;
line-height: 1.35;
}
.chatpapers-lecture-selection-explain {
@@ -1208,20 +1279,26 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
.chatpapers-lecture-para-original-label,
.chatpapers-lecture-para-translation-label,
.chatpapers-lecture-para-context-label {
display: block;
font-size: 10px;
font-weight: 600;
opacity: 0.6;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-top: 4px;
margin-top: 8px;
margin-bottom: 2px;
}
.chatpapers-lecture-para-original {
display: block;
opacity: 0.9;
margin-bottom: 4px;
}
.chatpapers-lecture-para-translation {
display: block;
font-weight: 500;
margin-bottom: 4px;
}
.chatpapers-lecture-para-context {
@@ -1337,33 +1414,7 @@ collapsible-section[open] > :not(.head) .chatpapers-lecture-root {
opacity: 0.55;
}
.chatpapers-lecture-beat-line {
display: flex;
align-items: center;
gap: 8px;
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
}
.chatpapers-lecture-beat-label {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
line-height: 1.3;
opacity: 0.72;
white-space: nowrap;
}
.chatpapers-lecture-beat-preview {
flex: 1 1 auto;
min-width: 0;
font-size: 12px;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* beat-line / beat-preview 规则见上方 .chatpapers-lecture-beat-list 作用域 */
.chatpapers-lecture-beat-status {
flex-shrink: 0;
+3
View File
@@ -82,6 +82,9 @@ lecture-status-ready-phase1 = Preparation done (Phase 1: summary ready)
lecture-status-ready-step1 = Ready: { $count } segments — tap a segment or press Play
lecture-status-failed = Preparation incomplete
lecture-beat-list-title = Overview lecture
lecture-beat-list-hint = Click a segment for details; click ▶ to play
lecture-beat-detail-empty = Select a segment above to view the full script
lecture-beat-label = Segment { $n }
lecture-play-start = Play
lecture-play-pause = Pause
lecture-play-next = Next
+3
View File
@@ -82,6 +82,9 @@ lecture-status-ready-phase1 = 备课完成(Phase 1:摘要已生成)
lecture-status-ready-step1 = 备课完成:{ $count } 段讲解可播放,点击下方段落或播放按钮开始听课
lecture-status-failed = 备课未完成,请重试或检查 LLM 设置
lecture-beat-list-title = 整体讲解
lecture-beat-list-hint = 点击讲解段查看详情,点击 ▶ 开始朗读
lecture-beat-detail-empty = 点击上方讲解段查看完整讲稿
lecture-beat-label = 讲解 { $n }
lecture-play-start = 播放
lecture-play-pause = 暂停
lecture-play-next = 下一段
+2
View File
@@ -23,6 +23,8 @@
"scripts": {
"start": "zotero-plugin serve",
"build": "zotero-plugin build && tsc --noEmit",
"clean": "rm -rf .scaffold/build",
"rebuild": "npm run clean && npm run build",
"lint:check": "prettier --check . && eslint .",
"lint:fix": "prettier --write . && eslint . --fix",
"release": "zotero-plugin release",
+166 -60
View File
@@ -72,6 +72,8 @@ export class LecturePaneView {
private playMode: "overview" | "paragraph" = "paragraph";
private readGranularity: ReadGranularity = "paragraph";
private activeBeatId?: string;
private selectedBeatId?: string;
private cachedBeats: StoredBeat[] = [];
private activeParagraphId?: string;
private activeSentenceId?: string;
private selectedParagraphId?: string;
@@ -209,10 +211,7 @@ export class LecturePaneView {
);
this.paraListHintEl.hidden = true;
this.beatListEl = this.el("div", "chatpapers-lecture-beat-list chatpapers-lecture-scroll-y");
this.paragraphDetailEl = this.el(
"div",
"chatpapers-lecture-para-detail chatpapers-lecture-scroll-y",
);
this.paragraphDetailEl = this.el("div", "chatpapers-lecture-para-detail");
this.selectionExplainEl = this.el("div", "chatpapers-lecture-selection-explain");
this.selectionExplainEl.hidden = true;
@@ -551,6 +550,7 @@ export class LecturePaneView {
? getString("lecture-sentence-list-title")
: getString("lecture-paragraph-list-title");
this.paraListHintEl.hidden = false;
this.paraListHintEl.textContent = getString("lecture-para-list-hint");
this.renderParagraphList(paragraphs);
this.renderParagraphDetail(
paragraphs,
@@ -598,11 +598,13 @@ export class LecturePaneView {
this.granularityTabsEl.classList.add("is-visible");
} else if (hasBeats) {
this.granularityTabsEl.classList.remove("is-visible");
this.paraListHintEl.hidden = true;
this.paraListHintEl.hidden = false;
this.paraListHintEl.textContent = getString("lecture-beat-list-hint");
this.playerTitleEl.textContent = getString("lecture-beat-list-title");
this.beatListEl.hidden = false;
this.paragraphDetailEl.hidden = true;
this.cachedBeats = beats;
this.renderBeatList(beats);
this.renderBeatDetail(beats, this.selectedBeatId ?? this.activeBeatId);
if (!this.player) {
this.player = createPlayLecture({
@@ -610,7 +612,9 @@ export class LecturePaneView {
beats,
onBeatChange: (id) => {
this.activeBeatId = id;
this.highlightBeat(id);
if (id) this.selectedBeatId = id;
this.renderBeatList(this.cachedBeats);
this.renderBeatDetail(this.cachedBeats, id);
},
onPlayingChange: (playing) => {
this.playBtn.hidden = playing;
@@ -843,7 +847,10 @@ export class LecturePaneView {
if (!para) {
this.paragraphDetailEl.hidden = false;
this.paragraphDetailEl.classList.add("is-empty");
this.paragraphDetailEl.classList.remove("chatpapers-lecture-scroll-y");
this.paragraphDetailEl.classList.remove(
"chatpapers-lecture-scroll-y",
"chatpapers-lecture-beat-detail",
);
this.paragraphDetailEl.append(
this.el(
"div",
@@ -854,7 +861,7 @@ export class LecturePaneView {
return;
}
this.paragraphDetailEl.hidden = false;
this.paragraphDetailEl.classList.remove("is-empty");
this.paragraphDetailEl.classList.remove("is-empty", "chatpapers-lecture-beat-detail");
this.paragraphDetailEl.classList.add("chatpapers-lecture-scroll-y");
const sentence = sentenceId
@@ -896,7 +903,11 @@ export class LecturePaneView {
}
const originalText = sentence?.text ?? para.originalText;
const translationText = sentence?.translation ?? para.translation;
const translationText = (
sentence?.translation ||
(!sentence ? para.translation : "") ||
""
).trim();
this.paragraphDetailEl.append(
this.el("div", "chatpapers-lecture-para-original-label", getString("lecture-para-original")),
@@ -905,11 +916,20 @@ export class LecturePaneView {
`chatpapers-lecture-para-original${sentence ? " is-active-sentence" : ""}`,
originalText,
),
this.el("div", "chatpapers-lecture-para-translation-label", getString("lecture-para-translation")),
this.el("div", "chatpapers-lecture-para-translation", translationText),
);
if (sentence && (para.sentences?.length ?? 0) > 1) {
if (translationText) {
this.paragraphDetailEl.append(
this.el("div", "chatpapers-lecture-para-translation-label", getString("lecture-para-translation")),
this.el("div", "chatpapers-lecture-para-translation", translationText),
);
}
if (
sentence &&
this.readGranularity !== "sentence" &&
(para.sentences?.length ?? 0) > 1
) {
const sentenceList = this.el("div", "chatpapers-lecture-sentence-list");
for (const s of para.sentences ?? []) {
const item = this.doc.createElement("button");
@@ -1097,57 +1117,143 @@ export class LecturePaneView {
private renderBeatList(beats: StoredBeat[]): void {
this.beatListEl.replaceChildren();
for (const beat of beats) {
const row = this.doc.createElement("button") as HTMLButtonElement;
row.type = "button";
row.className = "chatpapers-lecture-beat-item";
row.dataset.beatId = beat.id;
if (beat.id === this.activeBeatId) {
row.classList.add("is-active");
}
if (beat.ttsStatus !== "ready") {
row.classList.add("is-pending");
row.disabled = true;
}
const meta = this.el("div", "chatpapers-lecture-beat-meta");
meta.append(
this.el(
"span",
"chatpapers-lecture-beat-type",
BEAT_TYPE_LABELS[beat.type] || beat.type,
),
this.el(
"span",
"chatpapers-lecture-beat-title",
beat.title || beat.id,
),
);
const status = this.el("span", "chatpapers-lecture-beat-status");
if (beat.ttsStatus === "ready") {
status.textContent = "✓";
} else if (beat.ttsStatus === "failed") {
status.textContent = getString("lecture-beat-tts-failed");
} else {
status.textContent = getString("lecture-beat-tts-pending");
}
row.append(meta, status);
row.addEventListener("click", () => {
void this.player?.jumpToBeatId(beat.id);
});
this.beatListEl.append(row);
this.beatListEl.append(this.makeBeatRow(beats, beat));
}
}
private highlightBeat(beatId?: string): void {
this.activeBeatId = beatId;
for (const node of this.beatListEl.querySelectorAll(
".chatpapers-lecture-beat-item",
)) {
const el = node as HTMLElement;
el.classList.toggle("is-active", el.dataset.beatId === beatId);
private isBeatSelected(beatId: string): boolean {
return this.selectedBeatId === beatId;
}
private isBeatPlaying(beatId: string): boolean {
return this.activeBeatId === beatId;
}
private selectBeat(beats: StoredBeat[], beatId: string): void {
this.selectedBeatId = beatId;
this.renderBeatList(beats);
this.renderBeatDetail(beats, beatId);
}
private playBeat(beat: StoredBeat): void {
void this.player?.jumpToBeatId(beat.id);
}
private makeBeatPlayButton(beat: StoredBeat): HTMLButtonElement {
const playBtn = this.doc.createElement("button");
playBtn.type = "button";
playBtn.className = "chatpapers-lecture-beat-play";
playBtn.title = getString("lecture-para-play");
playBtn.setAttribute("aria-label", getString("lecture-para-play"));
playBtn.append(
createLucideIcon(this.doc, Play, {
size: 14,
className: "chatpapers-lecture-btn-icon",
}),
);
playBtn.disabled = beat.ttsStatus !== "ready";
playBtn.addEventListener("click", (event) => {
event.stopPropagation();
this.playBeat(beat);
});
return playBtn;
}
private makeBeatRow(beats: StoredBeat[], beat: StoredBeat): HTMLElement {
const row = this.el("div", "chatpapers-lecture-beat-item");
row.dataset.beatId = beat.id;
if (this.isBeatSelected(beat.id)) row.classList.add("is-selected");
if (this.isBeatPlaying(beat.id)) row.classList.add("is-active");
if (beat.ttsStatus !== "ready") row.classList.add("is-pending");
const main = this.doc.createElement("button");
main.type = "button";
main.className = "chatpapers-lecture-beat-main";
const preview = beat.title?.trim() || beat.script;
const line = this.el("div", "chatpapers-lecture-beat-line");
line.append(
this.el(
"span",
"chatpapers-lecture-beat-label",
getString("lecture-beat-label", { args: { n: beat.orderIndex + 1 } }),
),
this.el("span", "chatpapers-lecture-beat-preview", preview),
);
const status = this.el("span", "chatpapers-lecture-beat-status");
if (beat.ttsStatus === "ready") {
status.textContent = "✓";
} else if (beat.ttsStatus === "failed") {
status.textContent = getString("lecture-beat-tts-failed");
} else {
status.textContent = getString("lecture-beat-tts-pending");
}
main.append(line, status);
main.title = preview;
main.addEventListener("click", () => {
this.selectBeat(beats, beat.id);
});
row.append(main, this.makeBeatPlayButton(beat));
return row;
}
private renderBeatDetail(beats: StoredBeat[], beatId?: string): void {
const beat = beatId ? beats.find((b) => b.id === beatId) : undefined;
this.paragraphDetailEl.replaceChildren();
if (!beat) {
this.paragraphDetailEl.hidden = false;
this.paragraphDetailEl.classList.add("is-empty");
this.paragraphDetailEl.classList.remove(
"chatpapers-lecture-scroll-y",
"chatpapers-lecture-beat-detail",
);
this.paragraphDetailEl.append(
this.el(
"div",
"chatpapers-lecture-para-detail-empty",
getString("lecture-beat-detail-empty"),
),
);
return;
}
this.paragraphDetailEl.hidden = false;
this.paragraphDetailEl.classList.remove("is-empty");
this.paragraphDetailEl.classList.add(
"chatpapers-lecture-scroll-y",
"chatpapers-lecture-beat-detail",
);
const playBtn = this.doc.createElement("button");
playBtn.type = "button";
playBtn.className =
"chatpapers-lecture-secondary chatpapers-lecture-para-play-btn";
playBtn.append(
createLucideIcon(this.doc, Play, {
size: 14,
className: "chatpapers-lecture-btn-icon",
}),
this.doc.createTextNode(getString("lecture-para-play")),
);
playBtn.disabled = beat.ttsStatus !== "ready";
playBtn.addEventListener("click", () => {
this.playBeat(beat);
});
this.paragraphDetailEl.append(playBtn);
const typeLabel = BEAT_TYPE_LABELS[beat.type] || beat.type;
const title = beat.title?.trim();
this.paragraphDetailEl.append(
this.el(
"div",
"chatpapers-lecture-beat-detail-meta",
title ? `${typeLabel} · ${title}` : typeLabel,
),
this.el("div", "chatpapers-lecture-beat-script", beat.script),
);
}
private async statusMessage(
+16 -3
View File
@@ -88,14 +88,27 @@ export function registerPrefs() {
});
}
export function ensureChatPapersStyles(doc: Document) {
export function ensureChatPapersStyles(doc: Document): void {
const id = "chatpapers-styles";
if (doc.getElementById(id)) return;
const base = `chrome://${config.addonRef}/content/chatpapers.css`;
const href =
typeof __env__ !== "undefined" && __env__ === "development"
? `${base}?v=${Date.now()}`
: base;
const existing = doc.getElementById(id) as HTMLLinkElement | null;
if (existing) {
if (typeof __env__ !== "undefined" && __env__ === "development") {
existing.href = href;
}
return;
}
const link = doc.createElement("link");
link.id = id;
link.rel = "stylesheet";
link.type = "text/css";
link.href = `chrome://${config.addonRef}/content/chatpapers.css`;
link.href = href;
doc.documentElement?.appendChild(link);
}
+3
View File
@@ -44,6 +44,9 @@ export type FluentMessageId =
| 'itemmenu-compare'
| 'itemmenu-label'
| 'lecture-attach-error'
| 'lecture-beat-detail-empty'
| 'lecture-beat-label'
| 'lecture-beat-list-hint'
| 'lecture-beat-list-title'
| 'lecture-beat-tts-failed'
| 'lecture-beat-tts-pending'