mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-06 04:42:40 +02:00
fix(core): preserve Interpolate children semantics (#7103)
* fix(core): preserve Interpolate children semantics * fix * fix again
This commit is contained in:
parent
c7c0ee4e7c
commit
85f47fd8f7
4 changed files with 54 additions and 86 deletions
|
@ -9,7 +9,6 @@ import React, {isValidElement, type ReactNode} from 'react';
|
||||||
import type {
|
import type {
|
||||||
InterpolateProps,
|
InterpolateProps,
|
||||||
InterpolateValues,
|
InterpolateValues,
|
||||||
ExtractInterpolatePlaceholders,
|
|
||||||
} from '@docusaurus/Interpolate';
|
} from '@docusaurus/Interpolate';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -18,8 +17,6 @@ We don't ship a markdown parser nor a feature-complete i18n library on purpose.
|
||||||
More details here: https://github.com/facebook/docusaurus/pull/4295
|
More details here: https://github.com/facebook/docusaurus/pull/4295
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ValueFoundMarker = '{}'; // does not care much
|
|
||||||
|
|
||||||
// If all the values are plain strings, then interpolate returns a simple string
|
// If all the values are plain strings, then interpolate returns a simple string
|
||||||
export function interpolate<Str extends string>(
|
export function interpolate<Str extends string>(
|
||||||
text: Str,
|
text: Str,
|
||||||
|
@ -36,46 +33,26 @@ export function interpolate<Str extends string, Value extends ReactNode>(
|
||||||
text: Str,
|
text: Str,
|
||||||
values?: InterpolateValues<Str, Value>,
|
values?: InterpolateValues<Str, Value>,
|
||||||
): ReactNode {
|
): ReactNode {
|
||||||
const elements: (Value | string)[] = [];
|
// eslint-disable-next-line prefer-named-capture-group
|
||||||
|
const segments = text.split(/(\{\w+\})/).map((seg, index) => {
|
||||||
const processedText = text.replace(
|
// Odd indices (1, 3, 5...) of the segments are (potentially) interpolatable
|
||||||
// eslint-disable-next-line prefer-named-capture-group
|
if (index % 2 === 1) {
|
||||||
/\{(\w+)\}/g,
|
const value = values?.[seg.slice(1, -1) as keyof typeof values];
|
||||||
(match, key: ExtractInterpolatePlaceholders<Str>) => {
|
if (value !== undefined) {
|
||||||
const value = values?.[key];
|
return value;
|
||||||
|
|
||||||
if (typeof value !== 'undefined') {
|
|
||||||
const element = isValidElement(value)
|
|
||||||
? value
|
|
||||||
: // For non-React elements: basic primitive->string conversion
|
|
||||||
String(value);
|
|
||||||
elements.push(element);
|
|
||||||
return ValueFoundMarker;
|
|
||||||
}
|
}
|
||||||
return match; // no match? add warning?
|
// No match: add warning? There's no way to "escape" interpolation though
|
||||||
},
|
}
|
||||||
);
|
return seg;
|
||||||
|
});
|
||||||
// No interpolation to be done: just return the text
|
if (segments.some((seg) => isValidElement(seg))) {
|
||||||
if (elements.length === 0) {
|
return segments
|
||||||
return text;
|
.map((seg, index) =>
|
||||||
|
isValidElement(seg) ? React.cloneElement(seg, {key: index}) : seg,
|
||||||
|
)
|
||||||
|
.filter((seg) => seg !== '');
|
||||||
}
|
}
|
||||||
// Basic string interpolation: returns interpolated string
|
return segments.join('');
|
||||||
if (elements.every((el): el is string => typeof el === 'string')) {
|
|
||||||
return processedText
|
|
||||||
.split(ValueFoundMarker)
|
|
||||||
.reduce<string>(
|
|
||||||
(str, value, index) => str.concat(value).concat(elements[index] ?? ''),
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// JSX interpolation: returns ReactNode
|
|
||||||
return processedText.split(ValueFoundMarker).map((value, index) => (
|
|
||||||
<React.Fragment key={index}>
|
|
||||||
{value}
|
|
||||||
{elements[index]}
|
|
||||||
</React.Fragment>
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Interpolate<Str extends string>({
|
export default function Interpolate<Str extends string>({
|
||||||
|
@ -83,9 +60,10 @@ export default function Interpolate<Str extends string>({
|
||||||
values,
|
values,
|
||||||
}: InterpolateProps<Str>): JSX.Element {
|
}: InterpolateProps<Str>): JSX.Element {
|
||||||
if (typeof children !== 'string') {
|
if (typeof children !== 'string') {
|
||||||
console.warn('Illegal <Interpolate> children', children);
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The Docusaurus <Interpolate> component only accept simple string values',
|
`The Docusaurus <Interpolate> component only accept simple string values. Received: ${
|
||||||
|
isValidElement(children) ? 'React element' : typeof children
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <>{interpolate(children, values)}</>;
|
return <>{interpolate(children, values)}</>;
|
||||||
|
|
|
@ -130,7 +130,7 @@ describe('<Interpolate>', () => {
|
||||||
</Interpolate>,
|
</Interpolate>,
|
||||||
),
|
),
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"The Docusaurus <Interpolate> component only accept simple string values"`,
|
`"The Docusaurus <Interpolate> component only accept simple string values. Received: React element"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,62 +8,48 @@ exports[`<Interpolate> acceptance test 1`] = `
|
||||||
<span>
|
<span>
|
||||||
today
|
today
|
||||||
</span>,
|
</span>,
|
||||||
"? Another {unprovidedValue}!",
|
"? Another ",
|
||||||
|
"{unprovidedValue}",
|
||||||
|
"!",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`interpolate acceptance test 1`] = `
|
exports[`interpolate acceptance test 1`] = `
|
||||||
[
|
[
|
||||||
<React.Fragment>
|
"Hello ",
|
||||||
Hello
|
"Sébastien",
|
||||||
Sébastien
|
" how are you ",
|
||||||
</React.Fragment>,
|
<span>
|
||||||
<React.Fragment>
|
today
|
||||||
how are you
|
</span>,
|
||||||
<span>
|
"? Another ",
|
||||||
today
|
"{unprovidedValue}",
|
||||||
</span>
|
"!",
|
||||||
</React.Fragment>,
|
|
||||||
<React.Fragment>
|
|
||||||
? Another {unprovidedValue}!
|
|
||||||
</React.Fragment>,
|
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`interpolate placeholders with JSX values 1`] = `
|
exports[`interpolate placeholders with JSX values 1`] = `
|
||||||
[
|
[
|
||||||
<React.Fragment>
|
"Hello ",
|
||||||
Hello
|
<b>
|
||||||
<b>
|
Sébastien
|
||||||
Sébastien
|
</b>,
|
||||||
</b>
|
" how are you ",
|
||||||
</React.Fragment>,
|
<span>
|
||||||
<React.Fragment>
|
today
|
||||||
how are you
|
</span>,
|
||||||
<span>
|
"?",
|
||||||
today
|
|
||||||
</span>
|
|
||||||
</React.Fragment>,
|
|
||||||
<React.Fragment>
|
|
||||||
?
|
|
||||||
</React.Fragment>,
|
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`interpolate placeholders with mixed vales 1`] = `
|
exports[`interpolate placeholders with mixed vales 1`] = `
|
||||||
[
|
[
|
||||||
<React.Fragment>
|
"Hello ",
|
||||||
Hello
|
"Sébastien",
|
||||||
Sébastien
|
" how are you ",
|
||||||
</React.Fragment>,
|
<span>
|
||||||
<React.Fragment>
|
today
|
||||||
how are you
|
</span>,
|
||||||
<span>
|
"?",
|
||||||
today
|
|
||||||
</span>
|
|
||||||
</React.Fragment>,
|
|
||||||
<React.Fragment>
|
|
||||||
?
|
|
||||||
</React.Fragment>,
|
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
|
import Interpolate from '@docusaurus/Interpolate';
|
||||||
|
|
||||||
import ErrorBoundaryTestButton from '@site/src/components/ErrorBoundaryTestButton';
|
import ErrorBoundaryTestButton from '@site/src/components/ErrorBoundaryTestButton';
|
||||||
|
|
||||||
|
@ -22,6 +23,9 @@ export default function ErrorBoundaryTests(): JSX.Element {
|
||||||
Crash inside layout
|
Crash inside layout
|
||||||
</ErrorBoundaryTestButton>
|
</ErrorBoundaryTestButton>
|
||||||
</div>
|
</div>
|
||||||
|
<Interpolate values={{foo: <span>FooFoo</span>, bar: <b>BarBar</b>}}>
|
||||||
|
{'{foo} is {bar}'}
|
||||||
|
</Interpolate>
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
</>
|
</>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue