Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/BloomBrowserUI/bookEdit/StyleEditor/StyleEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,10 +676,21 @@ export default class StyleEditor {
if (!rule) {
return; // paranoia
}
// Keep the original properties for older render paths, but also expose the colors via
// CSS variables so custom highlights can read the same user-configured values.
rule.style.setProperty(
"--bloom-audio-highlight-background",
hiliteBgColor,
);
rule.style.setProperty("background-color", hiliteBgColor);
if (hiliteTextColor) {
rule.style.setProperty(
"--bloom-audio-highlight-text-color",
hiliteTextColor,
);
rule.style.setProperty("color", hiliteTextColor);
} else {
rule.style.removeProperty("--bloom-audio-highlight-text-color");
rule.style.removeProperty("color");
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/BloomBrowserUI/bookEdit/StyleEditor/StyleEditorSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,41 @@ describe("StyleEditor", () => {
if (rule != null) expect(ParseRuleForFontSize(rule.cssText)).toBe(20);
});

it("putAudioHiliteRulesInDom stores audio highlight css variables", () => {
const editor = new StyleEditor(
"file://" + "C:/dev/Bloom/src/BloomBrowserUI/bookEdit",
);

editor.putAudioHiliteRulesInDom(
"foo-style",
"rgb(1, 2, 3)",
"rgb(4, 5, 6)",
);

const sentenceRule = GetRuleMatchingSelector(
"foo-style span.ui-audioCurrent",
);
const paddedSentenceRule = GetRuleMatchingSelector(
"foo-style span.ui-audioCurrent > span.ui-enableHighlight",
);
const paragraphRule = GetRuleMatchingSelector(
"foo-style.ui-audioCurrent p",
);

expect(sentenceRule?.cssText).toContain(
"--bloom-audio-highlight-background: rgb(4, 5, 6)",
);
expect(sentenceRule?.cssText).toContain(
"--bloom-audio-highlight-color: rgb(1, 2, 3)",
);
Comment on lines +347 to +349
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Test uses wrong CSS variable name --bloom-audio-highlight-color instead of --bloom-audio-highlight-text-color

The test at line 348 asserts that sentenceRule?.cssText contains "--bloom-audio-highlight-color: rgb(1, 2, 3)", but the production code in StyleEditor.ts:688 sets the property as "--bloom-audio-highlight-text-color". The substring --bloom-audio-highlight-color does not appear in --bloom-audio-highlight-text-color (which has an extra text- segment), so the toContain assertion will fail.

Suggested change
expect(sentenceRule?.cssText).toContain(
"--bloom-audio-highlight-color: rgb(1, 2, 3)",
);
expect(sentenceRule?.cssText).toContain(
"--bloom-audio-highlight-text-color: rgb(1, 2, 3)",
);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

expect(paddedSentenceRule?.cssText).toContain(
"--bloom-audio-highlight-background: rgb(4, 5, 6)",
);
expect(paragraphRule?.cssText).toContain(
"--bloom-audio-highlight-background: rgb(4, 5, 6)",
);
});

// Skipped because currently we're running in jsdom. Making use of the existing rule depends on
// getComputedStyle, which jsdom does not support. ChatGpt thinks it also depends on actual
// dom element sizes, which jsdom also does not support. Attempts to polyfill proved difficult.
Expand Down
50 changes: 36 additions & 14 deletions src/BloomBrowserUI/bookEdit/toolbox/talkingBook/audioRecording.less
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ div.ui-audioCurrent:not(.ui-suppressHighlight):not(.ui-disableHighlight) p {
// color: @textOnLightBackground;
}

body.bloom-audio-customHighlights {
span.ui-audioCurrent:not(.ui-suppressHighlight):not(.ui-disableHighlight),
div.ui-audioCurrent:not(.ui-suppressHighlight):not(.ui-disableHighlight) p,
.ui-audioCurrent .ui-enableHighlight {
// When CSS.highlights is available, let the pseudo highlight own the paint so
// the legacy yellow background does not stack on top of it.
background-color: transparent !important;
color: inherit !important;
}
}

.bloom-ui-current-audio-marker:before {
background-image: url(currentTextIndicator.svg);
background-repeat: no-repeat;
Expand Down Expand Up @@ -76,24 +87,35 @@ div.ui-audioCurrent p {
position: unset; // BL-11633, works around Chromium bug
}

::highlight(bloom-audio-current) {
// Read from CSS variables so the Format dialog's stored audio highlight colors still
// control the yellow recording highlight even though it is now painted as a pseudo highlight.
background-color: var(
--bloom-audio-current-highlight-background,
@highlightColor
);
color: var(--bloom-audio-current-highlight-color, @textOnLightBackground);
}

::highlight(bloom-audio-split-1) {
background-color: #bfedf3;
}

::highlight(bloom-audio-split-2) {
background-color: #7fdae6;
}

::highlight(bloom-audio-split-3) {
background-color: #29c2d6;
}

.ui-audioCurrent.bloom-postAudioSplit[data-audiorecordingmode="TextBox"]:not(
.ui-suppressHighlight
):not(.ui-disableHighlight) {
// Special highlighting after the Split button completes to show it completed.
// Note: This highlighting is expected to persist across sessions, but to be hidden (displayed with the yellow color) while each segment is playing.
// This is accomplished because this rule temporarily drops out of effect when .ui-audioCurrent is moved to the span as that segment plays.
// (The rule requires a span BELOW the .ui-audioCurrent, so it drops out of effect the span IS the .ui-audioCurrent).
span:nth-child(3n + 1 of .bloom-highlightSegment) {
background-color: #bfedf3;
}

span:nth-child(3n + 2 of .bloom-highlightSegment) {
background-color: #7fdae6;
}

span:nth-child(3n + 3 of .bloom-highlightSegment) {
background-color: #29c2d6;
}
// The actual colors are now applied with named ::highlight() rules populated from TS.
// Note: This highlighting is expected to persist across sessions, but to be hidden
// (displayed with the yellow color) while each segment is playing.

span {
position: unset; // BL-11633, works around Chromium bug
Expand Down
30 changes: 30 additions & 0 deletions src/BloomBrowserUI/bookEdit/toolbox/talkingBook/audioRecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
} from "../../../react_components/featureStatus";
import { animateStyleName } from "../../../utils/shared";
import jQuery from "jquery";
import { AudioTextHighlightManager } from "./audioTextHighlightManager";

enum Status {
Disabled, // Can't use button now (e.g., Play when there is no recording)
Expand Down Expand Up @@ -207,6 +208,7 @@ export default class AudioRecording implements IAudioRecorder {

private playbackOrderCache: IPlaybackOrderInfo[] = [];
private disablingOverlay: HTMLDivElement;
private audioTextHighlightManager = new AudioTextHighlightManager();

constructor(maySetHighlight: boolean = true) {
this.audioSplitButton = <HTMLButtonElement>(
Expand Down Expand Up @@ -898,6 +900,10 @@ export default class AudioRecording implements IAudioRecorder {
if (pageDocBody) {
this.removeAudioCurrent(pageDocBody);
}

this.audioTextHighlightManager.clearManagedHighlights(
pageDocBody ?? undefined,
);
}

private removeAudioCurrent(parentElement: Element) {
Expand Down Expand Up @@ -997,6 +1003,7 @@ export default class AudioRecording implements IAudioRecorder {

if (oldElement === newElement && !forceRedisplay) {
// No need to do much, and better not to so we can avoid any temporary flashes as the highlight is removed and re-applied
this.refreshAudioTextHighlights(newElement);
return;
}

Expand Down Expand Up @@ -1069,6 +1076,23 @@ export default class AudioRecording implements IAudioRecorder {
);
}
}

this.refreshAudioTextHighlights(newElement);
}

private refreshAudioTextHighlights(currentHighlight?: Element | null) {
const activeHighlight = currentHighlight ?? this.getCurrentHighlight();
const currentTextBox = activeHighlight
? ((this.getTextBoxOfElement(
activeHighlight,
) as HTMLElement | null) ?? null)
: null;
// The manager keeps both the yellow current highlight and the blue post-split
// highlights in sync so callers do not need separate refresh paths.
this.audioTextHighlightManager.refreshHighlights(
activeHighlight,
currentTextBox,
);
}

// Scrolls an element into view.
Expand Down Expand Up @@ -4375,6 +4399,7 @@ export default class AudioRecording implements IAudioRecorder {
const currentTextBox = this.getCurrentTextBox();
if (currentTextBox) {
currentTextBox.classList.add("bloom-postAudioSplit");
this.refreshAudioTextHighlights(currentTextBox);
}
}

Expand All @@ -4384,6 +4409,10 @@ export default class AudioRecording implements IAudioRecorder {
currentTextBox.classList.remove("bloom-postAudioSplit");
currentTextBox.removeAttribute("data-audioRecordingEndTimes");
}

this.audioTextHighlightManager.clearManagedHighlights(
currentTextBox ?? undefined,
);
Comment on lines +4413 to +4415
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 clearAudioSplit silently skips highlight cleanup when currentTextBox is null

At audioRecording.ts:4411-4413, if getCurrentTextBox() returns null, clearManagedHighlights receives undefined and returns immediately at audioTextHighlightManager.ts:48-49 — leaving any existing ::highlight() entries in the registry. With the old CSS approach, removing the bloom-postAudioSplit class from the element was sufficient since the CSS selectors would stop matching. With the new approach, the registry entries persist independently. In practice this is mitigated because removeAudioCurrentFromPageDocBody also clears highlights and is called on most highlight transitions. But if a code path calls clearAudioSplit expecting to remove the visual split colors, and no highlight change follows, the colors could linger.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}

private getElementsToUpdateForCursor(): (Element | null)[] {
Expand Down Expand Up @@ -4823,6 +4852,7 @@ export default class AudioRecording implements IAudioRecorder {
}
});
this.nodesToRestoreAfterPlayEnded.clear();
this.refreshAudioTextHighlights();
}
}

Expand Down
Loading