@mui/internal-docs-infra 0.4.1-canary.9 → 0.6.1-canary.0
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.
- package/cli/index.mjs +1 -1
- package/createSitemap/createSitemap.mjs +1 -1
- package/createSitemap/types.d.mts +36 -1
- package/package.json +14 -4
- package/pipeline/enhanceCodeEmphasis/calculateFrameIndent.d.mts +12 -0
- package/pipeline/enhanceCodeEmphasis/calculateFrameIndent.mjs +62 -0
- package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.d.mts +27 -6
- package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.mjs +210 -65
- package/pipeline/parseSource/addLineGutters.mjs +4 -19
- package/pipeline/parseSource/calculateFrameRanges.d.mts +59 -0
- package/pipeline/parseSource/calculateFrameRanges.mjs +209 -0
- package/pipeline/parseSource/createFrame.d.mts +9 -0
- package/pipeline/parseSource/createFrame.mjs +29 -0
- package/pipeline/parseSource/restructureFrames.d.mts +15 -0
- package/pipeline/parseSource/restructureFrames.mjs +100 -0
- package/pipeline/syncPageIndex/mergeMetadataMarkdown.d.mts +2 -2
- package/pipeline/syncPageIndex/mergeMetadataMarkdown.mjs +16 -8
- package/pipeline/syncPageIndex/metadataToMarkdown.d.mts +17 -0
- package/pipeline/syncPageIndex/metadataToMarkdown.mjs +314 -87
- package/pipeline/syncPageIndex/syncPageIndex.mjs +18 -7
- package/pipeline/transformMarkdownMetadata/transformMarkdownMetadata.mjs +8 -3
- package/pipeline/transformMarkdownMetadata/types.d.mts +8 -0
- package/useCode/Pre.mjs +2 -0
- package/useSearch/types.d.mts +13 -0
- package/useSearch/useSearch.mjs +7 -2
- package/withDocsInfra/withDeploymentConfig.mjs +3 -0
package/cli/index.mjs
CHANGED
|
@@ -7,4 +7,4 @@ function getVersion() {
|
|
|
7
7
|
}
|
|
8
8
|
yargs().scriptName('docs-infra').usage('$0 <command> [args]').command(runValidate).demandCommand(1, 'You need at least one command before moving on').strict().help()
|
|
9
9
|
// MUI_VERSION is set through the code-infra build command.
|
|
10
|
-
.version("0.
|
|
10
|
+
.version("0.6.0" || getVersion()).parse(hideBin(process.argv));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
1
2
|
/**
|
|
2
3
|
* Section data structure from sitemap
|
|
3
4
|
*/
|
|
@@ -35,6 +36,8 @@ export interface SitemapPage {
|
|
|
35
36
|
exports?: Record<string, SitemapExport>;
|
|
36
37
|
tags?: string[];
|
|
37
38
|
skipDetailSection?: boolean;
|
|
39
|
+
audience?: Audience;
|
|
40
|
+
index?: boolean;
|
|
38
41
|
image?: {
|
|
39
42
|
url: string;
|
|
40
43
|
alt?: string;
|
|
@@ -59,4 +62,36 @@ export type OramaSchemaType = 'string' | 'number' | 'boolean' | 'string[]' | 'nu
|
|
|
59
62
|
export interface Sitemap {
|
|
60
63
|
schema: Record<string, OramaSchemaType>;
|
|
61
64
|
data: Record<string, SitemapSectionData>;
|
|
62
|
-
}
|
|
65
|
+
}
|
|
66
|
+
export type Audience = 'private' | 'introductory' | 'intermediate' | 'advanced' | 'business';
|
|
67
|
+
/**
|
|
68
|
+
* Page metadata type extending Next.js `Metadata`.
|
|
69
|
+
*
|
|
70
|
+
* Adds the `audience` field under `other` using the WHATWG MetaExtensions `audience` meta name.
|
|
71
|
+
* All standard Next.js metadata fields (title, description, openGraph, etc.) remain available.
|
|
72
|
+
*
|
|
73
|
+
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadata-fields
|
|
74
|
+
*/
|
|
75
|
+
export type NextMetadata = Metadata & {
|
|
76
|
+
other?: {
|
|
77
|
+
/**
|
|
78
|
+
* Categorize the principal intended audience for the page.
|
|
79
|
+
* Uses the WHATWG MetaExtensions `audience` meta name.
|
|
80
|
+
*
|
|
81
|
+
* When omitted, the page is public and intended for all audiences.
|
|
82
|
+
*
|
|
83
|
+
* - `'private'`: Internal page, not intended for public consumption.
|
|
84
|
+
* Should be paired with `robots: { index: false }` to exclude from public indexing.
|
|
85
|
+
* - `'introductory'`: Content aimed at beginners.
|
|
86
|
+
* - `'intermediate'`: Content aimed at intermediate users.
|
|
87
|
+
* - `'advanced'`: Content aimed at advanced users.
|
|
88
|
+
* - `'business'`: Content aimed at prospective customers and decision-makers
|
|
89
|
+
* (e.g. marketing pages, pricing, product overviews).
|
|
90
|
+
*
|
|
91
|
+
* @see https://wiki.whatwg.org/wiki/MetaExtensions
|
|
92
|
+
* @see https://brittlebit.org/specifications/html-meta-audience/specification-for-html-meta-element-with-name-value-audience.html
|
|
93
|
+
*/
|
|
94
|
+
audience?: Audience;
|
|
95
|
+
[key: string]: unknown;
|
|
96
|
+
};
|
|
97
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-docs-infra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1-canary.0",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "MUI Infra - internal documentation creation tools.",
|
|
6
6
|
"keywords": [
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"homepage": "https://github.com/mui/mui-public/tree/master/packages/docs-infra",
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@babel/runtime": "^7.28.6",
|
|
25
|
-
"@babel/standalone": "^7.
|
|
25
|
+
"@babel/standalone": "^7.29.1",
|
|
26
26
|
"@orama/orama": "^3.1.18",
|
|
27
27
|
"@orama/plugin-qps": "^3.1.18",
|
|
28
28
|
"@orama/stemmers": "^3.1.18",
|
|
29
29
|
"@orama/stopwords": "^3.1.18",
|
|
30
|
-
"@wooorm/starry-night": "^3.
|
|
30
|
+
"@wooorm/starry-night": "^3.9.0",
|
|
31
31
|
"chalk": "^5.6.2",
|
|
32
32
|
"clipboard-copy": "^4.0.1",
|
|
33
33
|
"fflate": "^0.8.2",
|
|
@@ -452,6 +452,16 @@
|
|
|
452
452
|
"default": "./pipeline/loadServerSource/index.mjs"
|
|
453
453
|
}
|
|
454
454
|
},
|
|
455
|
+
"./pipeline/enhanceCodeEmphasis": {
|
|
456
|
+
"import": {
|
|
457
|
+
"types": "./pipeline/enhanceCodeEmphasis/index.d.mts",
|
|
458
|
+
"default": "./pipeline/enhanceCodeEmphasis/index.mjs"
|
|
459
|
+
},
|
|
460
|
+
"default": {
|
|
461
|
+
"types": "./pipeline/enhanceCodeEmphasis/index.d.mts",
|
|
462
|
+
"default": "./pipeline/enhanceCodeEmphasis/index.mjs"
|
|
463
|
+
}
|
|
464
|
+
},
|
|
455
465
|
"./pipeline/parseSource": {
|
|
456
466
|
"import": {
|
|
457
467
|
"types": "./pipeline/parseSource/index.d.mts",
|
|
@@ -506,5 +516,5 @@
|
|
|
506
516
|
"bin": {
|
|
507
517
|
"docs-infra": "./cli/index.mjs"
|
|
508
518
|
},
|
|
509
|
-
"gitSha": "
|
|
519
|
+
"gitSha": "cad146f1682e0cb63baeeab28454423d77ef7c89"
|
|
510
520
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Element } from 'hast';
|
|
2
|
+
/**
|
|
3
|
+
* Calculates the shared indent level for a set of line elements.
|
|
4
|
+
*
|
|
5
|
+
* Finds the minimum leading whitespace across all non-empty lines,
|
|
6
|
+
* then divides by the indent size (2 spaces) and floors to get
|
|
7
|
+
* the indent level.
|
|
8
|
+
*
|
|
9
|
+
* @param lineElements - Array of HAST line elements to analyze
|
|
10
|
+
* @returns The shared indent level (e.g., 2 for 4 leading spaces with 2-space indent)
|
|
11
|
+
*/
|
|
12
|
+
export declare function calculateFrameIndent(lineElements: Element[]): number;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const INDENT_SIZE = 2;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Counts leading spaces in an element by walking the HAST tree.
|
|
5
|
+
*
|
|
6
|
+
* Returns the number of leading space characters before the first
|
|
7
|
+
* non-space character, or -1 if the line is empty/whitespace-only.
|
|
8
|
+
* Only counts space characters. Tab indentation is not supported
|
|
9
|
+
* since the input is HAST output from starry-night which uses spaces.
|
|
10
|
+
*/
|
|
11
|
+
function countLeadingSpaces(element) {
|
|
12
|
+
let spaces = 0;
|
|
13
|
+
function walk(node) {
|
|
14
|
+
for (const child of node.children) {
|
|
15
|
+
if (child.type === 'text') {
|
|
16
|
+
for (const char of child.value) {
|
|
17
|
+
if (char === ' ') {
|
|
18
|
+
spaces += 1;
|
|
19
|
+
} else {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} else if (child.type === 'element') {
|
|
24
|
+
if (walk(child)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const foundNonSpace = walk(element);
|
|
32
|
+
return foundNonSpace ? spaces : -1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Calculates the shared indent level for a set of line elements.
|
|
37
|
+
*
|
|
38
|
+
* Finds the minimum leading whitespace across all non-empty lines,
|
|
39
|
+
* then divides by the indent size (2 spaces) and floors to get
|
|
40
|
+
* the indent level.
|
|
41
|
+
*
|
|
42
|
+
* @param lineElements - Array of HAST line elements to analyze
|
|
43
|
+
* @returns The shared indent level (e.g., 2 for 4 leading spaces with 2-space indent)
|
|
44
|
+
*/
|
|
45
|
+
export function calculateFrameIndent(lineElements) {
|
|
46
|
+
let minLeadingSpaces = Infinity;
|
|
47
|
+
for (const element of lineElements) {
|
|
48
|
+
const leadingSpaces = countLeadingSpaces(element);
|
|
49
|
+
|
|
50
|
+
// Skip empty/whitespace-only lines
|
|
51
|
+
if (leadingSpaces === -1) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (leadingSpaces < minLeadingSpaces) {
|
|
55
|
+
minLeadingSpaces = leadingSpaces;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (minLeadingSpaces === Infinity) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
return Math.floor(minLeadingSpaces / INDENT_SIZE);
|
|
62
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { SourceEnhancer } from "../../CodeHighlighter/types.mjs";
|
|
2
|
+
import type { EnhanceCodeEmphasisOptions } from "../parseSource/calculateFrameRanges.mjs";
|
|
3
|
+
export type { EmphasisMeta, EnhanceCodeEmphasisOptions, FrameRange } from "../parseSource/calculateFrameRanges.mjs";
|
|
2
4
|
/**
|
|
3
5
|
* The prefix used to identify emphasis comments in source code.
|
|
4
6
|
* Comments starting with this prefix will be processed for emphasis.
|
|
5
7
|
*/
|
|
6
8
|
export declare const EMPHASIS_COMMENT_PREFIX = "@highlight";
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
10
|
+
* Creates a source enhancer that adds emphasis to code lines based on `@highlight` comments
|
|
11
|
+
* and restructures frames around highlighted regions.
|
|
9
12
|
*
|
|
10
|
-
* Supports
|
|
13
|
+
* Supports five patterns:
|
|
11
14
|
*
|
|
12
15
|
* 1. **Single line emphasis** - emphasizes the line containing the comment:
|
|
13
16
|
* ```jsx
|
|
@@ -37,12 +40,30 @@ export declare const EMPHASIS_COMMENT_PREFIX = "@highlight";
|
|
|
37
40
|
* <h1>Heading 1</h1> {/* @highlight-text "Heading 1" *\/}
|
|
38
41
|
* ```
|
|
39
42
|
*
|
|
43
|
+
* 5. **Focus override** - mark a region for padding focus:
|
|
44
|
+
* ```jsx
|
|
45
|
+
* <h1>Heading 1</h1> {/* @highlight @focus *\/}
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
40
48
|
* Emphasized lines receive a `data-hl` attribute on their `<span class="line">` element.
|
|
49
|
+
* When highlights exist, frames are restructured with `data-frame-type` attributes
|
|
50
|
+
* (`highlighted`, `padding-top`, `padding-bottom`, or omitted for normal).
|
|
51
|
+
* Highlighted frames also receive `data-frame-indent` with the shared indent level.
|
|
52
|
+
*
|
|
53
|
+
* @param options - Optional configuration for padding frames
|
|
54
|
+
* @returns A `SourceEnhancer` function
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { createEnhanceCodeEmphasis } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
|
|
41
59
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
* const enhancers = [createEnhanceCodeEmphasis({ paddingFrameMaxSize: 5, focusFramesMaxSize: 8 })];
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function createEnhanceCodeEmphasis(options?: EnhanceCodeEmphasisOptions): SourceEnhancer;
|
|
64
|
+
/**
|
|
65
|
+
* Default source enhancer that adds emphasis to code lines based on `@highlight` comments.
|
|
66
|
+
* Uses no padding frames by default. Use `createEnhanceCodeEmphasis` for configurable padding.
|
|
46
67
|
*
|
|
47
68
|
* @example
|
|
48
69
|
* ```ts
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { calculateFrameRanges } from "../parseSource/calculateFrameRanges.mjs";
|
|
2
|
+
import { calculateFrameIndent } from "./calculateFrameIndent.mjs";
|
|
3
|
+
import { restructureFrames } from "../parseSource/restructureFrames.mjs";
|
|
1
4
|
/**
|
|
2
5
|
* The prefix used to identify emphasis comments in source code.
|
|
3
6
|
* Comments starting with this prefix will be processed for emphasis.
|
|
@@ -8,19 +11,15 @@ export const EMPHASIS_COMMENT_PREFIX = '@highlight';
|
|
|
8
11
|
* Parsed emphasis directive from a comment.
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
|
-
/**
|
|
12
|
-
* Metadata for an emphasized line.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
14
|
/**
|
|
16
15
|
* Extracts a quoted string from content.
|
|
17
16
|
* Supports both double quotes ("...") and single quotes ('...').
|
|
17
|
+
* Escaped quotes within the string are not supported.
|
|
18
18
|
*
|
|
19
19
|
* @param content - The content to extract the quoted string from
|
|
20
20
|
* @returns The extracted string (without quotes) or undefined if no quoted string found
|
|
21
21
|
*/
|
|
22
22
|
function extractQuotedString(content) {
|
|
23
|
-
// Match either double-quoted or single-quoted string
|
|
24
23
|
const match = content.match(/^["'](.*)["']$/);
|
|
25
24
|
if (match) {
|
|
26
25
|
return match[1];
|
|
@@ -30,14 +29,40 @@ function extractQuotedString(content) {
|
|
|
30
29
|
return anyMatch?.[1];
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Extracts and removes the `@focus` keyword from content.
|
|
34
|
+
*
|
|
35
|
+
* @param content - The content to check for `@focus`
|
|
36
|
+
* @returns An object with `focus` boolean and the `remaining` content with `@focus` removed
|
|
37
|
+
*/
|
|
38
|
+
function extractFocus(content) {
|
|
39
|
+
// Match @focus only as a standalone token (not inside quotes)
|
|
40
|
+
const match = content.match(/(^|\s)@focus(\s|$)/);
|
|
41
|
+
if (!match) {
|
|
42
|
+
return {
|
|
43
|
+
focus: false,
|
|
44
|
+
remaining: content
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const start = match.index + match[1].length;
|
|
48
|
+
const remaining = (content.slice(0, start) + content.slice(start + '@focus'.length)).trim();
|
|
49
|
+
return {
|
|
50
|
+
focus: true,
|
|
51
|
+
remaining
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
/**
|
|
34
56
|
* Parses emphasis comments and returns structured directives.
|
|
35
57
|
*
|
|
36
58
|
* Supported formats:
|
|
37
59
|
* - Single line: `@highlight` or `@highlight "description"`
|
|
60
|
+
* - Single line focused: `@highlight @focus` or `@highlight @focus "description"`
|
|
38
61
|
* - Multiline start: `@highlight-start` or `@highlight-start "description"`
|
|
62
|
+
* - Multiline start focused: `@highlight-start @focus` or `@highlight-start @focus "description"`
|
|
39
63
|
* - Multiline end: `@highlight-end`
|
|
40
64
|
* - Text highlight: `@highlight-text "text to highlight"`
|
|
65
|
+
* - Text highlight focused: `@highlight-text @focus "text to highlight"`
|
|
41
66
|
*
|
|
42
67
|
* @param comments - Source comments keyed by line number
|
|
43
68
|
* @returns Array of parsed emphasis directives
|
|
@@ -63,31 +88,46 @@ function parseEmphasisDirectives(comments) {
|
|
|
63
88
|
} else if (content.startsWith('-start')) {
|
|
64
89
|
// Start of multiline emphasis: @highlight-start or @highlight-start "description"
|
|
65
90
|
const afterStart = content.slice('-start'.length).trim();
|
|
66
|
-
const
|
|
91
|
+
const {
|
|
92
|
+
focus,
|
|
93
|
+
remaining: remainingStart
|
|
94
|
+
} = extractFocus(afterStart);
|
|
95
|
+
const description = extractQuotedString(remainingStart);
|
|
67
96
|
directives.push({
|
|
68
97
|
line,
|
|
69
98
|
type: 'start',
|
|
70
|
-
description
|
|
99
|
+
description,
|
|
100
|
+
focus
|
|
71
101
|
});
|
|
72
102
|
} else if (content.startsWith('-text')) {
|
|
73
103
|
// Text highlight: @highlight-text "text to highlight"
|
|
74
104
|
const afterText = content.slice('-text'.length).trim();
|
|
75
|
-
const
|
|
105
|
+
const {
|
|
106
|
+
focus,
|
|
107
|
+
remaining: remainingText
|
|
108
|
+
} = extractFocus(afterText);
|
|
109
|
+
const highlightText = extractQuotedString(remainingText);
|
|
76
110
|
if (highlightText) {
|
|
77
111
|
directives.push({
|
|
78
112
|
line,
|
|
79
113
|
type: 'text',
|
|
80
|
-
highlightText
|
|
114
|
+
highlightText,
|
|
115
|
+
focus
|
|
81
116
|
});
|
|
82
117
|
}
|
|
83
118
|
} else {
|
|
84
119
|
// Single line emphasis: @highlight or @highlight "description"
|
|
85
120
|
const afterHighlight = content.trim();
|
|
86
|
-
const
|
|
121
|
+
const {
|
|
122
|
+
focus,
|
|
123
|
+
remaining: remainingSingle
|
|
124
|
+
} = extractFocus(afterHighlight);
|
|
125
|
+
const description = extractQuotedString(remainingSingle) || undefined;
|
|
87
126
|
directives.push({
|
|
88
127
|
line,
|
|
89
128
|
type: 'single',
|
|
90
|
-
description
|
|
129
|
+
description,
|
|
130
|
+
focus
|
|
91
131
|
});
|
|
92
132
|
}
|
|
93
133
|
}
|
|
@@ -231,13 +271,15 @@ function calculateEmphasizedLines(directives, lineElements) {
|
|
|
231
271
|
emphasizedLines.set(directive.line, {
|
|
232
272
|
description,
|
|
233
273
|
strong,
|
|
234
|
-
position: 'single'
|
|
274
|
+
position: 'single',
|
|
275
|
+
focus: directive.focus
|
|
235
276
|
});
|
|
236
277
|
} else if (directive.type === 'text') {
|
|
237
278
|
// Text highlight - emphasize specific text within the line
|
|
238
279
|
emphasizedLines.set(directive.line, {
|
|
239
280
|
position: 'single',
|
|
240
|
-
highlightText: directive.highlightText
|
|
281
|
+
highlightText: directive.highlightText,
|
|
282
|
+
focus: directive.focus
|
|
241
283
|
});
|
|
242
284
|
}
|
|
243
285
|
}
|
|
@@ -286,11 +328,14 @@ function calculateEmphasizedLines(directives, lineElements) {
|
|
|
286
328
|
strong: true,
|
|
287
329
|
// Nested = always strong
|
|
288
330
|
description: existing.description ?? (line === startLine ? description : undefined),
|
|
289
|
-
position: existing.position ?? position
|
|
331
|
+
position: existing.position ?? position,
|
|
332
|
+
// Inner range position takes precedence
|
|
333
|
+
focus: existing.focus || startDirective.focus
|
|
290
334
|
} : {
|
|
291
335
|
strong,
|
|
292
336
|
description: line === startLine ? description : undefined,
|
|
293
|
-
position
|
|
337
|
+
position,
|
|
338
|
+
focus: startDirective.focus
|
|
294
339
|
};
|
|
295
340
|
emphasizedLines.set(line, meta);
|
|
296
341
|
}
|
|
@@ -362,52 +407,113 @@ function wrapTextInHighlightSpan(children, textToHighlight) {
|
|
|
362
407
|
}
|
|
363
408
|
|
|
364
409
|
/**
|
|
365
|
-
*
|
|
410
|
+
* Single-pass traversal that applies emphasis attributes to line elements
|
|
411
|
+
* AND collects leading whitespace for indent calculation on highlighted lines.
|
|
412
|
+
*
|
|
413
|
+
* This merges what would otherwise be two separate traversals into one.
|
|
366
414
|
*
|
|
367
415
|
* @param node - The node to process
|
|
368
416
|
* @param emphasizedLines - Map of line numbers to their emphasis metadata
|
|
417
|
+
* @returns Array of line elements that are highlighted, grouped by region
|
|
369
418
|
*/
|
|
370
|
-
function
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const child = node.children[i];
|
|
376
|
-
if (child.type !== 'element') {
|
|
377
|
-
continue;
|
|
419
|
+
function applyEmphasisAndCollectHighlightedElements(node, emphasizedLines) {
|
|
420
|
+
const highlightedLineElements = [];
|
|
421
|
+
function traverse(n) {
|
|
422
|
+
if (!('children' in n) || !n.children) {
|
|
423
|
+
return;
|
|
378
424
|
}
|
|
425
|
+
for (let i = 0; i < n.children.length; i += 1) {
|
|
426
|
+
const child = n.children[i];
|
|
427
|
+
if (child.type !== 'element') {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
379
430
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
431
|
+
// Check if this is a line element
|
|
432
|
+
if (child.tagName === 'span' && child.properties?.className === 'line' && typeof child.properties.dataLn === 'number') {
|
|
433
|
+
const lineNumber = child.properties.dataLn;
|
|
434
|
+
const meta = emphasizedLines.get(lineNumber);
|
|
435
|
+
if (meta !== undefined) {
|
|
436
|
+
if (meta.highlightText) {
|
|
437
|
+
// For text highlight, wrap the specific text in a span with data-hl
|
|
438
|
+
// Don't add data-hl to the line itself
|
|
439
|
+
child.children = wrapTextInHighlightSpan(child.children, meta.highlightText);
|
|
440
|
+
} else {
|
|
441
|
+
// Use data-hl with optional "strong" value on the line
|
|
442
|
+
child.properties.dataHl = meta.strong ? 'strong' : '';
|
|
443
|
+
if (meta.description) {
|
|
444
|
+
child.properties.dataHlDescription = meta.description;
|
|
445
|
+
}
|
|
446
|
+
if (meta.position) {
|
|
447
|
+
child.properties.dataHlPosition = meta.position;
|
|
448
|
+
}
|
|
397
449
|
}
|
|
450
|
+
|
|
451
|
+
// Collect this line element for indent calculation
|
|
452
|
+
highlightedLineElements.push(child);
|
|
398
453
|
}
|
|
399
454
|
}
|
|
455
|
+
|
|
456
|
+
// Recurse into children (for frames containing lines)
|
|
457
|
+
traverse(child);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
traverse(node);
|
|
461
|
+
return highlightedLineElements;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Groups highlighted line elements by their highlight regions and calculates
|
|
466
|
+
* the indent level for each region.
|
|
467
|
+
*
|
|
468
|
+
* @param highlightedElements - Line elements that are highlighted, in order
|
|
469
|
+
* @param emphasizedLines - The emphasis metadata map
|
|
470
|
+
* @returns Map from region index to indent level
|
|
471
|
+
*/
|
|
472
|
+
function calculateRegionIndentLevels(highlightedElements, emphasizedLines) {
|
|
473
|
+
const regionIndentLevels = new Map();
|
|
474
|
+
if (highlightedElements.length === 0) {
|
|
475
|
+
return regionIndentLevels;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Group elements by consecutive regions
|
|
479
|
+
const sortedLines = Array.from(emphasizedLines.keys()).sort((a, b) => a - b);
|
|
480
|
+
let regionIndex = 0;
|
|
481
|
+
let regionElements = [];
|
|
482
|
+
let prevLine = -1;
|
|
483
|
+
|
|
484
|
+
// Build a quick lookup from lineNumber to element
|
|
485
|
+
const elementByLine = new Map();
|
|
486
|
+
for (const el of highlightedElements) {
|
|
487
|
+
const ln = el.properties?.dataLn;
|
|
488
|
+
elementByLine.set(ln, el);
|
|
489
|
+
}
|
|
490
|
+
for (const line of sortedLines) {
|
|
491
|
+
const el = elementByLine.get(line);
|
|
492
|
+
if (!el) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (prevLine >= 0 && line !== prevLine + 1) {
|
|
496
|
+
// Gap: close current region
|
|
497
|
+
regionIndentLevels.set(regionIndex, calculateFrameIndent(regionElements));
|
|
498
|
+
regionIndex += 1;
|
|
499
|
+
regionElements = [];
|
|
400
500
|
}
|
|
501
|
+
regionElements.push(el);
|
|
502
|
+
prevLine = line;
|
|
503
|
+
}
|
|
401
504
|
|
|
402
|
-
|
|
403
|
-
|
|
505
|
+
// Close the last region
|
|
506
|
+
if (regionElements.length > 0) {
|
|
507
|
+
regionIndentLevels.set(regionIndex, calculateFrameIndent(regionElements));
|
|
404
508
|
}
|
|
509
|
+
return regionIndentLevels;
|
|
405
510
|
}
|
|
406
511
|
|
|
407
512
|
/**
|
|
408
|
-
*
|
|
513
|
+
* Creates a source enhancer that adds emphasis to code lines based on `@highlight` comments
|
|
514
|
+
* and restructures frames around highlighted regions.
|
|
409
515
|
*
|
|
410
|
-
* Supports
|
|
516
|
+
* Supports five patterns:
|
|
411
517
|
*
|
|
412
518
|
* 1. **Single line emphasis** - emphasizes the line containing the comment:
|
|
413
519
|
* ```jsx
|
|
@@ -437,12 +543,66 @@ function addEmphasisToLines(node, emphasizedLines) {
|
|
|
437
543
|
* <h1>Heading 1</h1> {/* @highlight-text "Heading 1" *\/}
|
|
438
544
|
* ```
|
|
439
545
|
*
|
|
546
|
+
* 5. **Focus override** - mark a region for padding focus:
|
|
547
|
+
* ```jsx
|
|
548
|
+
* <h1>Heading 1</h1> {/* @highlight @focus *\/}
|
|
549
|
+
* ```
|
|
550
|
+
*
|
|
440
551
|
* Emphasized lines receive a `data-hl` attribute on their `<span class="line">` element.
|
|
552
|
+
* When highlights exist, frames are restructured with `data-frame-type` attributes
|
|
553
|
+
* (`highlighted`, `padding-top`, `padding-bottom`, or omitted for normal).
|
|
554
|
+
* Highlighted frames also receive `data-frame-indent` with the shared indent level.
|
|
441
555
|
*
|
|
442
|
-
* @param
|
|
443
|
-
* @
|
|
444
|
-
*
|
|
445
|
-
* @
|
|
556
|
+
* @param options - Optional configuration for padding frames
|
|
557
|
+
* @returns A `SourceEnhancer` function
|
|
558
|
+
*
|
|
559
|
+
* @example
|
|
560
|
+
* ```ts
|
|
561
|
+
* import { createEnhanceCodeEmphasis } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
|
|
562
|
+
*
|
|
563
|
+
* const enhancers = [createEnhanceCodeEmphasis({ paddingFrameMaxSize: 5, focusFramesMaxSize: 8 })];
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
566
|
+
export function createEnhanceCodeEmphasis(options = {}) {
|
|
567
|
+
return (root, comments) => {
|
|
568
|
+
if (!comments || Object.keys(comments).length === 0) {
|
|
569
|
+
return root;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Step 1: Parse directives from comments (no tree traversal)
|
|
573
|
+
const directives = parseEmphasisDirectives(comments);
|
|
574
|
+
if (directives.length === 0) {
|
|
575
|
+
return root;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Step 2 (Traversal 1): Build line element map
|
|
579
|
+
const lineElements = buildLineElementMap(root);
|
|
580
|
+
|
|
581
|
+
// Step 3: Calculate which lines are emphasized (no tree traversal)
|
|
582
|
+
const emphasizedLines = calculateEmphasizedLines(directives, lineElements);
|
|
583
|
+
if (emphasizedLines.size === 0) {
|
|
584
|
+
return root;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Step 4 (Traversal 2): Apply emphasis attributes AND collect highlighted elements
|
|
588
|
+
const highlightedElements = applyEmphasisAndCollectHighlightedElements(root, emphasizedLines);
|
|
589
|
+
|
|
590
|
+
// Step 5: Calculate indent levels per region (uses collected elements, no tree traversal)
|
|
591
|
+
const regionIndentLevels = calculateRegionIndentLevels(highlightedElements, emphasizedLines);
|
|
592
|
+
|
|
593
|
+
// Step 6: Calculate frame ranges (pure math, no tree traversal)
|
|
594
|
+
const totalLines = root.data?.totalLines ?? lineElements.size;
|
|
595
|
+
const frameRanges = calculateFrameRanges(emphasizedLines, totalLines, options);
|
|
596
|
+
|
|
597
|
+
// Step 7: Restructure frames (flat iteration, not deep recursive traversal)
|
|
598
|
+
restructureFrames(root, frameRanges, regionIndentLevels);
|
|
599
|
+
return root;
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Default source enhancer that adds emphasis to code lines based on `@highlight` comments.
|
|
605
|
+
* Uses no padding frames by default. Use `createEnhanceCodeEmphasis` for configurable padding.
|
|
446
606
|
*
|
|
447
607
|
* @example
|
|
448
608
|
* ```ts
|
|
@@ -451,19 +611,4 @@ function addEmphasisToLines(node, emphasizedLines) {
|
|
|
451
611
|
* const enhancers = [enhanceCodeEmphasis];
|
|
452
612
|
* ```
|
|
453
613
|
*/
|
|
454
|
-
export const enhanceCodeEmphasis = (
|
|
455
|
-
if (!comments || Object.keys(comments).length === 0) {
|
|
456
|
-
return root;
|
|
457
|
-
}
|
|
458
|
-
const directives = parseEmphasisDirectives(comments);
|
|
459
|
-
if (directives.length === 0) {
|
|
460
|
-
return root;
|
|
461
|
-
}
|
|
462
|
-
const lineElements = buildLineElementMap(root);
|
|
463
|
-
const emphasizedLines = calculateEmphasizedLines(directives, lineElements);
|
|
464
|
-
if (emphasizedLines.size === 0) {
|
|
465
|
-
return root;
|
|
466
|
-
}
|
|
467
|
-
addEmphasisToLines(root, emphasizedLines);
|
|
468
|
-
return root;
|
|
469
|
-
};
|
|
614
|
+
export const enhanceCodeEmphasis = createEnhanceCodeEmphasis();
|