Merge remote-tracking branch 'origin/master' into techdocs-nfs

Signed-off-by: Gabriel Dugny <gabriel.dugny@believe.com>

# Conflicts:
#	packages/techdocs-cli-embedded-app/package.json
#	plugins/techdocs/src/alpha/index.tsx
#	yarn.lock
This commit is contained in:
Gabriel Dugny
2026-02-10 23:47:25 +01:00
2387 changed files with 108769 additions and 40604 deletions
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ApiBlueprint,
createApiRef,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha';
import { TechDocsAddonOptions } from '@backstage/plugin-techdocs-react';
interface TechDocsAddonsApi {
getAddons(): TechDocsAddonOptions[];
}
export const techdocsAddonsApiRef = createApiRef<TechDocsAddonsApi>({
id: 'plugin.techdocs.addons',
});
export const TechDocsAddonsApiExtension = ApiBlueprint.makeWithOverrides({
name: 'addons',
inputs: {
addons: createExtensionInput([AddonBlueprint.dataRefs.addon]),
},
factory(originalFactory, { inputs }) {
const addons = inputs.addons.map(output =>
output.get(AddonBlueprint.dataRefs.addon),
);
return originalFactory(defineParams =>
defineParams({
api: techdocsAddonsApiRef,
deps: {},
factory: () => ({
getAddons: () => addons,
}),
}),
);
},
});
+57 -21
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { Suspense } from 'react';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import {
createFrontendPlugin,
@@ -34,7 +35,11 @@ import {
EntityIconLinkBlueprint,
} from '@backstage/plugin-catalog-react/alpha';
import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha';
import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha';
import {
AddonBlueprint,
attachTechDocsAddonComponentData,
} from '@backstage/plugin-techdocs-react/alpha';
import { TechDocsAddonsApiExtension, techdocsAddonsApiRef } from './addonsApi';
import { TechDocsClient, TechDocsStorageClient } from '../client';
import {
rootCatalogDocsRouteRef,
@@ -42,7 +47,6 @@ import {
rootRouteRef,
} from '../routes';
import { TechDocsReaderLayout } from '../reader';
import { attachTechDocsAddonComponentData } from '@backstage/plugin-techdocs-react/alpha';
import {
TechDocsAddons,
techdocsApiRef,
@@ -159,6 +163,7 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
},
},
factory(originalFactory, { inputs, config }) {
const addonsApi = apis.get(techdocsAddonsApiRef);
const addons = inputs.addons.map(output => {
const options = output.get(AddonBlueprint.dataRefs.addon);
const Addon = options.component;
@@ -169,8 +174,25 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
return originalFactory({
path: '/docs/:namespace/:kind/:name',
routeRef: rootDocsRouteRef,
loader: async () =>
await import('../Router').then(({ TechDocsReaderRouter }) => (
loader: async () => {
// Merge addons from the API with old-style direct attachments
const apiAddons = addonsApi?.getAddons() ?? [];
const directAddons = inputs.addons.map(output =>
output.get(AddonBlueprint.dataRefs.addon),
);
const addonOptions = [...apiAddons, ...directAddons];
const addons = addonOptions.map(options => {
const Addon = options.component;
attachTechDocsAddonComponentData(Addon, options);
return (
<Suspense key={options.name} fallback={null}>
<Addon />
</Suspense>
);
});
return import('../Router').then(({ TechDocsReaderRouter }) => (
<TechDocsReaderRouter>
<TechDocsReaderLayout
withSearch={config.withSearch}
@@ -178,7 +200,8 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
/>
<TechDocsAddons>{addons}</TechDocsAddons>
</TechDocsReaderRouter>
)),
));
},
});
},
});
@@ -200,29 +223,41 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({
),
},
factory(originalFactory, context) {
const addonsApi = context.apis.get(techdocsAddonsApiRef);
return originalFactory(
{
path: 'docs',
title: 'TechDocs',
routeRef: rootCatalogDocsRouteRef,
loader: () =>
import('../Router').then(({ EmbeddedDocsRouter }) => {
const addons = context.inputs.addons.map(output => {
const options = output.get(AddonBlueprint.dataRefs.addon);
const Addon = options.component;
attachTechDocsAddonComponentData(Addon, options);
return <Addon key={options.name} />;
});
loader: () => {
// Merge addons from the API with old-style direct attachments
const apiAddons = addonsApi?.getAddons() ?? [];
const directAddons = context.inputs.addons.map(output =>
output.get(AddonBlueprint.dataRefs.addon),
);
const addonOptions = [...apiAddons, ...directAddons];
const addons = addonOptions.map(options => {
const Addon = options.component;
attachTechDocsAddonComponentData(Addon, options);
return (
<EmbeddedDocsRouter
emptyState={context.inputs.emptyState?.get(
coreExtensionData.reactElement,
)}
>
<TechDocsAddons>{addons}</TechDocsAddons>
</EmbeddedDocsRouter>
<Suspense key={options.name} fallback={null}>
<Addon />
</Suspense>
);
}),
});
return import('../Router').then(({ EmbeddedDocsRouter }) => (
<EmbeddedDocsRouter
emptyState={context.inputs.emptyState?.get(
coreExtensionData.reactElement,
)}
>
<TechDocsAddons>{addons}</TechDocsAddons>
</EmbeddedDocsRouter>
));
},
},
context,
);
@@ -253,6 +288,7 @@ export default createFrontendPlugin({
extensions: [
techDocsClientApi,
techDocsStorageApi,
TechDocsAddonsApiExtension,
techDocsNavItem,
techDocsPage,
techDocsReaderPage,
@@ -67,7 +67,12 @@ const CopyToClipboardButton = ({ text }: CopyToClipboardButtonProps) => {
leaveDelay={1000}
>
<IconButton
style={{ position: 'absolute' }}
style={{
position: 'absolute',
// top & right was removed from upstream .md-clipboard in mkdocs-material 9.7.0
top: '0.5rem',
right: '0.5rem',
}}
className="md-clipboard md-icon"
onClick={handleClick}
aria-label="Copy to clipboard"
@@ -81,6 +86,8 @@ const CopyToClipboardButton = ({ text }: CopyToClipboardButtonProps) => {
/**
* Recreates copy-to-clipboard functionality attached to <code> snippets that
* is native to mkdocs-material theme.
*
* Unlike native mkdocs-material theme, this is always enabled and does not respect the mkdocs's config `theme.features` `content.code.copy` setting.
*/
export const copyToClipboard = (theme: Theme): Transformer => {
return dom => {
@@ -81,15 +81,12 @@ export default ({ theme, sidebar }: RuleOptions) => `
bottom: 75px;
position: fixed;
width: 16rem;
overflow-y: auto;
overflow-x: hidden;
scrollbar-color: rgb(193, 193, 193) #eee;
scrollbar-width: thin;
}
.md-sidebar .md-sidebar__scrollwrap {
width: calc(16rem);
overflow-y: hidden;
height: 100%
}
@supports selector(::-webkit-scrollbar) {
[dir=ltr] .md-sidebar__inner {
padding-right: calc(100% - 15.1rem);
@@ -98,28 +95,6 @@ export default ({ theme, sidebar }: RuleOptions) => `
.md-sidebar--secondary {
right: ${theme.spacing(3)}px;
}
.md-sidebar::-webkit-scrollbar {
width: 5px;
}
.md-sidebar::-webkit-scrollbar-button {
width: 5px;
height: 5px;
}
.md-sidebar::-webkit-scrollbar-track {
background: #eee;
border: 1 px solid rgb(250, 250, 250);
box-shadow: 0px 0px 3px #dfdfdf inset;
border-radius: 3px;
}
.md-sidebar::-webkit-scrollbar-thumb {
width: 5px;
background: rgb(193, 193, 193);
border: transparent;
border-radius: 3px;
}
.md-sidebar::-webkit-scrollbar-thumb:hover {
background: rgb(125, 125, 125);
}
.md-content {
max-width: calc(100% - 16rem * 2);
@@ -155,6 +130,8 @@ export default ({ theme, sidebar }: RuleOptions) => `
@media screen and (min-width: 76.25em) {
.md-sidebar {
height: auto;
/* Less padding before the Previous / Next buttons */
padding-bottom: 0 !important;
}
}
@@ -110,7 +110,7 @@ ${headings.reduce<string>((style, heading) => {
.md-typeset table:not([class]) {
font-size: var(--md-typeset-font-size);
border: 1px solid var(--md-default-fg-color);
border: 1px solid var(--md-typeset-table-color);
border-bottom: none;
border-collapse: collapse;
border-radius: ${theme.shape.borderRadius}px;
@@ -119,7 +119,7 @@ ${headings.reduce<string>((style, heading) => {
font-weight: bold;
}
.md-typeset table:not([class]) td, .md-typeset table:not([class]) th {
border-bottom: 1px solid var(--md-default-fg-color);
border-bottom: 1px solid var(--md-typeset-table-color);
}
.md-typeset pre > code::-webkit-scrollbar-thumb {
@@ -28,11 +28,8 @@ export default ({ theme }: RuleOptions) => `
/* FONT */
--md-default-fg-color: ${theme.palette.text.primary};
--md-default-fg-color--light: ${theme.palette.text.secondary};
--md-default-fg-color--lighter: ${lighten(theme.palette.text.secondary, 0.7)};
--md-default-fg-color--lightest: ${lighten(
theme.palette.text.secondary,
0.3,
)};
--md-default-fg-color--lighter: ${alpha(theme.palette.text.secondary, 0.3)};
--md-default-fg-color--lightest: ${alpha(theme.palette.text.secondary, 0.15)};
/* BACKGROUND */
--md-default-bg-color:${theme.palette.background.default};
@@ -146,7 +143,7 @@ export default ({ theme }: RuleOptions) => `
--md-typeset-font-size: 1rem;
--md-typeset-color: var(--md-default-fg-color);
--md-typeset-a-color: ${theme.palette.link};
--md-typeset-table-color: ${theme.palette.text.primary};
--md-typeset-table-color: ${alpha(theme.palette.text.primary, 0.15)};
--md-typeset-table-color--light: ${alpha(theme.palette.text.primary, 0.05)};
--md-typeset-del-color: ${
theme.palette.type === 'dark'