diff --git a/src/app/content/components/Assigned.tsx b/src/app/content/components/Assigned.tsx index 708e57e4d0..a803be3425 100644 --- a/src/app/content/components/Assigned.tsx +++ b/src/app/content/components/Assigned.tsx @@ -109,7 +109,7 @@ export default () => { - + {prevNext ? { ); }; + describe('Content tweaks for assignable', () => { + let pageElement: HTMLElement; + + const htmlHelper = async(html: string) => { + archiveLoader.mock.cachedPage.mockImplementation(() => ({ + ...page, + content: html, + })); + + const {root} = renderToDom( + + + + + + + + + + ); + const query = root.querySelector('#main-content'); + + if (!query) { + return expect(query).toBeTruthy(); + } + pageElement = query; + + // page lifecycle hooks + await Promise.resolve(); + + return pageElement.innerHTML; + }; + + it('changes heading levels when specified', async() => { + jest.spyOn(selectNavigation, 'query').mockReturnValue({ + section: [page.id, shortPage.id], + }); + jest.spyOn(select, 'book') + .mockReturnValue(formatBookData(book, mockCmsBook)); + + expect(await htmlHelper('

Largest heading

Second largest heading

')) + .toEqual('

Largest heading

Second largest heading

'); + }); + }); + describe('Content tweaks for generic styles', () => { let pageElement: HTMLElement; diff --git a/src/app/content/components/Page/PageComponent.tsx b/src/app/content/components/Page/PageComponent.tsx index da426581ff..46c53e6993 100644 --- a/src/app/content/components/Page/PageComponent.tsx +++ b/src/app/content/components/Page/PageComponent.tsx @@ -47,7 +47,7 @@ export default class PageComponent extends Component { contentLinks.reduceReferences(parsedContent, this.props.contentLinks); lazyResources.makeResourcesLazy(parsedContent); - transformContent(parsedContent, parsedContent.body, this.props.intl, services); + transformContent(parsedContent, parsedContent.body, this.props, services); if (this.props.lockNavigation) { linksToOtherPagesOpenInNewTab(parsedContent.body, this.props.currentPath); diff --git a/src/app/content/components/Page/connector.ts b/src/app/content/components/Page/connector.ts index 54165bb51b..30d39f86bf 100644 --- a/src/app/content/components/Page/connector.ts +++ b/src/app/content/components/Page/connector.ts @@ -34,6 +34,7 @@ export interface PagePropTypes { textSize: TextResizerValue; lockNavigation: boolean; ToastOverride: StyledComponent<'div', any, {}, never>; + topHeadingLevel?: number; } export default connect( diff --git a/src/app/content/components/Page/contentDOMTransformations.ts b/src/app/content/components/Page/contentDOMTransformations.ts index 6791541c86..7848f5e002 100644 --- a/src/app/content/components/Page/contentDOMTransformations.ts +++ b/src/app/content/components/Page/contentDOMTransformations.ts @@ -21,15 +21,19 @@ export function linksToOtherPagesOpenInNewTab(rootEl: HTMLElement, currentPath: // .../src/scripts/modules/media/body/body.coffee#L123 // We are passing Document because it is required to prerender. export const transformContent = ( - document: Document, rootEl: HTMLElement, intl: IntlShape, services: AppServices & MiddlewareAPI + document: Document, + rootEl: HTMLElement, + props: { intl: IntlShape, topHeadingLevel?: number }, + services: AppServices & MiddlewareAPI ) => { removeDocumentTitle(rootEl); + if (props.topHeadingLevel) { changeHeadingLevels(document, rootEl, props.topHeadingLevel); } wrapElements(document, rootEl); tweakFigures(rootEl); fixLists(rootEl); - wrapSolutions(document, rootEl, intl); + wrapSolutions(document, rootEl, props.intl); expandSolutionForFragment(document); - moveFootnotes(document, rootEl, intl); + moveFootnotes(document, rootEl, props.intl); optimizeImages(rootEl, services); }; @@ -42,6 +46,40 @@ function removeDocumentTitle(rootEl: HTMLElement) { ].join(',')).forEach((el) => el.remove()); } +// set the top heading's level to topHeadingLevel and adjust other headings accordingly +function changeHeadingLevels(document: Document, rootEl: HTMLElement, topHeadingLevel: number) { + const headingLevels = [ 1, 2, 3, 4, 5, 6 ]; + const currentTopHeading = headingLevels.find((level) => rootEl.querySelectorAll(`h${level}`)?.length); + + if (!currentTopHeading || topHeadingLevel === currentTopHeading) { + return; + } + + const differenceInLevels = topHeadingLevel - currentTopHeading; + + headingLevels.forEach((level) => { + const origTagName = `h${level}`; + const tags = rootEl.querySelectorAll(origTagName); + + if (tags.length > 0) { + const newTagName = `h${level + differenceInLevels}`; + + tags.forEach((tag) => { + const contents = tag.innerHTML; + const newTagEl = document.createElement(newTagName); + + Array.from(tag.attributes).forEach((attr) => { + newTagEl.setAttribute(attr.name, attr.value); + }); + + newTagEl.innerHTML = contents; + + tag.replaceWith(newTagEl); + }); + } + }); +} + // Wrap title and content elements in header and section elements, respectively function wrapElements(document: Document, rootEl: HTMLElement) { rootEl.querySelectorAll(`.example, .exercise, .note, .abstract,