@wix/web5-core 1.63.30 → 1.63.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/cjs/component/componentDefinitions/ComparisonSectionDefinition.js +21 -4
  2. package/dist/cjs/component/componentDefinitions/ComparisonSectionDefinition.js.map +1 -1
  3. package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js +1 -1
  4. package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
  5. package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js +1 -1
  6. package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
  7. package/dist/cjs/component/componentDefinitions/NextStepsSectionDefinition.js +19 -4
  8. package/dist/cjs/component/componentDefinitions/NextStepsSectionDefinition.js.map +1 -1
  9. package/dist/cjs/component/componentDefinitions/parse-utils.js +87 -30
  10. package/dist/cjs/component/componentDefinitions/parse-utils.js.map +1 -1
  11. package/dist/esm/component/componentDefinitions/ComparisonSectionDefinition.js +22 -5
  12. package/dist/esm/component/componentDefinitions/ComparisonSectionDefinition.js.map +1 -1
  13. package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js +2 -2
  14. package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
  15. package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js +2 -2
  16. package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
  17. package/dist/esm/component/componentDefinitions/NextStepsSectionDefinition.js +20 -5
  18. package/dist/esm/component/componentDefinitions/NextStepsSectionDefinition.js.map +1 -1
  19. package/dist/esm/component/componentDefinitions/parse-utils.js +86 -30
  20. package/dist/esm/component/componentDefinitions/parse-utils.js.map +1 -1
  21. package/dist/types/component/componentDefinitions/ComparisonSectionDefinition.d.ts.map +1 -1
  22. package/dist/types/component/componentDefinitions/NextStepsSectionDefinition.d.ts.map +1 -1
  23. package/dist/types/component/componentDefinitions/parse-utils.d.ts +41 -7
  24. package/dist/types/component/componentDefinitions/parse-utils.d.ts.map +1 -1
  25. package/package.json +2 -2
@@ -25,7 +25,21 @@ function isBlankCell(raw) {
25
25
  class ComparisonSectionDefinition {
26
26
  constructor() {
27
27
  (0, _defineProperty2.default)(this, "sectionType", 'comparison');
28
- (0, _defineProperty2.default)(this, "patterns", ['html_comment[section^="comparison"] > (h2 | h3 | h4 | t) > html_comment? > t* > table > (t | link)?']);
28
+ // The pre-table slot is the description paragraph, so it has to admit
29
+ // `link`: `convertToBlockElements` unwraps a paragraph into its inline
30
+ // nodes, so `Questions? Email support@shop.example for help.` arrives as
31
+ // `t link t` — GFM autolinks a bare email, no author required — and an
32
+ // authored `[men's shoes](web5://ask/…)` does the same. With `t*` the run
33
+ // ended at that `link`, the whole pattern failed, and the section never
34
+ // reached `parse()`: the heading and prose were claimed by `textBlock` and
35
+ // the TABLE fell to `FallbackSectionDefinition`, whose `partsToMarkdown`
36
+ // has no `table` branch — so it rendered as an empty section and the
37
+ // comparison silently vanished.
38
+ //
39
+ // The trailing slot stays `(t | link)?`: it is the CTA, and `parse()` reads
40
+ // it positionally (see `ctaText` below). Widening it would let the section
41
+ // swallow a paragraph belonging to whatever follows.
42
+ (0, _defineProperty2.default)(this, "patterns", ['html_comment[section^="comparison"] > (h2 | h3 | h4 | t) > html_comment? > (t | link)* > table > (t | link)?']);
29
43
  (0, _defineProperty2.default)(this, "defaults", {
30
44
  title: 'Comparison',
31
45
  columnLabels: ['Option A', 'Option B'],
@@ -93,7 +107,7 @@ See how our platform stacks up against the competition.
93
107
  parse(parts, context) {
94
108
  var _tableParts$;
95
109
  const title = (0, _parseUtils.headingText)(parts, 2) || (0, _parseUtils.headingText)(parts, 3) || (0, _parseUtils.headingText)(parts, 4) || 'Comparison';
96
- const description = (0, _parseUtils.bodyText)(parts) || undefined;
110
+ const description = (0, _parseUtils.proseBodyMarkdown)(parts) || undefined;
97
111
 
98
112
  // Table meta from nodesToParts: { columnLabels, featureColumnLabel, rows: { feature, values }[] }
99
113
  const tableParts = (0, _parts.getPartsByType)(parts, 'table');
@@ -133,8 +147,11 @@ See how our platform stacks up against the competition.
133
147
  }));
134
148
  }
135
149
 
136
- // Extract CTA from link parts
137
- const link = (0, _parseUtils.firstLink)(parts);
150
+ // The CTA is the link AFTER the table, not merely the first link in the
151
+ // section the description may now carry its own inline links, and
152
+ // `firstLink(parts)` would promote one of those to the CTA button.
153
+ const lastTableIndex = parts.map(p => p.type).lastIndexOf('table');
154
+ const link = (0, _parseUtils.firstLink)(lastTableIndex >= 0 ? parts.slice(lastTableIndex + 1) : parts);
138
155
  const ctaText = (link == null ? void 0 : link.text) || undefined;
139
156
  const ctaHref = (link == null ? void 0 : link.href) || undefined;
140
157
 
@@ -1 +1 @@
1
- {"version":3,"names":["_parseUtils","require","_parts","_sectionDefinition","_diagnosticTypes","isBlankCell","raw","v","trim","toLowerCase","test","ComparisonSectionDefinition","constructor","_defineProperty2","default","title","columnLabels","rows","feature","values","markdown","expected","featureColumnLabel","description","ctaText","ctaHref","parse","parts","context","_tableParts$","headingText","bodyText","undefined","tableParts","getPartsByType","tableMeta","meta","columns","rawValueRows","map","row","iconHint","parseTableValue","allHeaders","headers","rawRows","slice","label","link","firstLink","text","href","keptRow","some","filteredRows","filter","_","i","filteredRawRows","droppedCount","length","reportDiagnostic","DIAGNOSTIC_TYPES","ITEMS_DROPPED","blankColumns","col","every","SECTION_REJECTED","c","join","DROP_SECTION","exports"],"sources":["../../../../src/component/componentDefinitions/ComparisonSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n bodyText,\n firstLink,\n parseTableValue,\n} from './parse-utils';\nimport { getPartsByType, type Part } from '../../parts/parts';\nimport type {\n ComparisonColumn,\n ComparisonCompProps,\n ComparisonRow,\n ComparisonSectionComponent,\n SectionFixture,\n} from '../types';\nimport type {\n SectionDefinition,\n ParseContext,\n DropSection,\n} from '../section-definition';\nimport { DROP_SECTION } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * A cell the writer left blank: empty, or a dash / \"n/a\" placeholder standing in\n * for \"we have no fact here\".\n *\n * This has to be judged on the RAW cell text, before `parseTableValue`, because\n * that maps an empty cell to `false` — the very same value a deliberate \"No\"\n * produces. After parsing, \"no data\" and \"no, it does not have that\" are\n * indistinguishable.\n */\nfunction isBlankCell(raw: string | undefined): boolean {\n const v = (raw ?? '').trim().toLowerCase();\n // U+2010–U+2015 are the hyphen/dash family; U+2212 is minus.\n return v === '' || v === 'n/a' || /^[-‐-―−]+$/.test(v);\n}\n\nexport class ComparisonSectionDefinition\n implements SectionDefinition<ComparisonSectionComponent>\n{\n sectionType = 'comparison' as const;\n patterns = [\n 'html_comment[section^=\"comparison\"] > (h2 | h3 | h4 | t) > html_comment? > t* > table > (t | link)?',\n ];\n\n defaults: ComparisonCompProps = {\n title: 'Comparison',\n columnLabels: ['Option A', 'Option B'],\n rows: [\n { feature: 'Price', values: ['$10', '$20'] },\n { feature: 'Storage', values: ['100GB', '1TB'] },\n ],\n };\n\n fixtures: SectionFixture<ComparisonCompProps>[] = [\n {\n markdown: `<!-- section: comparison -->\n## Compare Plans\n\n| Feature | Basic | Pro |\n|---------|-------|-----|\n| Users | 5 | Unlimited |\n| Storage | 100GB | 1TB |`,\n expected: {\n title: 'Compare Plans',\n featureColumnLabel: 'Feature',\n columnLabels: ['Basic', 'Pro'],\n rows: [\n { feature: 'Users', values: ['5', 'Unlimited'] },\n { feature: 'Storage', values: ['100GB', '1TB'] },\n ],\n },\n },\n {\n markdown: `<!-- section: comparison -->\n## Platform Comparison\n\nSee how our platform stacks up against the competition.\n\n| Feature | Our Platform | Competitor |\n|---------|--------------|------------|\n| Storage | 1TB | 100GB |\n| Users | Unlimited | 5 |\n| Support | 24/7 Priority | Email Only |\n\n[Get started](#signup)`,\n expected: {\n title: 'Platform Comparison',\n description: 'See how our platform stacks up against the competition.',\n featureColumnLabel: 'Feature',\n columnLabels: ['Our Platform', 'Competitor'],\n rows: [\n { feature: 'Storage', values: ['1TB', '100GB'] },\n { feature: 'Users', values: ['Unlimited', '5'] },\n { feature: 'Support', values: ['24/7 Priority', 'Email Only'] },\n ],\n ctaText: 'Get started',\n ctaHref: '#signup',\n },\n },\n ];\n\n parse(\n parts: Part[],\n context?: ParseContext,\n ): ComparisonCompProps | DropSection {\n const title =\n headingText(parts, 2) ||\n headingText(parts, 3) ||\n headingText(parts, 4) ||\n 'Comparison';\n const description = bodyText(parts) || undefined;\n\n // Table meta from nodesToParts: { columnLabels, featureColumnLabel, rows: { feature, values }[] }\n const tableParts = getPartsByType<Part>(parts, 'table');\n const tableMeta = tableParts[0]?.meta as\n | {\n columnLabels?: string[];\n columns?: ComparisonColumn[];\n featureColumnLabel?: string;\n rows?: { feature: string; iconHint?: string; values: string[] }[];\n // Legacy format from partsConverter\n headers?: string[];\n }\n | undefined;\n\n let columnLabels: string[];\n let columns: ComparisonColumn[] | undefined;\n let featureColumnLabel: string | undefined;\n let rows: ComparisonRow[];\n /** The cells exactly as written, index-aligned with `rows`. */\n let rawValueRows: string[][];\n\n if (tableMeta?.columnLabels !== undefined) {\n // SDK nodesToParts format\n columnLabels = tableMeta.columnLabels;\n columns = tableMeta.columns;\n featureColumnLabel = tableMeta.featureColumnLabel || undefined;\n rawValueRows = (tableMeta.rows ?? []).map((row) => row.values);\n rows = (tableMeta.rows ?? []).map((row) => ({\n feature: row.feature,\n ...(row.iconHint ? { iconHint: row.iconHint } : {}),\n values: row.values.map(parseTableValue),\n }));\n } else {\n // Legacy partsConverter format: { headers: string[], rows: string[][] }\n const allHeaders = tableMeta?.headers ?? [];\n const rawRows = (tableMeta as any)?.rows ?? [];\n featureColumnLabel = allHeaders[0] || undefined;\n columnLabels = allHeaders.slice(1);\n columns = columnLabels.map((label) => ({ label }));\n rawValueRows = rawRows.map((row: string[]) => row.slice(1));\n rows = rawRows.map((row: string[]) => ({\n feature: row[0] ?? '',\n values: row.slice(1).map(parseTableValue),\n }));\n }\n\n // Extract CTA from link parts\n const link = firstLink(parts);\n const ctaText = link?.text || undefined;\n const ctaHref = link?.href || undefined;\n\n // Filter out rows where all values are empty (e.g. trailing non-table text parsed as a row)\n const keptRow = rawValueRows.map((raw) => raw.some((v) => !isBlankCell(v)));\n const filteredRows = rows.filter((_, i) => keptRow[i]);\n const filteredRawRows = rawValueRows.filter((_, i) => keptRow[i]);\n const droppedCount = rows.length - filteredRows.length;\n if (droppedCount > 0) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.ITEMS_DROPPED,\n `comparison: ${droppedCount} row(s) dropped (all values empty)`,\n );\n }\n\n // A column blank in every row is a side of the comparison the writer had no\n // facts for — it renders as a full column of dashes next to a populated one,\n // which reads as a real answer and is not one. Half a comparison is worse\n // than none, so drop the whole section. (No rows left at all lands here too:\n // every column is vacuously blank.)\n const blankColumns = columnLabels\n .map((_, col) => col)\n .filter((col) =>\n filteredRawRows.every((raw) => isBlankCell(raw[col])),\n );\n if (columnLabels.length > 0 && blankColumns.length > 0) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n `comparison: column(s) ${blankColumns\n .map((c) => columnLabels[c] || `#${c + 1}`)\n .join(', ')} blank across all ${filteredRawRows.length} row(s) — section dropped`,\n );\n return DROP_SECTION;\n }\n\n return {\n title,\n description,\n columnLabels,\n columns,\n rows: filteredRows,\n featureColumnLabel,\n ctaText,\n ctaHref,\n };\n }\n}\n"],"mappings":";;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAMA,IAAAC,MAAA,GAAAD,OAAA;AAaA,IAAAE,kBAAA,GAAAF,OAAA;AACA,IAAAG,gBAAA,GAAAH,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASI,WAAWA,CAACC,GAAuB,EAAW;EACrD,MAAMC,CAAC,GAAG,CAACD,GAAG,IAAI,EAAE,EAAEE,IAAI,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC;EAC1C;EACA,OAAOF,CAAC,KAAK,EAAE,IAAIA,CAAC,KAAK,KAAK,IAAI,YAAY,CAACG,IAAI,CAACH,CAAC,CAAC;AACxD;AAEO,MAAMI,2BAA2B,CAExC;EAAAC,YAAA;IAAA,IAAAC,gBAAA,CAAAC,OAAA,uBACgB,YAAY;IAAA,IAAAD,gBAAA,CAAAC,OAAA,oBACf,CACT,qGAAqG,CACtG;IAAA,IAAAD,gBAAA,CAAAC,OAAA,oBAE+B;MAC9BC,KAAK,EAAE,YAAY;MACnBC,YAAY,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC;MACtCC,IAAI,EAAE,CACJ;QAAEC,OAAO,EAAE,OAAO;QAAEC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK;MAAE,CAAC,EAC5C;QAAED,OAAO,EAAE,SAAS;QAAEC,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK;MAAE,CAAC;IAEpD,CAAC;IAAA,IAAAN,gBAAA,CAAAC,OAAA,oBAEiD,CAChD;MACEM,QAAQ,EAAE;AAChB;AACA;AACA;AACA;AACA;AACA,0BAA0B;MACpBC,QAAQ,EAAE;QACRN,KAAK,EAAE,eAAe;QACtBO,kBAAkB,EAAE,SAAS;QAC7BN,YAAY,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC;QAC9BC,IAAI,EAAE,CACJ;UAAEC,OAAO,EAAE,OAAO;UAAEC,MAAM,EAAE,CAAC,GAAG,EAAE,WAAW;QAAE,CAAC,EAChD;UAAED,OAAO,EAAE,SAAS;UAAEC,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK;QAAE,CAAC;MAEpD;IACF,CAAC,EACD;MACEC,QAAQ,EAAE;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB;MACjBC,QAAQ,EAAE;QACRN,KAAK,EAAE,qBAAqB;QAC5BQ,WAAW,EAAE,yDAAyD;QACtED,kBAAkB,EAAE,SAAS;QAC7BN,YAAY,EAAE,CAAC,cAAc,EAAE,YAAY,CAAC;QAC5CC,IAAI,EAAE,CACJ;UAAEC,OAAO,EAAE,SAAS;UAAEC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO;QAAE,CAAC,EAChD;UAAED,OAAO,EAAE,OAAO;UAAEC,MAAM,EAAE,CAAC,WAAW,EAAE,GAAG;QAAE,CAAC,EAChD;UAAED,OAAO,EAAE,SAAS;UAAEC,MAAM,EAAE,CAAC,eAAe,EAAE,YAAY;QAAE,CAAC,CAChE;QACDK,OAAO,EAAE,aAAa;QACtBC,OAAO,EAAE;MACX;IACF,CAAC,CACF;EAAA;EAEDC,KAAKA,CACHC,KAAa,EACbC,OAAsB,EACa;IAAA,IAAAC,YAAA;IACnC,MAAMd,KAAK,GACT,IAAAe,uBAAW,EAACH,KAAK,EAAE,CAAC,CAAC,IACrB,IAAAG,uBAAW,EAACH,KAAK,EAAE,CAAC,CAAC,IACrB,IAAAG,uBAAW,EAACH,KAAK,EAAE,CAAC,CAAC,IACrB,YAAY;IACd,MAAMJ,WAAW,GAAG,IAAAQ,oBAAQ,EAACJ,KAAK,CAAC,IAAIK,SAAS;;IAEhD;IACA,MAAMC,UAAU,GAAG,IAAAC,qBAAc,EAAOP,KAAK,EAAE,OAAO,CAAC;IACvD,MAAMQ,SAAS,IAAAN,YAAA,GAAGI,UAAU,CAAC,CAAC,CAAC,qBAAbJ,YAAA,CAAeO,IASpB;IAEb,IAAIpB,YAAsB;IAC1B,IAAIqB,OAAuC;IAC3C,IAAIf,kBAAsC;IAC1C,IAAIL,IAAqB;IACzB;IACA,IAAIqB,YAAwB;IAE5B,IAAI,CAAAH,SAAS,oBAATA,SAAS,CAAEnB,YAAY,MAAKgB,SAAS,EAAE;MACzC;MACAhB,YAAY,GAAGmB,SAAS,CAACnB,YAAY;MACrCqB,OAAO,GAAGF,SAAS,CAACE,OAAO;MAC3Bf,kBAAkB,GAAGa,SAAS,CAACb,kBAAkB,IAAIU,SAAS;MAC9DM,YAAY,GAAG,CAACH,SAAS,CAAClB,IAAI,IAAI,EAAE,EAAEsB,GAAG,CAAEC,GAAG,IAAKA,GAAG,CAACrB,MAAM,CAAC;MAC9DF,IAAI,GAAG,CAACkB,SAAS,CAAClB,IAAI,IAAI,EAAE,EAAEsB,GAAG,CAAEC,GAAG,KAAM;QAC1CtB,OAAO,EAAEsB,GAAG,CAACtB,OAAO;QACpB,IAAIsB,GAAG,CAACC,QAAQ,GAAG;UAAEA,QAAQ,EAAED,GAAG,CAACC;QAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACnDtB,MAAM,EAAEqB,GAAG,CAACrB,MAAM,CAACoB,GAAG,CAACG,2BAAe;MACxC,CAAC,CAAC,CAAC;IACL,CAAC,MAAM;MACL;MACA,MAAMC,UAAU,GAAG,CAAAR,SAAS,oBAATA,SAAS,CAAES,OAAO,KAAI,EAAE;MAC3C,MAAMC,OAAO,GAAG,CAACV,SAAS,oBAATA,SAAS,CAAUlB,IAAI,KAAI,EAAE;MAC9CK,kBAAkB,GAAGqB,UAAU,CAAC,CAAC,CAAC,IAAIX,SAAS;MAC/ChB,YAAY,GAAG2B,UAAU,CAACG,KAAK,CAAC,CAAC,CAAC;MAClCT,OAAO,GAAGrB,YAAY,CAACuB,GAAG,CAAEQ,KAAK,KAAM;QAAEA;MAAM,CAAC,CAAC,CAAC;MAClDT,YAAY,GAAGO,OAAO,CAACN,GAAG,CAAEC,GAAa,IAAKA,GAAG,CAACM,KAAK,CAAC,CAAC,CAAC,CAAC;MAC3D7B,IAAI,GAAG4B,OAAO,CAACN,GAAG,CAAEC,GAAa,KAAM;QACrCtB,OAAO,EAAEsB,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE;QACrBrB,MAAM,EAAEqB,GAAG,CAACM,KAAK,CAAC,CAAC,CAAC,CAACP,GAAG,CAACG,2BAAe;MAC1C,CAAC,CAAC,CAAC;IACL;;IAEA;IACA,MAAMM,IAAI,GAAG,IAAAC,qBAAS,EAACtB,KAAK,CAAC;IAC7B,MAAMH,OAAO,GAAG,CAAAwB,IAAI,oBAAJA,IAAI,CAAEE,IAAI,KAAIlB,SAAS;IACvC,MAAMP,OAAO,GAAG,CAAAuB,IAAI,oBAAJA,IAAI,CAAEG,IAAI,KAAInB,SAAS;;IAEvC;IACA,MAAMoB,OAAO,GAAGd,YAAY,CAACC,GAAG,CAAEjC,GAAG,IAAKA,GAAG,CAAC+C,IAAI,CAAE9C,CAAC,IAAK,CAACF,WAAW,CAACE,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM+C,YAAY,GAAGrC,IAAI,CAACsC,MAAM,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKL,OAAO,CAACK,CAAC,CAAC,CAAC;IACtD,MAAMC,eAAe,GAAGpB,YAAY,CAACiB,MAAM,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKL,OAAO,CAACK,CAAC,CAAC,CAAC;IACjE,MAAME,YAAY,GAAG1C,IAAI,CAAC2C,MAAM,GAAGN,YAAY,CAACM,MAAM;IACtD,IAAID,YAAY,GAAG,CAAC,EAAE;MACpB/B,OAAO,YAAPA,OAAO,CAAEiC,gBAAgB,YAAzBjC,OAAO,CAAEiC,gBAAgB,CACvBC,iCAAgB,CAACC,aAAa,EAC9B,eAAeJ,YAAY,oCAC7B,CAAC;IACH;;IAEA;IACA;IACA;IACA;IACA;IACA,MAAMK,YAAY,GAAGhD,YAAY,CAC9BuB,GAAG,CAAC,CAACiB,CAAC,EAAES,GAAG,KAAKA,GAAG,CAAC,CACpBV,MAAM,CAAEU,GAAG,IACVP,eAAe,CAACQ,KAAK,CAAE5D,GAAG,IAAKD,WAAW,CAACC,GAAG,CAAC2D,GAAG,CAAC,CAAC,CACtD,CAAC;IACH,IAAIjD,YAAY,CAAC4C,MAAM,GAAG,CAAC,IAAII,YAAY,CAACJ,MAAM,GAAG,CAAC,EAAE;MACtDhC,OAAO,YAAPA,OAAO,CAAEiC,gBAAgB,YAAzBjC,OAAO,CAAEiC,gBAAgB,CACvBC,iCAAgB,CAACK,gBAAgB,EACjC,yBAAyBH,YAAY,CAClCzB,GAAG,CAAE6B,CAAC,IAAKpD,YAAY,CAACoD,CAAC,CAAC,IAAI,IAAIA,CAAC,GAAG,CAAC,EAAE,CAAC,CAC1CC,IAAI,CAAC,IAAI,CAAC,qBAAqBX,eAAe,CAACE,MAAM,2BAC1D,CAAC;MACD,OAAOU,+BAAY;IACrB;IAEA,OAAO;MACLvD,KAAK;MACLQ,WAAW;MACXP,YAAY;MACZqB,OAAO;MACPpB,IAAI,EAAEqC,YAAY;MAClBhC,kBAAkB;MAClBE,OAAO;MACPC;IACF,CAAC;EACH;AACF;AAAC8C,OAAA,CAAA5D,2BAAA,GAAAA,2BAAA","ignoreList":[]}
1
+ {"version":3,"names":["_parseUtils","require","_parts","_sectionDefinition","_diagnosticTypes","isBlankCell","raw","v","trim","toLowerCase","test","ComparisonSectionDefinition","constructor","_defineProperty2","default","title","columnLabels","rows","feature","values","markdown","expected","featureColumnLabel","description","ctaText","ctaHref","parse","parts","context","_tableParts$","headingText","proseBodyMarkdown","undefined","tableParts","getPartsByType","tableMeta","meta","columns","rawValueRows","map","row","iconHint","parseTableValue","allHeaders","headers","rawRows","slice","label","lastTableIndex","p","type","lastIndexOf","link","firstLink","text","href","keptRow","some","filteredRows","filter","_","i","filteredRawRows","droppedCount","length","reportDiagnostic","DIAGNOSTIC_TYPES","ITEMS_DROPPED","blankColumns","col","every","SECTION_REJECTED","c","join","DROP_SECTION","exports"],"sources":["../../../../src/component/componentDefinitions/ComparisonSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n proseBodyMarkdown,\n firstLink,\n parseTableValue,\n} from './parse-utils';\nimport { getPartsByType, type Part } from '../../parts/parts';\nimport type {\n ComparisonColumn,\n ComparisonCompProps,\n ComparisonRow,\n ComparisonSectionComponent,\n SectionFixture,\n} from '../types';\nimport type {\n SectionDefinition,\n ParseContext,\n DropSection,\n} from '../section-definition';\nimport { DROP_SECTION } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * A cell the writer left blank: empty, or a dash / \"n/a\" placeholder standing in\n * for \"we have no fact here\".\n *\n * This has to be judged on the RAW cell text, before `parseTableValue`, because\n * that maps an empty cell to `false` — the very same value a deliberate \"No\"\n * produces. After parsing, \"no data\" and \"no, it does not have that\" are\n * indistinguishable.\n */\nfunction isBlankCell(raw: string | undefined): boolean {\n const v = (raw ?? '').trim().toLowerCase();\n // U+2010–U+2015 are the hyphen/dash family; U+2212 is minus.\n return v === '' || v === 'n/a' || /^[-‐-―−]+$/.test(v);\n}\n\nexport class ComparisonSectionDefinition\n implements SectionDefinition<ComparisonSectionComponent>\n{\n sectionType = 'comparison' as const;\n // The pre-table slot is the description paragraph, so it has to admit\n // `link`: `convertToBlockElements` unwraps a paragraph into its inline\n // nodes, so `Questions? Email support@shop.example for help.` arrives as\n // `t link t` — GFM autolinks a bare email, no author required — and an\n // authored `[men's shoes](web5://ask/…)` does the same. With `t*` the run\n // ended at that `link`, the whole pattern failed, and the section never\n // reached `parse()`: the heading and prose were claimed by `textBlock` and\n // the TABLE fell to `FallbackSectionDefinition`, whose `partsToMarkdown`\n // has no `table` branch — so it rendered as an empty section and the\n // comparison silently vanished.\n //\n // The trailing slot stays `(t | link)?`: it is the CTA, and `parse()` reads\n // it positionally (see `ctaText` below). Widening it would let the section\n // swallow a paragraph belonging to whatever follows.\n patterns = [\n 'html_comment[section^=\"comparison\"] > (h2 | h3 | h4 | t) > html_comment? > (t | link)* > table > (t | link)?',\n ];\n\n defaults: ComparisonCompProps = {\n title: 'Comparison',\n columnLabels: ['Option A', 'Option B'],\n rows: [\n { feature: 'Price', values: ['$10', '$20'] },\n { feature: 'Storage', values: ['100GB', '1TB'] },\n ],\n };\n\n fixtures: SectionFixture<ComparisonCompProps>[] = [\n {\n markdown: `<!-- section: comparison -->\n## Compare Plans\n\n| Feature | Basic | Pro |\n|---------|-------|-----|\n| Users | 5 | Unlimited |\n| Storage | 100GB | 1TB |`,\n expected: {\n title: 'Compare Plans',\n featureColumnLabel: 'Feature',\n columnLabels: ['Basic', 'Pro'],\n rows: [\n { feature: 'Users', values: ['5', 'Unlimited'] },\n { feature: 'Storage', values: ['100GB', '1TB'] },\n ],\n },\n },\n {\n markdown: `<!-- section: comparison -->\n## Platform Comparison\n\nSee how our platform stacks up against the competition.\n\n| Feature | Our Platform | Competitor |\n|---------|--------------|------------|\n| Storage | 1TB | 100GB |\n| Users | Unlimited | 5 |\n| Support | 24/7 Priority | Email Only |\n\n[Get started](#signup)`,\n expected: {\n title: 'Platform Comparison',\n description: 'See how our platform stacks up against the competition.',\n featureColumnLabel: 'Feature',\n columnLabels: ['Our Platform', 'Competitor'],\n rows: [\n { feature: 'Storage', values: ['1TB', '100GB'] },\n { feature: 'Users', values: ['Unlimited', '5'] },\n { feature: 'Support', values: ['24/7 Priority', 'Email Only'] },\n ],\n ctaText: 'Get started',\n ctaHref: '#signup',\n },\n },\n ];\n\n parse(\n parts: Part[],\n context?: ParseContext,\n ): ComparisonCompProps | DropSection {\n const title =\n headingText(parts, 2) ||\n headingText(parts, 3) ||\n headingText(parts, 4) ||\n 'Comparison';\n const description = proseBodyMarkdown(parts) || undefined;\n\n // Table meta from nodesToParts: { columnLabels, featureColumnLabel, rows: { feature, values }[] }\n const tableParts = getPartsByType<Part>(parts, 'table');\n const tableMeta = tableParts[0]?.meta as\n | {\n columnLabels?: string[];\n columns?: ComparisonColumn[];\n featureColumnLabel?: string;\n rows?: { feature: string; iconHint?: string; values: string[] }[];\n // Legacy format from partsConverter\n headers?: string[];\n }\n | undefined;\n\n let columnLabels: string[];\n let columns: ComparisonColumn[] | undefined;\n let featureColumnLabel: string | undefined;\n let rows: ComparisonRow[];\n /** The cells exactly as written, index-aligned with `rows`. */\n let rawValueRows: string[][];\n\n if (tableMeta?.columnLabels !== undefined) {\n // SDK nodesToParts format\n columnLabels = tableMeta.columnLabels;\n columns = tableMeta.columns;\n featureColumnLabel = tableMeta.featureColumnLabel || undefined;\n rawValueRows = (tableMeta.rows ?? []).map((row) => row.values);\n rows = (tableMeta.rows ?? []).map((row) => ({\n feature: row.feature,\n ...(row.iconHint ? { iconHint: row.iconHint } : {}),\n values: row.values.map(parseTableValue),\n }));\n } else {\n // Legacy partsConverter format: { headers: string[], rows: string[][] }\n const allHeaders = tableMeta?.headers ?? [];\n const rawRows = (tableMeta as any)?.rows ?? [];\n featureColumnLabel = allHeaders[0] || undefined;\n columnLabels = allHeaders.slice(1);\n columns = columnLabels.map((label) => ({ label }));\n rawValueRows = rawRows.map((row: string[]) => row.slice(1));\n rows = rawRows.map((row: string[]) => ({\n feature: row[0] ?? '',\n values: row.slice(1).map(parseTableValue),\n }));\n }\n\n // The CTA is the link AFTER the table, not merely the first link in the\n // section — the description may now carry its own inline links, and\n // `firstLink(parts)` would promote one of those to the CTA button.\n const lastTableIndex = parts.map((p) => p.type).lastIndexOf('table');\n const link = firstLink(\n lastTableIndex >= 0 ? parts.slice(lastTableIndex + 1) : parts,\n );\n const ctaText = link?.text || undefined;\n const ctaHref = link?.href || undefined;\n\n // Filter out rows where all values are empty (e.g. trailing non-table text parsed as a row)\n const keptRow = rawValueRows.map((raw) => raw.some((v) => !isBlankCell(v)));\n const filteredRows = rows.filter((_, i) => keptRow[i]);\n const filteredRawRows = rawValueRows.filter((_, i) => keptRow[i]);\n const droppedCount = rows.length - filteredRows.length;\n if (droppedCount > 0) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.ITEMS_DROPPED,\n `comparison: ${droppedCount} row(s) dropped (all values empty)`,\n );\n }\n\n // A column blank in every row is a side of the comparison the writer had no\n // facts for — it renders as a full column of dashes next to a populated one,\n // which reads as a real answer and is not one. Half a comparison is worse\n // than none, so drop the whole section. (No rows left at all lands here too:\n // every column is vacuously blank.)\n const blankColumns = columnLabels\n .map((_, col) => col)\n .filter((col) =>\n filteredRawRows.every((raw) => isBlankCell(raw[col])),\n );\n if (columnLabels.length > 0 && blankColumns.length > 0) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n `comparison: column(s) ${blankColumns\n .map((c) => columnLabels[c] || `#${c + 1}`)\n .join(', ')} blank across all ${filteredRawRows.length} row(s) — section dropped`,\n );\n return DROP_SECTION;\n }\n\n return {\n title,\n description,\n columnLabels,\n columns,\n rows: filteredRows,\n featureColumnLabel,\n ctaText,\n ctaHref,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -121,7 +121,7 @@ Nike offerings span performance and lifestyle lines.
121
121
  // formatting, not just the plain text. See FeatureHeroEntity.tsx.
122
122
  headline: (0, _parseUtils.headingMarkdown)(parts, 1),
123
123
  badge: (0, _parseUtils.headingText)(parts, 3) || undefined,
124
- content: (0, _parseUtils.bodyText)(parts) || undefined,
124
+ content: (0, _parseUtils.proseBodyMarkdown)(parts) || undefined,
125
125
  image: (0, _parseUtils.firstImageSrc)(parts),
126
126
  leadingQuote,
127
127
  items
@@ -1 +1 @@
1
- {"version":3,"names":["_parseUtils","require","_parts","_diagnosticTypes","HeroEntitySectionDefinition","constructor","_defineProperty2","default","headline","content","items","entityId","entityType","entityUrl","linkText","markdown","expected","image","parse","parts","context","entityRefs","extractEntityRefs","length","reportDiagnostic","DIAGNOSTIC_TYPES","SECTION_REJECTED","map","e","undefined","description","imageUrl","markdownImageUrl","callout","h1Index","findIndex","p","_p$meta","type","meta","level","blockquotes","getPartsByType","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","trim","parseCalloutTag","text","headingMarkdown","badge","headingText","bodyText","firstImageSrc","exports"],"sources":["../../../../src/component/componentDefinitions/HeroEntitySectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n headingMarkdown,\n bodyText,\n firstImageSrc,\n parseCalloutTag,\n extractEntityRefs,\n} from './parse-utils';\nimport type { Part } from '../../parts/parts';\nimport { getPartsByType } from '../../parts/parts';\nimport type {\n EntityItem,\n HeroEntityCompProps,\n HeroEntitySectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * Hero section whose body carries one or more web5 entity links\n * (`web5://entity/...`). It mirrors the framing of {@link HeroSectionDefinition}\n * (leading callout, h1, image, intro text) but, instead of turning the links\n * into CTA buttons, surfaces them as resolvable entities.\n *\n * Registered ahead of `HeroSectionDefinition` so an entity-bearing hero is\n * claimed here first. The pattern requires at least one `link[entityType]`, and\n * `parse()` returns `null` when no entity refs are found, letting a plain hero\n * fall through to `HeroSectionDefinition`.\n */\nexport class HeroEntitySectionDefinition\n implements SectionDefinition<HeroEntitySectionComponent>\n{\n sectionType = 'hero-entity' as const;\n singleInstance = true;\n patterns = [\n // Leading slots mirror HeroSectionDefinition: section descriptor, then the\n // optional leading ECHO callout, then an optional h3 badge, then the h1.\n // Callout and badge are independent; the second `html_comment?` preserves the\n // legacy callout-before-descriptor ordering for pages saved before this change.\n // Entity links carried as list items (the canonical shape).\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t | link)* > list{item[1-]:link[entityType] > image? > (t | callout)*}',\n // Entity links sitting at the top level of the hero body.\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t)* > (link[entityType] > image? > (t | callout)*)[1-] > list?',\n ];\n\n defaults: HeroEntityCompProps = {\n headline: 'Featured collection',\n content: 'Explore the highlights from this collection.',\n items: [\n {\n entityId: 'example-1',\n entityType: 'product',\n entityUrl: 'web5://entity/product/example-1',\n linkText: 'Example product',\n },\n ],\n };\n\n fixtures: SectionFixture<HeroEntityCompProps>[] = [\n {\n markdown: `<!-- section:hero, id:hero, semantic:hero -->\n\n# **Nike** collections\n\n![Nike Life collection](web5://image/Nike+Life+collection)\nNike offerings span performance and lifestyle lines.\n\n* [Nike Throwback Future Collection](web5://entity/article/WAyuiC63OSjwpvnOyZcblQ)\n* [Nike Life Cable Knit Turtleneck Sweater](web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw)\n* [Nike x Jacquemus NRG HE Swoosh Hoodie](web5://entity/product/qH2lqINGneHWYVZzzGnXTA)`,\n expected: {\n headline: '**Nike** collections',\n content: 'Nike offerings span performance and lifestyle lines.',\n image: 'web5://image/Nike+Life+collection',\n items: [\n {\n entityId: 'WAyuiC63OSjwpvnOyZcblQ',\n entityType: 'article',\n entityUrl:\n 'web5://entity/article/WAyuiC63OSjwpvnOyZcblQ',\n linkText: 'Nike Throwback Future Collection',\n },\n {\n entityId: 'ZDvHxsa3IJA8CyGX8BNpdw',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw',\n linkText: 'Nike Life Cable Knit Turtleneck Sweater',\n },\n {\n entityId: 'qH2lqINGneHWYVZzzGnXTA',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/qH2lqINGneHWYVZzzGnXTA',\n linkText: 'Nike x Jacquemus NRG HE Swoosh Hoodie',\n },\n ],\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): HeroEntityCompProps | null {\n const entityRefs = extractEntityRefs(parts);\n\n if (entityRefs.length === 0) {\n // Not an entity hero — let HeroSectionDefinition claim it.\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'hero-entity: no entity links found',\n );\n return null;\n }\n\n const items: EntityItem[] = entityRefs.map((e) => ({\n entityId: e.entityId,\n entityType: e.entityType,\n entityUrl: e.entityUrl,\n linkText: e.linkText || undefined,\n description: e.description || undefined,\n imageUrl: e.markdownImageUrl,\n callout: e.callout,\n }));\n\n // The leading callout before the h1 — the ECHO the orchestrator emits as the\n // hero's opening line, right after the section descriptor — is part of the\n // hero, mirroring HeroSectionDefinition. A trailing callout falls through to a\n // downstream callout section. (Per-item callouts inside the entity list are\n // handled by extractEntityRefs above and are unaffected.)\n const h1Index = parts.findIndex(\n (p) =>\n p.type === 'heading' && (p.meta as { level?: number })?.level === 1,\n );\n const blockquotes = getPartsByType<Part>(parts, 'callout');\n let leadingQuote: string | undefined;\n\n for (const bq of blockquotes) {\n const bqIndex = parts.indexOf(bq);\n if (h1Index >= 0 && bqIndex > h1Index) {\n continue;\n }\n const raw = (bq.meta?.markdown as string)?.trim() || bq.content?.trim();\n if (!raw) {\n continue;\n }\n leadingQuote = parseCalloutTag(raw).text || undefined;\n }\n\n return {\n // Markdown-preserving (not headingText): the hero title now carries the\n // bold+italic hook the ECHO callout used to own, so the FE needs the\n // formatting, not just the plain text. See FeatureHeroEntity.tsx.\n headline: headingMarkdown(parts, 1),\n badge: headingText(parts, 3) || undefined,\n content: bodyText(parts) || undefined,\n image: firstImageSrc(parts),\n leadingQuote,\n items,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_parseUtils","require","_parts","_diagnosticTypes","HeroEntitySectionDefinition","constructor","_defineProperty2","default","headline","content","items","entityId","entityType","entityUrl","linkText","markdown","expected","image","parse","parts","context","entityRefs","extractEntityRefs","length","reportDiagnostic","DIAGNOSTIC_TYPES","SECTION_REJECTED","map","e","undefined","description","imageUrl","markdownImageUrl","callout","h1Index","findIndex","p","_p$meta","type","meta","level","blockquotes","getPartsByType","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","trim","parseCalloutTag","text","headingMarkdown","badge","headingText","proseBodyMarkdown","firstImageSrc","exports"],"sources":["../../../../src/component/componentDefinitions/HeroEntitySectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n headingMarkdown,\n proseBodyMarkdown,\n firstImageSrc,\n parseCalloutTag,\n extractEntityRefs,\n} from './parse-utils';\nimport type { Part } from '../../parts/parts';\nimport { getPartsByType } from '../../parts/parts';\nimport type {\n EntityItem,\n HeroEntityCompProps,\n HeroEntitySectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * Hero section whose body carries one or more web5 entity links\n * (`web5://entity/...`). It mirrors the framing of {@link HeroSectionDefinition}\n * (leading callout, h1, image, intro text) but, instead of turning the links\n * into CTA buttons, surfaces them as resolvable entities.\n *\n * Registered ahead of `HeroSectionDefinition` so an entity-bearing hero is\n * claimed here first. The pattern requires at least one `link[entityType]`, and\n * `parse()` returns `null` when no entity refs are found, letting a plain hero\n * fall through to `HeroSectionDefinition`.\n */\nexport class HeroEntitySectionDefinition\n implements SectionDefinition<HeroEntitySectionComponent>\n{\n sectionType = 'hero-entity' as const;\n singleInstance = true;\n patterns = [\n // Leading slots mirror HeroSectionDefinition: section descriptor, then the\n // optional leading ECHO callout, then an optional h3 badge, then the h1.\n // Callout and badge are independent; the second `html_comment?` preserves the\n // legacy callout-before-descriptor ordering for pages saved before this change.\n // Entity links carried as list items (the canonical shape).\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t | link)* > list{item[1-]:link[entityType] > image? > (t | callout)*}',\n // Entity links sitting at the top level of the hero body.\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t)* > (link[entityType] > image? > (t | callout)*)[1-] > list?',\n ];\n\n defaults: HeroEntityCompProps = {\n headline: 'Featured collection',\n content: 'Explore the highlights from this collection.',\n items: [\n {\n entityId: 'example-1',\n entityType: 'product',\n entityUrl: 'web5://entity/product/example-1',\n linkText: 'Example product',\n },\n ],\n };\n\n fixtures: SectionFixture<HeroEntityCompProps>[] = [\n {\n markdown: `<!-- section:hero, id:hero, semantic:hero -->\n\n# **Nike** collections\n\n![Nike Life collection](web5://image/Nike+Life+collection)\nNike offerings span performance and lifestyle lines.\n\n* [Nike Throwback Future Collection](web5://entity/article/WAyuiC63OSjwpvnOyZcblQ)\n* [Nike Life Cable Knit Turtleneck Sweater](web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw)\n* [Nike x Jacquemus NRG HE Swoosh Hoodie](web5://entity/product/qH2lqINGneHWYVZzzGnXTA)`,\n expected: {\n headline: '**Nike** collections',\n content: 'Nike offerings span performance and lifestyle lines.',\n image: 'web5://image/Nike+Life+collection',\n items: [\n {\n entityId: 'WAyuiC63OSjwpvnOyZcblQ',\n entityType: 'article',\n entityUrl:\n 'web5://entity/article/WAyuiC63OSjwpvnOyZcblQ',\n linkText: 'Nike Throwback Future Collection',\n },\n {\n entityId: 'ZDvHxsa3IJA8CyGX8BNpdw',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw',\n linkText: 'Nike Life Cable Knit Turtleneck Sweater',\n },\n {\n entityId: 'qH2lqINGneHWYVZzzGnXTA',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/qH2lqINGneHWYVZzzGnXTA',\n linkText: 'Nike x Jacquemus NRG HE Swoosh Hoodie',\n },\n ],\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): HeroEntityCompProps | null {\n const entityRefs = extractEntityRefs(parts);\n\n if (entityRefs.length === 0) {\n // Not an entity hero — let HeroSectionDefinition claim it.\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'hero-entity: no entity links found',\n );\n return null;\n }\n\n const items: EntityItem[] = entityRefs.map((e) => ({\n entityId: e.entityId,\n entityType: e.entityType,\n entityUrl: e.entityUrl,\n linkText: e.linkText || undefined,\n description: e.description || undefined,\n imageUrl: e.markdownImageUrl,\n callout: e.callout,\n }));\n\n // The leading callout before the h1 — the ECHO the orchestrator emits as the\n // hero's opening line, right after the section descriptor — is part of the\n // hero, mirroring HeroSectionDefinition. A trailing callout falls through to a\n // downstream callout section. (Per-item callouts inside the entity list are\n // handled by extractEntityRefs above and are unaffected.)\n const h1Index = parts.findIndex(\n (p) =>\n p.type === 'heading' && (p.meta as { level?: number })?.level === 1,\n );\n const blockquotes = getPartsByType<Part>(parts, 'callout');\n let leadingQuote: string | undefined;\n\n for (const bq of blockquotes) {\n const bqIndex = parts.indexOf(bq);\n if (h1Index >= 0 && bqIndex > h1Index) {\n continue;\n }\n const raw = (bq.meta?.markdown as string)?.trim() || bq.content?.trim();\n if (!raw) {\n continue;\n }\n leadingQuote = parseCalloutTag(raw).text || undefined;\n }\n\n return {\n // Markdown-preserving (not headingText): the hero title now carries the\n // bold+italic hook the ECHO callout used to own, so the FE needs the\n // formatting, not just the plain text. See FeatureHeroEntity.tsx.\n headline: headingMarkdown(parts, 1),\n badge: headingText(parts, 3) || undefined,\n content: proseBodyMarkdown(parts) || undefined,\n image: firstImageSrc(parts),\n leadingQuote,\n items,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -208,7 +208,7 @@ Get started today with our amazing features.
208
208
  // formatting, not just the plain text. See FeatureComparisonHero.tsx.
209
209
  headline: (0, _parseUtils.headingMarkdown)(parts, 1),
210
210
  badge: (0, _parseUtils.headingText)(parts, 3) || undefined,
211
- content: (0, _parseUtils.bodyText)(parts) || undefined,
211
+ content: (0, _parseUtils.proseBodyMarkdown)(parts) || undefined,
212
212
  buttons: buttons.length > 0 ? buttons : undefined,
213
213
  image,
214
214
  trailingList,
@@ -1 +1 @@
1
- {"version":3,"names":["_parseUtils","require","_parts","HeroSectionDefinition","constructor","_defineProperty2","default","headline","content","buttons","id","text","variant","markdown","expected","onClick","image","parse","parts","firstImageSrc","lists","getPartsByType","list","item","_list$meta","meta","items","_item$meta","itemParts","nestedImage","buttonIndex","getLinks","map","l","i","_l$content","trim","href","length","trailingList","filteredItems","_list$meta2","_item$meta2","hasImage","some","p","type","push","allItemsAreLinks","listLinks","_item$meta3","_itemLinkParts$0$cont","itemLinkParts","filter","allText","extractListItemText","linkTexts","_l$content2","join","link","isFirstButton","lines","_item$meta4","h1Index","findIndex","_p$meta","level","blockquotes","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","parseCalloutTag","undefined","headingMarkdown","badge","headingText","bodyText","exports"],"sources":["../../../../src/component/componentDefinitions/HeroSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n headingMarkdown,\n bodyText,\n firstImageSrc,\n parseCalloutTag,\n extractListItemText,\n} from './parse-utils';\nimport type { Part, ListPart, LinkPart, ListItemPart } from '../../parts/parts';\nimport { getPartsByType, getLinks } from '../../parts/parts';\nimport type {\n HeroButton,\n HeroCompProps,\n HeroSectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition } from '../section-definition';\n\nexport class HeroSectionDefinition\n implements SectionDefinition<HeroSectionComponent>\n{\n sectionType = 'hero' as const;\n singleInstance = true;\n patterns = [\n // Canonical hero shape: section descriptor, then the leading ECHO callout\n // (optional in the grammar — the orchestrator always emits it, but a missing\n // ECHO must not stop the hero from rendering), then an optional h3 badge,\n // then the h1. The callout and badge are independent slots so a hero can\n // carry both. The second `html_comment?` keeps the legacy ordering\n // (callout before the descriptor) matching for pages saved before this change.\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t | link)* > list?',\n ];\n\n defaults: HeroCompProps = {\n headline: 'Build something amazing',\n content: 'Your platform for data-driven decisions.',\n buttons: [{ id: 'btn-0', text: 'Get started', variant: 'primary' }],\n };\n\n fixtures: SectionFixture<HeroCompProps>[] = [\n {\n markdown:\n '# Welcome\\n\\nBuild amazing things.\\n\\n[Get Started](/) [Learn More](/about)',\n expected: {\n headline: 'Welcome',\n content: 'Build amazing things.',\n buttons: [\n {\n id: 'btn-0',\n text: 'Get Started',\n variant: 'primary',\n onClick: '/',\n },\n {\n id: 'btn-1',\n text: 'Learn More',\n variant: 'secondary',\n onClick: '/about',\n },\n ],\n },\n },\n {\n markdown: `<!-- Hero Section -->\n\n# Welcome to Our Platform\n\n![Hero Image](hero.jpg)\n\nGet started today with our amazing features.\n\n[Get Started](/signup) [Learn More](/about)`,\n expected: {\n headline: 'Welcome to Our Platform',\n content: 'Get started today with our amazing features.',\n buttons: [\n {\n id: 'btn-0',\n text: 'Get Started',\n variant: 'primary',\n onClick: '/signup',\n },\n {\n id: 'btn-1',\n text: 'Learn More',\n variant: 'secondary',\n onClick: '/about',\n },\n ],\n image: 'hero.jpg',\n },\n },\n ];\n\n parse(parts: Part[]): HeroCompProps {\n let image = firstImageSrc(parts);\n const lists = getPartsByType<ListPart>(parts, 'list');\n\n if (!image) {\n for (const list of lists) {\n for (const item of list.meta?.items ?? []) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n const nestedImage = firstImageSrc(itemParts);\n if (nestedImage) {\n image = nestedImage;\n break;\n }\n }\n if (image) {\n break;\n }\n }\n }\n\n // Start with top-level links as buttons\n let buttonIndex = 0;\n const buttons: HeroButton[] = getLinks(parts).map(\n (l, i): HeroButton => ({\n id: `btn-${i}`,\n text: l.content?.trim() || 'Learn more',\n variant: i === 0 ? 'primary' : 'secondary',\n onClick: l.meta.href,\n }),\n );\n buttonIndex = buttons.length;\n\n // Process lists: filter out image-only items, then check if remaining\n // items are all link-only. If so, convert to buttons; otherwise keep as trailingList.\n let trailingList: string | undefined;\n if (lists.length > 0) {\n for (const list of lists) {\n // Filter out image-only items\n const filteredItems: ListItemPart[] = [];\n for (const item of list.meta?.items ?? []) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n const hasImage = itemParts.some((p) => p.type === 'image');\n if (hasImage) {\n continue;\n }\n filteredItems.push(item);\n }\n\n if (filteredItems.length === 0) {\n continue;\n }\n\n // Check if ALL remaining items contain only links (no other text)\n let allItemsAreLinks = true;\n const listLinks: { text: string; href: string }[] = [];\n\n for (const item of filteredItems) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n const itemLinkParts = itemParts.filter(\n (p): p is LinkPart => p.type === 'link',\n );\n\n if (itemLinkParts.length === 0) {\n allItemsAreLinks = false;\n break;\n }\n\n // Check if the item text is entirely link text (no extra content)\n const allText = extractListItemText(item).trim();\n const linkTexts = itemLinkParts\n .map((l) => l.content?.trim() || '')\n .join(' ')\n .trim();\n\n if (allText !== linkTexts) {\n allItemsAreLinks = false;\n break;\n }\n\n listLinks.push({\n text: itemLinkParts[0].content?.trim() || 'Learn more',\n href: itemLinkParts[0].meta.href,\n });\n }\n\n if (allItemsAreLinks && listLinks.length > 0) {\n // Convert link-only list items to buttons\n for (const link of listLinks) {\n const isFirstButton = buttons.length === 0;\n buttons.push({\n id: `btn-${buttonIndex++}`,\n text: link.text,\n variant: isFirstButton ? 'primary' : 'secondary',\n onClick: link.href,\n });\n }\n } else {\n // Keep as trailing list markdown\n const lines: string[] = [];\n for (const item of filteredItems) {\n const text =\n (item.meta?.markdown as string) || extractListItemText(item);\n if (text) {\n lines.push(`- ${text}`);\n }\n }\n if (lines.length > 0) {\n trailingList = lines.join('\\n');\n }\n }\n }\n }\n\n // The hero claims the leading callout before the h1 — the ECHO the\n // orchestrator emits as the hero's opening line, right after the section\n // descriptor. A trailing blockquote is not part of the hero pattern, so it\n // falls through to a downstream callout section instead.\n const h1Index = parts.findIndex(\n (p) => p.type === 'heading' && (p.meta as { level: number })?.level === 1,\n );\n const blockquotes = getPartsByType<Part>(parts, 'callout');\n let leadingQuote: string | undefined;\n\n for (const bq of blockquotes) {\n const bqIndex = parts.indexOf(bq);\n if (h1Index >= 0 && bqIndex > h1Index) {\n continue;\n }\n const raw = (bq.meta?.markdown as string)?.trim() || bq.content?.trim();\n if (!raw) {\n continue;\n }\n leadingQuote = parseCalloutTag(raw).text || undefined;\n }\n\n return {\n // Markdown-preserving (not headingText): the hero title now carries the\n // bold+italic hook the ECHO callout used to own, so the FE needs the\n // formatting, not just the plain text. See FeatureComparisonHero.tsx.\n headline: headingMarkdown(parts, 1),\n badge: headingText(parts, 3) || undefined,\n content: bodyText(parts) || undefined,\n buttons: buttons.length > 0 ? buttons : undefined,\n image,\n trailingList,\n leadingQuote,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_parseUtils","require","_parts","HeroSectionDefinition","constructor","_defineProperty2","default","headline","content","buttons","id","text","variant","markdown","expected","onClick","image","parse","parts","firstImageSrc","lists","getPartsByType","list","item","_list$meta","meta","items","_item$meta","itemParts","nestedImage","buttonIndex","getLinks","map","l","i","_l$content","trim","href","length","trailingList","filteredItems","_list$meta2","_item$meta2","hasImage","some","p","type","push","allItemsAreLinks","listLinks","_item$meta3","_itemLinkParts$0$cont","itemLinkParts","filter","allText","extractListItemText","linkTexts","_l$content2","join","link","isFirstButton","lines","_item$meta4","h1Index","findIndex","_p$meta","level","blockquotes","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","parseCalloutTag","undefined","headingMarkdown","badge","headingText","proseBodyMarkdown","exports"],"sources":["../../../../src/component/componentDefinitions/HeroSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n headingMarkdown,\n proseBodyMarkdown,\n firstImageSrc,\n parseCalloutTag,\n extractListItemText,\n} from './parse-utils';\nimport type { Part, ListPart, LinkPart, ListItemPart } from '../../parts/parts';\nimport { getPartsByType, getLinks } from '../../parts/parts';\nimport type {\n HeroButton,\n HeroCompProps,\n HeroSectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition } from '../section-definition';\n\nexport class HeroSectionDefinition\n implements SectionDefinition<HeroSectionComponent>\n{\n sectionType = 'hero' as const;\n singleInstance = true;\n patterns = [\n // Canonical hero shape: section descriptor, then the leading ECHO callout\n // (optional in the grammar — the orchestrator always emits it, but a missing\n // ECHO must not stop the hero from rendering), then an optional h3 badge,\n // then the h1. The callout and badge are independent slots so a hero can\n // carry both. The second `html_comment?` keeps the legacy ordering\n // (callout before the descriptor) matching for pages saved before this change.\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t | link)* > list?',\n ];\n\n defaults: HeroCompProps = {\n headline: 'Build something amazing',\n content: 'Your platform for data-driven decisions.',\n buttons: [{ id: 'btn-0', text: 'Get started', variant: 'primary' }],\n };\n\n fixtures: SectionFixture<HeroCompProps>[] = [\n {\n markdown:\n '# Welcome\\n\\nBuild amazing things.\\n\\n[Get Started](/) [Learn More](/about)',\n expected: {\n headline: 'Welcome',\n content: 'Build amazing things.',\n buttons: [\n {\n id: 'btn-0',\n text: 'Get Started',\n variant: 'primary',\n onClick: '/',\n },\n {\n id: 'btn-1',\n text: 'Learn More',\n variant: 'secondary',\n onClick: '/about',\n },\n ],\n },\n },\n {\n markdown: `<!-- Hero Section -->\n\n# Welcome to Our Platform\n\n![Hero Image](hero.jpg)\n\nGet started today with our amazing features.\n\n[Get Started](/signup) [Learn More](/about)`,\n expected: {\n headline: 'Welcome to Our Platform',\n content: 'Get started today with our amazing features.',\n buttons: [\n {\n id: 'btn-0',\n text: 'Get Started',\n variant: 'primary',\n onClick: '/signup',\n },\n {\n id: 'btn-1',\n text: 'Learn More',\n variant: 'secondary',\n onClick: '/about',\n },\n ],\n image: 'hero.jpg',\n },\n },\n ];\n\n parse(parts: Part[]): HeroCompProps {\n let image = firstImageSrc(parts);\n const lists = getPartsByType<ListPart>(parts, 'list');\n\n if (!image) {\n for (const list of lists) {\n for (const item of list.meta?.items ?? []) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n const nestedImage = firstImageSrc(itemParts);\n if (nestedImage) {\n image = nestedImage;\n break;\n }\n }\n if (image) {\n break;\n }\n }\n }\n\n // Start with top-level links as buttons\n let buttonIndex = 0;\n const buttons: HeroButton[] = getLinks(parts).map(\n (l, i): HeroButton => ({\n id: `btn-${i}`,\n text: l.content?.trim() || 'Learn more',\n variant: i === 0 ? 'primary' : 'secondary',\n onClick: l.meta.href,\n }),\n );\n buttonIndex = buttons.length;\n\n // Process lists: filter out image-only items, then check if remaining\n // items are all link-only. If so, convert to buttons; otherwise keep as trailingList.\n let trailingList: string | undefined;\n if (lists.length > 0) {\n for (const list of lists) {\n // Filter out image-only items\n const filteredItems: ListItemPart[] = [];\n for (const item of list.meta?.items ?? []) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n const hasImage = itemParts.some((p) => p.type === 'image');\n if (hasImage) {\n continue;\n }\n filteredItems.push(item);\n }\n\n if (filteredItems.length === 0) {\n continue;\n }\n\n // Check if ALL remaining items contain only links (no other text)\n let allItemsAreLinks = true;\n const listLinks: { text: string; href: string }[] = [];\n\n for (const item of filteredItems) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n const itemLinkParts = itemParts.filter(\n (p): p is LinkPart => p.type === 'link',\n );\n\n if (itemLinkParts.length === 0) {\n allItemsAreLinks = false;\n break;\n }\n\n // Check if the item text is entirely link text (no extra content)\n const allText = extractListItemText(item).trim();\n const linkTexts = itemLinkParts\n .map((l) => l.content?.trim() || '')\n .join(' ')\n .trim();\n\n if (allText !== linkTexts) {\n allItemsAreLinks = false;\n break;\n }\n\n listLinks.push({\n text: itemLinkParts[0].content?.trim() || 'Learn more',\n href: itemLinkParts[0].meta.href,\n });\n }\n\n if (allItemsAreLinks && listLinks.length > 0) {\n // Convert link-only list items to buttons\n for (const link of listLinks) {\n const isFirstButton = buttons.length === 0;\n buttons.push({\n id: `btn-${buttonIndex++}`,\n text: link.text,\n variant: isFirstButton ? 'primary' : 'secondary',\n onClick: link.href,\n });\n }\n } else {\n // Keep as trailing list markdown\n const lines: string[] = [];\n for (const item of filteredItems) {\n const text =\n (item.meta?.markdown as string) || extractListItemText(item);\n if (text) {\n lines.push(`- ${text}`);\n }\n }\n if (lines.length > 0) {\n trailingList = lines.join('\\n');\n }\n }\n }\n }\n\n // The hero claims the leading callout before the h1 — the ECHO the\n // orchestrator emits as the hero's opening line, right after the section\n // descriptor. A trailing blockquote is not part of the hero pattern, so it\n // falls through to a downstream callout section instead.\n const h1Index = parts.findIndex(\n (p) => p.type === 'heading' && (p.meta as { level: number })?.level === 1,\n );\n const blockquotes = getPartsByType<Part>(parts, 'callout');\n let leadingQuote: string | undefined;\n\n for (const bq of blockquotes) {\n const bqIndex = parts.indexOf(bq);\n if (h1Index >= 0 && bqIndex > h1Index) {\n continue;\n }\n const raw = (bq.meta?.markdown as string)?.trim() || bq.content?.trim();\n if (!raw) {\n continue;\n }\n leadingQuote = parseCalloutTag(raw).text || undefined;\n }\n\n return {\n // Markdown-preserving (not headingText): the hero title now carries the\n // bold+italic hook the ECHO callout used to own, so the FE needs the\n // formatting, not just the plain text. See FeatureComparisonHero.tsx.\n headline: headingMarkdown(parts, 1),\n badge: headingText(parts, 3) || undefined,\n content: proseBodyMarkdown(parts) || undefined,\n buttons: buttons.length > 0 ? buttons : undefined,\n image,\n trailingList,\n leadingQuote,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -41,8 +41,23 @@ class NextStepsSectionDefinition {
41
41
  constructor() {
42
42
  (0, _defineProperty2.default)(this, "sectionType", 'nextSteps');
43
43
  (0, _defineProperty2.default)(this, "patterns", [
44
- // Framing sentence (`t?`) then one chip per bullet — the authored shape.
45
- '(html_comment[section^="next"] | html_comment[semantic^="next"]) > h2 > image? > t? > list{item[1-]:(icon | t)* > (t? > link)+}',
44
+ // Framing sentence then one chip per bullet — the authored shape. The
45
+ // framing slot is `(t | link)*`, not `t?`, because a paragraph is unwrapped
46
+ // into its inline nodes: `Questions? Email support@shop.example for help.`
47
+ // arrives as `t link t` (GFM autolinks a bare email) and so does an
48
+ // authored `[men's shoes](web5://ask/…)`. With `t?` the run ended at that
49
+ // `link`, this pattern failed, and the legacy pattern below claimed the
50
+ // section instead — turning the prose link into a chip and stranding the
51
+ // real chip list in the fallback as raw bullets.
52
+ //
53
+ // This does NOT steal the legacy pattern's inputs: the `list` here is
54
+ // mandatory, so a section with chips in the prose and no list still falls
55
+ // through. It does change one rare shape — prose that is nothing but links
56
+ // ALONGSIDE a chip list now matches here, and `proseBodyMarkdown` drops
57
+ // link-only paragraphs, so those links are dropped rather than rendered
58
+ // inline. That shape previously stranded the list entirely, so neither
59
+ // outcome was whole; this one at least renders the authored chips.
60
+ '(html_comment[section^="next"] | html_comment[semantic^="next"]) > h2 > image? > (t | link)* > list{item[1-]:(icon | t)* > (t? > link)+}',
46
61
  // Legacy inline shape: chips embedded in the prose. Still arrives from
47
62
  // sites whose next-steps prompt has not moved to the list shape.
48
63
  '(html_comment[section^="next"] | html_comment[semantic^="next"]) > h2 > image? > (t | link)+']);
@@ -100,8 +115,8 @@ Want to focus on rich hydrators and barrier builders?
100
115
  const lists = (0, _parts.getPartsByType)(parts, 'list');
101
116
  if (lists.length > 0) {
102
117
  // The framing sentence is the only top-level prose — list items live
103
- // under each list's meta.items, so bodyText never picks up a chip.
104
- body = (0, _parseUtils.bodyText)(parts) || undefined;
118
+ // under each list's meta.items, so this never picks up a chip.
119
+ body = (0, _parseUtils.proseBodyMarkdown)(parts) || undefined;
105
120
  for (const list of lists) {
106
121
  for (const item of ((_list$meta = list.meta) == null ? void 0 : _list$meta.items) ?? []) {
107
122
  var _list$meta, _item$meta, _iconPart$meta;
@@ -1 +1 @@
1
- {"version":3,"names":["_parseUtils","require","_parts","linkToMarkdown","link","_link$content","_link$meta","content","trim","meta","href","appendInlineToken","current","token","trimmed","test","inlineBodyMarkdown","parts","body","reduce","part","type","_part$meta","markdown","undefined","NextStepsSectionDefinition","constructor","_defineProperty2","default","title","backgroundImage","steps","expected","parse","headingText","firstImageSrc","chipsInlineInBody","lists","getPartsByType","length","bodyText","list","item","_list$meta","items","_item$meta","_iconPart$meta","itemParts","iconPart","find","p","iconHint","contentFragments","push","_p$content","filter","Boolean","join","extractListItemText","links","getLinks","_link$content2","_link$meta2","text","exports"],"sources":["../../../../src/component/componentDefinitions/NextStepsSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n bodyText,\n extractListItemText,\n firstImageSrc,\n} from './parse-utils';\nimport {\n getPartsByType,\n getLinks,\n type Part,\n type ListPart,\n type LinkPart,\n type IconPart,\n} from '../../parts/parts';\nimport type {\n NextStepsCompProps,\n NextStepsSectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition } from '../section-definition';\n\nfunction linkToMarkdown(link: LinkPart): string {\n return `[${link.content?.trim() || ''}](${link.meta?.href || ''})`;\n}\n\nfunction appendInlineToken(current: string, token: string): string {\n const trimmed = token.trim();\n if (!trimmed) {\n return current;\n }\n if (!current) {\n return trimmed;\n }\n if (/^[,.;:!?)]/.test(trimmed)) {\n return `${current}${trimmed}`;\n }\n return `${current} ${trimmed}`;\n}\n\nfunction inlineBodyMarkdown(parts: Part[]): string | undefined {\n const body = parts.reduce((current, part) => {\n if (part.type === 'link') {\n return appendInlineToken(current, linkToMarkdown(part as LinkPart));\n }\n if (part.type === 'text' || part.type === 'richText') {\n const markdown = (part.meta?.markdown as string | undefined)?.trim();\n return appendInlineToken(current, markdown || part.content || '');\n }\n return current;\n }, '');\n\n return body || undefined;\n}\n\nexport class NextStepsSectionDefinition\n implements SectionDefinition<NextStepsSectionComponent>\n{\n sectionType = 'nextSteps' as const;\n patterns = [\n // Framing sentence (`t?`) then one chip per bullet — the authored shape.\n '(html_comment[section^=\"next\"] | html_comment[semantic^=\"next\"]) > h2 > image? > t? > list{item[1-]:(icon | t)* > (t? > link)+}',\n // Legacy inline shape: chips embedded in the prose. Still arrives from\n // sites whose next-steps prompt has not moved to the list shape.\n '(html_comment[section^=\"next\"] | html_comment[semantic^=\"next\"]) > h2 > image? > (t | link)+',\n ];\n\n defaults: NextStepsCompProps = {\n title: 'Next Steps',\n body: 'Where would you like to go next?',\n backgroundImage: undefined,\n steps: [{ content: '[Get started](/start)' }],\n };\n\n fixtures: SectionFixture<NextStepsCompProps>[] = [\n {\n markdown: `<!-- section:next-steps, semantic:next_steps -->\n\n## Find the right care for mature, sensitive skin\n\nWant to focus on rich hydrators and barrier builders?\n\n- [hydrating creams →](web5://ask/hydrating+creams+for+mature+skin)\n- [gentle resurfacing serums →](web5://ask/gentle+resurfacing+serums)`,\n expected: {\n title: 'Find the right care for mature, sensitive skin',\n body: 'Want to focus on rich hydrators and barrier builders?',\n steps: [\n {\n content:\n '[hydrating creams →](web5://ask/hydrating+creams+for+mature+skin)',\n },\n {\n content:\n '[gentle resurfacing serums →](web5://ask/gentle+resurfacing+serums)',\n },\n ],\n },\n },\n {\n markdown: `<!-- section: next_steps -->\n\n## Next Steps\n\n- Check out [Documentation](/docs)\n- Try [Examples](/examples)`,\n expected: {\n title: 'Next Steps',\n steps: [\n { content: 'Check out [Documentation](/docs)' },\n { content: 'Try [Examples](/examples)' },\n ],\n },\n },\n ];\n\n parse(parts: Part[]): NextStepsCompProps {\n const title = headingText(parts, 2) || 'Next Steps';\n const backgroundImage = firstImageSrc(parts);\n const steps: { content: string; iconHint?: string }[] = [];\n let body: string | undefined;\n let chipsInlineInBody = false;\n\n // Try list-based pattern first\n const lists = getPartsByType<ListPart>(parts, 'list');\n if (lists.length > 0) {\n // The framing sentence is the only top-level prose — list items live\n // under each list's meta.items, so bodyText never picks up a chip.\n body = bodyText(parts) || undefined;\n\n for (const list of lists) {\n for (const item of list.meta?.items ?? []) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n\n const iconPart = itemParts.find((p) => p.type === 'icon') as\n | IconPart\n | undefined;\n const iconHint = (iconPart?.meta as { iconHint?: string })?.iconHint;\n\n const contentFragments: string[] = [];\n for (const p of itemParts) {\n if (p.type === 'link') {\n const link = p as LinkPart;\n contentFragments.push(linkToMarkdown(link));\n } else if (p.type === 'text' || p.type === 'richText') {\n contentFragments.push(p.content?.trim() || '');\n }\n }\n const content =\n contentFragments.filter(Boolean).join(' ') ||\n extractListItemText(item);\n if (content.trim()) {\n steps.push({\n content: content.trim(),\n ...(iconHint ? { iconHint } : {}),\n });\n }\n }\n }\n } else {\n body = inlineBodyMarkdown(parts);\n // body carries the chips as inline links — flag it so renderers show the\n // prose alone instead of repeating every chip as a row beneath it.\n chipsInlineInBody = Boolean(body);\n }\n\n // Fallback: inline links pattern\n if (steps.length === 0) {\n const links = getLinks(parts);\n for (const link of links) {\n const text = link.content?.trim();\n const href = link.meta?.href;\n if (text && href) {\n steps.push({ content: linkToMarkdown(link) });\n }\n }\n }\n\n return {\n title,\n ...(body ? { body } : {}),\n ...(chipsInlineInBody ? { chipsInlineInBody } : {}),\n ...(backgroundImage ? { backgroundImage } : {}),\n steps,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_parseUtils","require","_parts","linkToMarkdown","link","_link$content","_link$meta","content","trim","meta","href","appendInlineToken","current","token","trimmed","test","inlineBodyMarkdown","parts","body","reduce","part","type","_part$meta","markdown","undefined","NextStepsSectionDefinition","constructor","_defineProperty2","default","title","backgroundImage","steps","expected","parse","headingText","firstImageSrc","chipsInlineInBody","lists","getPartsByType","length","proseBodyMarkdown","list","item","_list$meta","items","_item$meta","_iconPart$meta","itemParts","iconPart","find","p","iconHint","contentFragments","push","_p$content","filter","Boolean","join","extractListItemText","links","getLinks","_link$content2","_link$meta2","text","exports"],"sources":["../../../../src/component/componentDefinitions/NextStepsSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n proseBodyMarkdown,\n extractListItemText,\n firstImageSrc,\n} from './parse-utils';\nimport {\n getPartsByType,\n getLinks,\n type Part,\n type ListPart,\n type LinkPart,\n type IconPart,\n} from '../../parts/parts';\nimport type {\n NextStepsCompProps,\n NextStepsSectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition } from '../section-definition';\n\nfunction linkToMarkdown(link: LinkPart): string {\n return `[${link.content?.trim() || ''}](${link.meta?.href || ''})`;\n}\n\nfunction appendInlineToken(current: string, token: string): string {\n const trimmed = token.trim();\n if (!trimmed) {\n return current;\n }\n if (!current) {\n return trimmed;\n }\n if (/^[,.;:!?)]/.test(trimmed)) {\n return `${current}${trimmed}`;\n }\n return `${current} ${trimmed}`;\n}\n\nfunction inlineBodyMarkdown(parts: Part[]): string | undefined {\n const body = parts.reduce((current, part) => {\n if (part.type === 'link') {\n return appendInlineToken(current, linkToMarkdown(part as LinkPart));\n }\n if (part.type === 'text' || part.type === 'richText') {\n const markdown = (part.meta?.markdown as string | undefined)?.trim();\n return appendInlineToken(current, markdown || part.content || '');\n }\n return current;\n }, '');\n\n return body || undefined;\n}\n\nexport class NextStepsSectionDefinition\n implements SectionDefinition<NextStepsSectionComponent>\n{\n sectionType = 'nextSteps' as const;\n patterns = [\n // Framing sentence then one chip per bullet — the authored shape. The\n // framing slot is `(t | link)*`, not `t?`, because a paragraph is unwrapped\n // into its inline nodes: `Questions? Email support@shop.example for help.`\n // arrives as `t link t` (GFM autolinks a bare email) and so does an\n // authored `[men's shoes](web5://ask/…)`. With `t?` the run ended at that\n // `link`, this pattern failed, and the legacy pattern below claimed the\n // section instead — turning the prose link into a chip and stranding the\n // real chip list in the fallback as raw bullets.\n //\n // This does NOT steal the legacy pattern's inputs: the `list` here is\n // mandatory, so a section with chips in the prose and no list still falls\n // through. It does change one rare shape — prose that is nothing but links\n // ALONGSIDE a chip list now matches here, and `proseBodyMarkdown` drops\n // link-only paragraphs, so those links are dropped rather than rendered\n // inline. That shape previously stranded the list entirely, so neither\n // outcome was whole; this one at least renders the authored chips.\n '(html_comment[section^=\"next\"] | html_comment[semantic^=\"next\"]) > h2 > image? > (t | link)* > list{item[1-]:(icon | t)* > (t? > link)+}',\n // Legacy inline shape: chips embedded in the prose. Still arrives from\n // sites whose next-steps prompt has not moved to the list shape.\n '(html_comment[section^=\"next\"] | html_comment[semantic^=\"next\"]) > h2 > image? > (t | link)+',\n ];\n\n defaults: NextStepsCompProps = {\n title: 'Next Steps',\n body: 'Where would you like to go next?',\n backgroundImage: undefined,\n steps: [{ content: '[Get started](/start)' }],\n };\n\n fixtures: SectionFixture<NextStepsCompProps>[] = [\n {\n markdown: `<!-- section:next-steps, semantic:next_steps -->\n\n## Find the right care for mature, sensitive skin\n\nWant to focus on rich hydrators and barrier builders?\n\n- [hydrating creams →](web5://ask/hydrating+creams+for+mature+skin)\n- [gentle resurfacing serums →](web5://ask/gentle+resurfacing+serums)`,\n expected: {\n title: 'Find the right care for mature, sensitive skin',\n body: 'Want to focus on rich hydrators and barrier builders?',\n steps: [\n {\n content:\n '[hydrating creams →](web5://ask/hydrating+creams+for+mature+skin)',\n },\n {\n content:\n '[gentle resurfacing serums →](web5://ask/gentle+resurfacing+serums)',\n },\n ],\n },\n },\n {\n markdown: `<!-- section: next_steps -->\n\n## Next Steps\n\n- Check out [Documentation](/docs)\n- Try [Examples](/examples)`,\n expected: {\n title: 'Next Steps',\n steps: [\n { content: 'Check out [Documentation](/docs)' },\n { content: 'Try [Examples](/examples)' },\n ],\n },\n },\n ];\n\n parse(parts: Part[]): NextStepsCompProps {\n const title = headingText(parts, 2) || 'Next Steps';\n const backgroundImage = firstImageSrc(parts);\n const steps: { content: string; iconHint?: string }[] = [];\n let body: string | undefined;\n let chipsInlineInBody = false;\n\n // Try list-based pattern first\n const lists = getPartsByType<ListPart>(parts, 'list');\n if (lists.length > 0) {\n // The framing sentence is the only top-level prose — list items live\n // under each list's meta.items, so this never picks up a chip.\n body = proseBodyMarkdown(parts) || undefined;\n\n for (const list of lists) {\n for (const item of list.meta?.items ?? []) {\n const itemParts = (item.meta?.parts ?? []) as Part[];\n\n const iconPart = itemParts.find((p) => p.type === 'icon') as\n | IconPart\n | undefined;\n const iconHint = (iconPart?.meta as { iconHint?: string })?.iconHint;\n\n const contentFragments: string[] = [];\n for (const p of itemParts) {\n if (p.type === 'link') {\n const link = p as LinkPart;\n contentFragments.push(linkToMarkdown(link));\n } else if (p.type === 'text' || p.type === 'richText') {\n contentFragments.push(p.content?.trim() || '');\n }\n }\n const content =\n contentFragments.filter(Boolean).join(' ') ||\n extractListItemText(item);\n if (content.trim()) {\n steps.push({\n content: content.trim(),\n ...(iconHint ? { iconHint } : {}),\n });\n }\n }\n }\n } else {\n body = inlineBodyMarkdown(parts);\n // body carries the chips as inline links — flag it so renderers show the\n // prose alone instead of repeating every chip as a row beneath it.\n chipsInlineInBody = Boolean(body);\n }\n\n // Fallback: inline links pattern\n if (steps.length === 0) {\n const links = getLinks(parts);\n for (const link of links) {\n const text = link.content?.trim();\n const href = link.meta?.href;\n if (text && href) {\n steps.push({ content: linkToMarkdown(link) });\n }\n }\n }\n\n return {\n title,\n ...(body ? { body } : {}),\n ...(chipsInlineInBody ? { chipsInlineInBody } : {}),\n ...(backgroundImage ? { backgroundImage } : {}),\n steps,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -21,6 +21,7 @@ exports.parseEntityUrl = parseEntityUrl;
21
21
  exports.parseKpiItem = parseKpiItem;
22
22
  exports.parseTableValue = parseTableValue;
23
23
  exports.partsToMarkdown = partsToMarkdown;
24
+ exports.proseBodyMarkdown = proseBodyMarkdown;
24
25
  exports.stripMarkdown = stripMarkdown;
25
26
  var _parts = require("../../parts/parts");
26
27
  var _entityLinkParser = require("../../utils/entityLinkParser");
@@ -63,26 +64,13 @@ function bodyText(parts) {
63
64
  }
64
65
 
65
66
  /**
66
- * Body copy as markdown, with inline links intact and paragraph breaks kept.
67
+ * Group a run of inline parts back into paragraphs.
67
68
  *
68
- * `bodyText` reads text parts only, so a paragraph carrying an inline link
69
- * `Try narrowing to [men](web5://ask/…) instead` arrives as `t + link + t`
70
- * and comes back as `"Try narrowing to\n\ninstead"`: label and url both
71
- * deleted, and the sentence broken in two at the gap. That is DL #134's list
72
- * bug in a different helper, and it is why an ask link in prose never reached
73
- * the page.
74
- *
75
- * `inlinePartsToMarkdown` already rebuilds a run of inline parts with links
76
- * intact, but it flattens to one line, which is wrong for multi-paragraph
77
- * copy. So group first: `astToParts` marks every part after the first of a
78
- * given AST node with `meta.continuesNode`, which is exactly "same paragraph,
79
- * keep going". Parts without it open a new paragraph.
80
- *
81
- * Kept separate from `bodyText` deliberately — the other seven callers hand
82
- * their result to consumers that do not render markdown, and would show a
83
- * visitor a raw `[label](url)`.
69
+ * `astToParts` marks every part after the first of a given AST node with
70
+ * `meta.continuesNode`, which is exactly "same paragraph, keep going". Parts
71
+ * without it open a new paragraph.
84
72
  */
85
- function bodyMarkdown(parts) {
73
+ function inlineParagraphs(parts) {
86
74
  const paragraphs = [];
87
75
  for (const part of parts) {
88
76
  var _part$meta;
@@ -95,9 +83,78 @@ function bodyMarkdown(parts) {
95
83
  paragraphs.push([part]);
96
84
  }
97
85
  }
86
+ return paragraphs;
87
+ }
88
+ function paragraphsToMarkdown(paragraphs) {
98
89
  return paragraphs.map(inlinePartsToMarkdown).map(paragraph => paragraph.trim()).filter(Boolean).join('\n\n');
99
90
  }
100
91
 
92
+ /**
93
+ * Body copy as markdown, with inline links intact and paragraph breaks kept.
94
+ *
95
+ * `bodyText` reads text parts only, so a paragraph carrying an inline link —
96
+ * `Try narrowing to [men](web5://ask/…) instead` — arrives as `t + link + t`
97
+ * and comes back as `"Try narrowing to\n\ninstead"`: label and url both
98
+ * deleted, and the sentence broken in two at the gap. That is DL #134's list
99
+ * bug in a different helper, and it is why an ask link in prose never reached
100
+ * the page. A bare `support@shop.example` is the same bug with no author in
101
+ * sight: GFM autolinks an email into a `link` node, so plain prose loses the
102
+ * address.
103
+ *
104
+ * `inlinePartsToMarkdown` already rebuilds a run of inline parts with links
105
+ * intact, but it flattens to one line, which is wrong for multi-paragraph
106
+ * copy — hence `inlineParagraphs` first.
107
+ *
108
+ * This keeps EVERY link, including one standing alone as its own block. That
109
+ * is right for `textBlock`, whose section consumes nothing but the copy. It is
110
+ * wrong for a section that already spends its links elsewhere — see
111
+ * {@link proseBodyMarkdown}.
112
+ *
113
+ * Kept separate from `bodyText` deliberately, but not for the reason this
114
+ * comment used to give. Of the four callers still on `bodyText`:
115
+ * `ErrorSectionDefinition` and `CtaBannerSectionDefinition` do hand their
116
+ * result to a consumer that renders it raw (`ErrorSection` prints the copy in
117
+ * a `<p>`, `CtaBannerSection` the same), so markdown there would show a
118
+ * visitor a literal `[label](url)`. `FeatureCardsSectionDefinition` does NOT —
119
+ * both `FeatureCardsSection` renderers in this repo put `card.description`
120
+ * through `MarkdownText`, so it loses inline links exactly like the sections
121
+ * fixed here; it is left alone only because its callers were not verified end
122
+ * to end. `FeatureSection9PlusDefinition` has no registered renderer at all.
123
+ */
124
+ function bodyMarkdown(parts) {
125
+ return paragraphsToMarkdown(inlineParagraphs(parts));
126
+ }
127
+
128
+ /**
129
+ * Body copy as markdown for a section that spends some of its links on another
130
+ * slot — hero buttons, a comparison CTA, entity items.
131
+ *
132
+ * Same fix as {@link bodyMarkdown} for the case that matters — a link INSIDE a
133
+ * sentence survives instead of being deleted with its label — but it drops the
134
+ * two shapes those sections have already claimed, so nothing renders twice:
135
+ *
136
+ * - A paragraph that is nothing but links. `[Get started](/) [Learn more](/x)`
137
+ * under a hero h1 is the button row `HeroSectionDefinition` turns into
138
+ * `buttons`; the trailing link after a comparison table is its `ctaText` /
139
+ * `ctaHref`. Whole-paragraph is the right test because a link that is part
140
+ * of a sentence always shares its paragraph with the text around it.
141
+ * - Everything from the first `web5://entity/…` link onward, which is item
142
+ * copy rather than section copy — `HeroEntitySectionDefinition` reads those
143
+ * links (and the text after each) into `items`. Same cut
144
+ * {@link entitySectionBodyText} makes, for the same reason.
145
+ *
146
+ * Nothing is lost by either rule: the dropped links are exactly the ones the
147
+ * section renders through some other prop.
148
+ */
149
+ function proseBodyMarkdown(parts) {
150
+ const firstEntityLinkIndex = parts.findIndex(p => {
151
+ var _meta;
152
+ return p.type === 'link' && isEntityHref((_meta = p.meta) == null ? void 0 : _meta.href);
153
+ });
154
+ const sectionParts = firstEntityLinkIndex >= 0 ? parts.slice(0, firstEntityLinkIndex) : parts;
155
+ return paragraphsToMarkdown(inlineParagraphs(sectionParts).filter(paragraph => !paragraph.every(part => part.type === 'link')));
156
+ }
157
+
101
158
  /**
102
159
  * Entity sections may be authored as one paragraph per entity:
103
160
  * `[Product](web5://entity/...) - why it matches`.
@@ -106,8 +163,8 @@ function bodyMarkdown(parts) {
106
163
  */
107
164
  function entitySectionBodyText(parts) {
108
165
  const firstEntityLinkIndex = parts.findIndex(p => {
109
- var _meta;
110
- return p.type === 'link' && isEntityHref((_meta = p.meta) == null ? void 0 : _meta.href);
166
+ var _meta2;
167
+ return p.type === 'link' && isEntityHref((_meta2 = p.meta) == null ? void 0 : _meta2.href);
111
168
  });
112
169
  const sectionParts = firstEntityLinkIndex >= 0 ? parts.slice(0, firstEntityLinkIndex) : parts;
113
170
  return sectionParts.filter(p => p.type === 'text' || p.type === 'richText').map(markdownOrText).filter(Boolean).join('\n\n');
@@ -299,8 +356,8 @@ function inlinePartsToMarkdown(parts) {
299
356
  return parts.reduce((text, part) => {
300
357
  var _part$meta2;
301
358
  if (part.type === 'link') {
302
- var _meta2, _part$content;
303
- const href = ((_meta2 = part.meta) == null ? void 0 : _meta2.href) ?? '';
359
+ var _meta3, _part$content;
360
+ const href = ((_meta3 = part.meta) == null ? void 0 : _meta3.href) ?? '';
304
361
  const label = ((_part$content = part.content) == null ? void 0 : _part$content.trim()) ?? '';
305
362
  return appendInlineToken(text, href ? `[${label}](${href})` : label);
306
363
  }
@@ -406,8 +463,8 @@ function extractEntityRefs(parts) {
406
463
  var _list$meta4, _item$meta4;
407
464
  const itemParts = ((_item$meta4 = item.meta) == null ? void 0 : _item$meta4.parts) ?? [];
408
465
  const entityLinks = itemParts.filter(p => {
409
- var _meta3;
410
- return p.type === 'link' && isEntityHref((_meta3 = p.meta) == null ? void 0 : _meta3.href);
466
+ var _meta4;
467
+ return p.type === 'link' && isEntityHref((_meta4 = p.meta) == null ? void 0 : _meta4.href);
411
468
  });
412
469
  for (const link of entityLinks) {
413
470
  const parsed = parseEntityUrl(link.meta.href);
@@ -466,18 +523,18 @@ function extractEntityRefs(parts) {
466
523
  if (linkIndex >= 0) {
467
524
  // Scan following parts for description, image, callout, and list items
468
525
  for (let j = linkIndex + 1; j < parts.length; j++) {
469
- var _meta4, _p$content3;
526
+ var _meta5, _p$content3;
470
527
  const p = parts[j];
471
528
  // Stop at next entity link
472
- if (p.type === 'link' && isEntityHref((_meta4 = p.meta) == null ? void 0 : _meta4.href)) {
529
+ if (p.type === 'link' && isEntityHref((_meta5 = p.meta) == null ? void 0 : _meta5.href)) {
473
530
  break;
474
531
  }
475
532
  if (p.type === 'text' && (_p$content3 = p.content) != null && _p$content3.trim()) {
476
533
  const text = markdownOrText(p);
477
534
  description = description ? `${description} ${text}` : text;
478
535
  } else if (p.type === 'image' && !markdownImageUrl) {
479
- var _meta5;
480
- markdownImageUrl = (_meta5 = p.meta) == null ? void 0 : _meta5.src;
536
+ var _meta6;
537
+ markdownImageUrl = (_meta6 = p.meta) == null ? void 0 : _meta6.src;
481
538
  } else if ((p.type === 'callout' || p.type === 'blockquote') && !callout) {
482
539
  var _p$meta4;
483
540
  const raw = ((_p$meta4 = p.meta) == null || (_p$meta4 = _p$meta4.markdown) == null ? void 0 : _p$meta4.trim()) || str(p);
@@ -489,8 +546,8 @@ function extractEntityRefs(parts) {
489
546
  };
490
547
  }
491
548
  } else if (p.type === 'list') {
492
- for (const item of ((_meta6 = p.meta) == null ? void 0 : _meta6.items) ?? []) {
493
- var _meta6;
549
+ for (const item of ((_meta7 = p.meta) == null ? void 0 : _meta7.items) ?? []) {
550
+ var _meta7;
494
551
  const text = extractListItemText(item);
495
552
  if (text) {
496
553
  features.push(text);