From 2b114e784dc3fcdc40bd589aa03b71f3c6bceac8 Mon Sep 17 00:00:00 2001 From: hawkgs Date: Wed, 26 Feb 2025 17:27:37 +0200 Subject: [PATCH] docs(docs-infra): API doc content rendering fixes (#60116) The PR introduces a few doc content rendering fixes: - Fix highlighted section heading styles (regression from #59965). - Convert JSDoc links within 'Usage Notes' sections to HTML and render them. - Add IDs to doc content headings. This, by itself, makes these headings available in the page ToC. PR Close #60116 --- .../api-gen/rendering/marked/renderer.ts | 35 ++++++++++++++----- .../api-gen/rendering/styling/css-classes.ts | 1 + .../rendering/transforms/code-transforms.ts | 7 ++-- .../rendering/transforms/jsdoc-transforms.ts | 28 +++++---------- adev/shared-docs/styles/_reference.scss | 8 +++-- adev/shared-docs/styles/_typography.scss | 2 ++ .../api-reference-details-page.component.ts | 2 +- .../core/src/application/application_ref.ts | 1 - packages/upgrade/static/src/upgrade_module.ts | 5 ++- 9 files changed, 52 insertions(+), 37 deletions(-) diff --git a/adev/shared-docs/pipeline/api-gen/rendering/marked/renderer.ts b/adev/shared-docs/pipeline/api-gen/rendering/marked/renderer.ts index 54da6d99b6d..b801c486de8 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/marked/renderer.ts +++ b/adev/shared-docs/pipeline/api-gen/rendering/marked/renderer.ts @@ -8,6 +8,7 @@ import {Renderer, Tokens} from 'marked'; import {codeToHtml} from '../shiki/shiki'; +import {SECTION_HEADING, SECTION_SUB_HEADING} from '../styling/css-classes'; /** * Custom renderer for marked that will be used to transform markdown files to HTML @@ -55,21 +56,39 @@ export const renderer: Partial = {
- ${this.tablerow({ - text: header.map((cell) => this.tablecell(cell)).join(''), - })} + ${this.tablerow({text: header.map((cell) => this.tablecell(cell)).join('')})} ${rows - .map((row) => - this.tablerow({ - text: row.map((cell) => this.tablecell(cell)).join(''), - }), - ) + .map((row) => this.tablerow({text: row.map((cell) => this.tablecell(cell)).join('')})) .join('')}
`; }, + + heading(this: Renderer, {text, depth, tokens}: Tokens.Heading) { + const id = text + .toLowerCase() + .replaceAll(' ', '-') + .replace(/[^a-z0-9-]/g, ''); + + // Since we have a code transformer `addApiLinksToHtml` which adds anchors + // to code blocks of known symbols, we add an additional `data-skip-anchor` + // attribute that prevents the transformation. This is needed since nested + // anchor tags are illegal and break the HTML. + const textRenderer = new Renderer(); + textRenderer.codespan = ({text}) => `${text}`; + const parsedText = this.parser.parseInline(tokens, textRenderer); + + // The template matches templates/section-heading.tsx + return ` + + + ${parsedText} + + + `; + }, }; diff --git a/adev/shared-docs/pipeline/api-gen/rendering/styling/css-classes.ts b/adev/shared-docs/pipeline/api-gen/rendering/styling/css-classes.ts index 5bb2a5942dd..760b0985d65 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/styling/css-classes.ts +++ b/adev/shared-docs/pipeline/api-gen/rendering/styling/css-classes.ts @@ -27,3 +27,4 @@ export const HEADER_ENTRY_LABEL = 'docs-api-item-label'; export const SECTION_CONTAINER = 'docs-reference-section'; export const SECTION_HEADING = 'docs-reference-section-heading'; +export const SECTION_SUB_HEADING = 'docs-reference-section-heading--sub'; diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.ts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.ts index 2a75eea225a..1a87f865f41 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.ts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.ts @@ -476,10 +476,11 @@ function appendPrefixAndSuffix(entry: DocEntry, codeTocData: CodeTableOfContents */ export function addApiLinksToHtml(htmlString: string): string { const result = htmlString.replace( - // This regex looks for span/code blocks not wrapped by an anchor block. + // This regex looks for span/code blocks not wrapped by an anchor block + // or the tag doesn't contain `data-skip-anchor` attribute. // Their content are then replaced with a link if the symbol is known - // The captured content ==> vvvvvvvv - /(?]*>)(<(?:(?:span)|(?:code))[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g, + // The captured content ==> vvvvvvvv + /(?]*>)(<(?:(?:span)|(?:code))(?!\sdata-skip-anchor)[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g, (type: string, span1: string, potentialSymbolName: string, span2: string) => { let [symbol, subSymbol] = potentialSymbolName.split(/(?:#|\.)/) as [string, string?]; diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.ts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.ts index ea684031c79..529e0df952e 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.ts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.ts @@ -58,16 +58,10 @@ export function addHtmlDescription( const description = !!entry.description ? entry.description : jsDocDescription; const shortTextMatch = description.match(firstParagraphRule); - const htmlDescription = getHtmlForJsDocText(description, entry).trim(); - const shortHtmlDescription = getHtmlForJsDocText( - shortTextMatch ? shortTextMatch[0] : '', - entry, - ).trim(); - return { - ...entry, - htmlDescription, - shortHtmlDescription, - }; + const htmlDescription = getHtmlForJsDocText(description).trim(); + const shortHtmlDescription = getHtmlForJsDocText(shortTextMatch ? shortTextMatch[0] : '').trim(); + + return {...entry, htmlDescription, shortHtmlDescription}; } /** @@ -81,7 +75,7 @@ export function addHtmlJsDocTagComments( ...entry, jsdocTags: entry.jsdocTags.map((tag) => ({ ...tag, - htmlComment: getHtmlForJsDocText(tag.comment, entry), + htmlComment: getHtmlForJsDocText(tag.comment), })), }; } @@ -100,20 +94,16 @@ export function addHtmlUsageNotes(entry: T): T & HasHtml const usageNotesTag = entry.jsdocTags.find( ({name}) => name === JS_DOC_USAGE_NOTES_TAG || name === JS_DOC_REMARKS_TAG, ); - const htmlUsageNotes = usageNotesTag - ? (marked.parse(wrapExampleHtmlElementsWithCode(usageNotesTag.comment)) as string) - : ''; - - const transformedHtml = addApiLinksToHtml(htmlUsageNotes); + const htmlUsageNotes = usageNotesTag ? getHtmlForJsDocText(usageNotesTag.comment) : ''; return { ...entry, - htmlUsageNotes: transformedHtml, + htmlUsageNotes, }; } /** Given a markdown JsDoc text, gets the rendered HTML. */ -function getHtmlForJsDocText(text: string, entry: T): string { +function getHtmlForJsDocText(text: string): string { const parsed = marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text))) as string; return addApiLinksToHtml(parsed); } @@ -126,7 +116,7 @@ export function setEntryFlags( ...entry, isDeprecated: isDeprecatedEntry(entry), deprecationMessage: deprecationMessage - ? getHtmlForJsDocText(deprecationMessage, entry) + ? getHtmlForJsDocText(deprecationMessage) : deprecationMessage, isDeveloperPreview: isDeveloperPreview(entry), isExperimental: isExperimental(entry), diff --git a/adev/shared-docs/styles/_reference.scss b/adev/shared-docs/styles/_reference.scss index c1e9d4f3452..691254e139e 100644 --- a/adev/shared-docs/styles/_reference.scss +++ b/adev/shared-docs/styles/_reference.scss @@ -66,7 +66,11 @@ .docs-reference-section-heading { padding-block-start: 3rem; - a { + &--sub { + padding-block-start: 1rem; + } + + & > a { @include anchor.docs-anchor(); color: inherit; } @@ -98,7 +102,7 @@ z-index: 0; } - &.highlighted { + &.docs-highlighted-card { box-shadow: 10px 4px 40px 0 rgba(0, 0, 0, 0.01); &::before { diff --git a/adev/shared-docs/styles/_typography.scss b/adev/shared-docs/styles/_typography.scss index 99a7c78ef2d..1c4e2ce0e7b 100644 --- a/adev/shared-docs/styles/_typography.scss +++ b/adev/shared-docs/styles/_typography.scss @@ -79,6 +79,7 @@ } p > a, + p > em > a, td > a, div > a:not(.docs-card), code > a, @@ -93,6 +94,7 @@ } p > a, + p > em > a, .docs-list a, .docs-card a { margin-block: 0; diff --git a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts index 77201a355f4..0ede953f360 100644 --- a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts +++ b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts @@ -14,7 +14,7 @@ import {DOCUMENT} from '@angular/common'; import {ReferenceScrollHandler} from '../services/reference-scroll-handler.service'; import {API_SECTION_CLASS_NAME} from '../constants/api-reference-prerender.constants'; -const HIGHLIGHTED_CARD_CLASS = 'highlighted'; +const HIGHLIGHTED_CARD_CLASS = 'docs-highlighted-card'; @Component({ selector: 'adev-reference-page', diff --git a/packages/core/src/application/application_ref.ts b/packages/core/src/application/application_ref.ts index df1cc51e972..c4fcb4c029f 100644 --- a/packages/core/src/application/application_ref.ts +++ b/packages/core/src/application/application_ref.ts @@ -188,7 +188,6 @@ export function optionsReducer(dst: T, objs: T | T[]): T { * A reference to an Angular application running on a page. * * @usageNotes - * {@a is-stable-examples} * ### isStable examples and caveats * * Note two important points about `isStable`, demonstrated in the examples below: diff --git a/packages/upgrade/static/src/upgrade_module.ts b/packages/upgrade/static/src/upgrade_module.ts index 54396733c68..6befffc1a72 100644 --- a/packages/upgrade/static/src/upgrade_module.ts +++ b/packages/upgrade/static/src/upgrade_module.ts @@ -36,7 +36,7 @@ import {NgAdapterInjector} from './util'; * {@link UpgradeModule#upgrading-an-angular-1-service Upgrading an AngularJS service} below. * 4. Creation of an AngularJS service that wraps and exposes an Angular injectable * so that it can be injected into an AngularJS context. See `downgradeInjectable`. - * 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks + * 5. Bootstrapping of a hybrid Angular application which contains both of the frameworks * coexisting in a single application. * * @usageNotes @@ -102,7 +102,7 @@ import {NgAdapterInjector} from './util'; * * ### Examples * - * Import the `UpgradeModule` into your top level {@link NgModule Angular `NgModule`}. + * Import the `UpgradeModule` into your top level Angular {@link NgModule NgModule}. * * {@example upgrade/static/ts/full/module.ts region='ng2-module'} * @@ -116,7 +116,6 @@ import {NgAdapterInjector} from './util'; * * {@example upgrade/static/ts/full/module.ts region='bootstrap-ng2'} * - * {@a upgrading-an-angular-1-service} * ### Upgrading an AngularJS service * * There is no specific API for upgrading an AngularJS service. Instead you should just follow the