Skip to content

Commit 3f60382

Browse files
committed
BL-15934 switch edit tab highlighting to pseudo element
1 parent 879c9e2 commit 3f60382

12 files changed

Lines changed: 1076 additions & 64 deletions

File tree

src/BloomBrowserUI/bookEdit/StyleEditor/StyleEditor.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ import { CanvasElementManager } from "../js/CanvasElementManager";
4444
import { kCanvasElementSelector } from "../toolbox/canvas/canvasElementUtils";
4545
import { getPageIFrame } from "../../utils/shared";
4646

47+
const kAudioHighlightBackgroundCssVar = "--bloom-audio-highlight-background";
48+
const kAudioHighlightTextColorCssVar = "--bloom-audio-highlight-text-color";
49+
4750
// Controls the CSS text-align value
4851
// Note: CSS text-align W3 standard does not specify "start" or "end", but Firefox/Chrome/Edge do support it.
4952
// Note: CSS text-align also has values "initial", and "inherit", which we do not support currently.
@@ -676,12 +679,20 @@ export default class StyleEditor {
676679
if (!rule) {
677680
return; // paranoia
678681
}
679-
rule.style.setProperty("background-color", hiliteBgColor);
682+
// (March 2026) We are switching to using CSS variables for the hilite colors, so they can be used by both the
683+
// css rules here (used in e.g. bloom player) and the highlighting pseudoelements which we will now be using
684+
// edit tab.
685+
rule.style.setProperty(kAudioHighlightBackgroundCssVar, hiliteBgColor);
686+
rule.style.removeProperty("background-color");
680687
if (hiliteTextColor) {
681-
rule.style.setProperty("color", hiliteTextColor);
688+
rule.style.setProperty(
689+
kAudioHighlightTextColorCssVar,
690+
hiliteTextColor,
691+
);
682692
} else {
683-
rule.style.removeProperty("color");
693+
rule.style.removeProperty(kAudioHighlightTextColorCssVar);
684694
}
695+
rule.style.removeProperty("color");
685696
}
686697

687698
public getAudioHiliteProps(styleName: string): {
@@ -694,8 +705,16 @@ export default class StyleEditor {
694705
this.sentenceHiliteRuleSelector,
695706
false,
696707
);
697-
const hiliteTextColor = sentenceRule?.style?.color;
698-
let hiliteBgColor = sentenceRule?.style?.backgroundColor;
708+
const hiliteTextColor =
709+
sentenceRule?.style
710+
?.getPropertyValue(kAudioHighlightTextColorCssVar)
711+
.trim() ||
712+
sentenceRule?.style?.color ||
713+
undefined;
714+
let hiliteBgColor =
715+
sentenceRule?.style
716+
?.getPropertyValue(kAudioHighlightBackgroundCssVar)
717+
.trim() || sentenceRule?.style?.backgroundColor;
699718
if (!hiliteBgColor) {
700719
hiliteBgColor = kBloomYellow;
701720
}

src/BloomBrowserUI/bookEdit/StyleEditor/StyleEditorSpec.ts

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,12 @@ function ChangeSizeAbsolute(
5151
editor.changeSizeInternal(newSize + "pt", shouldSetDefaultRule);
5252
}
5353

54-
function GetUserModifiedStyleSheet(): any {
54+
function GetUserModifiedStyleSheet(): CSSStyleSheet | undefined {
5555
for (let i = 0; i < document.styleSheets.length; i++) {
56-
if (document.styleSheets[i].title == "userModifiedStyles")
56+
if (document.styleSheets[i].title === "userModifiedStyles")
5757
return <CSSStyleSheet>document.styleSheets[i];
5858
}
59-
// this is not a valid constructor
60-
//return new CSSStyleSheet();
61-
return {};
59+
return undefined;
6260
}
6361

6462
function GetFooStyleRuleFontSize(): number {
@@ -83,8 +81,8 @@ function ParseRuleForFontSize(ruleText: string): number {
8381
}
8482

8583
function GetRuleForFooStyle(): CSSRule | null {
86-
const x: CSSRuleList = (<CSSStyleSheet>GetUserModifiedStyleSheet())
87-
.cssRules;
84+
const x = GetUserModifiedStyleSheet()?.cssRules;
85+
if (!x) return null;
8886

8987
for (let i = 0; i < x.length; i++) {
9088
if (x[i].cssText.indexOf("foo-style") > -1) {
@@ -95,8 +93,7 @@ function GetRuleForFooStyle(): CSSRule | null {
9593
}
9694

9795
function GetRuleForNormalStyle(): CSSRule | null {
98-
const x: CSSRuleList = (<CSSStyleSheet>GetUserModifiedStyleSheet())
99-
.cssRules;
96+
const x = GetUserModifiedStyleSheet()?.cssRules;
10097
if (!x) return null;
10198

10299
for (let i = 0; i < x.length; i++) {
@@ -108,8 +105,7 @@ function GetRuleForNormalStyle(): CSSRule | null {
108105
}
109106

110107
function GetRuleForCoverTitleStyle(): CSSRule | null {
111-
const x: CSSRuleList = (<CSSStyleSheet>GetUserModifiedStyleSheet())
112-
.cssRules;
108+
const x = GetUserModifiedStyleSheet()?.cssRules;
113109
if (!x) return null;
114110
for (let i = 0; i < x.length; i++) {
115111
if (x[i].cssText.indexOf("Title-On-Cover-style") > -1) {
@@ -128,8 +124,9 @@ function GetCalculatedFontSize(target: string): number {
128124
}
129125

130126
function GetRuleMatchingSelector(selector: string): CSSRule | null {
131-
const x = (<CSSStyleSheet>GetUserModifiedStyleSheet()).cssRules;
132-
const count = 0;
127+
const sheet = GetUserModifiedStyleSheet();
128+
const x = sheet?.cssRules;
129+
if (!x) return null;
133130
for (let i = 0; i < x.length; i++) {
134131
if (x[i].cssText.indexOf(selector) > -1) {
135132
return x[i];
@@ -139,7 +136,9 @@ function GetRuleMatchingSelector(selector: string): CSSRule | null {
139136
}
140137

141138
function HasRuleMatchingThisSelector(selector: string): boolean {
142-
const x = (<CSSStyleSheet>GetUserModifiedStyleSheet()).cssRules;
139+
const sheet = GetUserModifiedStyleSheet();
140+
const x = sheet?.cssRules;
141+
if (!x) return false;
143142
let count = 0;
144143
for (let i = 0; i < x.length; i++) {
145144
if (x[i].cssText.indexOf(selector) > -1) {
@@ -150,8 +149,8 @@ function HasRuleMatchingThisSelector(selector: string): boolean {
150149
}
151150

152151
function countFooStyleRules(): number {
153-
const x: CSSRuleList = (<CSSStyleSheet>GetUserModifiedStyleSheet())
154-
.cssRules;
152+
const x = GetUserModifiedStyleSheet()?.cssRules;
153+
if (!x) return 0;
155154

156155
let count = 0;
157156
for (let i = 0; i < x.length; i++) {
@@ -320,6 +319,79 @@ describe("StyleEditor", () => {
320319
if (rule != null) expect(ParseRuleForFontSize(rule.cssText)).toBe(20);
321320
});
322321

322+
it("putAudioHiliteRulesInDom stores audio highlight css variables", () => {
323+
const editor = new StyleEditor(
324+
"file://" + "C:/dev/Bloom/src/BloomBrowserUI/bookEdit",
325+
);
326+
327+
const sentenceSelector = "foo-style span.ui-audioCurrent";
328+
const paddedSentenceSelector =
329+
"foo-style span.ui-audioCurrent > span.ui-enableHighlight";
330+
const paragraphSelector = "foo-style.ui-audioCurrent p";
331+
332+
// sanity check that the rules do not yet contain the variables
333+
expect(HasRuleMatchingThisSelector(sentenceSelector)).toBeFalsy();
334+
expect(HasRuleMatchingThisSelector(paddedSentenceSelector)).toBeFalsy();
335+
expect(HasRuleMatchingThisSelector(paragraphSelector)).toBeFalsy();
336+
337+
editor.putAudioHiliteRulesInDom(
338+
"foo-style",
339+
"rgb(1, 2, 3)",
340+
"rgb(4, 5, 6)",
341+
);
342+
343+
const sentenceRule = GetRuleMatchingSelector(sentenceSelector);
344+
const paddedSentenceRule = GetRuleMatchingSelector(
345+
paddedSentenceSelector,
346+
);
347+
const paragraphRule = GetRuleMatchingSelector(paragraphSelector);
348+
349+
expect(sentenceRule?.cssText).toContain(
350+
"--bloom-audio-highlight-background: rgb(4, 5, 6)",
351+
);
352+
expect(sentenceRule?.cssText).toContain(
353+
"--bloom-audio-highlight-text-color: rgb(1, 2, 3)",
354+
);
355+
expect(paddedSentenceRule?.cssText).toContain(
356+
"--bloom-audio-highlight-background: rgb(4, 5, 6)",
357+
);
358+
expect(paragraphRule?.cssText).toContain(
359+
"--bloom-audio-highlight-background: rgb(4, 5, 6)",
360+
);
361+
expect(sentenceRule?.cssText).not.toMatch(
362+
/(^|[;{\s])background-color\s*:/,
363+
);
364+
expect(sentenceRule?.cssText).not.toMatch(/(^|[;{\s])color\s*:/);
365+
expect(paddedSentenceRule?.cssText).not.toMatch(
366+
/(^|[;{\s])background-color\s*:/,
367+
);
368+
expect(paragraphRule?.cssText).not.toMatch(
369+
/(^|[;{\s])background-color\s*:/,
370+
);
371+
});
372+
373+
it("getAudioHiliteProps reads colors from audio highlight css variables", () => {
374+
const editor = new StyleEditor(
375+
"file://" + "C:/dev/Bloom/src/BloomBrowserUI/bookEdit",
376+
);
377+
378+
const origProps = editor.getAudioHiliteProps("foo-style");
379+
380+
expect(origProps?.hiliteTextColor).not.toBe("rgb(1, 2, 3)");
381+
expect(origProps?.hiliteBgColor).not.toBe("rgb(4, 5, 6)");
382+
383+
editor.putAudioHiliteRulesInDom(
384+
"foo-style",
385+
"rgb(1, 2, 3)",
386+
"rgb(4, 5, 6)",
387+
);
388+
389+
const props = editor.getAudioHiliteProps("foo-style");
390+
391+
expect(props.hiliteTextColor).toBe("rgb(1, 2, 3)");
392+
expect(props.hiliteBgColor).toBe("rgb(4, 5, 6)");
393+
});
394+
323395
// Skipped because currently we're running in jsdom. Making use of the existing rule depends on
324396
// getComputedStyle, which jsdom does not support. ChatGpt thinks it also depends on actual
325397
// dom element sizes, which jsdom also does not support. Attempts to polyfill proved difficult.

src/BloomBrowserUI/bookEdit/toolbox/talkingBook/audioRecording.less

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ div.ui-audioCurrent:not(.ui-suppressHighlight):not(.ui-disableHighlight) p {
3131
// color: @textOnLightBackground;
3232
}
3333

34+
body.bloom-audio-customHighlights {
35+
span.ui-audioCurrent:not(.ui-suppressHighlight):not(.ui-disableHighlight),
36+
div.ui-audioCurrent:not(.ui-suppressHighlight):not(.ui-disableHighlight) p,
37+
.ui-audioCurrent .ui-enableHighlight {
38+
// When CSS.highlights is available, let the pseudo highlight own the paint so
39+
// the legacy yellow background does not stack on top of it.
40+
background-color: transparent !important;
41+
color: inherit !important;
42+
}
43+
}
44+
3445
.bloom-ui-current-audio-marker:before {
3546
background-image: url(currentTextIndicator.svg);
3647
background-repeat: no-repeat;
@@ -76,33 +87,28 @@ div.ui-audioCurrent p {
7687
position: unset; // BL-11633, works around Chromium bug
7788
}
7889

79-
.ui-audioCurrent.bloom-postAudioSplit[data-audiorecordingmode="TextBox"]:not(
80-
.ui-suppressHighlight
81-
):not(.ui-disableHighlight) {
82-
// Special highlighting after the Split button completes to show it completed.
83-
// Note: This highlighting is expected to persist across sessions, but to be hidden (displayed with the yellow color) while each segment is playing.
84-
// This is accomplished because this rule temporarily drops out of effect when .ui-audioCurrent is moved to the span as that segment plays.
85-
// (The rule requires a span BELOW the .ui-audioCurrent, so it drops out of effect the span IS the .ui-audioCurrent).
86-
span:nth-child(3n + 1 of .bloom-highlightSegment) {
87-
background-color: #bfedf3;
88-
}
89-
90-
span:nth-child(3n + 2 of .bloom-highlightSegment) {
91-
background-color: #7fdae6;
92-
}
93-
94-
span:nth-child(3n + 3 of .bloom-highlightSegment) {
95-
background-color: #29c2d6;
96-
}
97-
98-
span {
99-
position: unset; // BL-11633, works around Chromium bug
100-
}
90+
::highlight(bloom-audio-current) {
91+
// Read from CSS variables so the Format dialog's stored audio highlight colors
92+
// control the normally-yellow recording highlight
93+
background-color: var(
94+
--bloom-audio-current-highlight-background,
95+
@highlightColor
96+
);
97+
color: var(--bloom-audio-current-highlight-color, @textOnLightBackground);
98+
}
10199

102-
p {
103-
// Override the normal yellow highlight so it doesn't clash with the ones we just added
104-
background: none;
105-
}
100+
// Special highlighting after the Split button completes to show it completed.
101+
// The actual colors are now applied with named ::highlight() rules populated from TS.
102+
// Note: This highlighting is expected to persist across sessions, but to be hidden
103+
// (displayed with the yellow color) while each segment is playing.
104+
::highlight(bloom-audio-split-1) {
105+
background-color: #bfedf3;
106+
}
107+
::highlight(bloom-audio-split-2) {
108+
background-color: #7fdae6;
109+
}
110+
::highlight(bloom-audio-split-3) {
111+
background-color: #29c2d6;
106112
}
107113

108114
.ui-audioBody {

0 commit comments

Comments
 (0)