From bf238b46c3a2411d62e02068030e4d4dd42147be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLilyL0u=E2=80=9D?= Date: Tue, 14 Jul 2026 14:20:23 +0100 Subject: [PATCH 01/14] formatting and work in progress timestamp --- src/app/components/MediaLoader/types.ts | 1 + .../PlainTextFormatter/index.module.scss | 89 +++++++++ .../PlainTextFormatter/index.test.tsx | 169 ++++++++++++++++++ .../components/PlainTextFormatter/index.tsx | 136 ++++++++++++++ .../onDemandAudio/OnDemandAudioLayout.tsx | 24 ++- 5 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 src/app/components/PlainTextFormatter/index.module.scss create mode 100644 src/app/components/PlainTextFormatter/index.test.tsx create mode 100644 src/app/components/PlainTextFormatter/index.tsx diff --git a/src/app/components/MediaLoader/types.ts b/src/app/components/MediaLoader/types.ts index 5bc45b5df8a..bd1abc321f0 100644 --- a/src/app/components/MediaLoader/types.ts +++ b/src/app/components/MediaLoader/types.ts @@ -154,6 +154,7 @@ export type MediaInfo = { }; export type Player = { + currentTime: number; dispatchEvent( dispatchEvent: string, parameters?: { adTag: string | null }, diff --git a/src/app/components/PlainTextFormatter/index.module.scss b/src/app/components/PlainTextFormatter/index.module.scss new file mode 100644 index 00000000000..528253bebf0 --- /dev/null +++ b/src/app/components/PlainTextFormatter/index.module.scss @@ -0,0 +1,89 @@ +@use '@scss/themeTokens' as theme; + +.paragraph { + @include theme.fontSizes-gel-font-size(pica); + @include theme.fontVariants-gel-font-variant('sans-regular'); + color: theme.$palette-metal; + margin: 0 0 #{theme.$spacings-double} 0; + + :global([data-is-dark-ui='true']) & { + color: theme.$palette-ghost; + } +} + +.link { + color: theme.$palette-postbox; + text-decoration: underline; + + :global([data-is-dark-ui='true']) & { + color: theme.$palette-pebble; + } +} + +.chapterBlock { + margin-bottom: #{theme.$spacings-double}; +} + +.chapterList { + list-style: none; + margin: 0; + padding: 0; +} + +.chapterItem { + display: flex; + gap: #{theme.$spacings-full}; + padding: #{theme.$spacings-half} 0; + border-bottom: 1px solid theme.$palette-pebble; + align-items: baseline; + + &:first-child { + border-top: 1px solid theme.$palette-pebble; + } + + :global([data-is-dark-ui='true']) & { + border-color: theme.$palette-shadow; + } +} + +.timestamp { + @include theme.fontSizes-gel-font-size(pica); + @include theme.fontVariants-gel-font-variant('sans-bold'); + color: theme.$palette-postbox; + min-width: 3.5rem; + flex-shrink: 0; + font-variant-numeric: tabular-nums; +} + +.timestampButton { + background: none; + border: none; + padding: 0; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 2px; + + &:hover, + &:focus { + color: theme.$palette-shadow; + outline: 2px solid currentColor; + outline-offset: 2px; + } + + :global([data-is-dark-ui='true']) & { + &:hover, + &:focus { + color: theme.$palette-ghost; + } + } +} + +.chapterLabel { + @include theme.fontSizes-gel-font-size(pica); + @include theme.fontVariants-gel-font-variant('sans-regular'); + color: theme.$palette-metal; + + :global([data-is-dark-ui='true']) & { + color: theme.$palette-ghost; + } +} diff --git a/src/app/components/PlainTextFormatter/index.test.tsx b/src/app/components/PlainTextFormatter/index.test.tsx new file mode 100644 index 00000000000..68baa47dd04 --- /dev/null +++ b/src/app/components/PlainTextFormatter/index.test.tsx @@ -0,0 +1,169 @@ +import { + render, + screen, + fireEvent, +} from '#app/components/react-testing-library-with-providers'; +import PlainTextFormatter from '.'; + +const CHAPTER_TEXT = '00:00 Introduction\n00:43 Chapter one\n06:25 Chapter two'; + +describe('PlainTextFormatter', () => { + it('renders nothing when given an empty string', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('renders a single paragraph for plain text without blank lines', () => { + render(); + expect( + screen.getByText('This is a simple description.'), + ).toBeInTheDocument(); + expect(screen.getByText('This is a simple description.').tagName).toBe('P'); + }); + + it('renders multiple paragraphs for blank-line-separated text', () => { + const text = 'First paragraph.\n\nSecond paragraph.'; + // p elements don't have an implicit role; check by tag name + const { container } = render(); + const ps = container.querySelectorAll('p'); + expect(ps).toHaveLength(2); + expect(ps[0]).toHaveTextContent('First paragraph.'); + expect(ps[1]).toHaveTextContent('Second paragraph.'); + }); + + it('renders a chapter list when all lines start with timecodes', () => { + const { container } = render(); + const list = container.querySelector('ol'); + expect(list).toBeInTheDocument(); + const items = container.querySelectorAll('li'); + expect(items).toHaveLength(3); + }); + + it('renders timestamps inside