@backstage/plugin-techdocs 1.14.2-next.1 → 1.15.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/CHANGELOG.md +37 -0
- package/dist/package.json.esm.js +2 -2
- package/dist/reader/components/TechDocsReaderPage/TechDocsReaderPage.esm.js +64 -14
- package/dist/reader/components/TechDocsReaderPage/TechDocsReaderPage.esm.js.map +1 -1
- package/dist/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.esm.js +3 -3
- package/dist/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.esm.js.map +1 -1
- package/dist/reader/transformers/html/hooks/attributes.esm.js +10 -0
- package/dist/reader/transformers/html/hooks/attributes.esm.js.map +1 -0
- package/dist/reader/transformers/html/hooks/iframes.esm.js +3 -1
- package/dist/reader/transformers/html/hooks/iframes.esm.js.map +1 -1
- package/dist/reader/transformers/html/hooks/links.esm.js +3 -1
- package/dist/reader/transformers/html/hooks/links.esm.js.map +1 -1
- package/dist/reader/transformers/html/hooks/metatags.esm.js +16 -0
- package/dist/reader/transformers/html/hooks/metatags.esm.js.map +1 -0
- package/dist/reader/transformers/html/transformer.esm.js +4 -15
- package/dist/reader/transformers/html/transformer.esm.js.map +1 -1
- package/dist/reader/transformers/html/utils.esm.js +6 -0
- package/dist/reader/transformers/html/utils.esm.js.map +1 -0
- package/package.json +25 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @backstage/plugin-techdocs
|
|
2
2
|
|
|
3
|
+
## 1.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a0b604c: Adding redirect handling for TechDocs URLs that reference entities that now reference an external entity for TechDocs. Including tests and documentation.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 313cec7: Updated dependency `dompurify` to `^3.2.4`.
|
|
12
|
+
- 8d18d23: TechDocs page titles have been improved, especially for deeply nested pages.
|
|
13
|
+
- 1dfee19: Reverts a change in CSS layout that shifted the content of the Techdocs too far to the left.
|
|
14
|
+
- 4ce5831: Support Techdocs redirect with dompurify 3.2.6+
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- @backstage/plugin-catalog-react@1.21.0
|
|
17
|
+
- @backstage/plugin-techdocs-react@1.3.3
|
|
18
|
+
- @backstage/frontend-plugin-api@0.12.0
|
|
19
|
+
- @backstage/plugin-auth-react@0.1.19
|
|
20
|
+
- @backstage/core-plugin-api@1.11.0
|
|
21
|
+
- @backstage/catalog-client@1.12.0
|
|
22
|
+
- @backstage/integration@1.18.0
|
|
23
|
+
- @backstage/core-components@0.18.0
|
|
24
|
+
- @backstage/core-compat-api@0.5.2
|
|
25
|
+
- @backstage/plugin-search-react@1.9.4
|
|
26
|
+
- @backstage/integration-react@1.2.10
|
|
27
|
+
|
|
28
|
+
## 1.14.2-next.2
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- Updated dependencies
|
|
33
|
+
- @backstage/plugin-auth-react@0.1.19-next.1
|
|
34
|
+
- @backstage/catalog-client@1.12.0-next.0
|
|
35
|
+
- @backstage/plugin-catalog-react@1.21.0-next.2
|
|
36
|
+
- @backstage/core-components@0.17.6-next.1
|
|
37
|
+
- @backstage/integration@1.18.0-next.0
|
|
38
|
+
- @backstage/core-compat-api@0.5.2-next.2
|
|
39
|
+
|
|
3
40
|
## 1.14.2-next.1
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
package/dist/package.json.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "@backstage/plugin-techdocs";
|
|
2
|
-
var version = "1.
|
|
2
|
+
var version = "1.15.0";
|
|
3
3
|
var description = "The Backstage plugin that renders technical documentation for your components";
|
|
4
4
|
var backstage = {
|
|
5
5
|
role: "frontend-plugin",
|
|
@@ -80,7 +80,7 @@ var dependencies = {
|
|
|
80
80
|
"@material-ui/lab": "4.0.0-alpha.61",
|
|
81
81
|
"@material-ui/styles": "^4.10.0",
|
|
82
82
|
"@microsoft/fetch-event-source": "^2.0.1",
|
|
83
|
-
dompurify: "^3.
|
|
83
|
+
dompurify: "^3.2.4",
|
|
84
84
|
"git-url-parse": "^15.0.0",
|
|
85
85
|
jss: "~10.10.0",
|
|
86
86
|
lodash: "^4.17.21",
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import { Children } from 'react';
|
|
3
|
-
import { useOutlet } from 'react-router-dom';
|
|
4
|
-
import { Page } from '@backstage/core-components';
|
|
5
|
-
import { TECHDOCS_ADDONS_WRAPPER_KEY, TECHDOCS_ADDONS_KEY, TechDocsReaderPageProvider } from '@backstage/plugin-techdocs-react';
|
|
2
|
+
import { useMemo, useCallback, useEffect, Children } from 'react';
|
|
3
|
+
import { useOutlet, useNavigate } from 'react-router-dom';
|
|
4
|
+
import { Page, Progress } from '@backstage/core-components';
|
|
5
|
+
import { buildTechDocsURL, TECHDOCS_ADDONS_WRAPPER_KEY, TECHDOCS_ADDONS_KEY, TechDocsReaderPageProvider } from '@backstage/plugin-techdocs-react';
|
|
6
|
+
import { TECHDOCS_EXTERNAL_ANNOTATION } from '@backstage/plugin-techdocs-common';
|
|
7
|
+
import useAsync from 'react-use/esm/useAsync';
|
|
6
8
|
import { TechDocsReaderPageContent } from '../TechDocsReaderPageContent/TechDocsReaderPageContent.esm.js';
|
|
7
9
|
import { TechDocsReaderPageHeader } from '../TechDocsReaderPageHeader/TechDocsReaderPageHeader.esm.js';
|
|
8
10
|
import { TechDocsReaderPageSubheader } from '../TechDocsReaderPageSubheader/TechDocsReaderPageSubheader.esm.js';
|
|
9
11
|
import { rootDocsRouteRef } from '../../../routes.esm.js';
|
|
10
|
-
import { useRouteRefParams, getComponentData } from '@backstage/core-plugin-api';
|
|
12
|
+
import { useRouteRefParams, useApi, useRouteRef, getComponentData } from '@backstage/core-plugin-api';
|
|
11
13
|
import { CookieAuthRefreshProvider } from '@backstage/plugin-auth-react';
|
|
14
|
+
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
|
12
15
|
import { styled, useTheme, createTheme, ThemeProvider } from '@material-ui/core/styles';
|
|
13
16
|
|
|
14
17
|
const TechDocsReaderLayout = (props) => {
|
|
@@ -25,30 +28,77 @@ const StyledPage = styled(Page)({
|
|
|
25
28
|
});
|
|
26
29
|
const TechDocsReaderPage = (props) => {
|
|
27
30
|
const currentTheme = useTheme();
|
|
28
|
-
const readerPageTheme =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const readerPageTheme = useMemo(
|
|
32
|
+
() => createTheme({
|
|
33
|
+
...currentTheme,
|
|
34
|
+
...props.overrideThemeOptions || {}
|
|
35
|
+
}),
|
|
36
|
+
[currentTheme, props.overrideThemeOptions]
|
|
37
|
+
);
|
|
32
38
|
const { kind, name, namespace } = useRouteRefParams(rootDocsRouteRef);
|
|
33
39
|
const { children, entityRef = { kind, name, namespace } } = props;
|
|
34
40
|
const outlet = useOutlet();
|
|
35
|
-
|
|
41
|
+
const catalogApi = useApi(catalogApiRef);
|
|
42
|
+
const navigate = useNavigate();
|
|
43
|
+
const viewTechdocLink = useRouteRef(rootDocsRouteRef);
|
|
44
|
+
const memoizedEntityRef = useMemo(
|
|
45
|
+
() => ({
|
|
46
|
+
kind: entityRef.kind,
|
|
47
|
+
name: entityRef.name,
|
|
48
|
+
namespace: entityRef.namespace
|
|
49
|
+
}),
|
|
50
|
+
[entityRef.kind, entityRef.name, entityRef.namespace]
|
|
51
|
+
);
|
|
52
|
+
const externalEntityTechDocsUrl = useAsync(async () => {
|
|
53
|
+
try {
|
|
54
|
+
const catalogEntity = await catalogApi.getEntityByRef(memoizedEntityRef);
|
|
55
|
+
if (catalogEntity?.metadata?.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]) {
|
|
56
|
+
return buildTechDocsURL(catalogEntity, viewTechdocLink);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
}
|
|
60
|
+
return void 0;
|
|
61
|
+
}, [memoizedEntityRef, catalogApi, viewTechdocLink]);
|
|
62
|
+
const handleNavigation = useCallback(
|
|
63
|
+
(url) => {
|
|
64
|
+
navigate(url, { replace: true });
|
|
65
|
+
},
|
|
66
|
+
[navigate]
|
|
67
|
+
);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!externalEntityTechDocsUrl.loading && externalEntityTechDocsUrl.value) {
|
|
70
|
+
handleNavigation(externalEntityTechDocsUrl.value);
|
|
71
|
+
}
|
|
72
|
+
}, [
|
|
73
|
+
externalEntityTechDocsUrl.loading,
|
|
74
|
+
externalEntityTechDocsUrl.value,
|
|
75
|
+
handleNavigation
|
|
76
|
+
]);
|
|
77
|
+
const page = useMemo(() => {
|
|
78
|
+
if (children) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
36
81
|
const childrenList = outlet ? Children.toArray(outlet.props.children) : [];
|
|
37
82
|
const grandChildren = childrenList.flatMap(
|
|
38
83
|
(child) => child?.props?.children ?? []
|
|
39
84
|
);
|
|
40
|
-
|
|
85
|
+
return grandChildren.find(
|
|
41
86
|
(grandChild) => !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY) && !getComponentData(grandChild, TECHDOCS_ADDONS_KEY)
|
|
42
87
|
);
|
|
43
|
-
|
|
88
|
+
}, [children, outlet]);
|
|
89
|
+
if (externalEntityTechDocsUrl.loading || externalEntityTechDocsUrl.value) {
|
|
90
|
+
return /* @__PURE__ */ jsx(Progress, {});
|
|
91
|
+
}
|
|
92
|
+
if (!children) {
|
|
93
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { theme: readerPageTheme, children: /* @__PURE__ */ jsx(CookieAuthRefreshProvider, { pluginId: "techdocs", children: /* @__PURE__ */ jsx(TechDocsReaderPageProvider, { entityRef: memoizedEntityRef, children: page || /* @__PURE__ */ jsx(TechDocsReaderLayout, {}) }) }) });
|
|
44
94
|
}
|
|
45
|
-
return /* @__PURE__ */ jsx(ThemeProvider, { theme: readerPageTheme, children: /* @__PURE__ */ jsx(CookieAuthRefreshProvider, { pluginId: "techdocs", children: /* @__PURE__ */ jsx(TechDocsReaderPageProvider, { entityRef, children: ({ metadata, entityMetadata, onReady }) => /* @__PURE__ */ jsx(
|
|
95
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { theme: readerPageTheme, children: /* @__PURE__ */ jsx(CookieAuthRefreshProvider, { pluginId: "techdocs", children: /* @__PURE__ */ jsx(TechDocsReaderPageProvider, { entityRef: memoizedEntityRef, children: ({ metadata, entityMetadata, onReady }) => /* @__PURE__ */ jsx(
|
|
46
96
|
StyledPage,
|
|
47
97
|
{
|
|
48
98
|
themeId: "documentation",
|
|
49
99
|
className: "techdocs-reader-page",
|
|
50
100
|
children: children instanceof Function ? children({
|
|
51
|
-
entityRef,
|
|
101
|
+
entityRef: memoizedEntityRef,
|
|
52
102
|
techdocsMetadataValue: metadata.value,
|
|
53
103
|
entityMetadataValue: entityMetadata.value,
|
|
54
104
|
onReady
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TechDocsReaderPage.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Children, ReactElement, ReactNode } from 'react';\nimport { useOutlet } from 'react-router-dom';\n\nimport { Page } from '@backstage/core-components';\nimport { CompoundEntityRef } from '@backstage/catalog-model';\nimport {\n TECHDOCS_ADDONS_KEY,\n TECHDOCS_ADDONS_WRAPPER_KEY,\n TechDocsReaderPageProvider,\n} from '@backstage/plugin-techdocs-react';\n\nimport { TechDocsReaderPageRenderFunction } from '../../../types';\n\nimport { TechDocsReaderPageContent } from '../TechDocsReaderPageContent';\nimport { TechDocsReaderPageHeader } from '../TechDocsReaderPageHeader';\nimport { TechDocsReaderPageSubheader } from '../TechDocsReaderPageSubheader';\nimport { rootDocsRouteRef } from '../../../routes';\nimport {\n getComponentData,\n useRouteRefParams,\n} from '@backstage/core-plugin-api';\n\nimport { CookieAuthRefreshProvider } from '@backstage/plugin-auth-react';\nimport {\n createTheme,\n styled,\n ThemeOptions,\n ThemeProvider,\n useTheme,\n} from '@material-ui/core/styles';\n\n/* An explanation for the multiple ways of customizing the TechDocs reader page\n\nPlease refer to this page on the microsite for the latest recommended approach:\nhttps://backstage.io/docs/features/techdocs/how-to-guides#how-to-customize-the-techdocs-reader-page\n\nThe <TechDocsReaderPage> component is responsible for rendering the <TechDocsReaderPageProvider> and\nits contained version of a <Page>, which in turn renders the <TechDocsReaderPageContent>.\n\nHistorically, there have been different approaches on how this <Page> can be customized, and how the\n<TechDocsReaderPageContent> inside could be exchanged for a custom implementation (which was not\npossible before). Also, the current implementation supports every scenario to avoid breaking default\nconfigurations of TechDocs.\n\nIn particular, there are 4 different TechDocs page configurations:\n\nCONFIGURATION 1: <TechDocsReaderPage> only, no children\n\n<Route path=\"/docs/:namespace/:kind/:name/*\" element={<TechDocsReaderPage />} >\n\nThis is the simplest way to use TechDocs. Only a full page is passed, assuming that it comes with\nits content inside. Since we allowed customizing it, we started providing <TechDocsReaderLayout> as\na default implementation (which contains <TechDocsReaderPageContent>).\n\nCONFIGURATION 2 (not advised): <TechDocsReaderPage> with element children\n\n<Route\n path=\"/docs/:namespace/:kind/:name/*\"\n element={\n <TechDocsReaderPage>\n {techdocsPage}\n </TechDocsReaderPage>\n }\n/>\n\nPreviously, there were two ways of passing children to <TechDocsReaderPage>: either as elements (as\nshown above), or as a render function (described below in CONFIGURATION 3). The \"techdocsPage\" is\nlocated in packages/app/src/components/techdocs and is the default implementation of the content\ninside.\n\nCONFIGURATION 3 (not advised): <TechDocsReaderPage> with render function as child\n\n<Route\n path=\"/docs/:namespace/:kind/:name/*\"\n element={\n <TechDocsReaderPage>\n {({ metadata, entityMetadata, onReady }) => (\n techdocsPage\n )}\n </TechDocsReaderPage>\n }\n/>\n\nSimilar to CONFIGURATION 2, the direct children will be passed to the <TechDocsReaderPage> but in\nthis case interpreted as render prop.\n\nCONFIGURATION 4: <TechDocsReaderPage> and provided content in <Route>\n\n<Route\n path=\"/docs/:namespace/:kind/:name/*\"\n element={<TechDocsReaderPage />}\n>\n {techDocsPage}\n <TechDocsAddons>\n <ExpandableNavigation />\n <ReportIssue />\n <TextSize />\n <LightBox />\n </TechDocsAddons>\n</Route>\n\nThis is the current state in packages/app/src/App.tsx and moved the location of children from inside\nthe element prop in the <Route> to the children of the <Route>. Then, in <TechDocsReaderPage> they\nare retrieved using the useOutlet hook from React Router.\n\nNOTE: Render functions are no longer supported in this approach.\n*/\n\n/**\n * Props for {@link TechDocsReaderLayout}\n * @public\n */\nexport type TechDocsReaderLayoutProps = {\n /**\n * Show or hide the header, defaults to true.\n */\n withHeader?: boolean;\n /**\n * Show or hide the content search bar, defaults to true.\n */\n withSearch?: boolean;\n};\n\n/**\n * Default TechDocs reader page structure composed with a header and content\n * @public\n */\nexport const TechDocsReaderLayout = (props: TechDocsReaderLayoutProps) => {\n const { withSearch, withHeader = true } = props;\n return (\n <Page themeId=\"documentation\">\n {withHeader && <TechDocsReaderPageHeader />}\n <TechDocsReaderPageSubheader />\n <TechDocsReaderPageContent withSearch={withSearch} />\n </Page>\n );\n};\n\n/**\n * @public\n */\nexport type TechDocsReaderPageProps = {\n entityRef?: CompoundEntityRef;\n children?: TechDocsReaderPageRenderFunction | ReactNode;\n overrideThemeOptions?: Partial<ThemeOptions>;\n};\n\n/**\n * Styled Backstage Page that fills available vertical space\n */\nconst StyledPage = styled(Page)({\n height: 'inherit',\n overflowY: 'visible',\n});\n\n/**\n * An addon-aware implementation of the TechDocsReaderPage.\n *\n * @public\n */\nexport const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {\n const currentTheme = useTheme();\n\n const readerPageTheme = createTheme({\n ...currentTheme,\n ...(props.overrideThemeOptions || {}),\n });\n const { kind, name, namespace } = useRouteRefParams(rootDocsRouteRef);\n const { children, entityRef = { kind, name, namespace } } = props;\n\n const outlet = useOutlet();\n\n if (!children) {\n const childrenList = outlet ? Children.toArray(outlet.props.children) : [];\n\n const grandChildren = childrenList.flatMap<ReactElement>(\n child => (child as ReactElement)?.props?.children ?? [],\n );\n\n const page: ReactNode = grandChildren.find(\n grandChild =>\n !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY) &&\n !getComponentData(grandChild, TECHDOCS_ADDONS_KEY),\n );\n\n // As explained above, \"page\" is configuration 4 and <TechDocsReaderLayout> is 1\n return (\n <ThemeProvider theme={readerPageTheme}>\n <CookieAuthRefreshProvider pluginId=\"techdocs\">\n <TechDocsReaderPageProvider entityRef={entityRef}>\n {(page as JSX.Element) || <TechDocsReaderLayout />}\n </TechDocsReaderPageProvider>\n </CookieAuthRefreshProvider>\n </ThemeProvider>\n );\n }\n // As explained above, a render function is configuration 3 and React element is 2\n return (\n <ThemeProvider theme={readerPageTheme}>\n <CookieAuthRefreshProvider pluginId=\"techdocs\">\n <TechDocsReaderPageProvider entityRef={entityRef}>\n {({ metadata, entityMetadata, onReady }) => (\n <StyledPage\n themeId=\"documentation\"\n className=\"techdocs-reader-page\"\n >\n {children instanceof Function\n ? children({\n entityRef,\n techdocsMetadataValue: metadata.value,\n entityMetadataValue: entityMetadata.value,\n onReady,\n })\n : children}\n </StyledPage>\n )}\n </TechDocsReaderPageProvider>\n </CookieAuthRefreshProvider>\n </ThemeProvider>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;AA+IO,MAAM,oBAAA,GAAuB,CAAC,KAAA,KAAqC;AACxE,EAAA,MAAM,EAAE,UAAA,EAAY,UAAA,GAAa,IAAA,EAAK,GAAI,KAAA;AAC1C,EAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAK,OAAA,EAAQ,eAAA,EACX,QAAA,EAAA;AAAA,IAAA,UAAA,wBAAe,wBAAA,EAAA,EAAyB,CAAA;AAAA,wBACxC,2BAAA,EAAA,EAA4B,CAAA;AAAA,oBAC7B,GAAA,CAAC,6BAA0B,UAAA,EAAwB;AAAA,GAAA,EACrD,CAAA;AAEJ;AAcA,MAAM,UAAA,GAAa,MAAA,CAAO,IAAI,CAAA,CAAE;AAAA,EAC9B,MAAA,EAAQ,SAAA;AAAA,EACR,SAAA,EAAW;AACb,CAAC,CAAA;AAOM,MAAM,kBAAA,GAAqB,CAAC,KAAA,KAAmC;AACpE,EAAA,MAAM,eAAe,QAAA,EAAS;AAE9B,EAAA,MAAM,kBAAkB,WAAA,CAAY;AAAA,IAClC,GAAG,YAAA;AAAA,IACH,GAAI,KAAA,CAAM,oBAAA,IAAwB;AAAC,GACpC,CAAA;AACD,EAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAM,SAAA,EAAU,GAAI,kBAAkB,gBAAgB,CAAA;AACpE,EAAA,MAAM,EAAE,UAAU,SAAA,GAAY,EAAE,MAAM,IAAA,EAAM,SAAA,IAAY,GAAI,KAAA;AAE5D,EAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,YAAA,GAAe,SAAS,QAAA,CAAS,OAAA,CAAQ,OAAO,KAAA,CAAM,QAAQ,IAAI,EAAC;AAEzE,IAAA,MAAM,gBAAgB,YAAA,CAAa,OAAA;AAAA,MACjC,CAAA,KAAA,KAAU,KAAA,EAAwB,KAAA,EAAO,QAAA,IAAY;AAAC,KACxD;AAEA,IAAA,MAAM,OAAkB,aAAA,CAAc,IAAA;AAAA,MACpC,CAAA,UAAA,KACE,CAAC,gBAAA,CAAiB,UAAA,EAAY,2BAA2B,CAAA,IACzD,CAAC,gBAAA,CAAiB,UAAA,EAAY,mBAAmB;AAAA,KACrD;AAGA,IAAA,2BACG,aAAA,EAAA,EAAc,KAAA,EAAO,eAAA,EACpB,QAAA,kBAAA,GAAA,CAAC,6BAA0B,QAAA,EAAS,UAAA,EAClC,QAAA,kBAAA,GAAA,CAAC,0BAAA,EAAA,EAA2B,WACxB,QAAA,EAAA,IAAA,oBAAwB,GAAA,CAAC,oBAAA,EAAA,EAAqB,CAAA,EAClD,GACF,CAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,2BACG,aAAA,EAAA,EAAc,KAAA,EAAO,eAAA,EACpB,QAAA,kBAAA,GAAA,CAAC,6BAA0B,QAAA,EAAS,UAAA,EAClC,QAAA,kBAAA,GAAA,CAAC,0BAAA,EAAA,EAA2B,WACzB,QAAA,EAAA,CAAC,EAAE,QAAA,EAAU,cAAA,EAAgB,SAAQ,qBACpC,GAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,eAAA;AAAA,MACR,SAAA,EAAU,sBAAA;AAAA,MAET,QAAA,EAAA,QAAA,YAAoB,WACjB,QAAA,CAAS;AAAA,QACP,SAAA;AAAA,QACA,uBAAuB,QAAA,CAAS,KAAA;AAAA,QAChC,qBAAqB,cAAA,CAAe,KAAA;AAAA,QACpC;AAAA,OACD,CAAA,GACD;AAAA;AAAA,GACN,EAEJ,GACF,CAAA,EACF,CAAA;AAEJ;;;;"}
|
|
1
|
+
{"version":3,"file":"TechDocsReaderPage.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Children,\n ReactElement,\n ReactNode,\n useEffect,\n useMemo,\n useCallback,\n} from 'react';\nimport { useOutlet, useNavigate } from 'react-router-dom';\nimport { Page } from '@backstage/core-components';\nimport { CompoundEntityRef } from '@backstage/catalog-model';\nimport {\n TECHDOCS_ADDONS_KEY,\n TECHDOCS_ADDONS_WRAPPER_KEY,\n TechDocsReaderPageProvider,\n buildTechDocsURL,\n} from '@backstage/plugin-techdocs-react';\nimport { TECHDOCS_EXTERNAL_ANNOTATION } from '@backstage/plugin-techdocs-common';\nimport useAsync from 'react-use/esm/useAsync';\nimport { TechDocsReaderPageRenderFunction } from '../../../types';\nimport { TechDocsReaderPageContent } from '../TechDocsReaderPageContent';\nimport { TechDocsReaderPageHeader } from '../TechDocsReaderPageHeader';\nimport { TechDocsReaderPageSubheader } from '../TechDocsReaderPageSubheader';\nimport { rootDocsRouteRef } from '../../../routes';\nimport {\n getComponentData,\n useRouteRefParams,\n useApi,\n useRouteRef,\n} from '@backstage/core-plugin-api';\nimport { CookieAuthRefreshProvider } from '@backstage/plugin-auth-react';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport {\n createTheme,\n styled,\n ThemeOptions,\n ThemeProvider,\n useTheme,\n} from '@material-ui/core/styles';\nimport { Progress } from '@backstage/core-components';\n\n/* An explanation for the multiple ways of customizing the TechDocs reader page\n\nPlease refer to this page on the microsite for the latest recommended approach:\nhttps://backstage.io/docs/features/techdocs/how-to-guides#how-to-customize-the-techdocs-reader-page\n\nThe <TechDocsReaderPage> component is responsible for rendering the <TechDocsReaderPageProvider> and\nits contained version of a <Page>, which in turn renders the <TechDocsReaderPageContent>.\n\nHistorically, there have been different approaches on how this <Page> can be customized, and how the\n<TechDocsReaderPageContent> inside could be exchanged for a custom implementation (which was not\npossible before). Also, the current implementation supports every scenario to avoid breaking default\nconfigurations of TechDocs.\n\nIn particular, there are 4 different TechDocs page configurations:\n\nCONFIGURATION 1: <TechDocsReaderPage> only, no children\n\n<Route path=\"/docs/:namespace/:kind/:name/*\" element={<TechDocsReaderPage />} >\n\nThis is the simplest way to use TechDocs. Only a full page is passed, assuming that it comes with\nits content inside. Since we allowed customizing it, we started providing <TechDocsReaderLayout> as\na default implementation (which contains <TechDocsReaderPageContent>).\n\nCONFIGURATION 2 (not advised): <TechDocsReaderPage> with element children\n\n<Route\n path=\"/docs/:namespace/:kind/:name/*\"\n element={\n <TechDocsReaderPage>\n {techdocsPage}\n </TechDocsReaderPage>\n }\n/>\n\nPreviously, there were two ways of passing children to <TechDocsReaderPage>: either as elements (as\nshown above), or as a render function (described below in CONFIGURATION 3). The \"techdocsPage\" is\nlocated in packages/app/src/components/techdocs and is the default implementation of the content\ninside.\n\nCONFIGURATION 3 (not advised): <TechDocsReaderPage> with render function as child\n\n<Route\n path=\"/docs/:namespace/:kind/:name/*\"\n element={\n <TechDocsReaderPage>\n {({ metadata, entityMetadata, onReady }) => (\n techdocsPage\n )}\n </TechDocsReaderPage>\n }\n/>\n\nSimilar to CONFIGURATION 2, the direct children will be passed to the <TechDocsReaderPage> but in\nthis case interpreted as render prop.\n\nCONFIGURATION 4: <TechDocsReaderPage> and provided content in <Route>\n\n<Route\n path=\"/docs/:namespace/:kind/:name/*\"\n element={<TechDocsReaderPage />}\n>\n {techDocsPage}\n <TechDocsAddons>\n <ExpandableNavigation />\n <ReportIssue />\n <TextSize />\n <LightBox />\n </TechDocsAddons>\n</Route>\n\nThis is the current state in packages/app/src/App.tsx and moved the location of children from inside\nthe element prop in the <Route> to the children of the <Route>. Then, in <TechDocsReaderPage> they\nare retrieved using the useOutlet hook from React Router.\n\nNOTE: Render functions are no longer supported in this approach.\n*/\n\n/**\n * Props for {@link TechDocsReaderLayout}\n * @public\n */\nexport type TechDocsReaderLayoutProps = {\n /**\n * Show or hide the header, defaults to true.\n */\n withHeader?: boolean;\n /**\n * Show or hide the content search bar, defaults to true.\n */\n withSearch?: boolean;\n};\n\n/**\n * Default TechDocs reader page structure composed with a header and content\n * @public\n */\nexport const TechDocsReaderLayout = (props: TechDocsReaderLayoutProps) => {\n const { withSearch, withHeader = true } = props;\n return (\n <Page themeId=\"documentation\">\n {withHeader && <TechDocsReaderPageHeader />}\n <TechDocsReaderPageSubheader />\n <TechDocsReaderPageContent withSearch={withSearch} />\n </Page>\n );\n};\n\n/**\n * @public\n */\nexport type TechDocsReaderPageProps = {\n entityRef?: CompoundEntityRef;\n children?: TechDocsReaderPageRenderFunction | ReactNode;\n overrideThemeOptions?: Partial<ThemeOptions>;\n};\n\n/**\n * Styled Backstage Page that fills available vertical space\n */\nconst StyledPage = styled(Page)({\n height: 'inherit',\n overflowY: 'visible',\n});\n\n/**\n * An addon-aware implementation of the TechDocsReaderPage.\n *\n * @public\n */\nexport const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {\n const currentTheme = useTheme();\n\n const readerPageTheme = useMemo(\n () =>\n createTheme({\n ...currentTheme,\n ...(props.overrideThemeOptions || {}),\n }),\n [currentTheme, props.overrideThemeOptions],\n );\n\n const { kind, name, namespace } = useRouteRefParams(rootDocsRouteRef);\n const { children, entityRef = { kind, name, namespace } } = props;\n\n const outlet = useOutlet();\n\n const catalogApi = useApi(catalogApiRef);\n const navigate = useNavigate();\n const viewTechdocLink = useRouteRef(rootDocsRouteRef);\n\n const memoizedEntityRef = useMemo(\n () => ({\n kind: entityRef.kind,\n name: entityRef.name,\n namespace: entityRef.namespace,\n }),\n [entityRef.kind, entityRef.name, entityRef.namespace],\n );\n\n const externalEntityTechDocsUrl = useAsync(async () => {\n try {\n const catalogEntity = await catalogApi.getEntityByRef(memoizedEntityRef);\n\n if (\n catalogEntity?.metadata?.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]\n ) {\n return buildTechDocsURL(catalogEntity, viewTechdocLink);\n }\n } catch (error) {\n // Ignore error and allow an attempt at loading the current entity's TechDocs when unable to fetch an external entity from the catalog.\n }\n\n return undefined;\n }, [memoizedEntityRef, catalogApi, viewTechdocLink]);\n\n const handleNavigation = useCallback(\n (url: string) => {\n navigate(url, { replace: true });\n },\n [navigate],\n );\n\n useEffect(() => {\n if (!externalEntityTechDocsUrl.loading && externalEntityTechDocsUrl.value) {\n handleNavigation(externalEntityTechDocsUrl.value);\n }\n }, [\n externalEntityTechDocsUrl.loading,\n externalEntityTechDocsUrl.value,\n handleNavigation,\n ]);\n\n const page: ReactNode = useMemo(() => {\n if (children) {\n return null;\n }\n\n const childrenList = outlet ? Children.toArray(outlet.props.children) : [];\n\n const grandChildren = childrenList.flatMap<ReactElement>(\n child => (child as ReactElement)?.props?.children ?? [],\n );\n\n return grandChildren.find(\n grandChild =>\n !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY) &&\n !getComponentData(grandChild, TECHDOCS_ADDONS_KEY),\n );\n }, [children, outlet]);\n\n if (externalEntityTechDocsUrl.loading || externalEntityTechDocsUrl.value) {\n return <Progress />;\n }\n\n // As explained above, \"page\" is configuration 4 and <TechDocsReaderLayout> is 1\n if (!children) {\n return (\n <ThemeProvider theme={readerPageTheme}>\n <CookieAuthRefreshProvider pluginId=\"techdocs\">\n <TechDocsReaderPageProvider entityRef={memoizedEntityRef}>\n {(page as JSX.Element) || <TechDocsReaderLayout />}\n </TechDocsReaderPageProvider>\n </CookieAuthRefreshProvider>\n </ThemeProvider>\n );\n }\n\n // As explained above, a render function is configuration 3 and React element is 2\n return (\n <ThemeProvider theme={readerPageTheme}>\n <CookieAuthRefreshProvider pluginId=\"techdocs\">\n <TechDocsReaderPageProvider entityRef={memoizedEntityRef}>\n {({ metadata, entityMetadata, onReady }) => (\n <StyledPage\n themeId=\"documentation\"\n className=\"techdocs-reader-page\"\n >\n {children instanceof Function\n ? children({\n entityRef: memoizedEntityRef,\n techdocsMetadataValue: metadata.value,\n entityMetadataValue: entityMetadata.value,\n onReady,\n })\n : children}\n </StyledPage>\n )}\n </TechDocsReaderPageProvider>\n </CookieAuthRefreshProvider>\n </ThemeProvider>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -94,11 +94,11 @@ const TechDocsReaderPageHeader = (props) => {
|
|
|
94
94
|
const stringEntityRef = stringifyEntityRef(entityRef);
|
|
95
95
|
const entityDisplayName = entityPresentationApi.forEntity(stringEntityRef).snapshot.primaryTitle;
|
|
96
96
|
const removeTrailingSlash = (str) => str.replace(/\/$/, "");
|
|
97
|
-
const normalizeAndSpace = (str) => str.replace(
|
|
97
|
+
const normalizeAndSpace = (str) => str.replace(/[-_]/g, " ").split(" ").map(capitalize).join(" ");
|
|
98
98
|
let techdocsTabTitleItems = [];
|
|
99
99
|
if (path !== "")
|
|
100
|
-
techdocsTabTitleItems = removeTrailingSlash(path).split("/").
|
|
101
|
-
const tabTitleItems = [
|
|
100
|
+
techdocsTabTitleItems = removeTrailingSlash(path).split("/").map(normalizeAndSpace);
|
|
101
|
+
const tabTitleItems = [entityDisplayName, ...techdocsTabTitleItems, appTitle];
|
|
102
102
|
const tabTitle = tabTitleItems.join(" | ");
|
|
103
103
|
return /* @__PURE__ */ jsxs(
|
|
104
104
|
Header,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TechDocsReaderPageHeader.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PropsWithChildren, useEffect } from 'react';\nimport Helmet from 'react-helmet';\n\nimport Grid from '@material-ui/core/Grid';\nimport Skeleton from '@material-ui/lab/Skeleton';\nimport CodeIcon from '@material-ui/icons/Code';\n\nimport {\n TechDocsAddonLocations as locations,\n useTechDocsAddons,\n useTechDocsReaderPage,\n TechDocsEntityMetadata,\n TechDocsMetadata,\n} from '@backstage/plugin-techdocs-react';\nimport {\n entityPresentationApiRef,\n EntityRefLink,\n EntityRefLinks,\n getEntityRelations,\n} from '@backstage/plugin-catalog-react';\nimport {\n RELATION_OWNED_BY,\n CompoundEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { Header, HeaderLabel } from '@backstage/core-components';\nimport { useRouteRef, configApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport capitalize from 'lodash/capitalize';\n\nimport { rootRouteRef } from '../../../routes';\nimport { useParams } from 'react-router-dom';\n\nconst skeleton = <Skeleton animation=\"wave\" variant=\"text\" height={40} />;\n\n/**\n * Props for {@link TechDocsReaderPageHeader}\n *\n * @public\n * @deprecated No need to pass down properties anymore. The component consumes data from `TechDocsReaderPageContext` instead. Use the {@link @backstage/plugin-techdocs-react#useTechDocsReaderPage} hook for custom header.\n */\nexport type TechDocsReaderPageHeaderProps = PropsWithChildren<{\n entityRef?: CompoundEntityRef;\n entityMetadata?: TechDocsEntityMetadata;\n techDocsMetadata?: TechDocsMetadata;\n}>;\n\n/**\n * Renders the reader page header.\n * This component does not accept props, please use\n * the Tech Docs add-ons to customize it\n * @public\n */\nexport const TechDocsReaderPageHeader = (\n props: TechDocsReaderPageHeaderProps,\n) => {\n const { children } = props;\n const addons = useTechDocsAddons();\n const configApi = useApi(configApiRef);\n\n const entityPresentationApi = useApi(entityPresentationApiRef);\n const { '*': path = '' } = useParams();\n\n const {\n title,\n setTitle,\n subtitle,\n setSubtitle,\n entityRef,\n metadata: { value: metadata, loading: metadataLoading },\n entityMetadata: { value: entityMetadata, loading: entityMetadataLoading },\n } = useTechDocsReaderPage();\n\n useEffect(() => {\n if (!metadata) return;\n setTitle(metadata.site_name);\n setSubtitle(() => {\n let { site_description } = metadata;\n if (!site_description || site_description === 'None') {\n site_description = '';\n }\n return site_description;\n });\n }, [metadata, setTitle, setSubtitle]);\n\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n\n const { locationMetadata, spec } = entityMetadata || {};\n const lifecycle = spec?.lifecycle;\n\n const ownedByRelations = entityMetadata\n ? getEntityRelations(entityMetadata, RELATION_OWNED_BY)\n : [];\n\n const docsRootLink = useRouteRef(rootRouteRef)();\n\n const labels = (\n <>\n <HeaderLabel\n label={capitalize(entityMetadata?.kind || 'entity')}\n value={\n <EntityRefLink\n color=\"inherit\"\n entityRef={entityRef}\n title={entityMetadata?.metadata.title}\n defaultKind=\"Component\"\n />\n }\n />\n {ownedByRelations.length > 0 && (\n <HeaderLabel\n label=\"Owner\"\n value={\n <EntityRefLinks\n color=\"inherit\"\n entityRefs={ownedByRelations}\n defaultKind=\"group\"\n />\n }\n />\n )}\n {lifecycle ? (\n <HeaderLabel label=\"Lifecycle\" value={String(lifecycle)} />\n ) : null}\n {locationMetadata &&\n locationMetadata.type !== 'dir' &&\n locationMetadata.type !== 'file' ? (\n <HeaderLabel\n label=\"\"\n value={\n <Grid container direction=\"column\" alignItems=\"center\">\n <Grid style={{ padding: 0 }} item>\n <CodeIcon style={{ marginTop: '-25px' }} />\n </Grid>\n <Grid style={{ padding: 0 }} item>\n Source\n </Grid>\n </Grid>\n }\n url={locationMetadata.target}\n />\n ) : null}\n </>\n );\n\n // If there is no entity or techdocs metadata, there's no reason to show the\n // header (hides the header on 404 error pages).\n const noEntMetadata = !entityMetadataLoading && entityMetadata === undefined;\n const noTdMetadata = !metadataLoading && metadata === undefined;\n if (noEntMetadata || noTdMetadata) return null;\n\n const stringEntityRef = stringifyEntityRef(entityRef);\n\n const entityDisplayName =\n entityPresentationApi.forEntity(stringEntityRef).snapshot.primaryTitle;\n\n const removeTrailingSlash = (str: string) => str.replace(/\\/$/, '');\n const normalizeAndSpace = (str: string) =>\n str.replace(/-/g, ' ').split(' ').map(capitalize).join(' ');\n\n let techdocsTabTitleItems: string[] = [];\n\n if (path !== '')\n techdocsTabTitleItems = removeTrailingSlash(path)\n .split('/')\n .slice(0, 3)\n .map(normalizeAndSpace);\n\n const tabTitleItems = [appTitle, entityDisplayName, ...techdocsTabTitleItems];\n const tabTitle = tabTitleItems.join(' | ');\n\n return (\n <Header\n type=\"Documentation\"\n typeLink={docsRootLink}\n title={title || skeleton}\n subtitle={subtitle === '' ? undefined : subtitle || skeleton}\n >\n <Helmet titleTemplate=\"%s\">\n <title>{tabTitle}</title>\n </Helmet>\n {labels}\n {children}\n {addons.renderComponentsByLocation(locations.Header)}\n </Header>\n );\n};\n"],"names":["locations"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TechDocsReaderPageHeader.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PropsWithChildren, useEffect } from 'react';\nimport Helmet from 'react-helmet';\n\nimport Grid from '@material-ui/core/Grid';\nimport Skeleton from '@material-ui/lab/Skeleton';\nimport CodeIcon from '@material-ui/icons/Code';\n\nimport {\n TechDocsAddonLocations as locations,\n useTechDocsAddons,\n useTechDocsReaderPage,\n TechDocsEntityMetadata,\n TechDocsMetadata,\n} from '@backstage/plugin-techdocs-react';\nimport {\n entityPresentationApiRef,\n EntityRefLink,\n EntityRefLinks,\n getEntityRelations,\n} from '@backstage/plugin-catalog-react';\nimport {\n RELATION_OWNED_BY,\n CompoundEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { Header, HeaderLabel } from '@backstage/core-components';\nimport { useRouteRef, configApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport capitalize from 'lodash/capitalize';\n\nimport { rootRouteRef } from '../../../routes';\nimport { useParams } from 'react-router-dom';\n\nconst skeleton = <Skeleton animation=\"wave\" variant=\"text\" height={40} />;\n\n/**\n * Props for {@link TechDocsReaderPageHeader}\n *\n * @public\n * @deprecated No need to pass down properties anymore. The component consumes data from `TechDocsReaderPageContext` instead. Use the {@link @backstage/plugin-techdocs-react#useTechDocsReaderPage} hook for custom header.\n */\nexport type TechDocsReaderPageHeaderProps = PropsWithChildren<{\n entityRef?: CompoundEntityRef;\n entityMetadata?: TechDocsEntityMetadata;\n techDocsMetadata?: TechDocsMetadata;\n}>;\n\n/**\n * Renders the reader page header.\n * This component does not accept props, please use\n * the Tech Docs add-ons to customize it\n * @public\n */\nexport const TechDocsReaderPageHeader = (\n props: TechDocsReaderPageHeaderProps,\n) => {\n const { children } = props;\n const addons = useTechDocsAddons();\n const configApi = useApi(configApiRef);\n\n const entityPresentationApi = useApi(entityPresentationApiRef);\n const { '*': path = '' } = useParams();\n\n const {\n title,\n setTitle,\n subtitle,\n setSubtitle,\n entityRef,\n metadata: { value: metadata, loading: metadataLoading },\n entityMetadata: { value: entityMetadata, loading: entityMetadataLoading },\n } = useTechDocsReaderPage();\n\n useEffect(() => {\n if (!metadata) return;\n setTitle(metadata.site_name);\n setSubtitle(() => {\n let { site_description } = metadata;\n if (!site_description || site_description === 'None') {\n site_description = '';\n }\n return site_description;\n });\n }, [metadata, setTitle, setSubtitle]);\n\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n\n const { locationMetadata, spec } = entityMetadata || {};\n const lifecycle = spec?.lifecycle;\n\n const ownedByRelations = entityMetadata\n ? getEntityRelations(entityMetadata, RELATION_OWNED_BY)\n : [];\n\n const docsRootLink = useRouteRef(rootRouteRef)();\n\n const labels = (\n <>\n <HeaderLabel\n label={capitalize(entityMetadata?.kind || 'entity')}\n value={\n <EntityRefLink\n color=\"inherit\"\n entityRef={entityRef}\n title={entityMetadata?.metadata.title}\n defaultKind=\"Component\"\n />\n }\n />\n {ownedByRelations.length > 0 && (\n <HeaderLabel\n label=\"Owner\"\n value={\n <EntityRefLinks\n color=\"inherit\"\n entityRefs={ownedByRelations}\n defaultKind=\"group\"\n />\n }\n />\n )}\n {lifecycle ? (\n <HeaderLabel label=\"Lifecycle\" value={String(lifecycle)} />\n ) : null}\n {locationMetadata &&\n locationMetadata.type !== 'dir' &&\n locationMetadata.type !== 'file' ? (\n <HeaderLabel\n label=\"\"\n value={\n <Grid container direction=\"column\" alignItems=\"center\">\n <Grid style={{ padding: 0 }} item>\n <CodeIcon style={{ marginTop: '-25px' }} />\n </Grid>\n <Grid style={{ padding: 0 }} item>\n Source\n </Grid>\n </Grid>\n }\n url={locationMetadata.target}\n />\n ) : null}\n </>\n );\n\n // If there is no entity or techdocs metadata, there's no reason to show the\n // header (hides the header on 404 error pages).\n const noEntMetadata = !entityMetadataLoading && entityMetadata === undefined;\n const noTdMetadata = !metadataLoading && metadata === undefined;\n if (noEntMetadata || noTdMetadata) return null;\n\n const stringEntityRef = stringifyEntityRef(entityRef);\n\n const entityDisplayName =\n entityPresentationApi.forEntity(stringEntityRef).snapshot.primaryTitle;\n\n const removeTrailingSlash = (str: string) => str.replace(/\\/$/, '');\n const normalizeAndSpace = (str: string) =>\n str.replace(/[-_]/g, ' ').split(' ').map(capitalize).join(' ');\n\n let techdocsTabTitleItems: string[] = [];\n\n if (path !== '')\n techdocsTabTitleItems = removeTrailingSlash(path)\n .split('/')\n .map(normalizeAndSpace);\n\n const tabTitleItems = [entityDisplayName, ...techdocsTabTitleItems, appTitle];\n const tabTitle = tabTitleItems.join(' | ');\n\n return (\n <Header\n type=\"Documentation\"\n typeLink={docsRootLink}\n title={title || skeleton}\n subtitle={subtitle === '' ? undefined : subtitle || skeleton}\n >\n <Helmet titleTemplate=\"%s\">\n <title>{tabTitle}</title>\n </Helmet>\n {labels}\n {children}\n {addons.renderComponentsByLocation(locations.Header)}\n </Header>\n );\n};\n"],"names":["locations"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const removeRestrictedAttributes = (node, data) => {
|
|
2
|
+
if (node.tagName !== "META") {
|
|
3
|
+
if (data.attrName === "http-equiv" || data.attrName === "content") {
|
|
4
|
+
node.removeAttribute(data.attrName);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export { removeRestrictedAttributes };
|
|
10
|
+
//# sourceMappingURL=attributes.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.esm.js","sources":["../../../../../src/reader/transformers/html/hooks/attributes.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { UponSanitizeAttributeHook } from 'dompurify';\n\n/**\n * Removes attributes that should only be present on meta tags from other elements.\n * This ensures that http-equiv and content attributes are only allowed on meta tags\n * where they are required for the redirect feature.\n */\nexport const removeRestrictedAttributes: UponSanitizeAttributeHook = (\n node,\n data,\n) => {\n if (node.tagName !== 'META') {\n if (data.attrName === 'http-equiv' || data.attrName === 'content') {\n node.removeAttribute(data.attrName);\n }\n }\n};\n"],"names":[],"mappings":"AAsBO,MAAM,0BAAA,GAAwD,CACnE,IAAA,EACA,IAAA,KACG;AACH,EAAA,IAAI,IAAA,CAAK,YAAY,MAAA,EAAQ;AAC3B,IAAA,IAAI,IAAA,CAAK,QAAA,KAAa,YAAA,IAAgB,IAAA,CAAK,aAAa,SAAA,EAAW;AACjE,MAAA,IAAA,CAAK,eAAA,CAAgB,KAAK,QAAQ,CAAA;AAAA,IACpC;AAAA,EACF;AACF;;;;"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isElement } from '../utils.esm.js';
|
|
2
|
+
|
|
1
3
|
const isIframe = (node) => node.nodeName === "IFRAME";
|
|
2
4
|
const isSafe = (node, hosts) => {
|
|
3
5
|
const src = node.getAttribute("src") || "";
|
|
@@ -9,10 +11,10 @@ const isSafe = (node, hosts) => {
|
|
|
9
11
|
}
|
|
10
12
|
};
|
|
11
13
|
const removeUnsafeIframes = (hosts) => (node) => {
|
|
14
|
+
if (!isElement(node)) return;
|
|
12
15
|
if (isIframe(node) && !isSafe(node, hosts)) {
|
|
13
16
|
node.remove();
|
|
14
17
|
}
|
|
15
|
-
return node;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
export { removeUnsafeIframes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iframes.esm.js","sources":["../../../../../src/reader/transformers/html/hooks/iframes.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Checks whether a node is iframe or not.\n * @param node - can be any element.\n * @returns true when node is iframe.\n */\nconst isIframe = (node: Element) => node.nodeName === 'IFRAME';\n\n/**\n * Checks whether a iframe is safe or not.\n * @param node - is an iframe element.\n * @param hosts - list of allowed hosts.\n * @returns true when iframe is included in hosts.\n */\nconst isSafe = (node: Element, hosts: string[]) => {\n const src = node.getAttribute('src') || '';\n try {\n const { host } = new URL(src);\n return hosts.includes(host);\n } catch {\n return false;\n }\n};\n\n/**\n * Returns a function that removes unsafe iframe nodes.\n * @param node - can be any element.\n * @param hosts - list of allowed hosts.\n */\nexport const removeUnsafeIframes = (hosts: string[]) => (node:
|
|
1
|
+
{"version":3,"file":"iframes.esm.js","sources":["../../../../../src/reader/transformers/html/hooks/iframes.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isElement } from '../utils';\n\n/**\n * Checks whether a node is iframe or not.\n * @param node - can be any element.\n * @returns true when node is iframe.\n */\nconst isIframe = (node: Element) => node.nodeName === 'IFRAME';\n\n/**\n * Checks whether a iframe is safe or not.\n * @param node - is an iframe element.\n * @param hosts - list of allowed hosts.\n * @returns true when iframe is included in hosts.\n */\nconst isSafe = (node: Element, hosts: string[]) => {\n const src = node.getAttribute('src') || '';\n try {\n const { host } = new URL(src);\n return hosts.includes(host);\n } catch {\n return false;\n }\n};\n\n/**\n * Returns a function that removes unsafe iframe nodes.\n * @param node - can be any element.\n * @param hosts - list of allowed hosts.\n */\nexport const removeUnsafeIframes = (hosts: string[]) => (node: Node) => {\n if (!isElement(node)) return;\n\n if (isIframe(node) && !isSafe(node, hosts)) {\n node.remove();\n }\n};\n"],"names":[],"mappings":";;AAuBA,MAAM,QAAA,GAAW,CAAC,IAAA,KAAkB,IAAA,CAAK,QAAA,KAAa,QAAA;AAQtD,MAAM,MAAA,GAAS,CAAC,IAAA,EAAe,KAAA,KAAoB;AACjD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,YAAA,CAAa,KAAK,CAAA,IAAK,EAAA;AACxC,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,IAAI,IAAI,GAAG,CAAA;AAC5B,IAAA,OAAO,KAAA,CAAM,SAAS,IAAI,CAAA;AAAA,EAC5B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF,CAAA;AAOO,MAAM,mBAAA,GAAsB,CAAC,KAAA,KAAoB,CAAC,IAAA,KAAe;AACtE,EAAA,IAAI,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG;AAEtB,EAAA,IAAI,SAAS,IAAI,CAAA,IAAK,CAAC,MAAA,CAAO,IAAA,EAAM,KAAK,CAAA,EAAG;AAC1C,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AACF;;;;"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isElement } from '../utils.esm.js';
|
|
2
|
+
|
|
1
3
|
const MKDOCS_CSS = /main\.[A-Fa-f0-9]{8}\.min\.css$/;
|
|
2
4
|
const GOOGLE_FONTS = /^https:\/\/fonts\.googleapis\.com/;
|
|
3
5
|
const GSTATIC_FONTS = /^https:\/\/fonts\.gstatic\.com/;
|
|
@@ -10,10 +12,10 @@ const isSafe = (node) => {
|
|
|
10
12
|
return isMkdocsCss || isGoogleFonts || isGstaticFonts;
|
|
11
13
|
};
|
|
12
14
|
const removeUnsafeLinks = (node) => {
|
|
15
|
+
if (!isElement(node)) return;
|
|
13
16
|
if (isLink(node) && !isSafe(node)) {
|
|
14
17
|
node.remove();
|
|
15
18
|
}
|
|
16
|
-
return node;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export { removeUnsafeLinks };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.esm.js","sources":["../../../../../src/reader/transformers/html/hooks/links.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst MKDOCS_CSS = /main\\.[A-Fa-f0-9]{8}\\.min\\.css$/;\nconst GOOGLE_FONTS = /^https:\\/\\/fonts\\.googleapis\\.com/;\nconst GSTATIC_FONTS = /^https:\\/\\/fonts\\.gstatic\\.com/;\n\n/**\n * Checks whether a node is link or not.\n * @param node - can be any element.\n * @returns true when node is link.\n */\nconst isLink = (node: Element) => node.nodeName === 'LINK';\n\n/**\n * Checks whether a link is safe or not.\n * @param node - is an link element.\n * @returns true when link is mkdocs css, google fonts or gstatic fonts.\n */\nconst isSafe = (node: Element) => {\n const href = node?.getAttribute('href') || '';\n const isMkdocsCss = href.match(MKDOCS_CSS);\n const isGoogleFonts = href.match(GOOGLE_FONTS);\n const isGstaticFonts = href.match(GSTATIC_FONTS);\n return isMkdocsCss || isGoogleFonts || isGstaticFonts;\n};\n\n/**\n * Function that removes unsafe link nodes.\n * @param node - can be any element.\n
|
|
1
|
+
{"version":3,"file":"links.esm.js","sources":["../../../../../src/reader/transformers/html/hooks/links.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isElement } from '../utils';\n\nconst MKDOCS_CSS = /main\\.[A-Fa-f0-9]{8}\\.min\\.css$/;\nconst GOOGLE_FONTS = /^https:\\/\\/fonts\\.googleapis\\.com/;\nconst GSTATIC_FONTS = /^https:\\/\\/fonts\\.gstatic\\.com/;\n\n/**\n * Checks whether a node is link or not.\n * @param node - can be any element.\n * @returns true when node is link.\n */\nconst isLink = (node: Element) => node.nodeName === 'LINK';\n\n/**\n * Checks whether a link is safe or not.\n * @param node - is an link element.\n * @returns true when link is mkdocs css, google fonts or gstatic fonts.\n */\nconst isSafe = (node: Element) => {\n const href = node?.getAttribute('href') || '';\n const isMkdocsCss = href.match(MKDOCS_CSS);\n const isGoogleFonts = href.match(GOOGLE_FONTS);\n const isGstaticFonts = href.match(GSTATIC_FONTS);\n return isMkdocsCss || isGoogleFonts || isGstaticFonts;\n};\n\n/**\n * Function that removes unsafe link nodes.\n * @param node - can be any element.\n */\nexport const removeUnsafeLinks = (node: Node) => {\n if (!isElement(node)) return;\n\n if (isLink(node) && !isSafe(node)) {\n node.remove();\n }\n};\n"],"names":[],"mappings":";;AAkBA,MAAM,UAAA,GAAa,iCAAA;AACnB,MAAM,YAAA,GAAe,mCAAA;AACrB,MAAM,aAAA,GAAgB,gCAAA;AAOtB,MAAM,MAAA,GAAS,CAAC,IAAA,KAAkB,IAAA,CAAK,QAAA,KAAa,MAAA;AAOpD,MAAM,MAAA,GAAS,CAAC,IAAA,KAAkB;AAChC,EAAA,MAAM,IAAA,GAAO,IAAA,EAAM,YAAA,CAAa,MAAM,CAAA,IAAK,EAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA;AACzC,EAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,KAAA,CAAM,YAAY,CAAA;AAC7C,EAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,KAAA,CAAM,aAAa,CAAA;AAC/C,EAAA,OAAO,eAAe,aAAA,IAAiB,cAAA;AACzC,CAAA;AAMO,MAAM,iBAAA,GAAoB,CAAC,IAAA,KAAe;AAC/C,EAAA,IAAI,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG;AAEtB,EAAA,IAAI,OAAO,IAAI,CAAA,IAAK,CAAC,MAAA,CAAO,IAAI,CAAA,EAAG;AACjC,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AACF;;;;"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isElement } from '../utils.esm.js';
|
|
2
|
+
|
|
3
|
+
const isAllowedMetaRefreshTag = (element) => {
|
|
4
|
+
const httpEquiv = element.getAttribute("http-equiv");
|
|
5
|
+
const content = element.getAttribute("content");
|
|
6
|
+
return httpEquiv === "refresh" && content?.includes("url=") === true;
|
|
7
|
+
};
|
|
8
|
+
const removeUnsafeMetaTags = (node, data) => {
|
|
9
|
+
if (!isElement(node)) return;
|
|
10
|
+
if (data.tagName === "meta" && !isAllowedMetaRefreshTag(node)) {
|
|
11
|
+
node.parentNode?.removeChild(node);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export { removeUnsafeMetaTags };
|
|
16
|
+
//# sourceMappingURL=metatags.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metatags.esm.js","sources":["../../../../../src/reader/transformers/html/hooks/metatags.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { UponSanitizeElementHook } from 'dompurify';\nimport { isElement } from '../utils';\n\n/**\n * Checks if a meta tag is a refresh redirect tag that should be allowed.\n * These tags are required for the TechDocs redirect feature.\n */\nconst isAllowedMetaRefreshTag = (element: Element): boolean => {\n const httpEquiv = element.getAttribute('http-equiv');\n const content = element.getAttribute('content');\n\n return httpEquiv === 'refresh' && content?.includes('url=') === true;\n};\n\n/**\n * Removes unsafe meta tags from the DOM while preserving allowed refresh redirect tags.\n * Only meta tags used for page refreshing/redirects are allowed as they are required\n * for the TechDocs redirect feature.\n */\nexport const removeUnsafeMetaTags: UponSanitizeElementHook = (node, data) => {\n if (!isElement(node)) return;\n\n if (data.tagName === 'meta' && !isAllowedMetaRefreshTag(node)) {\n node.parentNode?.removeChild(node);\n }\n};\n"],"names":[],"mappings":";;AAsBA,MAAM,uBAAA,GAA0B,CAAC,OAAA,KAA8B;AAC7D,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,YAAA,CAAa,YAAY,CAAA;AACnD,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,YAAA,CAAa,SAAS,CAAA;AAE9C,EAAA,OAAO,SAAA,KAAc,SAAA,IAAa,OAAA,EAAS,QAAA,CAAS,MAAM,CAAA,KAAM,IAAA;AAClE,CAAA;AAOO,MAAM,oBAAA,GAAgD,CAAC,IAAA,EAAM,IAAA,KAAS;AAC3E,EAAA,IAAI,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG;AAEtB,EAAA,IAAI,KAAK,OAAA,KAAY,MAAA,IAAU,CAAC,uBAAA,CAAwB,IAAI,CAAA,EAAG;AAC7D,IAAA,IAAA,CAAK,UAAA,EAAY,YAAY,IAAI,CAAA;AAAA,EACnC;AACF;;;;"}
|
|
@@ -3,6 +3,8 @@ import { useCallback, useMemo } from 'react';
|
|
|
3
3
|
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
|
4
4
|
import { removeUnsafeLinks } from './hooks/links.esm.js';
|
|
5
5
|
import { removeUnsafeIframes } from './hooks/iframes.esm.js';
|
|
6
|
+
import { removeUnsafeMetaTags } from './hooks/metatags.esm.js';
|
|
7
|
+
import { removeRestrictedAttributes } from './hooks/attributes.esm.js';
|
|
6
8
|
|
|
7
9
|
const useSanitizerConfig = () => {
|
|
8
10
|
const configApi = useApi(configApiRef);
|
|
@@ -21,21 +23,8 @@ const useSanitizerTransformer = () => {
|
|
|
21
23
|
tags.push("iframe");
|
|
22
24
|
DOMPurify.addHook("beforeSanitizeElements", removeUnsafeIframes(hosts));
|
|
23
25
|
}
|
|
24
|
-
DOMPurify.addHook("uponSanitizeElement",
|
|
25
|
-
|
|
26
|
-
const isMetaRefreshTag = currNode.getAttribute("http-equiv") === "refresh" && currNode.getAttribute("content")?.includes("url=");
|
|
27
|
-
if (!isMetaRefreshTag) {
|
|
28
|
-
currNode.parentNode?.removeChild(currNode);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
DOMPurify.addHook("uponSanitizeAttribute", (currNode, data) => {
|
|
33
|
-
if (currNode.tagName !== "meta") {
|
|
34
|
-
if (data.attrName === "http-equiv" || data.attrName === "content") {
|
|
35
|
-
currNode.removeAttribute(data.attrName);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
});
|
|
26
|
+
DOMPurify.addHook("uponSanitizeElement", removeUnsafeMetaTags);
|
|
27
|
+
DOMPurify.addHook("uponSanitizeAttribute", removeRestrictedAttributes);
|
|
39
28
|
const tagNameCheck = config?.getOptionalString(
|
|
40
29
|
"allowedCustomElementTagNameRegExp"
|
|
41
30
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformer.esm.js","sources":["../../../../src/reader/transformers/html/transformer.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport DOMPurify from 'dompurify';\nimport { useCallback, useMemo } from 'react';\n\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport { Transformer } from '../transformer';\nimport {
|
|
1
|
+
{"version":3,"file":"transformer.esm.js","sources":["../../../../src/reader/transformers/html/transformer.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport DOMPurify from 'dompurify';\nimport { useCallback, useMemo } from 'react';\n\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport { Transformer } from '../transformer';\nimport {\n removeRestrictedAttributes,\n removeUnsafeIframes,\n removeUnsafeLinks,\n removeUnsafeMetaTags,\n} from './hooks';\n\n/**\n * Returns html sanitizer configuration\n */\nconst useSanitizerConfig = () => {\n const configApi = useApi(configApiRef);\n\n return useMemo(() => {\n return configApi.getOptionalConfig('techdocs.sanitizer');\n }, [configApi]);\n};\n\n/**\n * Returns a transformer that sanitizes the dom\n */\nexport const useSanitizerTransformer = (): Transformer => {\n const config = useSanitizerConfig();\n\n return useCallback(\n async (dom: Element) => {\n const hosts = config?.getOptionalStringArray('allowedIframeHosts');\n\n DOMPurify.addHook('beforeSanitizeElements', removeUnsafeLinks);\n const tags = ['link', 'meta'];\n\n if (hosts) {\n tags.push('iframe');\n DOMPurify.addHook('beforeSanitizeElements', removeUnsafeIframes(hosts));\n }\n\n DOMPurify.addHook('uponSanitizeElement', removeUnsafeMetaTags);\n\n DOMPurify.addHook('uponSanitizeAttribute', removeRestrictedAttributes);\n\n const tagNameCheck = config?.getOptionalString(\n 'allowedCustomElementTagNameRegExp',\n );\n const attributeNameCheck = config?.getOptionalString(\n 'allowedCustomElementAttributeNameRegExp',\n );\n const additionalAllowedURIProtocols =\n config?.getOptionalStringArray('additionalAllowedURIProtocols') || [];\n\n // Define allowed URI protocols, including any additional ones from the config.\n // The default protocols are based on the DOMPurify defaults.\n const allowedURIProtocols = [\n 'callto',\n 'cid',\n 'ftp',\n 'ftps',\n 'http',\n 'https',\n 'mailto',\n 'matrix',\n 'sms',\n 'tel',\n 'xmpp',\n ...additionalAllowedURIProtocols,\n ].filter(Boolean);\n\n const allowedURIRegExp = new RegExp(\n // This regex is not exposed by DOMPurify, so we need to define it ourselves.\n // It is possible for this to drift from the default in future versions of DOMPurify.\n // See: https://raw.githubusercontent.com/cure53/DOMPurify/master/src/regexp.ts\n `^(?:${allowedURIProtocols.join(\n '|',\n )}:|[^a-z]|[a-z+.-]+(?:[^a-z+.\\\\-:]|$))`,\n 'i',\n );\n\n // using outerHTML as we want to preserve the html tag attributes (lang)\n return DOMPurify.sanitize(dom.outerHTML, {\n ADD_TAGS: tags,\n FORBID_TAGS: ['style'],\n ADD_ATTR: ['http-equiv', 'content', 'dominant-baseline'],\n WHOLE_DOCUMENT: true,\n RETURN_DOM: true,\n ALLOWED_URI_REGEXP: allowedURIRegExp,\n CUSTOM_ELEMENT_HANDLING: {\n tagNameCheck: tagNameCheck ? new RegExp(tagNameCheck) : undefined,\n attributeNameCheck: attributeNameCheck\n ? new RegExp(attributeNameCheck)\n : undefined,\n },\n }) as Element;\n },\n [config],\n );\n};\n"],"names":[],"mappings":";;;;;;;;AAgCA,MAAM,qBAAqB,MAAM;AAC/B,EAAA,MAAM,SAAA,GAAY,OAAO,YAAY,CAAA;AAErC,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,OAAO,SAAA,CAAU,kBAAkB,oBAAoB,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAChB,CAAA;AAKO,MAAM,0BAA0B,MAAmB;AACxD,EAAA,MAAM,SAAS,kBAAA,EAAmB;AAElC,EAAA,OAAO,WAAA;AAAA,IACL,OAAO,GAAA,KAAiB;AACtB,MAAA,MAAM,KAAA,GAAQ,MAAA,EAAQ,sBAAA,CAAuB,oBAAoB,CAAA;AAEjE,MAAA,SAAA,CAAU,OAAA,CAAQ,0BAA0B,iBAAiB,CAAA;AAC7D,MAAA,MAAM,IAAA,GAAO,CAAC,MAAA,EAAQ,MAAM,CAAA;AAE5B,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,IAAA,CAAK,KAAK,QAAQ,CAAA;AAClB,QAAA,SAAA,CAAU,OAAA,CAAQ,wBAAA,EAA0B,mBAAA,CAAoB,KAAK,CAAC,CAAA;AAAA,MACxE;AAEA,MAAA,SAAA,CAAU,OAAA,CAAQ,uBAAuB,oBAAoB,CAAA;AAE7D,MAAA,SAAA,CAAU,OAAA,CAAQ,yBAAyB,0BAA0B,CAAA;AAErE,MAAA,MAAM,eAAe,MAAA,EAAQ,iBAAA;AAAA,QAC3B;AAAA,OACF;AACA,MAAA,MAAM,qBAAqB,MAAA,EAAQ,iBAAA;AAAA,QACjC;AAAA,OACF;AACA,MAAA,MAAM,6BAAA,GACJ,MAAA,EAAQ,sBAAA,CAAuB,+BAA+B,KAAK,EAAC;AAItE,MAAA,MAAM,mBAAA,GAAsB;AAAA,QAC1B,QAAA;AAAA,QACA,KAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,OAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA;AAAA,QACA,KAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA;AAAA,QACA,GAAG;AAAA,OACL,CAAE,OAAO,OAAO,CAAA;AAEhB,MAAA,MAAM,mBAAmB,IAAI,MAAA;AAAA;AAAA;AAAA;AAAA,QAI3B,OAAO,mBAAA,CAAoB,IAAA;AAAA,UACzB;AAAA,SACD,CAAA,qCAAA,CAAA;AAAA,QACD;AAAA,OACF;AAGA,MAAA,OAAO,SAAA,CAAU,QAAA,CAAS,GAAA,CAAI,SAAA,EAAW;AAAA,QACvC,QAAA,EAAU,IAAA;AAAA,QACV,WAAA,EAAa,CAAC,OAAO,CAAA;AAAA,QACrB,QAAA,EAAU,CAAC,YAAA,EAAc,SAAA,EAAW,mBAAmB,CAAA;AAAA,QACvD,cAAA,EAAgB,IAAA;AAAA,QAChB,UAAA,EAAY,IAAA;AAAA,QACZ,kBAAA,EAAoB,gBAAA;AAAA,QACpB,uBAAA,EAAyB;AAAA,UACvB,YAAA,EAAc,YAAA,GAAe,IAAI,MAAA,CAAO,YAAY,CAAA,GAAI,MAAA;AAAA,UACxD,kBAAA,EAAoB,kBAAA,GAChB,IAAI,MAAA,CAAO,kBAAkB,CAAA,GAC7B;AAAA;AACN,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AACF;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.esm.js","sources":["../../../../src/reader/transformers/html/utils.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport const isElement = (node: Node): node is Element => {\n return node.nodeType === Node.ELEMENT_NODE;\n};\n"],"names":[],"mappings":"AAgBO,MAAM,SAAA,GAAY,CAAC,IAAA,KAAgC;AACxD,EAAA,OAAO,IAAA,CAAK,aAAa,IAAA,CAAK,YAAA;AAChC;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-techdocs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "The Backstage plugin that renders technical documentation for your components",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "frontend-plugin",
|
|
@@ -71,29 +71,29 @@
|
|
|
71
71
|
"test": "backstage-cli package test"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@backstage/catalog-client": "1.
|
|
75
|
-
"@backstage/catalog-model": "1.7.5",
|
|
76
|
-
"@backstage/config": "1.3.3",
|
|
77
|
-
"@backstage/core-compat-api": "0.5.2
|
|
78
|
-
"@backstage/core-components": "0.
|
|
79
|
-
"@backstage/core-plugin-api": "1.
|
|
80
|
-
"@backstage/errors": "1.2.7",
|
|
81
|
-
"@backstage/frontend-plugin-api": "0.
|
|
82
|
-
"@backstage/integration": "1.18.0
|
|
83
|
-
"@backstage/integration-react": "1.2.10
|
|
84
|
-
"@backstage/plugin-auth-react": "0.1.19
|
|
85
|
-
"@backstage/plugin-catalog-react": "1.
|
|
86
|
-
"@backstage/plugin-search-common": "1.2.19",
|
|
87
|
-
"@backstage/plugin-search-react": "1.9.4
|
|
88
|
-
"@backstage/plugin-techdocs-common": "0.1.1",
|
|
89
|
-
"@backstage/plugin-techdocs-react": "1.3.3
|
|
90
|
-
"@backstage/theme": "0.6.8",
|
|
74
|
+
"@backstage/catalog-client": "^1.12.0",
|
|
75
|
+
"@backstage/catalog-model": "^1.7.5",
|
|
76
|
+
"@backstage/config": "^1.3.3",
|
|
77
|
+
"@backstage/core-compat-api": "^0.5.2",
|
|
78
|
+
"@backstage/core-components": "^0.18.0",
|
|
79
|
+
"@backstage/core-plugin-api": "^1.11.0",
|
|
80
|
+
"@backstage/errors": "^1.2.7",
|
|
81
|
+
"@backstage/frontend-plugin-api": "^0.12.0",
|
|
82
|
+
"@backstage/integration": "^1.18.0",
|
|
83
|
+
"@backstage/integration-react": "^1.2.10",
|
|
84
|
+
"@backstage/plugin-auth-react": "^0.1.19",
|
|
85
|
+
"@backstage/plugin-catalog-react": "^1.21.0",
|
|
86
|
+
"@backstage/plugin-search-common": "^1.2.19",
|
|
87
|
+
"@backstage/plugin-search-react": "^1.9.4",
|
|
88
|
+
"@backstage/plugin-techdocs-common": "^0.1.1",
|
|
89
|
+
"@backstage/plugin-techdocs-react": "^1.3.3",
|
|
90
|
+
"@backstage/theme": "^0.6.8",
|
|
91
91
|
"@material-ui/core": "^4.12.2",
|
|
92
92
|
"@material-ui/icons": "^4.9.1",
|
|
93
93
|
"@material-ui/lab": "4.0.0-alpha.61",
|
|
94
94
|
"@material-ui/styles": "^4.10.0",
|
|
95
95
|
"@microsoft/fetch-event-source": "^2.0.1",
|
|
96
|
-
"dompurify": "^3.
|
|
96
|
+
"dompurify": "^3.2.4",
|
|
97
97
|
"git-url-parse": "^15.0.0",
|
|
98
98
|
"jss": "~10.10.0",
|
|
99
99
|
"lodash": "^4.17.21",
|
|
@@ -101,12 +101,12 @@
|
|
|
101
101
|
"react-use": "^17.2.4"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@backstage/cli": "0.34.2
|
|
105
|
-
"@backstage/core-app-api": "1.
|
|
106
|
-
"@backstage/dev-utils": "1.1.14
|
|
107
|
-
"@backstage/plugin-catalog": "1.31.3
|
|
108
|
-
"@backstage/plugin-techdocs-module-addons-contrib": "1.1.28
|
|
109
|
-
"@backstage/test-utils": "1.7.11",
|
|
104
|
+
"@backstage/cli": "^0.34.2",
|
|
105
|
+
"@backstage/core-app-api": "^1.19.0",
|
|
106
|
+
"@backstage/dev-utils": "^1.1.14",
|
|
107
|
+
"@backstage/plugin-catalog": "^1.31.3",
|
|
108
|
+
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.28",
|
|
109
|
+
"@backstage/test-utils": "^1.7.11",
|
|
110
110
|
"@testing-library/dom": "^10.0.0",
|
|
111
111
|
"@testing-library/jest-dom": "^6.0.0",
|
|
112
112
|
"@testing-library/react": "^16.0.0",
|