feat: add YouTube chapter markers to community feed - #880
Conversation
|
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
|
|
There was a problem hiding this comment.
Pull Request Overview
This PR adds automatic detection and display of YouTube chapter markers from video descriptions in the community feed. When a video description contains timestamps, the system now parses them and creates clickable chapter links that navigate to specific moments in the video.
- Parses timestamp formats (00:00, 1:23, 1:23:45) from video descriptions
- Creates interactive chapter UI with deep links to specific video moments
- Adds responsive styling for chapter markers that integrates with existing feed design
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| blocks/feed/feed.js | Adds timestamp parsing logic and chapter marker creation functionality |
| blocks/feed/feed.css | Adds responsive styling for chapter markers with hover states and typography |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| let match = timestampRegex.exec(description); | ||
|
|
||
| while (match !== null) { | ||
| const [, time, title] = match; | ||
| const seconds = timeToSeconds(time); | ||
| chapters.push({ | ||
| timestamp: time, | ||
| title: title.trim(), | ||
| seconds, | ||
| url: `${videoUrl}&t=${seconds}s`, | ||
| }); | ||
| match = timestampRegex.exec(description); | ||
| } |
There was a problem hiding this comment.
Using exec() with a global regex in a while loop can create an infinite loop if the regex continues to match the same position. Consider using matchAll() or resetting the regex's lastIndex property, or use a non-global regex with match() method instead.
Automatically detect and display chapter timestamps from YouTube video descriptions, creating deep links to specific moments in videos. Each chapter shows its timestamp and title, making it easy for users to navigate to specific topics. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Lars Trieloff <lars@trieloff.net>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The community page moved to the `feed compact categorized` variant, so the chapter markers added to renderFeed never reached it. Move them onto the compact cards instead, as a collapsed disclosure below each card so the four-column grid stays compact until someone asks for the chapter list. The card's own anchor cannot contain the chapter links, so the card chrome moves to a wrapper element and the anchor keeps only the thumbnail and title. Chapters now come from the sheet's Chapters column rather than from the description: the descriptions authors write into Document Authoring are abridged and never carry the "Topics" block, so parsing them found nothing on any of the 116 recordings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Lars Trieloff <lars@trieloff.net>
Adds a daily workflow that reads the community feed sheet from Document Authoring, looks its videos up against the YouTube Data API, extracts the chapter list from each description, and writes it back as a Chapters column before previewing and publishing the sheet. The feed block then renders chapters without calling YouTube at runtime. A run of timestamped lines is only accepted as chapters when it opens the video at 0:00, runs strictly forwards, and has at least three entries, which is close to what YouTube itself requires. Checked against all 116 recordings in the feed: it finds the four that have chapter lists and nothing else. Runs are idempotent - nothing is written when no chapter list changed - and rows YouTube does not answer for keep whatever an author typed by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Lars Trieloff <lars@trieloff.net>
c52ae87 to
5deb5fb
Compare
Reading and writing the feed sheet, previewing and publishing now all go through https://api.aem.live, whose source bus exposes the same document that admin.da.live did. That drops the separate DA_TOKEN secret and the Adobe Developer Console OAuth server-to-server credential behind it - the admin API key that Track Publishes already uses covers the whole run. Setup is down to two secrets: AEM_LIVE_ADMIN_TOKEN and YOUTUBE_API_KEY. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Lars Trieloff <lars@trieloff.net>
A run wrote an empty Chapters cell for any video whose description has no Topics block - which is all but four of the 116 recordings. That is fine while the column is only ever filled from YouTube, but it makes the sync destructive the moment chapters arrive from anywhere else: the first run after a transcript-derived backfill would wipe every one of them. A ChaptersSource column now records where each cell came from - youtube, generated or manual - and a run only replaces cells it owns. An author's own Topics block still wins over anything else, since it is the most direct statement of intent, but a description that is merely silent is not an instruction to delete someone else's work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Lars Trieloff <lars@trieloff.net>
Summary
Adds YouTube chapter markers to the community feed: a scheduled workflow pulls chapter lists out of video descriptions and writes them into the feed sheet, and the feed block renders them as a collapsible list of deep links under each recording card.
Test Plan
aem-website): https://youtube-chapter-markers--aem-website--adobe.aem.page/communityChapters will not appear on either link until the workflow has run once with credentials — the
Chapterscolumn does not exist in the sheet yet. See "Before this does anything" below. Rendering was verified locally against the real feed data with the column populated by the sync script.What changed since the first version of this PR
The original approach parsed timestamps out of
page.DescriptioninsiderenderFeed. Neither half of that works against the site as it stands today:renderFeedis not what/communityuses. The page moved to thefeed compact categorizedvariant, which renders throughrenderFeedCompact. Across all 226 pages in the sitemap,renderFeedonly runs on/blog/archive's "favorite blogs" block.Descriptioncell. Only 4 have one on YouTube itself, and the sheet's descriptions are abridged copies that drop the trailing "Topics" block.So the feature now gets its data from a new
Chapterscolumn, populated from YouTube server-side.Sync workflow
.github/workflows/youtube-chapters.ymlrunstools/youtube-chapters/sync-chapters.jsdaily. Each run reads/community-feeds.jsonfrom the source bus, looks theyoutubesheet's video ids up against the YouTube Data API (3 batched calls, 3 of 10,000 daily quota units), extracts chapters, writes the sheet back, and previews and publishes it.Everything on the AEM side — source read, source write, preview, publish — goes through the Helix 6 admin API at
https://api.aem.live, so one admin API key covers the whole run.A run of timestamped lines is only accepted as a chapter list when it opens the video at
0:00, runs strictly forwards, and has at least three entries — close to what YouTube itself requires. Run against all 116 real descriptions, it finds exactly the four videos that have chapter lists and produces no false positives.Runs are idempotent: nothing is written when no chapter list changed. Rows YouTube does not answer for (deleted or private videos) keep whatever an author typed by hand.
Feed block
Chapters render as a collapsed
N chaptersdisclosure below each card, so the four-column grid stays compact until someone opens one. The card's own anchor cannot contain the chapter links, so the card chrome moved to a wrapper element and the anchor now holds only the thumbnail and title.The column is also editable by hand in DA — one
MM:SS Titleline per chapter — and a manual edit survives until that video's own description gains a chapter list.Before this does anything
Two repository secrets are needed;
tools/youtube-chapters/README.mdhas the full walkthrough.AEM_LIVE_ADMIN_TOKENadobe/aem-website, scopedpublish+author, valid until 2027-08-31YOUTUBE_API_KEYhelix-225321GCP project, restricted toyoutube.googleapis.comBoth are set. Once this merges, run the workflow manually with dry run checked to see what it would write, then again without it. (It cannot be dispatched before merge —
workflow_dispatchneeds the workflow file on the default branch.)The admin key has been checked against every route this workflow uses —
GETandPUTonsource,
POSTon preview and live — all pass. It deliberately cannot write configuration(
403on anyconfig/path), so it cannot widen its own access.Verification
npm run lintandnpm testpass (35 tests, including 9 new ones for the chapter parser).Parser checked against all 116 real video descriptions: 4 chapter lists found, 0 false positives.
Sync flow exercised end to end against stubbed admin API and YouTube endpoints: correct call sequence (
GET/PUT source→POST preview→POST live), correct multi-sheet round-trip, other sheets untouched,:colWidthspadded, second run a no-op.Dry run against the production source bus, YouTube stubbed: read the real sheet from
api.aem.live(116 rows,:type: multi-sheet,:colWidthsintact), found 4 chaptered videos, stopped before writing.Dry run against the real YouTube Data API, source stubbed from that same captured sheet: 3 batched
videos.listcalls, same 4 videos, same chapter counts. The cells it would write, verbatim:The site's configured content source is
{"type":"markup","url":"https://api.aem.live/adobe/sites/aem-website/source"}— the source bus is the content source, so this is the correct write path and not merely a convenient one.Rendering checked in Chrome at 1280/800/380px, light and dark, with keyboard toggling and no nested anchors.
Known, pre-existing, not fixed here
The compact card's background is
light-dark(var(--color-white), var(--color-gray-900)), and--color-gray-900is itself alight-dark()pair, so cards come out near-white in dark mode. Its hover rule also references--color-brand-purpleand--color-accent-purple, neither of which is defined. Both are onmainand are left alone here; the new chapter styles follow the card's existing text treatment so a single fix would correct them together.🤖 Generated with Claude Code