mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(docs-infra): parse docs-callout attributes correctly
Three ways a callout could be misparsed: - A title quoted with `'` or a backtick was dropped, leaving an empty heading. Two callouts lose their title on angular.dev today, on guide/forms/template-driven-forms and guide/i18n/prepare. The first has to use single quotes because its title contains `"pristine"`. - A title containing `>` was dropped, because the attribute capture stopped at the first `>` even inside a quoted value. - The severity was matched anywhere in the tag, so a title such as "Why this is important" silently rendered an important callout. Scan attributes with quoting in mind, accept all three quote characters as #69268 did for docs-code-block, and match the severity flags against the tag with attribute values removed. The i18n callout also spelled the attribute `header`, which the extension has never read.
This commit is contained in:
@@ -27,11 +27,13 @@ interface DocsCalloutToken extends Tokens.Generic {
|
||||
|
||||
// Capture group 1: all attributes on the opening tag
|
||||
// Capture group 2: all content between the open and close tags
|
||||
const calloutRule = /^<docs-callout([^>]*)>((?:.(?!\/docs-callout))*)<\/docs-callout>/s;
|
||||
const calloutRule =
|
||||
/^<docs-callout((?:[^>"'`]|"[^"]*"|'[^']*'|`[^`]*`)*)>((?:.(?!\/docs-callout))*)<\/docs-callout>/s;
|
||||
|
||||
const titleRule = /title="([^"]*)"/;
|
||||
const isImportantRule = /important/;
|
||||
const isCriticalRule = /critical/;
|
||||
const titleRule = /title=(['"`])(.*?)\1/; // The 2nd capture matters here
|
||||
const attributeValueRule = /=(['"`])(?:.*?)\1/gs;
|
||||
const isImportantRule = /\bimportant\b/;
|
||||
const isCriticalRule = /\bcritical\b/;
|
||||
|
||||
export const docsCalloutExtension = {
|
||||
name: 'docs-callout',
|
||||
@@ -46,9 +48,12 @@ export const docsCalloutExtension = {
|
||||
const attr = match[1].trim();
|
||||
const title = titleRule.exec(attr);
|
||||
|
||||
// Severity is a bare word on the tag, so it must not be matched inside an attribute value.
|
||||
const flags = attr.replace(attributeValueRule, '');
|
||||
|
||||
let severityLevel = CalloutSeverityLevel.HELPFUL;
|
||||
if (isImportantRule.exec(attr)) severityLevel = CalloutSeverityLevel.IMPORTANT;
|
||||
if (isCriticalRule.exec(attr)) severityLevel = CalloutSeverityLevel.CRITICAL;
|
||||
if (isImportantRule.exec(flags)) severityLevel = CalloutSeverityLevel.IMPORTANT;
|
||||
if (isCriticalRule.exec(flags)) severityLevel = CalloutSeverityLevel.CRITICAL;
|
||||
|
||||
const body = match[2].trim();
|
||||
|
||||
@@ -56,7 +61,7 @@ export const docsCalloutExtension = {
|
||||
type: 'docs-callout',
|
||||
raw: match[0],
|
||||
severityLevel: severityLevel,
|
||||
title: title ? title[1] : '',
|
||||
title: title ? title[2] : '',
|
||||
titleTokens: [],
|
||||
body: body ?? '',
|
||||
bodyTokens: [],
|
||||
|
||||
@@ -25,4 +25,56 @@ describe('markdown to html', () => {
|
||||
markdownDocument.querySelector('#default-marker')!.parentElement?.parentElement;
|
||||
calloutDiv?.classList.contains('docs-callout-helpful');
|
||||
});
|
||||
|
||||
const parse = (markdown: string) => JSDOM.fragment(parseMarkdown(markdown, rendererContext));
|
||||
|
||||
it('reads a title quoted with any of the supported quote characters', () => {
|
||||
for (const quote of ['"', "'", '`']) {
|
||||
expect(
|
||||
parse(`<docs-callout title=${quote}Quoted${quote}>Body</docs-callout>`).querySelector('h3')
|
||||
?.textContent,
|
||||
)
|
||||
.withContext(`quoted with ${quote}`)
|
||||
.toBe('Quoted');
|
||||
}
|
||||
});
|
||||
|
||||
it('reads a title that contains the other quote characters', () => {
|
||||
expect(
|
||||
parse(
|
||||
`<docs-callout title='Illustrating the "pristine" state'>Body</docs-callout>`,
|
||||
).querySelector('h3')?.textContent,
|
||||
).toBe('Illustrating the "pristine" state');
|
||||
expect(
|
||||
parse(`<docs-callout title="It's here">Body</docs-callout>`).querySelector('h3')?.textContent,
|
||||
).toBe("It's here");
|
||||
});
|
||||
|
||||
it('reads a title that contains a closing angle bracket', () => {
|
||||
expect(
|
||||
parse('<docs-callout title="Migrate a > b">Body</docs-callout>').querySelector('h3')
|
||||
?.textContent,
|
||||
).toBe('Migrate a > b');
|
||||
});
|
||||
|
||||
it('takes the severity from the tag, not from the title text', () => {
|
||||
expect(
|
||||
parse('<docs-callout title="Why this is important">Body</docs-callout>').querySelector(
|
||||
'.docs-callout',
|
||||
)?.className,
|
||||
).toBe('docs-callout docs-callout-helpful');
|
||||
expect(
|
||||
parse('<docs-callout title="A critical note">Body</docs-callout>').querySelector(
|
||||
'.docs-callout',
|
||||
)?.className,
|
||||
).toBe('docs-callout docs-callout-helpful');
|
||||
expect(
|
||||
parse('<docs-callout important title="X">Body</docs-callout>').querySelector('.docs-callout')
|
||||
?.className,
|
||||
).toBe('docs-callout docs-callout-important');
|
||||
expect(
|
||||
parse('<docs-callout critical title="X">Body</docs-callout>').querySelector('.docs-callout')
|
||||
?.className,
|
||||
).toBe('docs-callout docs-callout-critical');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -265,7 +265,7 @@ other { default_quantity }
|
||||
|
||||
HELPFUL: For more information about pluralization categories, see [Choosing plural category names][UnicodeCldrIndexCldrSpecPluralRulesTocChoosingPluralCategoryNames] in the [CLDR - Unicode Common Locale Data Repository][UnicodeCldrMain].
|
||||
|
||||
<docs-callout header='Background: Locales may not support some pluralization categories'>
|
||||
<docs-callout title="Background: Locales may not support some pluralization categories">
|
||||
|
||||
Many locales don't support some of the pluralization categories.
|
||||
The default locale \(`en-US`\) uses a very simple `plural()` function that doesn't support the `few` pluralization category.
|
||||
|
||||
Reference in New Issue
Block a user