From 078a6ddfca46432f50356f7f3a7b0fc30142796c Mon Sep 17 00:00:00 2001 From: jomcarvajal Date: Wed, 22 Oct 2025 09:38:14 -0600 Subject: [PATCH 1/2] add new titles for REX modals --- src/app/content/hooks/receiveContent.ts | 8 ++- src/app/content/utils/seoUtils.spec.ts | 84 +++++++++++++++++++++++++ src/app/content/utils/seoUtils.ts | 19 +++++- 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/app/content/hooks/receiveContent.ts b/src/app/content/hooks/receiveContent.ts index e8f44ff04a..784197c9cd 100644 --- a/src/app/content/hooks/receiveContent.ts +++ b/src/app/content/hooks/receiveContent.ts @@ -1,9 +1,10 @@ +import queryString from 'query-string'; import { setHead } from '../../head/actions'; import { initialState as headInitialState } from '../../head/reducer'; import { Link } from '../../head/types'; import createIntl from '../../messages/createIntl'; import { locationChange } from '../../navigation/actions'; -import { pathname } from '../../navigation/selectors'; +import { pathname, query } from '../../navigation/selectors'; import theme from '../../theme'; import { ActionHookBody } from '../../types'; import { receivePage } from '../actions'; @@ -41,6 +42,9 @@ const hookBody: ActionHookBody = (se const loadingBook = select.loadingBook(state); const loadingPage = select.loadingPage(state); const currentPath = pathname(state); + const queryParamsObj = query(state); + const queryParams = queryString.stringify(queryParamsObj); + const queryParamsWithPrefix = queryParams ? `?${queryParams}` : ''; if (!page || !book) { dispatch( @@ -57,7 +61,7 @@ const hookBody: ActionHookBody = (se const locale = book.language; const intl = await createIntl(locale); - const title = createTitle(page, book, intl); + const title = createTitle(page, book, intl, queryParamsWithPrefix); const description = getPageDescription(services, intl, book, page); const canonical = await getCanonicalUrlParams(archiveLoader, osWebLoader, book, page.id); const canonicalUrl = canonical && contentRoute.getUrl(canonical); diff --git a/src/app/content/utils/seoUtils.spec.ts b/src/app/content/utils/seoUtils.spec.ts index 0bbbaf8c3d..cbda27769e 100644 --- a/src/app/content/utils/seoUtils.spec.ts +++ b/src/app/content/utils/seoUtils.spec.ts @@ -144,3 +144,87 @@ describe('createTitle', () => { expect(title).toEqual('3 page1 - book | OpenStax'); }); }); +describe('createTitle (modal param)', () => { + const intl = createIntl(); + + it('returns modal title for MH modal param', () => { + const page = makeArchiveSection('page1'); + const book = { + title: 'book', + tree: makeArchiveTree('book', [page]), + }; + // params string with modal=MH + const params = 'modal=MH'; + const title = createTitle(page as any as Page, book as any as Book, intl, params); + expect(title).toEqual('My Highlights and Notes | OpenStax'); + }); + + it('returns modal title for KS modal param', () => { + const page = makeArchiveSection('page1'); + const book = { + title: 'book', + tree: makeArchiveTree('book', [page]), + }; + // params string with modal=KS + const params = 'modal=KS'; + const title = createTitle(page as any as Page, book as any as Book, intl, params); + expect(title).toEqual('REX Keyboard Shortcuts | OpenStax'); + }); + + it('returns modal title for PQ modal param', () => { + const page = makeArchiveSection('page1'); + const book = { + title: 'book', + tree: makeArchiveTree('book', [page]), + }; + // params string with modal=PQ + const params = 'modal=PQ'; + const title = createTitle(page as any as Page, book as any as Book, intl, params); + expect(title).toEqual('REX Practice Questions | OpenStax'); + }); + + it('returns modal title for SG modal param', () => { + const page = makeArchiveSection('page1'); + const book = { + title: 'book', + tree: makeArchiveTree('book', [page]), + }; + // params string with modal=SG + const params = 'modal=SG'; + const title = createTitle(page as any as Page, book as any as Book, intl, params); + expect(title).toEqual('REX Study Guides | OpenStax'); + }); + + it('returns normal title if modal param is not present', () => { + const page = makeArchiveSection('page1'); + const book = { + title: 'book', + tree: makeArchiveTree('book', [page]), + }; + const params = ''; + const title = createTitle(page as any as Page, book as any as Book, intl, params); + expect(title).toEqual('page1 - book | OpenStax'); + }); + + it('returns normal title if modal param is unknown', () => { + const page = makeArchiveSection('page1'); + const book = { + title: 'book', + tree: makeArchiveTree('book', [page]), + }; + const params = 'modal=UNKNOWN'; + const title = createTitle(page as any as Page, book as any as Book, intl, params); + expect(title).toEqual('page1 - book | OpenStax'); + }); + + it('returns modal title if modal param is present among other params', () => { + const page = makeArchiveSection('page1'); + const book = { + title: 'book', + tree: makeArchiveTree('book', [page]), + }; + const params = 'foo=bar&modal=MH&baz=qux'; + const title = createTitle(page as any as Page, book as any as Book, intl, params); + expect(title).toEqual('My Highlights and Notes | OpenStax'); + }); +}); diff --git a/src/app/content/utils/seoUtils.ts b/src/app/content/utils/seoUtils.ts index d5be3391cb..130fd2c659 100644 --- a/src/app/content/utils/seoUtils.ts +++ b/src/app/content/utils/seoUtils.ts @@ -111,15 +111,28 @@ export const getPageDescription = (services: Pick, return pageDescription || intl.formatMessage({id: 'i18n:metadata:description'}); }; -export const createTitle = (page: Page, book: Book, intl: IntlShape): string => { +const modalTitles = { + 'MH': 'My Highlights and Notes', + 'KS': 'REX Keyboard Shortcuts', + 'PQ': 'REX Practice Questions', + 'SG': 'REX Study Guides', +}; + +export const createTitle = (page: Page, book: Book, intl: IntlShape, params?: string): string => { const node = assertDefined( findArchiveTreeNodeById(book.tree, page.id), `couldn't find node for a page id: ${page.id}` ); const [nodeNumber, nodeTitle] = splitTitleParts(node.title); const title = `${nodeTitle} - ${book.title} | OpenStax`; - - if (nodeNumber) { + const searchParams = params ? new URLSearchParams(params) : undefined; + const modalParam = searchParams?.get('modal'); + const modalTitle = modalParam && modalParam in modalTitles + ? modalTitles[modalParam as keyof typeof modalTitles] + : null; + if (modalTitle) { + return `${modalTitle} | OpenStax`; + } else if (nodeNumber) { return `${nodeNumber} ${title}`; } return getParentPrefix(node.parent, intl) + title; From 8a4b83d4f98a4da33f0266e2d0bf0708103dab1f Mon Sep 17 00:00:00 2001 From: jomcarvajal Date: Mon, 3 Nov 2025 11:52:01 -0600 Subject: [PATCH 2/2] resolve comments --- src/app/content/hooks/receiveContent.ts | 3 +-- src/app/content/utils/seoUtils.ts | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/content/hooks/receiveContent.ts b/src/app/content/hooks/receiveContent.ts index 784197c9cd..e5dbdd61e8 100644 --- a/src/app/content/hooks/receiveContent.ts +++ b/src/app/content/hooks/receiveContent.ts @@ -42,8 +42,7 @@ const hookBody: ActionHookBody = (se const loadingBook = select.loadingBook(state); const loadingPage = select.loadingPage(state); const currentPath = pathname(state); - const queryParamsObj = query(state); - const queryParams = queryString.stringify(queryParamsObj); + const queryParams = queryString.stringify(query(state)); const queryParamsWithPrefix = queryParams ? `?${queryParams}` : ''; if (!page || !book) { diff --git a/src/app/content/utils/seoUtils.ts b/src/app/content/utils/seoUtils.ts index 130fd2c659..4f3cbe595b 100644 --- a/src/app/content/utils/seoUtils.ts +++ b/src/app/content/utils/seoUtils.ts @@ -132,7 +132,8 @@ export const createTitle = (page: Page, book: Book, intl: IntlShape, params?: st : null; if (modalTitle) { return `${modalTitle} | OpenStax`; - } else if (nodeNumber) { + } + if (nodeNumber) { return `${nodeNumber} ${title}`; } return getParentPrefix(node.parent, intl) + title;