From c15ec8db8fe666832658729f04006995fce099fd Mon Sep 17 00:00:00 2001 From: MykolaGolubyev Date: Tue, 2 Jun 2026 20:15:32 -0400 Subject: [PATCH] presentation: include chapter into slide title and breadcrumbs --- ...d-2026-06-02-presentation-chapter-title.md | 1 + znai-reactjs/src/doc-elements/page/Page.jsx | 9 ++- .../presentation/Presentation.css | 16 +++++ .../presentation/Presentation.demo.jsx | 2 + .../presentation/Presentation.jsx | 10 ++- .../presentation/PresentationRegistry.jsx | 9 ++- .../PresentationRegistry.test.jsx | 66 +++++++++++++++++++ 7 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 znai-docs/znai/release-notes/1.91/add-2026-06-02-presentation-chapter-title.md diff --git a/znai-docs/znai/release-notes/1.91/add-2026-06-02-presentation-chapter-title.md b/znai-docs/znai/release-notes/1.91/add-2026-06-02-presentation-chapter-title.md new file mode 100644 index 000000000..e46a66252 --- /dev/null +++ b/znai-docs/znai/release-notes/1.91/add-2026-06-02-presentation-chapter-title.md @@ -0,0 +1 @@ +* Add: [Presentation](flow/presentation) mode shows the chapter title on each page's title slide and in the top breadcrumb diff --git a/znai-reactjs/src/doc-elements/page/Page.jsx b/znai-reactjs/src/doc-elements/page/Page.jsx index e8647b1fc..78f9278fe 100644 --- a/znai-reactjs/src/doc-elements/page/Page.jsx +++ b/znai-reactjs/src/doc-elements/page/Page.jsx @@ -66,14 +66,19 @@ class Page extends Component { } const PresentationTitle = ({ tocItem }) => { - return ; + return ( +
+ {tocItem.chapterTitle &&
{tocItem.chapterTitle}
} + +
+ ); }; const presentationPageHandler = { component: PresentationTitle, numberOfSlides: () => 1, slideInfoProvider: ({ tocItem }) => { - return { pageTitle: tocItem.pageTitle }; + return { chapterTitle: tocItem.chapterTitle, pageTitle: tocItem.pageTitle }; }, }; diff --git a/znai-reactjs/src/doc-elements/presentation/Presentation.css b/znai-reactjs/src/doc-elements/presentation/Presentation.css index e98d5b8ba..8641a2c7c 100644 --- a/znai-reactjs/src/doc-elements/presentation/Presentation.css +++ b/znai-reactjs/src/doc-elements/presentation/Presentation.css @@ -108,6 +108,22 @@ border: none; } +.presentation-title-slide { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.presentation-title-chapter { + margin-bottom: 24px; + + font-size: 28px; + text-align: center; + color: var(--znai-presentation-title-color); + opacity: 0.7; +} + h1.presentation-title { font-size: 50px; } \ No newline at end of file diff --git a/znai-reactjs/src/doc-elements/presentation/Presentation.demo.jsx b/znai-reactjs/src/doc-elements/presentation/Presentation.demo.jsx index b27454e26..8807e6a53 100644 --- a/znai-reactjs/src/doc-elements/presentation/Presentation.demo.jsx +++ b/znai-reactjs/src/doc-elements/presentation/Presentation.demo.jsx @@ -56,6 +56,7 @@ function longHeadersAndSideNote() { return { "type": "Page", "tocItem": { + "chapterTitle": "Chapter Title", "sectionTitle": "Section Title", "pageTitle": "Long Page Title Long Page Title", "pageMeta": {}, @@ -87,6 +88,7 @@ function shortHeaders() { return { "type": "Page", "tocItem": { + "chapterTitle": "Intro", "sectionTitle": "Section Title", "pageTitle": "Shor", "pageMeta": {}, diff --git a/znai-reactjs/src/doc-elements/presentation/Presentation.jsx b/znai-reactjs/src/doc-elements/presentation/Presentation.jsx index 87c7a166e..bc4b2b2af 100644 --- a/znai-reactjs/src/doc-elements/presentation/Presentation.jsx +++ b/znai-reactjs/src/doc-elements/presentation/Presentation.jsx @@ -42,7 +42,7 @@ class Presentation extends React.Component { const {docMeta, presentationRegistry, pageGenError} = this.props const {currentSlideIdx} = this.state - const {pageTitle, sectionTitle} = presentationRegistry.extractCombinedSlideInfo(currentSlideIdx - 1) + const {chapterTitle, pageTitle, sectionTitle} = presentationRegistry.extractCombinedSlideInfo(currentSlideIdx - 1) const slide = presentationRegistry.slideByIdx(currentSlideIdx) const isSectionTitleOnSlide = !!slide.info.sectionTitle @@ -58,6 +58,14 @@ class Presentation extends React.Component {
+ {chapterTitle ? +
{chapterTitle}
: + null + } + {chapterTitle && pageTitle ? + >> : + null + }
{pageTitle}
{pageTitle && !isSectionTitleOnSlide && sectionTitle.length !== 0 ? >> : diff --git a/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.jsx b/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.jsx index b1a090178..0dd7eb82e 100644 --- a/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.jsx +++ b/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.jsx @@ -135,18 +135,23 @@ class PresentationRegistry { extractCombinedSlideInfo(pageLocalSlideIdx) { if (pageLocalSlideIdx < 0 || pageLocalSlideIdx >= this.slides.length) { - return {pageTitle: '', sectionTitle: '', slideVisibleNote: ''} + return {chapterTitle: '', pageTitle: '', sectionTitle: '', slideVisibleNote: ''} } const slideInfo = this.slides[pageLocalSlideIdx].info const slideVisibleNote = slideInfo.slideVisibleNote + let chapterTitle = "" let pageTitle = "" let sectionTitle = "" for (let i = pageLocalSlideIdx; i >= 0; i--) { const slide = this.slides[i]; + if (slide.info.chapterTitle && !chapterTitle) { + chapterTitle = slide.info.chapterTitle + } + if (slide.info.pageTitle && !pageTitle) { pageTitle = slide.info.pageTitle } @@ -156,7 +161,7 @@ class PresentationRegistry { } } - return {pageTitle, sectionTitle, slideVisibleNote} + return {chapterTitle, pageTitle, sectionTitle, slideVisibleNote} } renderSlide(slide, renderOpts) { diff --git a/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.test.jsx b/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.test.jsx index 1d1180230..5d8fd38a0 100644 --- a/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.test.jsx +++ b/znai-reactjs/src/doc-elements/presentation/PresentationRegistry.test.jsx @@ -18,6 +18,7 @@ import React from "react"; import PresentationRegistry from "./PresentationRegistry"; import {presentationSectionHandler} from "../default-elements/Section"; +import {presentationPageHandler} from "../page/Page"; const elementsLibrary = { 'Dummy': () =>
, @@ -37,6 +38,7 @@ const presentationElementHandlers = { component: () =>
, numberOfSlides: () => 0 }, + 'Page': presentationPageHandler, 'Section': presentationSectionHandler } @@ -78,6 +80,70 @@ describe('PresentationRegistry', () => { }) }) + describe('combined slide info', () => { + it('should carry chapter and page titles from the page title slide across the whole page', () => { + const registry = new PresentationRegistry(elementsLibrary, presentationElementHandlers, { + type: 'Page', + tocItem: { + chapterTitle: 'Chapter One', + pageTitle: 'Page One', + }, + content: [ + { + type: 'Section', + title: 'Section One', + id: 'section-one', + }, + { + type: 'Dummy', + snippet: 'code1', + }, + ] + }) + + // slide 0 is the page title slide + expect(registry.extractCombinedSlideInfo(0)).toMatchObject({ + chapterTitle: 'Chapter One', + pageTitle: 'Page One', + sectionTitle: '', + }) + + // slide 1 is the section title slide + expect(registry.extractCombinedSlideInfo(1)).toMatchObject({ + chapterTitle: 'Chapter One', + pageTitle: 'Page One', + sectionTitle: 'Section One', + }) + + // slide 2 is a content slide within the section + expect(registry.extractCombinedSlideInfo(2)).toMatchObject({ + chapterTitle: 'Chapter One', + pageTitle: 'Page One', + sectionTitle: 'Section One', + }) + }) + + it('should leave chapter title empty for a page without a chapter', () => { + const registry = new PresentationRegistry(elementsLibrary, presentationElementHandlers, { + type: 'Page', + tocItem: { + pageTitle: 'Page One', + }, + content: [ + { + type: 'Dummy', + snippet: 'code1', + }, + ] + }) + + expect(registry.extractCombinedSlideInfo(0)).toMatchObject({ + chapterTitle: '', + pageTitle: 'Page One', + }) + }) + }) + describe('sticky slides', () => { it('should clear sticky slide for after first non sticky slide', () => { const registry = new PresentationRegistry(elementsLibrary, presentationElementHandlers, [