mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-06 21:03:47 +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 {
|
||||
InterpolateProps,
|
||||
InterpolateValues,
|
||||
ExtractInterpolatePlaceholders,
|
||||
} 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
|
||||
*/
|
||||
|
||||
const ValueFoundMarker = '{}'; // does not care much
|
||||
|
||||
// If all the values are plain strings, then interpolate returns a simple string
|
||||
export function interpolate<Str extends string>(
|
||||
text: Str,
|
||||
|
@ -36,46 +33,26 @@ export function interpolate<Str extends string, Value extends ReactNode>(
|
|||
text: Str,
|
||||
values?: InterpolateValues<Str, Value>,
|
||||
): ReactNode {
|
||||
const elements: (Value | string)[] = [];
|
||||
|
||||
const processedText = text.replace(
|
||||
// eslint-disable-next-line prefer-named-capture-group
|
||||
/\{(\w+)\}/g,
|
||||
(match, key: ExtractInterpolatePlaceholders<Str>) => {
|
||||
const value = values?.[key];
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
const element = isValidElement(value)
|
||||
? value
|
||||
: // For non-React elements: basic primitive->string conversion
|
||||
String(value);
|
||||
elements.push(element);
|
||||
return ValueFoundMarker;
|
||||
// eslint-disable-next-line prefer-named-capture-group
|
||||
const segments = text.split(/(\{\w+\})/).map((seg, index) => {
|
||||
// Odd indices (1, 3, 5...) of the segments are (potentially) interpolatable
|
||||
if (index % 2 === 1) {
|
||||
const value = values?.[seg.slice(1, -1) as keyof typeof values];
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
return match; // no match? add warning?
|
||||
},
|
||||
);
|
||||
|
||||
// No interpolation to be done: just return the text
|
||||
if (elements.length === 0) {
|
||||
return text;
|
||||
// No match: add warning? There's no way to "escape" interpolation though
|
||||
}
|
||||
return seg;
|
||||
});
|
||||
if (segments.some((seg) => isValidElement(seg))) {
|
||||
return segments
|
||||
.map((seg, index) =>
|
||||
isValidElement(seg) ? React.cloneElement(seg, {key: index}) : seg,
|
||||
)
|
||||
.filter((seg) => seg !== '');
|
||||
}
|
||||
// Basic string interpolation: returns interpolated string
|
||||
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>
|
||||
));
|
||||
return segments.join('');
|
||||
}
|
||||
|
||||
export default function Interpolate<Str extends string>({
|
||||
|
@ -83,9 +60,10 @@ export default function Interpolate<Str extends string>({
|
|||
values,
|
||||
}: InterpolateProps<Str>): JSX.Element {
|
||||
if (typeof children !== 'string') {
|
||||
console.warn('Illegal <Interpolate> children', children);
|
||||
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)}</>;
|
||||
|
|
|
@ -130,7 +130,7 @@ describe('<Interpolate>', () => {
|
|||
</Interpolate>,
|
||||
),
|
||||
).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>
|
||||
today
|
||||
</span>,
|
||||
"? Another {unprovidedValue}!",
|
||||
"? Another ",
|
||||
"{unprovidedValue}",
|
||||
"!",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`interpolate acceptance test 1`] = `
|
||||
[
|
||||
<React.Fragment>
|
||||
Hello
|
||||
Sébastien
|
||||
</React.Fragment>,
|
||||
<React.Fragment>
|
||||
how are you
|
||||
<span>
|
||||
today
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
<React.Fragment>
|
||||
? Another {unprovidedValue}!
|
||||
</React.Fragment>,
|
||||
"Hello ",
|
||||
"Sébastien",
|
||||
" how are you ",
|
||||
<span>
|
||||
today
|
||||
</span>,
|
||||
"? Another ",
|
||||
"{unprovidedValue}",
|
||||
"!",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`interpolate placeholders with JSX values 1`] = `
|
||||
[
|
||||
<React.Fragment>
|
||||
Hello
|
||||
<b>
|
||||
Sébastien
|
||||
</b>
|
||||
</React.Fragment>,
|
||||
<React.Fragment>
|
||||
how are you
|
||||
<span>
|
||||
today
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
<React.Fragment>
|
||||
?
|
||||
</React.Fragment>,
|
||||
"Hello ",
|
||||
<b>
|
||||
Sébastien
|
||||
</b>,
|
||||
" how are you ",
|
||||
<span>
|
||||
today
|
||||
</span>,
|
||||
"?",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`interpolate placeholders with mixed vales 1`] = `
|
||||
[
|
||||
<React.Fragment>
|
||||
Hello
|
||||
Sébastien
|
||||
</React.Fragment>,
|
||||
<React.Fragment>
|
||||
how are you
|
||||
<span>
|
||||
today
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
<React.Fragment>
|
||||
?
|
||||
</React.Fragment>,
|
||||
"Hello ",
|
||||
"Sébastien",
|
||||
" how are you ",
|
||||
<span>
|
||||
today
|
||||
</span>,
|
||||
"?",
|
||||
]
|
||||
`;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import Layout from '@theme/Layout';
|
||||
import Interpolate from '@docusaurus/Interpolate';
|
||||
|
||||
import ErrorBoundaryTestButton from '@site/src/components/ErrorBoundaryTestButton';
|
||||
|
||||
|
@ -22,6 +23,9 @@ export default function ErrorBoundaryTests(): JSX.Element {
|
|||
Crash inside layout
|
||||
</ErrorBoundaryTestButton>
|
||||
</div>
|
||||
<Interpolate values={{foo: <span>FooFoo</span>, bar: <b>BarBar</b>}}>
|
||||
{'{foo} is {bar}'}
|
||||
</Interpolate>
|
||||
</main>
|
||||
</Layout>
|
||||
</>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue