修改文字堆砌的问题

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
+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);
}