@backstage/plugin-techdocs 1.11.0 → 1.11.1-next.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 CHANGED
@@ -1,5 +1,33 @@
1
1
  # @backstage/plugin-techdocs
2
2
 
3
+ ## 1.11.1-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 605bdc0: Avoid page re-rendering when clicking on anchor links in the same documentation page.
8
+ - 4f0cb89: Added DomPurify sanitizer configuration for custom elements implementing RFC https://github.com/backstage/backstage/issues/26988.
9
+ See https://backstage.io/docs/features/techdocs/how-to-guides#how-to-enable-custom-elements-in-techdocs for how to enable it in the configuration.
10
+ - f246178: Removed `canvas` dev dependency.
11
+ - 4a2f73a: Fix an issue that caused the current documentation page to be re-rendered when navigating to
12
+ another one.
13
+ - Updated dependencies
14
+ - @backstage/core-components@0.16.0-next.0
15
+ - @backstage/plugin-techdocs-react@1.2.10-next.0
16
+ - @backstage/catalog-model@1.7.0
17
+ - @backstage/config@1.2.0
18
+ - @backstage/core-compat-api@0.3.2-next.0
19
+ - @backstage/core-plugin-api@1.10.0
20
+ - @backstage/errors@1.2.4
21
+ - @backstage/frontend-plugin-api@0.9.1-next.0
22
+ - @backstage/integration@1.15.1
23
+ - @backstage/integration-react@1.2.0
24
+ - @backstage/theme@0.6.0
25
+ - @backstage/plugin-auth-react@0.1.8-next.0
26
+ - @backstage/plugin-catalog-react@1.14.1-next.0
27
+ - @backstage/plugin-search-common@1.2.14
28
+ - @backstage/plugin-search-react@1.8.2-next.0
29
+ - @backstage/plugin-techdocs-common@0.1.0
30
+
3
31
  ## 1.11.0
4
32
 
5
33
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-techdocs__alpha",
3
- "version": "1.11.0",
3
+ "version": "1.11.1-next.0",
4
4
  "main": "../dist/alpha.esm.js",
5
5
  "module": "../dist/alpha.esm.js",
6
6
  "types": "../dist/alpha.d.ts"
package/config.d.ts CHANGED
@@ -42,6 +42,22 @@ export interface Config {
42
42
  * @visibility frontend
43
43
  */
44
44
  allowedIframeHosts?: string[];
45
+ /**
46
+ * Allows listed custom element tag name regex
47
+ * Example:
48
+ * allowedCustomElementTagNameRegExp: '^backstage-'
49
+ * this will allow all custom elements with tag name matching `^backstage-` like <backstage-custom-element /> etc.
50
+ * @visibility frontend
51
+ */
52
+ allowedCustomElementTagNameRegExp: string;
53
+ /**
54
+ * Allows listed custom element attribute name regex
55
+ * Example:
56
+ * allowedCustomElementAttributeNameRegExp: 'attribute1|attribute2'
57
+ * this will allow all custom element attributes matching `attribute1` or `attribute2` like <backstage-custom-element attribute1="yes" attribute2/>
58
+ * @visibility frontend
59
+ */
60
+ allowedCustomElementAttributeNameRegExp: string;
45
61
  };
46
62
  };
47
63
  }
@@ -2,11 +2,11 @@ import React, { useEffect, useCallback } from 'react';
2
2
  import Grid from '@material-ui/core/Grid';
3
3
  import { makeStyles } from '@material-ui/core/styles';
4
4
  import { useTechDocsReaderPage, useShadowDomStylesLoading, useShadowRootElements, TechDocsShadowDom } from '@backstage/plugin-techdocs-react';
5
- import { ErrorPage, Content } from '@backstage/core-components';
5
+ import { ErrorPage, Content, Progress } from '@backstage/core-components';
6
6
  import { TechDocsSearch } from '../../../search/components/TechDocsSearch.esm.js';
7
7
  import { TechDocsStateIndicator } from '../TechDocsStateIndicator.esm.js';
8
8
  import { useTechDocsReaderDom } from './dom.esm.js';
9
- import { withTechDocsReaderProvider } from '../TechDocsReaderProvider.esm.js';
9
+ import { withTechDocsReaderProvider, useTechDocsReader } from '../TechDocsReaderProvider.esm.js';
10
10
  import { TechDocsReaderPageContentAddons } from './TechDocsReaderPageContentAddons.esm.js';
11
11
 
12
12
  const useStyles = makeStyles({
@@ -30,6 +30,7 @@ const TechDocsReaderPageContent = withTechDocsReaderProvider(
30
30
  entityRef,
31
31
  setShadowRoot
32
32
  } = useTechDocsReaderPage();
33
+ const { state } = useTechDocsReader();
33
34
  const dom = useTechDocsReaderDom(entityRef);
34
35
  const path = window.location.pathname;
35
36
  const hash = window.location.hash;
@@ -65,7 +66,7 @@ const TechDocsReaderPageContent = withTechDocsReaderProvider(
65
66
  entityId: entityRef,
66
67
  entityTitle: entityMetadata?.metadata?.title
67
68
  }
68
- )), /* @__PURE__ */ React.createElement(Grid, { xs: 12, item: true }, /* @__PURE__ */ React.createElement(TechDocsShadowDom, { element: dom, onAppend: handleAppend }, /* @__PURE__ */ React.createElement(TechDocsReaderPageContentAddons, null)))));
69
+ )), /* @__PURE__ */ React.createElement(Grid, { xs: 12, item: true }, (state === "CHECKING" || isStyleLoading) && /* @__PURE__ */ React.createElement(Progress, null), /* @__PURE__ */ React.createElement(TechDocsShadowDom, { element: dom, onAppend: handleAppend }, /* @__PURE__ */ React.createElement(TechDocsReaderPageContentAddons, null)))));
69
70
  }
70
71
  );
71
72
  const Reader = TechDocsReaderPageContent;
@@ -1 +1 @@
1
- {"version":3,"file":"TechDocsReaderPageContent.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.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 React, { useCallback, useEffect } from 'react';\n\nimport Grid from '@material-ui/core/Grid';\nimport { makeStyles } from '@material-ui/core/styles';\n\nimport {\n TechDocsShadowDom,\n useShadowDomStylesLoading,\n useShadowRootElements,\n useTechDocsReaderPage,\n} from '@backstage/plugin-techdocs-react';\nimport { CompoundEntityRef } from '@backstage/catalog-model';\nimport { Content, ErrorPage } from '@backstage/core-components';\n\nimport { TechDocsSearch } from '../../../search';\nimport { TechDocsStateIndicator } from '../TechDocsStateIndicator';\n\nimport { useTechDocsReaderDom } from './dom';\nimport { withTechDocsReaderProvider } from '../TechDocsReaderProvider';\nimport { TechDocsReaderPageContentAddons } from './TechDocsReaderPageContentAddons';\n\nconst useStyles = makeStyles({\n search: {\n width: '100%',\n '@media (min-width: 76.1875em)': {\n width: 'calc(100% - 34.4rem)',\n margin: '0 auto',\n },\n '@media print': {\n display: 'none',\n },\n },\n});\n\n/**\n * Props for {@link TechDocsReaderPageContent}\n * @public\n */\nexport type TechDocsReaderPageContentProps = {\n /**\n * @deprecated No need to pass down entityRef as property anymore. Consumes the entityName from `TechDocsReaderPageContext`. Use the {@link @backstage/plugin-techdocs-react#useTechDocsReaderPage} hook for custom reader page content.\n */\n entityRef?: CompoundEntityRef;\n /**\n * Show or hide the search bar, defaults to true.\n */\n withSearch?: boolean;\n /**\n * Callback called when the content is rendered.\n */\n onReady?: () => void;\n};\n\n/**\n * Renders the reader page content\n * @public\n */\nexport const TechDocsReaderPageContent = withTechDocsReaderProvider(\n (props: TechDocsReaderPageContentProps) => {\n const { withSearch = true, onReady } = props;\n const classes = useStyles();\n\n const {\n entityMetadata: { value: entityMetadata, loading: entityMetadataLoading },\n entityRef,\n setShadowRoot,\n } = useTechDocsReaderPage();\n const dom = useTechDocsReaderDom(entityRef);\n const path = window.location.pathname;\n const hash = window.location.hash;\n const isStyleLoading = useShadowDomStylesLoading(dom);\n const [hashElement] = useShadowRootElements([`[id=\"${hash.slice(1)}\"]`]);\n\n useEffect(() => {\n if (isStyleLoading) return;\n\n if (hash) {\n if (hashElement) {\n hashElement.scrollIntoView();\n }\n } else {\n document?.querySelector('header')?.scrollIntoView();\n }\n }, [path, hash, hashElement, isStyleLoading]);\n\n const handleAppend = useCallback(\n (newShadowRoot: ShadowRoot) => {\n setShadowRoot(newShadowRoot);\n if (onReady instanceof Function) {\n onReady();\n }\n },\n [setShadowRoot, onReady],\n );\n\n // No entity metadata = 404. Don't render content at all.\n if (entityMetadataLoading === false && !entityMetadata)\n return <ErrorPage status=\"404\" statusMessage=\"PAGE NOT FOUND\" />;\n\n // Do not return content until dom is ready; instead, render a state\n // indicator, which handles progress and content errors on our behalf.\n if (!dom) {\n return (\n <Content>\n <Grid container>\n <Grid xs={12} item>\n <TechDocsStateIndicator />\n </Grid>\n </Grid>\n </Content>\n );\n }\n\n return (\n <Content>\n <Grid container>\n <Grid xs={12} item>\n <TechDocsStateIndicator />\n </Grid>\n {withSearch && (\n <Grid className={classes.search} xs=\"auto\" item>\n <TechDocsSearch\n entityId={entityRef}\n entityTitle={entityMetadata?.metadata?.title}\n />\n </Grid>\n )}\n <Grid xs={12} item>\n {/* Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times */}\n <TechDocsShadowDom element={dom} onAppend={handleAppend}>\n <TechDocsReaderPageContentAddons />\n </TechDocsShadowDom>\n </Grid>\n </Grid>\n </Content>\n );\n },\n);\n\n/**\n * Props for {@link Reader}\n *\n * @public\n * @deprecated use `TechDocsReaderPageContentProps` instead.\n */\nexport type ReaderProps = TechDocsReaderPageContentProps;\n\n/**\n * Component responsible for rendering TechDocs documentation\n * @public\n * @deprecated use `TechDocsReaderPageContent` component instead.\n */\nexport const Reader = TechDocsReaderPageContent;\n"],"names":[],"mappings":";;;;;;;;;;;AAqCA,MAAM,YAAY,UAAW,CAAA;AAAA,EAC3B,MAAQ,EAAA;AAAA,IACN,KAAO,EAAA,MAAA;AAAA,IACP,+BAAiC,EAAA;AAAA,MAC/B,KAAO,EAAA,sBAAA;AAAA,MACP,MAAQ,EAAA,QAAA;AAAA,KACV;AAAA,IACA,cAAgB,EAAA;AAAA,MACd,OAAS,EAAA,MAAA;AAAA,KACX;AAAA,GACF;AACF,CAAC,CAAA,CAAA;AAyBM,MAAM,yBAA4B,GAAA,0BAAA;AAAA,EACvC,CAAC,KAA0C,KAAA;AACzC,IAAA,MAAM,EAAE,UAAA,GAAa,IAAM,EAAA,OAAA,EAAY,GAAA,KAAA,CAAA;AACvC,IAAA,MAAM,UAAU,SAAU,EAAA,CAAA;AAE1B,IAAM,MAAA;AAAA,MACJ,cAAgB,EAAA,EAAE,KAAO,EAAA,cAAA,EAAgB,SAAS,qBAAsB,EAAA;AAAA,MACxE,SAAA;AAAA,MACA,aAAA;AAAA,QACE,qBAAsB,EAAA,CAAA;AAC1B,IAAM,MAAA,GAAA,GAAM,qBAAqB,SAAS,CAAA,CAAA;AAC1C,IAAM,MAAA,IAAA,GAAO,OAAO,QAAS,CAAA,QAAA,CAAA;AAC7B,IAAM,MAAA,IAAA,GAAO,OAAO,QAAS,CAAA,IAAA,CAAA;AAC7B,IAAM,MAAA,cAAA,GAAiB,0BAA0B,GAAG,CAAA,CAAA;AACpD,IAAM,MAAA,CAAC,WAAW,CAAA,GAAI,qBAAsB,CAAA,CAAC,CAAQ,KAAA,EAAA,IAAA,CAAK,KAAM,CAAA,CAAC,CAAC,CAAA,EAAA,CAAI,CAAC,CAAA,CAAA;AAEvE,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,IAAI,cAAgB,EAAA,OAAA;AAEpB,MAAA,IAAI,IAAM,EAAA;AACR,QAAA,IAAI,WAAa,EAAA;AACf,UAAA,WAAA,CAAY,cAAe,EAAA,CAAA;AAAA,SAC7B;AAAA,OACK,MAAA;AACL,QAAU,QAAA,EAAA,aAAA,CAAc,QAAQ,CAAA,EAAG,cAAe,EAAA,CAAA;AAAA,OACpD;AAAA,OACC,CAAC,IAAA,EAAM,IAAM,EAAA,WAAA,EAAa,cAAc,CAAC,CAAA,CAAA;AAE5C,IAAA,MAAM,YAAe,GAAA,WAAA;AAAA,MACnB,CAAC,aAA8B,KAAA;AAC7B,QAAA,aAAA,CAAc,aAAa,CAAA,CAAA;AAC3B,QAAA,IAAI,mBAAmB,QAAU,EAAA;AAC/B,UAAQ,OAAA,EAAA,CAAA;AAAA,SACV;AAAA,OACF;AAAA,MACA,CAAC,eAAe,OAAO,CAAA;AAAA,KACzB,CAAA;AAGA,IAAI,IAAA,qBAAA,KAA0B,SAAS,CAAC,cAAA;AACtC,MAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,SAAA,EAAA,EAAU,MAAO,EAAA,KAAA,EAAM,eAAc,gBAAiB,EAAA,CAAA,CAAA;AAIhE,IAAA,IAAI,CAAC,GAAK,EAAA;AACR,MAAA,2CACG,OACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,IAAK,EAAA,EAAA,SAAA,EAAS,wBACZ,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,EAAI,EAAA,EAAA,EAAI,MAAI,IAChB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,sBAAuB,EAAA,IAAA,CAC1B,CACF,CACF,CAAA,CAAA;AAAA,KAEJ;AAEA,IACE,uBAAA,KAAA,CAAA,aAAA,CAAC,OACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,IAAK,EAAA,EAAA,SAAA,EAAS,wBACZ,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,EAAI,EAAA,EAAA,EAAI,IAAI,EAAA,IAAA,EAAA,sCACf,sBAAuB,EAAA,IAAA,CAC1B,CACC,EAAA,UAAA,oBACE,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,SAAW,EAAA,OAAA,CAAQ,MAAQ,EAAA,EAAA,EAAG,MAAO,EAAA,IAAA,EAAI,IAC7C,EAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,cAAA;AAAA,MAAA;AAAA,QACC,QAAU,EAAA,SAAA;AAAA,QACV,WAAA,EAAa,gBAAgB,QAAU,EAAA,KAAA;AAAA,OAAA;AAAA,KAE3C,CAEF,kBAAA,KAAA,CAAA,aAAA,CAAC,QAAK,EAAI,EAAA,EAAA,EAAI,MAAI,IAEhB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,qBAAkB,OAAS,EAAA,GAAA,EAAK,UAAU,YACzC,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,qCAAgC,CACnC,CACF,CACF,CACF,CAAA,CAAA;AAAA,GAEJ;AACF,EAAA;AAeO,MAAM,MAAS,GAAA;;;;"}
1
+ {"version":3,"file":"TechDocsReaderPageContent.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.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 React, { useCallback, useEffect } from 'react';\n\nimport Grid from '@material-ui/core/Grid';\nimport { makeStyles } from '@material-ui/core/styles';\n\nimport {\n TechDocsShadowDom,\n useShadowDomStylesLoading,\n useShadowRootElements,\n useTechDocsReaderPage,\n} from '@backstage/plugin-techdocs-react';\nimport { CompoundEntityRef } from '@backstage/catalog-model';\nimport { Content, ErrorPage, Progress } from '@backstage/core-components';\n\nimport { TechDocsSearch } from '../../../search';\nimport { TechDocsStateIndicator } from '../TechDocsStateIndicator';\n\nimport { useTechDocsReaderDom } from './dom';\nimport {\n useTechDocsReader,\n withTechDocsReaderProvider,\n} from '../TechDocsReaderProvider';\nimport { TechDocsReaderPageContentAddons } from './TechDocsReaderPageContentAddons';\n\nconst useStyles = makeStyles({\n search: {\n width: '100%',\n '@media (min-width: 76.1875em)': {\n width: 'calc(100% - 34.4rem)',\n margin: '0 auto',\n },\n '@media print': {\n display: 'none',\n },\n },\n});\n\n/**\n * Props for {@link TechDocsReaderPageContent}\n * @public\n */\nexport type TechDocsReaderPageContentProps = {\n /**\n * @deprecated No need to pass down entityRef as property anymore. Consumes the entityName from `TechDocsReaderPageContext`. Use the {@link @backstage/plugin-techdocs-react#useTechDocsReaderPage} hook for custom reader page content.\n */\n entityRef?: CompoundEntityRef;\n /**\n * Show or hide the search bar, defaults to true.\n */\n withSearch?: boolean;\n /**\n * Callback called when the content is rendered.\n */\n onReady?: () => void;\n};\n\n/**\n * Renders the reader page content\n * @public\n */\nexport const TechDocsReaderPageContent = withTechDocsReaderProvider(\n (props: TechDocsReaderPageContentProps) => {\n const { withSearch = true, onReady } = props;\n const classes = useStyles();\n\n const {\n entityMetadata: { value: entityMetadata, loading: entityMetadataLoading },\n entityRef,\n setShadowRoot,\n } = useTechDocsReaderPage();\n const { state } = useTechDocsReader();\n const dom = useTechDocsReaderDom(entityRef);\n const path = window.location.pathname;\n const hash = window.location.hash;\n const isStyleLoading = useShadowDomStylesLoading(dom);\n const [hashElement] = useShadowRootElements([`[id=\"${hash.slice(1)}\"]`]);\n\n useEffect(() => {\n if (isStyleLoading) return;\n\n if (hash) {\n if (hashElement) {\n hashElement.scrollIntoView();\n }\n } else {\n document?.querySelector('header')?.scrollIntoView();\n }\n }, [path, hash, hashElement, isStyleLoading]);\n\n const handleAppend = useCallback(\n (newShadowRoot: ShadowRoot) => {\n setShadowRoot(newShadowRoot);\n if (onReady instanceof Function) {\n onReady();\n }\n },\n [setShadowRoot, onReady],\n );\n\n // No entity metadata = 404. Don't render content at all.\n if (entityMetadataLoading === false && !entityMetadata)\n return <ErrorPage status=\"404\" statusMessage=\"PAGE NOT FOUND\" />;\n\n // Do not return content until dom is ready; instead, render a state\n // indicator, which handles progress and content errors on our behalf.\n if (!dom) {\n return (\n <Content>\n <Grid container>\n <Grid xs={12} item>\n <TechDocsStateIndicator />\n </Grid>\n </Grid>\n </Content>\n );\n }\n\n return (\n <Content>\n <Grid container>\n <Grid xs={12} item>\n <TechDocsStateIndicator />\n </Grid>\n {withSearch && (\n <Grid className={classes.search} xs=\"auto\" item>\n <TechDocsSearch\n entityId={entityRef}\n entityTitle={entityMetadata?.metadata?.title}\n />\n </Grid>\n )}\n <Grid xs={12} item>\n {/* Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times */}\n {(state === 'CHECKING' || isStyleLoading) && <Progress />}\n\n <TechDocsShadowDom element={dom} onAppend={handleAppend}>\n <TechDocsReaderPageContentAddons />\n </TechDocsShadowDom>\n </Grid>\n </Grid>\n </Content>\n );\n },\n);\n\n/**\n * Props for {@link Reader}\n *\n * @public\n * @deprecated use `TechDocsReaderPageContentProps` instead.\n */\nexport type ReaderProps = TechDocsReaderPageContentProps;\n\n/**\n * Component responsible for rendering TechDocs documentation\n * @public\n * @deprecated use `TechDocsReaderPageContent` component instead.\n */\nexport const Reader = TechDocsReaderPageContent;\n"],"names":[],"mappings":"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"}
@@ -135,7 +135,15 @@ const useTechDocsReaderDom = (entityRef) => {
135
135
  if (modifierActive) {
136
136
  window.open(url, "_blank");
137
137
  } else {
138
- navigate(url);
138
+ if (window.location.pathname !== parsedUrl.pathname) {
139
+ navigate(url);
140
+ } else {
141
+ window.history.pushState(
142
+ null,
143
+ document.title,
144
+ parsedUrl.hash
145
+ );
146
+ }
139
147
  transformedElement?.querySelector(`[id="${parsedUrl.hash.slice(1)}"]`)?.scrollIntoView();
140
148
  }
141
149
  } else {
@@ -182,6 +190,9 @@ const useTechDocsReaderDom = (entityRef) => {
182
190
  if (!shouldReplaceContent) {
183
191
  return;
184
192
  }
193
+ if (!window.location.pathname.endsWith(path)) {
194
+ return;
195
+ }
185
196
  window.scroll({ top: 0 });
186
197
  const postTransformedDomElement = await postRender(
187
198
  preTransformedDomElement
@@ -1 +1 @@
1
- {"version":3,"file":"dom.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPageContent/dom.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 { useCallback, useEffect, useState } from 'react';\n\nimport useMediaQuery from '@material-ui/core/useMediaQuery';\nimport { useTheme } from '@material-ui/core/styles';\n\nimport { CompoundEntityRef } from '@backstage/catalog-model';\nimport { useAnalytics, useApi } from '@backstage/core-plugin-api';\nimport { scmIntegrationsApiRef } from '@backstage/integration-react';\n\nimport {\n techdocsStorageApiRef,\n useShadowDomStylesLoading,\n} from '@backstage/plugin-techdocs-react';\n\nimport { useTechDocsReader } from '../TechDocsReaderProvider';\n\nimport {\n addBaseUrl,\n addGitFeedbackLink,\n addLinkClickListener,\n addSidebarToggle,\n onCssReady,\n removeMkdocsHeader,\n rewriteDocLinks,\n simplifyMkdocsFooter,\n scrollIntoNavigation,\n transform as transformer,\n copyToClipboard,\n useSanitizerTransformer,\n useStylesTransformer,\n handleMetaRedirects,\n} from '../../transformers';\nimport { useNavigateUrl } from './useNavigateUrl';\n\nconst MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)';\n\n/**\n * Hook that encapsulates the behavior of getting raw HTML and applying\n * transforms to it in order to make it function at a basic level in the\n * Backstage UI.\n */\nexport const useTechDocsReaderDom = (\n entityRef: CompoundEntityRef,\n): Element | null => {\n const navigate = useNavigateUrl();\n const theme = useTheme();\n const isMobileMedia = useMediaQuery(MOBILE_MEDIA_QUERY);\n const sanitizerTransformer = useSanitizerTransformer();\n const stylesTransformer = useStylesTransformer();\n const analytics = useAnalytics();\n\n const techdocsStorageApi = useApi(techdocsStorageApiRef);\n const scmIntegrationsApi = useApi(scmIntegrationsApiRef);\n\n const { state, path, content: rawPage } = useTechDocsReader();\n\n const [dom, setDom] = useState<HTMLElement | null>(null);\n const isStyleLoading = useShadowDomStylesLoading(dom);\n\n const updateSidebarPositionAndHeight = useCallback(() => {\n if (!dom) return;\n\n const sidebars = dom.querySelectorAll<HTMLElement>('.md-sidebar');\n\n sidebars.forEach(element => {\n // set sidebar position to render in correct position\n if (isMobileMedia) {\n element.style.top = '0px';\n } else {\n const page = document?.querySelector('.techdocs-reader-page');\n const pageTop = page?.getBoundingClientRect().top ?? 0;\n let domTop = dom.getBoundingClientRect().top ?? 0;\n\n const tabs = dom.querySelector('.md-container > .md-tabs');\n const tabsHeight = tabs?.getBoundingClientRect().height ?? 0;\n\n // the sidebars should not scroll beyond the total height of the header and tabs\n if (domTop < pageTop) {\n domTop = pageTop;\n }\n\n const scrollbarTopPx = Math.max(domTop, 0) + tabsHeight;\n\n element.style.top = `${scrollbarTopPx}px`;\n\n // set scrollbar height to ensure all links can be seen when content is small\n const footer = dom.querySelector('.md-container > .md-footer');\n // if no footer, fallback to using the bottom of the window\n const scrollbarEndPx =\n footer?.getBoundingClientRect().top ?? window.innerHeight;\n\n element.style.height = `${scrollbarEndPx - scrollbarTopPx}px`;\n }\n\n // show the sidebar only after updating its position\n element.style.setProperty('opacity', '1');\n });\n }, [dom, isMobileMedia]);\n\n useEffect(() => {\n window.addEventListener('resize', updateSidebarPositionAndHeight);\n window.addEventListener('scroll', updateSidebarPositionAndHeight, true);\n return () => {\n window.removeEventListener('resize', updateSidebarPositionAndHeight);\n window.removeEventListener(\n 'scroll',\n updateSidebarPositionAndHeight,\n true,\n );\n };\n }, [dom, updateSidebarPositionAndHeight]);\n\n // dynamically set width of footer to accommodate for pinning of the sidebar\n const updateFooterWidth = useCallback(() => {\n if (!dom) return;\n const footer = dom.querySelector<HTMLElement>('.md-footer');\n if (footer) {\n footer.style.width = `${dom.getBoundingClientRect().width}px`;\n }\n }, [dom]);\n\n useEffect(() => {\n window.addEventListener('resize', updateFooterWidth);\n return () => {\n window.removeEventListener('resize', updateFooterWidth);\n };\n }, [dom, updateFooterWidth]);\n\n // an update to \"state\" might lead to an updated UI so we include it as a trigger\n useEffect(() => {\n if (!isStyleLoading) {\n updateFooterWidth();\n updateSidebarPositionAndHeight();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n state,\n isStyleLoading,\n updateFooterWidth,\n updateSidebarPositionAndHeight,\n ]);\n\n // a function that performs transformations that are executed prior to adding it to the DOM\n const preRender = useCallback(\n (rawContent: string, contentPath: string) =>\n transformer(rawContent, [\n sanitizerTransformer,\n addBaseUrl({\n techdocsStorageApi,\n entityId: entityRef,\n path: contentPath,\n }),\n rewriteDocLinks(),\n addSidebarToggle(),\n removeMkdocsHeader(),\n simplifyMkdocsFooter(),\n addGitFeedbackLink(scmIntegrationsApi),\n stylesTransformer,\n ]),\n [\n // only add dependencies that are in state or memorized variables to avoid unnecessary calls between re-renders\n entityRef,\n scmIntegrationsApi,\n techdocsStorageApi,\n sanitizerTransformer,\n stylesTransformer,\n ],\n );\n\n // a function that performs transformations that are executed after adding it to the DOM\n const postRender = useCallback(\n async (transformedElement: Element) =>\n transformer(transformedElement, [\n handleMetaRedirects(navigate, entityRef.name),\n scrollIntoNavigation(),\n copyToClipboard(theme),\n addLinkClickListener({\n baseUrl: window.location.origin,\n onClick: (event: MouseEvent, url: string) => {\n // detect if CTRL or META keys are pressed so that links can be opened in a new tab with `window.open`\n const modifierActive = event.ctrlKey || event.metaKey;\n const parsedUrl = new URL(url);\n\n // capture link clicks within documentation\n const linkText =\n (event.target as HTMLAnchorElement | undefined)?.innerText || url;\n const to = url.replace(window.location.origin, '');\n analytics.captureEvent('click', linkText, { attributes: { to } });\n\n // hash exists when anchor is clicked on secondary sidebar\n if (parsedUrl.hash) {\n if (modifierActive) {\n window.open(url, '_blank');\n } else {\n navigate(url);\n // Scroll to hash if it's on the current page\n transformedElement\n ?.querySelector(`[id=\"${parsedUrl.hash.slice(1)}\"]`)\n ?.scrollIntoView();\n }\n } else {\n if (modifierActive) {\n window.open(url, '_blank');\n } else {\n navigate(url);\n }\n }\n },\n }),\n // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism)\n onCssReady({\n onLoading: () => {},\n onLoaded: () => {\n transformedElement\n .querySelector('.md-nav__title')\n ?.removeAttribute('for');\n },\n }),\n // hide sidebars until their positions are updated\n onCssReady({\n onLoading: () => {\n const sidebars = Array.from(\n transformedElement.querySelectorAll<HTMLElement>('.md-sidebar'),\n );\n sidebars.forEach(element => {\n element.style.setProperty('opacity', '0');\n });\n },\n onLoaded: () => {},\n }),\n ]),\n [theme, navigate, analytics, entityRef.name],\n );\n\n useEffect(() => {\n if (!rawPage) return () => {};\n\n // if false, there is already a newer execution of this effect\n let shouldReplaceContent = true;\n\n // Pre-render\n preRender(rawPage, path).then(async preTransformedDomElement => {\n if (!preTransformedDomElement?.innerHTML) {\n return; // An unexpected error occurred\n }\n\n // don't manipulate the shadow dom if this isn't the latest effect execution\n if (!shouldReplaceContent) {\n return;\n }\n\n // Scroll to top after render\n window.scroll({ top: 0 });\n\n // Post-render\n const postTransformedDomElement = await postRender(\n preTransformedDomElement,\n );\n setDom(postTransformedDomElement as HTMLElement);\n });\n\n // cancel this execution\n return () => {\n shouldReplaceContent = false;\n };\n }, [rawPage, path, preRender, postRender]);\n\n return dom;\n};\n"],"names":["transformer"],"mappings":"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"}
1
+ {"version":3,"file":"dom.esm.js","sources":["../../../../src/reader/components/TechDocsReaderPageContent/dom.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 { useCallback, useEffect, useState } from 'react';\n\nimport useMediaQuery from '@material-ui/core/useMediaQuery';\nimport { useTheme } from '@material-ui/core/styles';\n\nimport { CompoundEntityRef } from '@backstage/catalog-model';\nimport { useAnalytics, useApi } from '@backstage/core-plugin-api';\nimport { scmIntegrationsApiRef } from '@backstage/integration-react';\n\nimport {\n techdocsStorageApiRef,\n useShadowDomStylesLoading,\n} from '@backstage/plugin-techdocs-react';\n\nimport { useTechDocsReader } from '../TechDocsReaderProvider';\n\nimport {\n addBaseUrl,\n addGitFeedbackLink,\n addLinkClickListener,\n addSidebarToggle,\n onCssReady,\n removeMkdocsHeader,\n rewriteDocLinks,\n simplifyMkdocsFooter,\n scrollIntoNavigation,\n transform as transformer,\n copyToClipboard,\n useSanitizerTransformer,\n useStylesTransformer,\n handleMetaRedirects,\n} from '../../transformers';\nimport { useNavigateUrl } from './useNavigateUrl';\n\nconst MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)';\n\n/**\n * Hook that encapsulates the behavior of getting raw HTML and applying\n * transforms to it in order to make it function at a basic level in the\n * Backstage UI.\n */\nexport const useTechDocsReaderDom = (\n entityRef: CompoundEntityRef,\n): Element | null => {\n const navigate = useNavigateUrl();\n const theme = useTheme();\n const isMobileMedia = useMediaQuery(MOBILE_MEDIA_QUERY);\n const sanitizerTransformer = useSanitizerTransformer();\n const stylesTransformer = useStylesTransformer();\n const analytics = useAnalytics();\n\n const techdocsStorageApi = useApi(techdocsStorageApiRef);\n const scmIntegrationsApi = useApi(scmIntegrationsApiRef);\n\n const { state, path, content: rawPage } = useTechDocsReader();\n\n const [dom, setDom] = useState<HTMLElement | null>(null);\n const isStyleLoading = useShadowDomStylesLoading(dom);\n\n const updateSidebarPositionAndHeight = useCallback(() => {\n if (!dom) return;\n\n const sidebars = dom.querySelectorAll<HTMLElement>('.md-sidebar');\n\n sidebars.forEach(element => {\n // set sidebar position to render in correct position\n if (isMobileMedia) {\n element.style.top = '0px';\n } else {\n const page = document?.querySelector('.techdocs-reader-page');\n const pageTop = page?.getBoundingClientRect().top ?? 0;\n let domTop = dom.getBoundingClientRect().top ?? 0;\n\n const tabs = dom.querySelector('.md-container > .md-tabs');\n const tabsHeight = tabs?.getBoundingClientRect().height ?? 0;\n\n // the sidebars should not scroll beyond the total height of the header and tabs\n if (domTop < pageTop) {\n domTop = pageTop;\n }\n\n const scrollbarTopPx = Math.max(domTop, 0) + tabsHeight;\n\n element.style.top = `${scrollbarTopPx}px`;\n\n // set scrollbar height to ensure all links can be seen when content is small\n const footer = dom.querySelector('.md-container > .md-footer');\n // if no footer, fallback to using the bottom of the window\n const scrollbarEndPx =\n footer?.getBoundingClientRect().top ?? window.innerHeight;\n\n element.style.height = `${scrollbarEndPx - scrollbarTopPx}px`;\n }\n\n // show the sidebar only after updating its position\n element.style.setProperty('opacity', '1');\n });\n }, [dom, isMobileMedia]);\n\n useEffect(() => {\n window.addEventListener('resize', updateSidebarPositionAndHeight);\n window.addEventListener('scroll', updateSidebarPositionAndHeight, true);\n return () => {\n window.removeEventListener('resize', updateSidebarPositionAndHeight);\n window.removeEventListener(\n 'scroll',\n updateSidebarPositionAndHeight,\n true,\n );\n };\n }, [dom, updateSidebarPositionAndHeight]);\n\n // dynamically set width of footer to accommodate for pinning of the sidebar\n const updateFooterWidth = useCallback(() => {\n if (!dom) return;\n const footer = dom.querySelector<HTMLElement>('.md-footer');\n if (footer) {\n footer.style.width = `${dom.getBoundingClientRect().width}px`;\n }\n }, [dom]);\n\n useEffect(() => {\n window.addEventListener('resize', updateFooterWidth);\n return () => {\n window.removeEventListener('resize', updateFooterWidth);\n };\n }, [dom, updateFooterWidth]);\n\n // an update to \"state\" might lead to an updated UI so we include it as a trigger\n useEffect(() => {\n if (!isStyleLoading) {\n updateFooterWidth();\n updateSidebarPositionAndHeight();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n state,\n isStyleLoading,\n updateFooterWidth,\n updateSidebarPositionAndHeight,\n ]);\n\n // a function that performs transformations that are executed prior to adding it to the DOM\n const preRender = useCallback(\n (rawContent: string, contentPath: string) =>\n transformer(rawContent, [\n sanitizerTransformer,\n addBaseUrl({\n techdocsStorageApi,\n entityId: entityRef,\n path: contentPath,\n }),\n rewriteDocLinks(),\n addSidebarToggle(),\n removeMkdocsHeader(),\n simplifyMkdocsFooter(),\n addGitFeedbackLink(scmIntegrationsApi),\n stylesTransformer,\n ]),\n [\n // only add dependencies that are in state or memorized variables to avoid unnecessary calls between re-renders\n entityRef,\n scmIntegrationsApi,\n techdocsStorageApi,\n sanitizerTransformer,\n stylesTransformer,\n ],\n );\n\n // a function that performs transformations that are executed after adding it to the DOM\n const postRender = useCallback(\n async (transformedElement: Element) =>\n transformer(transformedElement, [\n handleMetaRedirects(navigate, entityRef.name),\n scrollIntoNavigation(),\n copyToClipboard(theme),\n addLinkClickListener({\n baseUrl: window.location.origin,\n onClick: (event: MouseEvent, url: string) => {\n // detect if CTRL or META keys are pressed so that links can be opened in a new tab with `window.open`\n const modifierActive = event.ctrlKey || event.metaKey;\n const parsedUrl = new URL(url);\n\n // capture link clicks within documentation\n const linkText =\n (event.target as HTMLAnchorElement | undefined)?.innerText || url;\n const to = url.replace(window.location.origin, '');\n analytics.captureEvent('click', linkText, { attributes: { to } });\n\n // hash exists when anchor is clicked on secondary sidebar\n if (parsedUrl.hash) {\n if (modifierActive) {\n window.open(url, '_blank');\n } else {\n // If it's in a different page, we navigate to it\n if (window.location.pathname !== parsedUrl.pathname) {\n navigate(url);\n } else {\n // If it's in the same page we avoid using navigate that causes\n // the page to rerender.\n window.history.pushState(\n null,\n document.title,\n parsedUrl.hash,\n );\n }\n // Scroll to hash if it's on the current page\n transformedElement\n ?.querySelector(`[id=\"${parsedUrl.hash.slice(1)}\"]`)\n ?.scrollIntoView();\n }\n } else {\n if (modifierActive) {\n window.open(url, '_blank');\n } else {\n navigate(url);\n }\n }\n },\n }),\n // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism)\n onCssReady({\n onLoading: () => {},\n onLoaded: () => {\n transformedElement\n .querySelector('.md-nav__title')\n ?.removeAttribute('for');\n },\n }),\n // hide sidebars until their positions are updated\n onCssReady({\n onLoading: () => {\n const sidebars = Array.from(\n transformedElement.querySelectorAll<HTMLElement>('.md-sidebar'),\n );\n sidebars.forEach(element => {\n element.style.setProperty('opacity', '0');\n });\n },\n onLoaded: () => {},\n }),\n ]),\n [theme, navigate, analytics, entityRef.name],\n );\n\n useEffect(() => {\n if (!rawPage) return () => {};\n\n // if false, there is already a newer execution of this effect\n let shouldReplaceContent = true;\n\n // Pre-render\n preRender(rawPage, path).then(async preTransformedDomElement => {\n if (!preTransformedDomElement?.innerHTML) {\n return; // An unexpected error occurred\n }\n\n // don't manipulate the shadow dom if this isn't the latest effect execution\n if (!shouldReplaceContent) {\n return;\n }\n\n // Skip this update if the location's path has changed but the state\n // contains a page for another page that isn't loaded yet.\n if (!window.location.pathname.endsWith(path)) {\n return;\n }\n\n // Scroll to top after render\n window.scroll({ top: 0 });\n\n // Post-render\n const postTransformedDomElement = await postRender(\n preTransformedDomElement,\n );\n\n setDom(postTransformedDomElement as HTMLElement);\n });\n\n // cancel this execution\n return () => {\n shouldReplaceContent = false;\n };\n }, [rawPage, path, preRender, postRender]);\n\n return dom;\n};\n"],"names":["transformer"],"mappings":"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"}
@@ -36,12 +36,22 @@ const useSanitizerTransformer = () => {
36
36
  }
37
37
  }
38
38
  });
39
+ const tagNameCheck = config?.getOptionalString(
40
+ "allowedCustomElementTagNameRegExp"
41
+ );
42
+ const attributeNameCheck = config?.getOptionalString(
43
+ "allowedCustomElementAttributeNameRegExp"
44
+ );
39
45
  return DOMPurify.sanitize(dom.outerHTML, {
40
46
  ADD_TAGS: tags,
41
47
  FORBID_TAGS: ["style"],
42
48
  ADD_ATTR: ["http-equiv", "content"],
43
49
  WHOLE_DOCUMENT: true,
44
- RETURN_DOM: true
50
+ RETURN_DOM: true,
51
+ CUSTOM_ELEMENT_HANDLING: {
52
+ tagNameCheck: tagNameCheck ? new RegExp(tagNameCheck) : void 0,
53
+ attributeNameCheck: attributeNameCheck ? new RegExp(attributeNameCheck) : void 0
54
+ }
45
55
  });
46
56
  },
47
57
  [config]
@@ -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 { removeUnsafeIframes, removeUnsafeLinks } 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 // Only allow meta tags if they are used for refreshing the page. They are required for the redirect feature.\n DOMPurify.addHook('uponSanitizeElement', (currNode, data) => {\n if (data.tagName === 'meta') {\n const isMetaRefreshTag =\n currNode.getAttribute('http-equiv') === 'refresh' &&\n currNode.getAttribute('content')?.includes('url=');\n if (!isMetaRefreshTag) {\n currNode.parentNode?.removeChild(currNode);\n }\n }\n });\n\n // Only allow http-equiv and content attributes on meta tags. They are required for the redirect feature.\n DOMPurify.addHook('uponSanitizeAttribute', (currNode, data) => {\n if (currNode.tagName !== 'meta') {\n if (data.attrName === 'http-equiv' || data.attrName === 'content') {\n currNode.removeAttribute(data.attrName);\n }\n }\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'],\n WHOLE_DOCUMENT: true,\n RETURN_DOM: true,\n });\n },\n [config],\n );\n};\n"],"names":[],"mappings":";;;;;;AA2BA,MAAM,qBAAqB,MAAM;AAC/B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA,CAAA;AAErC,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAO,OAAA,SAAA,CAAU,kBAAkB,oBAAoB,CAAA,CAAA;AAAA,GACzD,EAAG,CAAC,SAAS,CAAC,CAAA,CAAA;AAChB,CAAA,CAAA;AAKO,MAAM,0BAA0B,MAAmB;AACxD,EAAA,MAAM,SAAS,kBAAmB,EAAA,CAAA;AAElC,EAAO,OAAA,WAAA;AAAA,IACL,OAAO,GAAiB,KAAA;AACtB,MAAM,MAAA,KAAA,GAAQ,MAAQ,EAAA,sBAAA,CAAuB,oBAAoB,CAAA,CAAA;AAEjE,MAAU,SAAA,CAAA,OAAA,CAAQ,0BAA0B,iBAAiB,CAAA,CAAA;AAC7D,MAAM,MAAA,IAAA,GAAO,CAAC,MAAA,EAAQ,MAAM,CAAA,CAAA;AAE5B,MAAA,IAAI,KAAO,EAAA;AACT,QAAA,IAAA,CAAK,KAAK,QAAQ,CAAA,CAAA;AAClB,QAAA,SAAA,CAAU,OAAQ,CAAA,wBAAA,EAA0B,mBAAoB,CAAA,KAAK,CAAC,CAAA,CAAA;AAAA,OACxE;AAGA,MAAA,SAAA,CAAU,OAAQ,CAAA,qBAAA,EAAuB,CAAC,QAAA,EAAU,IAAS,KAAA;AAC3D,QAAI,IAAA,IAAA,CAAK,YAAY,MAAQ,EAAA;AAC3B,UAAM,MAAA,gBAAA,GACJ,QAAS,CAAA,YAAA,CAAa,YAAY,CAAA,KAAM,SACxC,IAAA,QAAA,CAAS,YAAa,CAAA,SAAS,CAAG,EAAA,QAAA,CAAS,MAAM,CAAA,CAAA;AACnD,UAAA,IAAI,CAAC,gBAAkB,EAAA;AACrB,YAAS,QAAA,CAAA,UAAA,EAAY,YAAY,QAAQ,CAAA,CAAA;AAAA,WAC3C;AAAA,SACF;AAAA,OACD,CAAA,CAAA;AAGD,MAAA,SAAA,CAAU,OAAQ,CAAA,uBAAA,EAAyB,CAAC,QAAA,EAAU,IAAS,KAAA;AAC7D,QAAI,IAAA,QAAA,CAAS,YAAY,MAAQ,EAAA;AAC/B,UAAA,IAAI,IAAK,CAAA,QAAA,KAAa,YAAgB,IAAA,IAAA,CAAK,aAAa,SAAW,EAAA;AACjE,YAAS,QAAA,CAAA,eAAA,CAAgB,KAAK,QAAQ,CAAA,CAAA;AAAA,WACxC;AAAA,SACF;AAAA,OACD,CAAA,CAAA;AAGD,MAAO,OAAA,SAAA,CAAU,QAAS,CAAA,GAAA,CAAI,SAAW,EAAA;AAAA,QACvC,QAAU,EAAA,IAAA;AAAA,QACV,WAAA,EAAa,CAAC,OAAO,CAAA;AAAA,QACrB,QAAA,EAAU,CAAC,YAAA,EAAc,SAAS,CAAA;AAAA,QAClC,cAAgB,EAAA,IAAA;AAAA,QAChB,UAAY,EAAA,IAAA;AAAA,OACb,CAAA,CAAA;AAAA,KACH;AAAA,IACA,CAAC,MAAM,CAAA;AAAA,GACT,CAAA;AACF;;;;"}
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 { removeUnsafeIframes, removeUnsafeLinks } 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 // Only allow meta tags if they are used for refreshing the page. They are required for the redirect feature.\n DOMPurify.addHook('uponSanitizeElement', (currNode, data) => {\n if (data.tagName === 'meta') {\n const isMetaRefreshTag =\n currNode.getAttribute('http-equiv') === 'refresh' &&\n currNode.getAttribute('content')?.includes('url=');\n if (!isMetaRefreshTag) {\n currNode.parentNode?.removeChild(currNode);\n }\n }\n });\n\n // Only allow http-equiv and content attributes on meta tags. They are required for the redirect feature.\n DOMPurify.addHook('uponSanitizeAttribute', (currNode, data) => {\n if (currNode.tagName !== 'meta') {\n if (data.attrName === 'http-equiv' || data.attrName === 'content') {\n currNode.removeAttribute(data.attrName);\n }\n }\n });\n\n const tagNameCheck = config?.getOptionalString(\n 'allowedCustomElementTagNameRegExp',\n );\n const attributeNameCheck = config?.getOptionalString(\n 'allowedCustomElementAttributeNameRegExp',\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'],\n WHOLE_DOCUMENT: true,\n RETURN_DOM: true,\n CUSTOM_ELEMENT_HANDLING: {\n tagNameCheck: tagNameCheck ? new RegExp(tagNameCheck) : undefined,\n attributeNameCheck: attributeNameCheck\n ? new RegExp(attributeNameCheck)\n : undefined,\n },\n });\n },\n [config],\n );\n};\n"],"names":[],"mappings":";;;;;;AA2BA,MAAM,qBAAqB,MAAM;AAC/B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA,CAAA;AAErC,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAO,OAAA,SAAA,CAAU,kBAAkB,oBAAoB,CAAA,CAAA;AAAA,GACzD,EAAG,CAAC,SAAS,CAAC,CAAA,CAAA;AAChB,CAAA,CAAA;AAKO,MAAM,0BAA0B,MAAmB;AACxD,EAAA,MAAM,SAAS,kBAAmB,EAAA,CAAA;AAElC,EAAO,OAAA,WAAA;AAAA,IACL,OAAO,GAAiB,KAAA;AACtB,MAAM,MAAA,KAAA,GAAQ,MAAQ,EAAA,sBAAA,CAAuB,oBAAoB,CAAA,CAAA;AAEjE,MAAU,SAAA,CAAA,OAAA,CAAQ,0BAA0B,iBAAiB,CAAA,CAAA;AAC7D,MAAM,MAAA,IAAA,GAAO,CAAC,MAAA,EAAQ,MAAM,CAAA,CAAA;AAE5B,MAAA,IAAI,KAAO,EAAA;AACT,QAAA,IAAA,CAAK,KAAK,QAAQ,CAAA,CAAA;AAClB,QAAA,SAAA,CAAU,OAAQ,CAAA,wBAAA,EAA0B,mBAAoB,CAAA,KAAK,CAAC,CAAA,CAAA;AAAA,OACxE;AAGA,MAAA,SAAA,CAAU,OAAQ,CAAA,qBAAA,EAAuB,CAAC,QAAA,EAAU,IAAS,KAAA;AAC3D,QAAI,IAAA,IAAA,CAAK,YAAY,MAAQ,EAAA;AAC3B,UAAM,MAAA,gBAAA,GACJ,QAAS,CAAA,YAAA,CAAa,YAAY,CAAA,KAAM,SACxC,IAAA,QAAA,CAAS,YAAa,CAAA,SAAS,CAAG,EAAA,QAAA,CAAS,MAAM,CAAA,CAAA;AACnD,UAAA,IAAI,CAAC,gBAAkB,EAAA;AACrB,YAAS,QAAA,CAAA,UAAA,EAAY,YAAY,QAAQ,CAAA,CAAA;AAAA,WAC3C;AAAA,SACF;AAAA,OACD,CAAA,CAAA;AAGD,MAAA,SAAA,CAAU,OAAQ,CAAA,uBAAA,EAAyB,CAAC,QAAA,EAAU,IAAS,KAAA;AAC7D,QAAI,IAAA,QAAA,CAAS,YAAY,MAAQ,EAAA;AAC/B,UAAA,IAAI,IAAK,CAAA,QAAA,KAAa,YAAgB,IAAA,IAAA,CAAK,aAAa,SAAW,EAAA;AACjE,YAAS,QAAA,CAAA,eAAA,CAAgB,KAAK,QAAQ,CAAA,CAAA;AAAA,WACxC;AAAA,SACF;AAAA,OACD,CAAA,CAAA;AAED,MAAA,MAAM,eAAe,MAAQ,EAAA,iBAAA;AAAA,QAC3B,mCAAA;AAAA,OACF,CAAA;AACA,MAAA,MAAM,qBAAqB,MAAQ,EAAA,iBAAA;AAAA,QACjC,yCAAA;AAAA,OACF,CAAA;AAGA,MAAO,OAAA,SAAA,CAAU,QAAS,CAAA,GAAA,CAAI,SAAW,EAAA;AAAA,QACvC,QAAU,EAAA,IAAA;AAAA,QACV,WAAA,EAAa,CAAC,OAAO,CAAA;AAAA,QACrB,QAAA,EAAU,CAAC,YAAA,EAAc,SAAS,CAAA;AAAA,QAClC,cAAgB,EAAA,IAAA;AAAA,QAChB,UAAY,EAAA,IAAA;AAAA,QACZ,uBAAyB,EAAA;AAAA,UACvB,YAAc,EAAA,YAAA,GAAe,IAAI,MAAA,CAAO,YAAY,CAAI,GAAA,KAAA,CAAA;AAAA,UACxD,kBAAoB,EAAA,kBAAA,GAChB,IAAI,MAAA,CAAO,kBAAkB,CAC7B,GAAA,KAAA,CAAA;AAAA,SACN;AAAA,OACD,CAAA,CAAA;AAAA,KACH;AAAA,IACA,CAAC,MAAM,CAAA;AAAA,GACT,CAAA;AACF;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-techdocs",
3
- "version": "1.11.0",
3
+ "version": "1.11.1-next.0",
4
4
  "description": "The Backstage plugin that renders technical documentation for your components",
5
5
  "backstage": {
6
6
  "role": "frontend-plugin",
@@ -59,22 +59,22 @@
59
59
  "test": "backstage-cli package test"
60
60
  },
61
61
  "dependencies": {
62
- "@backstage/catalog-model": "^1.7.0",
63
- "@backstage/config": "^1.2.0",
64
- "@backstage/core-compat-api": "^0.3.1",
65
- "@backstage/core-components": "^0.15.1",
66
- "@backstage/core-plugin-api": "^1.10.0",
67
- "@backstage/errors": "^1.2.4",
68
- "@backstage/frontend-plugin-api": "^0.9.0",
69
- "@backstage/integration": "^1.15.1",
70
- "@backstage/integration-react": "^1.2.0",
71
- "@backstage/plugin-auth-react": "^0.1.7",
72
- "@backstage/plugin-catalog-react": "^1.14.0",
73
- "@backstage/plugin-search-common": "^1.2.14",
74
- "@backstage/plugin-search-react": "^1.8.1",
75
- "@backstage/plugin-techdocs-common": "^0.1.0",
76
- "@backstage/plugin-techdocs-react": "^1.2.9",
77
- "@backstage/theme": "^0.6.0",
62
+ "@backstage/catalog-model": "1.7.0",
63
+ "@backstage/config": "1.2.0",
64
+ "@backstage/core-compat-api": "0.3.2-next.0",
65
+ "@backstage/core-components": "0.16.0-next.0",
66
+ "@backstage/core-plugin-api": "1.10.0",
67
+ "@backstage/errors": "1.2.4",
68
+ "@backstage/frontend-plugin-api": "0.9.1-next.0",
69
+ "@backstage/integration": "1.15.1",
70
+ "@backstage/integration-react": "1.2.0",
71
+ "@backstage/plugin-auth-react": "0.1.8-next.0",
72
+ "@backstage/plugin-catalog-react": "1.14.1-next.0",
73
+ "@backstage/plugin-search-common": "1.2.14",
74
+ "@backstage/plugin-search-react": "1.8.2-next.0",
75
+ "@backstage/plugin-techdocs-common": "0.1.0",
76
+ "@backstage/plugin-techdocs-react": "1.2.10-next.0",
77
+ "@backstage/theme": "0.6.0",
78
78
  "@material-ui/core": "^4.12.2",
79
79
  "@material-ui/icons": "^4.9.1",
80
80
  "@material-ui/lab": "4.0.0-alpha.61",
@@ -88,18 +88,17 @@
88
88
  "react-use": "^17.2.4"
89
89
  },
90
90
  "devDependencies": {
91
- "@backstage/cli": "^0.28.0",
92
- "@backstage/core-app-api": "^1.15.1",
93
- "@backstage/dev-utils": "^1.1.2",
94
- "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.16",
95
- "@backstage/test-utils": "^1.7.0",
91
+ "@backstage/cli": "0.29.0-next.0",
92
+ "@backstage/core-app-api": "1.15.1",
93
+ "@backstage/dev-utils": "1.1.3-next.0",
94
+ "@backstage/plugin-techdocs-module-addons-contrib": "1.1.17-next.0",
95
+ "@backstage/test-utils": "1.7.0",
96
96
  "@testing-library/dom": "^10.0.0",
97
97
  "@testing-library/jest-dom": "^6.0.0",
98
98
  "@testing-library/react": "^16.0.0",
99
99
  "@testing-library/user-event": "^14.0.0",
100
100
  "@types/dompurify": "^3.0.0",
101
101
  "@types/react": "^18.0.0",
102
- "canvas": "^2.10.2",
103
102
  "react": "^18.0.2",
104
103
  "react-dom": "^18.0.2",
105
104
  "react-router-dom": "^6.3.0"