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 (
+
+ {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, [