Conversation
There was a problem hiding this comment.
Pull request overview
This PR enhances the page gutter visibility in Bloom's edit mode by adding visual indicators and tooltips to help users understand the gutter space reserved for binding (staples, spiral binding, etc.). The gutter is shown as a colored overlay with a dashed border when hovering over pages, and tooltip targets enable helpful explanatory text when hovering over the gutter region.
Changes:
- Added visual gutter overlay that appears on hover in edit mode, using modern CSS
:is()selectors - Created a new tooltip system for page hover tooltips with localization support
- Refactored CSS selector in appearance theme to use cleaner
:is()syntax and added A4Landscape support - Added code style documentation about preferring TypeScript
functionsyntax and modern CSS features
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/content/appearanceThemes/appearance-theme-zero-margin-ebook.css | Refactored selector using :is() and added A4Landscape to zero-margin pages |
| src/BloomBrowserUI/bookEdit/js/pageHoverTooltips.ts | New module implementing tooltip system for gutter visualization with localization |
| src/BloomBrowserUI/bookEdit/js/bloomEditing.ts | Integrated new tooltip setup/cleanup into page lifecycle |
| src/BloomBrowserUI/bookEdit/css/editMode.less | Added CSS for tooltip bubble, targets, and gutter visualization overlay |
| src/BloomBrowserUI/AGENTS.md | Documented preferences for function syntax and modern CSS features |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| ) | ||
| .off("mousemove.bloomPageTooltip") | ||
| .on("mousemove.bloomPageTooltip", function (e) { | ||
| moveTooltipBubble(e as unknown as MousePositionEvent); | ||
| }) | ||
| .off("mouseleave.bloomPageTooltip") | ||
| .on( | ||
| "mouseleave.bloomPageTooltip", | ||
| `.${kTooltipTargetClass}, #formatButton`, | ||
| () => { | ||
| setActiveTooltipText(undefined); |
There was a problem hiding this comment.
The mousemove handler is attached to the entire document, which means it will fire on every mouse movement anywhere on the page, even when no tooltip is active. This could have minor performance implications on lower-end devices. Consider attaching the mousemove listener only when a tooltip is actually shown (in setActiveTooltipText when activeTooltipText is set) and removing it when hidden.
| }, | |
| ) | |
| .off("mousemove.bloomPageTooltip") | |
| .on("mousemove.bloomPageTooltip", function (e) { | |
| moveTooltipBubble(e as unknown as MousePositionEvent); | |
| }) | |
| .off("mouseleave.bloomPageTooltip") | |
| .on( | |
| "mouseleave.bloomPageTooltip", | |
| `.${kTooltipTargetClass}, #formatButton`, | |
| () => { | |
| setActiveTooltipText(undefined); | |
| // Attach mousemove handler only while a tooltip target is hovered | |
| $(document) | |
| .off("mousemove.bloomPageTooltip") | |
| .on( | |
| "mousemove.bloomPageTooltip", | |
| function (moveEvent) { | |
| moveTooltipBubble( | |
| moveEvent as unknown as MousePositionEvent, | |
| ); | |
| }, | |
| ); | |
| }, | |
| ) | |
| .off("mouseleave.bloomPageTooltip") | |
| .on( | |
| "mouseleave.bloomPageTooltip", | |
| `.${kTooltipTargetClass}, #formatButton`, | |
| () => { | |
| setActiveTooltipText(undefined); | |
| // Detach mousemove handler when tooltip is no longer active | |
| $(document).off("mousemove.bloomPageTooltip"); |
| ## Code Style | ||
|
|
||
| - Always use arrow functions and function components in React | ||
| - For functions, prefer typescript "function" syntax over const foo = () ==> functions. |
There was a problem hiding this comment.
There's a typo in the arrow function syntax. It should be "() =>" not "() ==>".
| - For functions, prefer typescript "function" syntax over const foo = () ==> functions. | |
| - For functions, prefer typescript "function" syntax over const foo = () => functions. |
| export function getBodyInnerHtmlWithoutPageHoverTooltips(): string { | ||
| const bodyClone = document.body.cloneNode(true) as HTMLElement; | ||
| removePageHoverTooltipMarkupFrom(bodyClone); | ||
| return bodyClone.innerHTML; | ||
| } |
There was a problem hiding this comment.
🚩 Clone-based save replaces direct innerHTML access — performance and correctness tradeoff
The previous code at src/BloomBrowserUI/bookEdit/js/bloomEditing.ts:1348 was simply document.body.innerHTML. The new code calls getBodyInnerHtmlWithoutPageHoverTooltips() which does a full document.body.cloneNode(true), then removes tooltip markup from the clone, then returns clone.innerHTML. This is a deep clone of the entire page body on every save. For complex pages with many elements (canvas elements, images, translation groups), this could be noticeably more expensive than the prior direct property access. The function comment at line 1311 notes "We don't want this to become an async method" suggesting performance sensitivity. The clone is synchronous so correctness is preserved, but the cost scales with page complexity. An alternative approach — temporarily removing tooltip elements from the live DOM, getting innerHTML, then re-inserting them — would avoid the clone cost but would be riskier if an error occurs mid-operation.
Was this helpful? React with 👍 or 👎 to provide feedback.
| .bloom-page:is(.side-left, .side-right):not(.outsideFrontCover):not( | ||
| .outsideBackCover | ||
| )::before { | ||
| content: ""; | ||
| position: absolute; | ||
| top: 0; | ||
| bottom: 0; | ||
| width: var(--page-gutter); | ||
| background-color: var(--page-structure-color); | ||
| box-sizing: border-box; | ||
| pointer-events: none; | ||
| z-index: 2; | ||
| opacity: 0; |
There was a problem hiding this comment.
🚩 ::before pseudo-element on .bloom-page conflicts with MXB-Book-Literacy-Prepub branding
The new gutter visualization uses ::before on .bloom-page:is(.side-left, .side-right) at line 183. The branding file at src/content/branding/MXB-Book-Literacy-Prepub/branding.less:20 also sets ::before on .bloom-page (with lower specificity) to show a "Versión Preliminar" watermark. Since an element can only have one ::before pseudo-element, the gutter rule (higher specificity due to additional class selectors) would win on pages with side-left or side-right, hiding the watermark on those pages in edit mode. This is a pre-existing branding that applies to all .bloom-page elements. The practical impact is limited since the branding is project-specific and the gutter only shows on hover, but it's worth being aware of if other brandings adopt similar patterns.
Was this helpful? React with 👍 or 👎 to provide feedback.
| .numberedPage:not(.bloom-interactive-page):is( | ||
| .Device16x9Landscape, | ||
| .Device16x9Portrait, | ||
| .A4Landscape | ||
| ) { |
There was a problem hiding this comment.
🚩 A4Landscape added to zero-margin theme — broadens scope of zero-margin behavior
The selector change at line 11-15 adds .A4Landscape to the list of page sizes that get --page-margin: 0mm in the zero-margin-ebook theme. Previously only Device16x9Landscape and Device16x9Portrait were affected. This is a behavioral change: A4Landscape pages using this theme will now have zero margins, zero vertical split width, and zero horizontal split height. The default theme at src/content/appearanceThemes/appearance-theme-default.css:132-143 sets --page-margin: 15mm and --page-gutter: 10mm for A4Landscape. This change would override both (gutter via the margin override, since gutter is added to margin in basePage.less:673,682). Confirm this is the intended behavior for A4Landscape books using this ebook theme.
Was this helpful? React with 👍 or 👎 to provide feedback.
6e431bb to
9fe955c
Compare
Also, can now add tooltips to various elements on the page
9fe955c to
687cad2
Compare
This change is