From 6e018566a40c54806e6bc02b49802f7a22ba826f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 08:52:54 +0000 Subject: [PATCH 1/4] Refresh workshop entry path rendering Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../workshop/WorkshopExperience.astro | 366 +++--------------- docs/tests/workshop.spec.ts | 21 +- 2 files changed, 62 insertions(+), 325 deletions(-) diff --git a/docs/src/components/workshop/WorkshopExperience.astro b/docs/src/components/workshop/WorkshopExperience.astro index 42375d4e543..62ba33a1cd3 100644 --- a/docs/src/components/workshop/WorkshopExperience.astro +++ b/docs/src/components/workshop/WorkshopExperience.astro @@ -1,5 +1,5 @@ --- -import { Card, CardGrid } from '@astrojs/starlight/components'; +import { CardGrid, LinkCard } from '@astrojs/starlight/components'; // @ts-ignore The docs app already uses this runtime package from Astro components. import octicons from '@primer/octicons'; import { @@ -87,10 +87,6 @@ function decodeLocalWorkshopStepLink(value: string) { return isLocalWorkshopStepLink(value) ? value : ''; } -function firstSentence(value: string) { - return value.match(/^.*?[.!?](?:\s|$)/u)?.[0].trim() ?? value.trim(); -} - function sanitizeWorkshopHtml(html: string) { return html .replace(/]*>[\s\S]*?<\/script>/giu, '') @@ -101,54 +97,17 @@ function sanitizeWorkshopHtml(html: string) { function transformTables(html: string) { return html.replace(/([\s\S]*?)<\/table>/gu, (tableHtml) => { - // Check if this is the entry path table by inspecting the cells. - const theadMatch = tableHtml.match(/([\s\S]*?)<\/thead>/u); - const thMatches = theadMatch ? [...theadMatch[1].matchAll(/]*)?>([\s\S]*?)<\/th>/gu)] : []; - const normalizeHeader = (value: string) => value - .replace(/<[^>]+>/gu, '') - .replace(/[‘’]/gu, "'") - .replace(/…/gu, '...') - .replace(/\s+/gu, ' ') - .trim(); - - if ( - thMatches.length >= 3 - && normalizeHeader(thMatches[0][1]) === "If you're a..." - && normalizeHeader(thMatches[1][1]) === 'Recommended setup adventure' - && normalizeHeader(thMatches[2][1]) === 'Why this path fits' - ) { - const tbodyMatch = tableHtml.match(/([\s\S]*?)<\/tbody>/u); - const rows: [string, string, string][] = []; - if (tbodyMatch) { - for (const rowMatch of tbodyMatch[1].matchAll(/([\s\S]*?)<\/tr>/gu)) { - const tdMatches = [...rowMatch[1].matchAll(/]*)?>([\s\S]*?)<\/td>/gu)]; - if (tdMatches.length >= 3) { - rows.push([tdMatches[0][1].trim(), tdMatches[1][1].trim(), tdMatches[2][1].trim()]); - } - } - } - - if (rows.length > 0) { - const cards = rows.map(([audience, nextStep, fit]) => [ - '
', - '

Entry path

', - `

${audience}

`, - `

${fit}

`, - '
', - 'Recommended next step', - `

${nextStep}

`, - '
', - '
', - ].join('')); - return `
${cards.join('')}
`; - } - } - return `
${tableHtml}
`; }); } function rewriteGfmAlerts(html: string) { + const renderAlert = (type: string, inner: string) => { + const cls = `aw-workshop-admonition-${type.toLowerCase()}`; + const title = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); + return ``; + }; + // Pattern to detect a blockquote that opens with a GFM alert type marker. const openingPattern = /
\s*

\[!(NOTE|TIP|WARNING|IMPORTANT|CAUTION)\]/giu; let result = ''; @@ -193,8 +152,6 @@ function rewriteGfmAlerts(html: string) { const blockquoteEnd = foundClose + '

'.length; const afterType = html.slice(afterTypeStart, foundClose); - const cls = `aw-workshop-admonition-${type.toLowerCase()}`; - const title = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); let inner: string; if (afterType.startsWith('

')) { // [!TYPE]

rest...

— type was alone in first paragraph @@ -207,7 +164,7 @@ function rewriteGfmAlerts(html: string) { } result += html.slice(lastIndex, matchStart); - result += ``; + result += renderAlert(type, inner); lastIndex = blockquoteEnd; // Skip the regex past the entire consumed blockquote so it does not // re-match patterns that appear inside the body we just replaced. @@ -215,7 +172,10 @@ function rewriteGfmAlerts(html: string) { } result += html.slice(lastIndex); - return result; + return result.replace( + /

\s*\[!(NOTE|TIP|WARNING|IMPORTANT|CAUTION)\]\s*([\s\S]*?)<\/p>/giu, + (_match, type, body) => renderAlert(String(type), `

${String(body).trim()}

`), + ); } function rewriteGfmTaskLists(html: string) { @@ -407,6 +367,28 @@ const entryPathStepCounts: Record = Object.fromEntries( ]), ); +function renderEntryPathDescription(path: typeof workshopEntryPaths[number], journeyLabel: string, stepCount: number) { + const stepLabel = `${stepCount} step${stepCount === 1 ? '' : 's'}`; + return [ + `${escapeHtml(journeyLabel)} · ${escapeHtml(stepLabel)}`, + `${escapeHtml(path.kicker)}. ${escapeHtml(path.summary)}`, + escapeHtml(path.fit), + ].join('
'); +} + +const entryPathTutorialHashes: Record = Object.fromEntries( + workshopEntryPaths.map((path) => { + const flow = buildWorkshopFlow(path.journeyId, workshopDefaults.scenarioId); + const firstStepKey = flow[0] ?? initialStepKey; + const hash = new URLSearchParams({ + j: path.journeyId, + s: workshopDefaults.scenarioId, + ...(firstStepKey ? { t: firstStepKey } : {}), + }).toString(); + return [path.id, `#${hash}`]; + }), +); + ---
= Object.fromEntries( {workshopEntryPaths.map((path) => { const journey = workshopJourneys.find((item) => item.id === path.journeyId) ?? workshopJourneys[0]; return ( - + /> ); })} @@ -808,10 +778,14 @@ const entryPathStepCounts: Record = Object.fromEntries( root.dataset.workshopFlowKeys = visibleFlow.join(','); root.dataset.workshopCurrentStep = activeStep.key; - root.querySelectorAll('[data-workshop-entry-path]').forEach((button) => { - const isActive = button.getAttribute('data-workshop-entry-path') === state.entryPathId; - button.classList.toggle('is-active', isActive); - button.setAttribute('aria-pressed', String(isActive)); + root.querySelectorAll('[data-workshop-entry-path]').forEach((entryPathLink) => { + const isActive = entryPathLink.getAttribute('data-workshop-entry-path') === state.entryPathId; + entryPathLink.classList.toggle('is-active', isActive); + if (isActive) { + entryPathLink.setAttribute('aria-current', 'true'); + } else { + entryPathLink.removeAttribute('aria-current'); + } }); const flowIndex = visibleFlow.indexOf(activeStep.key); @@ -838,6 +812,7 @@ const entryPathStepCounts: Record = Object.fromEntries( } if (target.matches('[data-workshop-entry-path]')) { + event.preventDefault(); setJourney(target.getAttribute('data-workshop-journey'), target.getAttribute('data-workshop-entry-path')); showTutorial(); return; @@ -900,9 +875,6 @@ const entryPathStepCounts: Record = Object.fromEntries(